Refactor ShortcutHelper and related classes to add icon to dialog

In order to get the icon delivered to the Java AddToHomeScreenDialog,
the pipeline has to be redone so that the icon is fetched and processed
asynchronously.  The dialog waits until the icon is available until
letting the user add the page to their homescreen.

This class breaks apart the ShortcutHelper class into two, making the
ownership semantics less prone to premature deletions, and adds a new
event for alerting the Java-side dialog that the icon is available.

Not done yet:
Add a timeout for waiting for the icon.

BUG=482157,457424,412391

Review URL: https://codereview.chromium.org/1114783002

Cr-Commit-Position: refs/heads/master@{#328482}
diff --git a/chrome/android/java/res/layout/add_to_homescreen_dialog.xml b/chrome/android/java/res/layout/add_to_homescreen_dialog.xml
new file mode 100644
index 0000000..6a84d0a
--- /dev/null
+++ b/chrome/android/java/res/layout/add_to_homescreen_dialog.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2015 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:gravity="start"
+    style="@style/AlertDialogContent" >
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:layout_marginTop="10dp"
+        android:layout_marginStart="10dp"
+        android:visibility="gone" />
+    <ProgressBar
+        android:id="@+id/spinny"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:layout_marginTop="10dp"
+        android:layout_marginStart="10dp" />
+    <EditText android:id="@+id/text"
+        android:inputType="text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:singleLine="true"
+        android:paddingTop="0dp"
+        android:layout_marginTop="10dp"
+        android:layout_marginStart="52dp" />
+</FrameLayout>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java
index f5708d5..571f0a9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java
@@ -4,7 +4,6 @@
 
 package org.chromium.chrome.browser;
 
-import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -35,6 +34,15 @@
     public static final String EXTRA_URL = "org.chromium.chrome.browser.webapp_url";
     public static final String EXTRA_ORIENTATION = ScreenOrientationConstants.EXTRA_ORIENTATION;
 
+    /** Observes the data fetching pipeline. */
+    public interface ShortcutHelperObserver {
+        /** Called when the title of the page is available. */
+        void onTitleAvailable(String title);
+
+        /** Called when the icon to use in the launcher is available. */
+        void onIconAvailable(Bitmap icon);
+    }
+
     private static String sFullScreenAction;
 
     /**
@@ -45,17 +53,10 @@
         sFullScreenAction = fullScreenAction;
     }
 
-    /**
-     * Callback to be passed to the initialized() method.
-     */
-    public interface OnInitialized {
-        public void onInitialized(String title);
-    }
-
     private final Context mAppContext;
     private final Tab mTab;
 
-    private OnInitialized mCallback;
+    private ShortcutHelperObserver mObserver;
     private boolean mIsInitialized;
     private long mNativeShortcutHelper;
 
@@ -65,12 +66,12 @@
     }
 
     /**
-     * Gets all the information required to initialize the UI, the passed
-     * callback will be run when those information will be available.
-     * @param callback Callback to be run when initialized.
+     * Gets all the information required to initialize the UI.  The observer will be notified as
+     * information required for the shortcut become available.
+     * @param observer Observer to notify.
      */
-    public void initialize(OnInitialized callback) {
-        mCallback = callback;
+    public void initialize(ShortcutHelperObserver observer) {
+        mObserver = observer;
         mNativeShortcutHelper = nativeInitialize(mTab.getWebContents());
     }
 
@@ -84,22 +85,24 @@
     /**
      * Puts the object in a state where it is safe to be destroyed.
      */
-    public void tearDown() {
-        nativeTearDown(mNativeShortcutHelper);
+    public void destroy() {
+        nativeDestroy(mNativeShortcutHelper);
 
         // Make sure the callback isn't run if the tear down happens before
         // onInitialized() is called.
-        mCallback = null;
+        mObserver = null;
         mNativeShortcutHelper = 0;
     }
 
     @CalledByNative
-    private void onInitialized(String title) {
-        mIsInitialized = true;
+    private void onTitleAvailable(String title) {
+        mObserver.onTitleAvailable(title);
+    }
 
-        if (mCallback != null) {
-            mCallback.onInitialized(title);
-        }
+    @CalledByNative
+    private void onIconAvailable(Bitmap icon) {
+        mObserver.onIconAvailable(icon);
+        mIsInitialized = true;
     }
 
     /**
@@ -111,13 +114,18 @@
             Log.e("ShortcutHelper", "ShortcutHelper is uninitialized.  Aborting.");
             return;
         }
-        ActivityManager am = (ActivityManager) mAppContext.getSystemService(
-                Context.ACTIVITY_SERVICE);
-        nativeAddShortcut(mNativeShortcutHelper, userRequestedTitle, am.getLauncherLargeIconSize());
 
-        // The C++ instance is no longer owned by the Java object.
-        mCallback = null;
-        mNativeShortcutHelper = 0;
+        nativeAddShortcut(mNativeShortcutHelper, userRequestedTitle);
+    }
+
+    /**
+     * Creates an icon that is acceptable to show on the launcher.
+     */
+    @CalledByNative
+    private static Bitmap finalizeLauncherIcon(
+            String url, Bitmap icon, int red, int green, int blue) {
+        return BookmarkUtils.createLauncherIcon(
+                ApplicationStatus.getApplicationContext(), icon, url, red, green, blue);
     }
 
     /**
@@ -129,8 +137,7 @@
     @SuppressWarnings("unused")
     @CalledByNative
     private static void addShortcut(Context context, String url, String title, Bitmap icon,
-            int red, int green, int blue, boolean isWebappCapable, int orientation,
-            boolean returnToHomescreen) {
+            boolean isWebappCapable, int orientation) {
         assert sFullScreenAction != null;
 
         Intent shortcutIntent;
@@ -159,30 +166,22 @@
         }
 
         shortcutIntent.setPackage(context.getPackageName());
-        Bitmap launchIcon = BookmarkUtils.createLauncherIcon(context, icon, url, red, green, blue);
-        context.sendBroadcast(BookmarkUtils.createAddToHomeIntent(shortcutIntent, title,
-                launchIcon, url));
+        context.sendBroadcast(
+                BookmarkUtils.createAddToHomeIntent(shortcutIntent, title, icon, url));
 
         // Alert the user about adding the shortcut.
-        if (returnToHomescreen) {
-            Intent homeIntent = new Intent(Intent.ACTION_MAIN);
-            homeIntent.addCategory(Intent.CATEGORY_HOME);
-            homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            context.startActivity(homeIntent);
-        } else {
-            final String shortUrl = UrlUtilities.getDomainAndRegistry(url, true);
-            Handler handler = new Handler(Looper.getMainLooper());
-            handler.post(new Runnable() {
-                @Override
-                public void run() {
-                    Context applicationContext = ApplicationStatus.getApplicationContext();
-                    String toastText =
-                            applicationContext.getString(R.string.added_to_homescreen, shortUrl);
-                    Toast toast = Toast.makeText(applicationContext, toastText, Toast.LENGTH_SHORT);
-                    toast.show();
-                }
-            });
-        }
+        final String shortUrl = UrlUtilities.getDomainAndRegistry(url, true);
+        Handler handler = new Handler(Looper.getMainLooper());
+        handler.post(new Runnable() {
+            @Override
+            public void run() {
+                Context applicationContext = ApplicationStatus.getApplicationContext();
+                String toastText =
+                        applicationContext.getString(R.string.added_to_homescreen, shortUrl);
+                Toast toast = Toast.makeText(applicationContext, toastText, Toast.LENGTH_SHORT);
+                toast.show();
+            }
+        });
     }
 
     /**
@@ -197,7 +196,6 @@
     }
 
     private native long nativeInitialize(WebContents webContents);
-    private native void nativeAddShortcut(long nativeShortcutHelper, String userRequestedTitle,
-            int launcherLargeIconSize);
-    private native void nativeTearDown(long nativeShortcutHelper);
+    private native void nativeAddShortcut(long nativeShortcutHelper, String userRequestedTitle);
+    private native void nativeDestroy(long nativeShortcutHelper);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenDialog.java
index 7c997f8..aca1d23 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenDialog.java
@@ -6,14 +6,14 @@
 
 import android.app.Activity;
 import android.content.DialogInterface;
-import android.os.Handler;
+import android.graphics.Bitmap;
 import android.support.v7.app.AlertDialog;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.view.View;
 import android.widget.EditText;
-import android.widget.TextView;
+import android.widget.ImageView;
 
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.R;
@@ -39,7 +39,7 @@
      */
     public static void show(final Activity activity, final Tab currentTab) {
         View view = activity.getLayoutInflater().inflate(
-                R.layout.single_line_edit_dialog, null);
+                R.layout.add_to_homescreen_dialog, null);
         AlertDialog.Builder builder = new AlertDialog.Builder(activity, R.style.AlertDialogTheme)
                 .setTitle(R.string.menu_add_to_homescreen)
                 .setNegativeButton(R.string.cancel,
@@ -54,9 +54,9 @@
         // On click of the menu item for "add to homescreen", an alert dialog pops asking the user
         // if the title needs to be edited. On click of "Add", shortcut is created. Default
         // title is the title of the page. OK button is disabled if the title text is empty.
-        TextView titleLabel = (TextView) view.findViewById(R.id.title);
+        final View progressBarView = (View) view.findViewById(R.id.spinny);
+        final ImageView iconView = (ImageView) view.findViewById(R.id.icon);
         final EditText input = (EditText) view.findViewById(R.id.text);
-        titleLabel.setText(R.string.add_to_homescreen_title);
         input.setEnabled(false);
 
         final ShortcutHelper shortcutHelper =
@@ -67,14 +67,20 @@
         // They will be enabled and pre-filled as soon as the onInitialized
         // callback will be run. The user will still be able to cancel the
         // operation.
-        shortcutHelper.initialize(new ShortcutHelper.OnInitialized() {
+        shortcutHelper.initialize(new ShortcutHelper.ShortcutHelperObserver() {
             @Override
-            public void onInitialized(String title) {
-                dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
-
+            public void onTitleAvailable(String title) {
                 input.setEnabled(true);
                 input.setText(title);
             }
+
+            @Override
+            public void onIconAvailable(Bitmap icon) {
+                progressBarView.setVisibility(View.GONE);
+                iconView.setVisibility(View.VISIBLE);
+                iconView.setImageBitmap(icon);
+                dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
+            }
         });
 
         input.addTextChangedListener(new TextWatcher() {
@@ -102,45 +108,17 @@
                 new DialogInterface.OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int id) {
-                        new Handler().postDelayed(new Runnable() {
-                            @Override
-                            public void run() {
-                                shortcutHelper.addShortcut(input.getText().toString());
-                                // No need to call tearDown() in that case,
-                                // |shortcutHelper| is expected to tear down itself
-                                // after that call.
-                            }
-                        }, 100);
-                        // We are adding arbitrary delay here to try and yield for main activity to
-                        // be focused before we create shortcut. Yielding helps clear this dialog
-                        // box from screenshot that Android's recents activity module shows.
-                        // I tried a bunch of solutions suggested in code review, but root cause
-                        // seems be that parent view/activity doesn't get chance to redraw itself
-                        // completely and Recent Activities seem to have old snapshot. Clean way
-                        // would be to let parent activity/view draw itself before we add the
-                        // shortcut, but that would require complete redrawing including rendered
-                        // page.
+                        shortcutHelper.addShortcut(input.getText().toString());
                     }
                 });
 
-        // The dialog is being cancel by clicking away or clicking the "cancel"
-        // button. |shortcutHelper| need to be tear down in that case to release
-        // all the objects, including the C++ ShortcutHelper.
-        dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
-            @Override
-            public void onCancel(DialogInterface dialog) {
-                shortcutHelper.tearDown();
-            }
-        });
-
         // The "OK" button should only be shown when |shortcutHelper| is
         // initialized, it should be kept disabled until then.
         dialog.setOnShowListener(new DialogInterface.OnShowListener() {
             @Override
             public void onShow(DialogInterface d) {
-                if (!shortcutHelper.isInitialized()) {
-                    dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
-                }
+                dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(
+                        shortcutHelper.isInitialized());
             }
         });
 
@@ -150,6 +128,7 @@
             @Override
             public void onDismiss(DialogInterface dialog) {
                 sCurrentDialog = null;
+                shortcutHelper.destroy();
             }
         });
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ShortcutHelperTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ShortcutHelperTest.java
index 3890d98..7511125f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ShortcutHelperTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ShortcutHelperTest.java
@@ -5,9 +5,11 @@
 package org.chromium.chrome.browser;
 
 import android.content.Intent;
+import android.graphics.Bitmap;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
 
+import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.UrlUtils;
 import org.chromium.chrome.shell.ChromeShellActivity;
@@ -18,6 +20,8 @@
 import org.chromium.content.browser.test.util.Criteria;
 import org.chromium.content.browser.test.util.CriteriaHelper;
 
+import java.util.concurrent.Callable;
+
 /**
  * Tests org.chromium.chrome.browser.ShortcutHelper and it's C++ counterpart.
  */
@@ -82,6 +86,7 @@
 
         ShortcutHelper.setFullScreenAction(WEBAPP_ACTION_NAME);
         mActivity = launchChromeShellWithBlankPage();
+        waitForActiveShellToBeDoneLoading();
 
         // Set up the observer.
         mTestObserver = new TestObserver();
@@ -172,20 +177,26 @@
         assertTrue(CriteriaHelper.pollForUIThreadCriteria(observer));
 
         // Add the shortcut.
-        getInstrumentation().runOnMainSync(new Runnable() {
+        Callable<ShortcutHelper> callable = new Callable<ShortcutHelper>() {
             @Override
-            public void run() {
-                final ShortcutHelper shortcutHelper = new ShortcutHelper(
+            public ShortcutHelper call() {
+                final ShortcutHelper helper = new ShortcutHelper(
                         mActivity.getApplicationContext(), mActivity.getActiveTab());
                 // Calling initialize() isn't strictly required but it is testing this code path.
-                shortcutHelper.initialize(new ShortcutHelper.OnInitialized() {
+                helper.initialize(new ShortcutHelper.ShortcutHelperObserver() {
                     @Override
-                    public void onInitialized(String t) {
-                        shortcutHelper.addShortcut(title);
+                    public void onTitleAvailable(String t) {
+                    }
+
+                    @Override
+                    public void onIconAvailable(Bitmap icon) {
+                        helper.addShortcut(title);
                     }
                 });
+                return helper;
             }
-        });
+        };
+        final ShortcutHelper helper = ThreadUtils.runOnUiThreadBlockingNoException(callable);
 
         // Make sure that the shortcut was added.
         assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() {
@@ -194,5 +205,12 @@
                 return mTestObserver.mFiredIntent != null;
             }
         }));
+
+        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+            @Override
+            public void run() {
+                helper.destroy();
+            }
+        });
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/AddToHomescreenDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/AddToHomescreenDialogTest.java
index 39993b6..e2d1b78 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/AddToHomescreenDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/AddToHomescreenDialogTest.java
@@ -47,7 +47,8 @@
 
                 assertTrue(dialog.isShowing());
 
-                assertNotNull(dialog.findViewById(R.id.title));
+                assertNotNull(dialog.findViewById(R.id.spinny));
+                assertNotNull(dialog.findViewById(R.id.icon));
                 assertNotNull(dialog.findViewById(R.id.text));
                 assertNotNull(dialog.getButton(DialogInterface.BUTTON_POSITIVE));
                 assertNotNull(dialog.getButton(DialogInterface.BUTTON_NEGATIVE));
diff --git a/chrome/browser/android/banners/app_banner_infobar_delegate.cc b/chrome/browser/android/banners/app_banner_infobar_delegate.cc
index f4040be4..77b9b5a 100644
--- a/chrome/browser/android/banners/app_banner_infobar_delegate.cc
+++ b/chrome/browser/android/banners/app_banner_infobar_delegate.cc
@@ -9,7 +9,6 @@
 #include "base/location.h"
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/threading/worker_pool.h"
 #include "chrome/browser/android/shortcut_helper.h"
 #include "chrome/browser/android/shortcut_info.h"
 #include "chrome/browser/android/tab_android.h"
@@ -21,6 +20,7 @@
 #include "chrome/browser/ui/android/infobars/app_banner_infobar.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/rappor/rappor_utils.h"
+#include "content/public/browser/browser_thread.h"
 #include "content/public/common/manifest.h"
 #include "jni/AppBannerInfoBarDelegate_jni.h"
 #include "ui/gfx/android/java_bitmap.h"
@@ -207,13 +207,12 @@
 
     ShortcutInfo info;
     info.UpdateFromManifest(web_app_data_);
-    base::WorkerPool::PostTask(
+    content::BrowserThread::PostTask(
+        content::BrowserThread::IO,
         FROM_HERE,
         base::Bind(&ShortcutHelper::AddShortcutInBackgroundWithSkBitmap,
                    info,
-                   *app_icon_.get(),
-                   false),
-        true);
+                   *app_icon_.get()));
 
     TrackInstallEvent(INSTALL_EVENT_WEB_APP_INSTALLED);
     rappor::SampleDomainAndRegistryFromGURL(g_browser_process->rappor_service(),
diff --git a/chrome/browser/android/shortcut_data_fetcher.cc b/chrome/browser/android/shortcut_data_fetcher.cc
new file mode 100644
index 0000000..e8868bc
--- /dev/null
+++ b/chrome/browser/android/shortcut_data_fetcher.cc
@@ -0,0 +1,240 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/android/shortcut_data_fetcher.h"
+
+#include "base/basictypes.h"
+#include "base/location.h"
+#include "base/strings/string16.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "chrome/browser/favicon/favicon_service_factory.h"
+#include "chrome/browser/manifest/manifest_icon_selector.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/common/web_application_info.h"
+#include "components/dom_distiller/core/url_utils.h"
+#include "components/favicon/core/favicon_service.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/user_metrics.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/frame_navigate_params.h"
+#include "content/public/common/manifest.h"
+#include "third_party/WebKit/public/platform/WebScreenOrientationLockType.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/favicon_size.h"
+#include "ui/gfx/screen.h"
+#include "url/gurl.h"
+
+using content::Manifest;
+
+// Android's preferred icon size in DP is 48, as defined in
+// http://developer.android.com/design/style/iconography.html
+const int ShortcutDataFetcher::kPreferredIconSizeInDp = 48;
+
+ShortcutDataFetcher::ShortcutDataFetcher(
+    content::WebContents* web_contents,
+    Observer* observer)
+    : WebContentsObserver(web_contents),
+      weak_observer_(observer),
+      is_ready_(false),
+      shortcut_info_(dom_distiller::url_utils::GetOriginalUrlFromDistillerUrl(
+                     web_contents->GetURL())),
+      preferred_icon_size_in_px_(kPreferredIconSizeInDp *
+          gfx::Screen::GetScreenFor(web_contents->GetNativeView())->
+              GetPrimaryDisplay().device_scale_factor()) {
+  // Send a message to the renderer to retrieve information about the page.
+  is_waiting_for_web_application_info_ = true;
+  Send(new ChromeViewMsg_GetWebApplicationInfo(routing_id()));
+}
+
+void ShortcutDataFetcher::OnDidGetWebApplicationInfo(
+    const WebApplicationInfo& received_web_app_info) {
+  is_waiting_for_web_application_info_ = false;
+  if (!web_contents() || !weak_observer_) return;
+
+  // Sanitize received_web_app_info.
+  WebApplicationInfo web_app_info = received_web_app_info;
+  web_app_info.title =
+      web_app_info.title.substr(0, chrome::kMaxMetaTagAttributeLength);
+  web_app_info.description =
+      web_app_info.description.substr(0, chrome::kMaxMetaTagAttributeLength);
+
+  shortcut_info_.title = web_app_info.title.empty() ? web_contents()->GetTitle()
+                                                    : web_app_info.title;
+
+  if (web_app_info.mobile_capable == WebApplicationInfo::MOBILE_CAPABLE ||
+      web_app_info.mobile_capable == WebApplicationInfo::MOBILE_CAPABLE_APPLE) {
+    shortcut_info_.display = content::Manifest::DISPLAY_MODE_STANDALONE;
+  }
+
+  // Record what type of shortcut was added by the user.
+  switch (web_app_info.mobile_capable) {
+    case WebApplicationInfo::MOBILE_CAPABLE:
+      content::RecordAction(
+          base::UserMetricsAction("webapps.AddShortcut.AppShortcut"));
+      break;
+    case WebApplicationInfo::MOBILE_CAPABLE_APPLE:
+      content::RecordAction(
+          base::UserMetricsAction("webapps.AddShortcut.AppShortcutApple"));
+      break;
+    case WebApplicationInfo::MOBILE_CAPABLE_UNSPECIFIED:
+      content::RecordAction(
+          base::UserMetricsAction("webapps.AddShortcut.Bookmark"));
+      break;
+  }
+
+  web_contents()->GetManifest(base::Bind(&ShortcutDataFetcher::OnDidGetManifest,
+                                         this));
+}
+
+void ShortcutDataFetcher::OnDidGetManifest(const content::Manifest& manifest) {
+  if (!web_contents() || !weak_observer_) return;
+
+  if (!manifest.IsEmpty()) {
+      content::RecordAction(
+          base::UserMetricsAction("webapps.AddShortcut.Manifest"));
+  }
+
+  shortcut_info_.UpdateFromManifest(manifest);
+
+  GURL icon_src = ManifestIconSelector::FindBestMatchingIcon(
+      manifest.icons,
+      kPreferredIconSizeInDp,
+      gfx::Screen::GetScreenFor(web_contents()->GetNativeView()));
+  if (icon_src.is_valid()) {
+    // Grab the best icon from the manifest.
+    web_contents()->DownloadImage(
+        icon_src,
+        false,
+        preferred_icon_size_in_px_,
+        false,
+        base::Bind(&ShortcutDataFetcher::OnManifestIconFetched,
+                   this));
+  } else {
+    // Grab the best favicon for the page.
+    FetchFavicon();
+  }
+
+  weak_observer_->OnTitleAvailable(shortcut_info_.title);
+}
+
+bool ShortcutDataFetcher::OnMessageReceived(const IPC::Message& message) {
+  if (!is_waiting_for_web_application_info_) return false;
+
+  bool handled = true;
+
+  IPC_BEGIN_MESSAGE_MAP(ShortcutDataFetcher, message)
+    IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidGetWebApplicationInfo,
+                        OnDidGetWebApplicationInfo)
+    IPC_MESSAGE_UNHANDLED(handled = false)
+  IPC_END_MESSAGE_MAP()
+
+  return handled;
+}
+
+ShortcutDataFetcher::~ShortcutDataFetcher() {
+  DCHECK(!weak_observer_);
+}
+
+void ShortcutDataFetcher::FetchFavicon() {
+  if (!web_contents() || !weak_observer_) return;
+
+  Profile* profile =
+      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
+
+  // Grab the best, largest icon we can find to represent this bookmark.
+  // TODO(dfalcantara): Try combining with the new BookmarksHandler once its
+  //                    rewrite is further along.
+  std::vector<int> icon_types;
+  icon_types.push_back(favicon_base::FAVICON);
+  icon_types.push_back(favicon_base::TOUCH_PRECOMPOSED_ICON |
+                       favicon_base::TOUCH_ICON);
+  favicon::FaviconService* favicon_service =
+      FaviconServiceFactory::GetForProfile(profile,
+                                           ServiceAccessType::EXPLICIT_ACCESS);
+
+  // Using favicon if its size is not smaller than platform required size,
+  // otherwise using the largest icon among all avaliable icons.
+  int threshold_to_get_any_largest_icon = preferred_icon_size_in_px_ - 1;
+  favicon_service->GetLargestRawFaviconForPageURL(
+      shortcut_info_.url,
+      icon_types,
+      threshold_to_get_any_largest_icon,
+      base::Bind(&ShortcutDataFetcher::OnFaviconFetched, this),
+      &cancelable_task_tracker_);
+}
+
+void ShortcutDataFetcher::OnFaviconFetched(
+    const favicon_base::FaviconRawBitmapResult& bitmap_result) {
+  if (!web_contents() || !weak_observer_) return;
+
+  content::BrowserThread::PostTask(
+      content::BrowserThread::IO,
+      FROM_HERE,
+      base::Bind(&ShortcutDataFetcher::CreateLauncherIcon,
+                 this,
+                 bitmap_result));
+}
+
+void ShortcutDataFetcher::CreateLauncherIcon(
+    const favicon_base::FaviconRawBitmapResult& bitmap_result) {
+  if (!web_contents() || !weak_observer_) return;
+
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+  SkBitmap icon_bitmap;
+  if (bitmap_result.is_valid()) {
+    gfx::PNGCodec::Decode(bitmap_result.bitmap_data->front(),
+                          bitmap_result.bitmap_data->size(),
+                          &icon_bitmap);
+  }
+
+  if (weak_observer_) {
+    shortcut_icon_ = weak_observer_->FinalizeLauncherIcon(icon_bitmap,
+                                                          shortcut_info_.url);
+  }
+
+  content::BrowserThread::PostTask(
+      content::BrowserThread::UI,
+      FROM_HERE,
+      base::Bind(&ShortcutDataFetcher::NotifyObserver, this));
+}
+
+void ShortcutDataFetcher::OnManifestIconFetched(
+    int id,
+    int http_status_code,
+    const GURL& url,
+    const std::vector<SkBitmap>& bitmaps,
+    const std::vector<gfx::Size>& sizes) {
+  if (!web_contents() || !weak_observer_) return;
+
+  // If getting the candidate manifest icon failed, the ShortcutHelper should
+  // fallback to the favicon.
+  // Otherwise, it sets the state as if there was no manifest icon pending.
+  if (bitmaps.empty()) {
+    FetchFavicon();
+    return;
+  }
+
+  // There might be multiple bitmaps returned. The one to pick is bigger or
+  // equal to the preferred size. |bitmaps| is ordered from bigger to smaller.
+  int preferred_bitmap_index = 0;
+  for (size_t i = 0; i < bitmaps.size(); ++i) {
+    if (bitmaps[i].height() < preferred_icon_size_in_px_)
+      break;
+    preferred_bitmap_index = i;
+  }
+
+  shortcut_icon_ = bitmaps[preferred_bitmap_index];
+  NotifyObserver();
+}
+
+void ShortcutDataFetcher::NotifyObserver() {
+  if (!web_contents() || !weak_observer_) return;
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  is_ready_ = true;
+  weak_observer_->OnDataAvailable(shortcut_info_, shortcut_icon_);
+}
diff --git a/chrome/browser/android/shortcut_data_fetcher.h b/chrome/browser/android/shortcut_data_fetcher.h
new file mode 100644
index 0000000..a0430fa
--- /dev/null
+++ b/chrome/browser/android/shortcut_data_fetcher.h
@@ -0,0 +1,109 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ANDROID_SHORTCUT_DATA_FETCHER_H_
+#define CHROME_BROWSER_ANDROID_SHORTCUT_DATA_FETCHER_H_
+
+#include "base/basictypes.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "chrome/browser/android/shortcut_info.h"
+#include "chrome/common/web_application_info.h"
+#include "components/favicon_base/favicon_types.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/manifest.h"
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+namespace IPC {
+class Message;
+}
+
+class GURL;
+
+// Aysnchronously fetches and processes data needed to create a shortcut for an
+// Android Home screen launcher.
+//
+// Because of the various asynchronous calls made by this class, it is
+// refcounted to prevent the class from being prematurely deleted.  If the
+// pointer to the ShortcutHelper becomes invalid, the pipeline should kill
+// itself.
+class ShortcutDataFetcher
+    : public base::RefCounted<ShortcutDataFetcher>,
+      public content::WebContentsObserver {
+ public:
+  class Observer {
+   public:
+    // Called when the title of the page is available.
+    virtual void OnTitleAvailable(const base::string16& title) = 0;
+
+    // Converts the icon into one that can be used on the Android Home screen.
+    virtual SkBitmap FinalizeLauncherIcon(const SkBitmap& icon,
+                                          const GURL& url) = 0;
+
+    // Called when all the data needed to create a shortcut is available.
+    virtual void OnDataAvailable(const ShortcutInfo& info,
+                                 const SkBitmap& icon) = 0;
+  };
+
+  // Initialize the fetcher by requesting the information about the page to the
+  // renderer process. The initialization is asynchronous and
+  // OnDidGetWebApplicationInfo is expected to be called when finished.
+  ShortcutDataFetcher(content::WebContents* web_contents, Observer* observer);
+
+  // IPC message received when the initialization is finished.
+  void OnDidGetWebApplicationInfo(const WebApplicationInfo& web_app_info);
+
+  // Called when the Manifest has been parsed, or if no Manifest was found.
+  void OnDidGetManifest(const content::Manifest& manifest);
+
+  // Accessors, etc.
+  void set_weak_observer(Observer* observer) { weak_observer_ = observer; }
+  bool is_ready() { return is_ready_; }
+  ShortcutInfo& shortcut_info() { return shortcut_info_; }
+  const SkBitmap& shortcut_icon() { return shortcut_icon_; }
+
+  // WebContentsObserver
+  bool OnMessageReceived(const IPC::Message& message) override;
+
+ private:
+  ~ShortcutDataFetcher() override;
+
+  // Grabs the favicon for the current URL.
+  void FetchFavicon();
+  void OnFaviconFetched(
+      const favicon_base::FaviconRawBitmapResult& bitmap_result);
+
+  // Creates the launcher icon from the given bitmap.
+  void CreateLauncherIcon(
+      const favicon_base::FaviconRawBitmapResult& bitmap_result);
+
+  // Callback run after an attempt to download manifest icon has been made.  May
+  // kick off the download of a favicon if it failed.
+  void OnManifestIconFetched(int id,
+                             int http_status_code,
+                             const GURL& url,
+                             const std::vector<SkBitmap>& bitmaps,
+                             const std::vector<gfx::Size>& sizes);
+
+  // Notifies the observer that the shortcut data is all available.
+  void NotifyObserver();
+
+  Observer* weak_observer_;
+
+  bool is_waiting_for_web_application_info_;
+  bool is_ready_;
+  ShortcutInfo shortcut_info_;
+  SkBitmap shortcut_icon_;
+  base::CancelableTaskTracker cancelable_task_tracker_;
+
+  const int preferred_icon_size_in_px_;
+  static const int kPreferredIconSizeInDp;
+
+  friend class base::RefCounted<ShortcutDataFetcher>;
+  DISALLOW_COPY_AND_ASSIGN(ShortcutDataFetcher);
+};
+
+#endif  // CHROME_BROWSER_ANDROID_SHORTCUT_DATA_FETCHER_H_
diff --git a/chrome/browser/android/shortcut_helper.cc b/chrome/browser/android/shortcut_helper.cc
index d9007c7..fc46ef7 100644
--- a/chrome/browser/android/shortcut_helper.cc
+++ b/chrome/browser/android/shortcut_helper.cc
@@ -5,7 +5,6 @@
 #include "chrome/browser/android/shortcut_helper.h"
 
 #include <jni.h>
-#include <limits>
 
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
@@ -13,313 +12,134 @@
 #include "base/location.h"
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/task/cancelable_task_tracker.h"
-#include "base/threading/worker_pool.h"
 #include "chrome/browser/banners/app_banner_settings_helper.h"
-#include "chrome/browser/favicon/favicon_service_factory.h"
-#include "chrome/browser/manifest/manifest_icon_selector.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/common/chrome_constants.h"
-#include "chrome/common/render_messages.h"
-#include "chrome/common/web_application_info.h"
-#include "components/dom_distiller/core/url_utils.h"
-#include "components/favicon/core/favicon_service.h"
-#include "content/public/browser/user_metrics.h"
+#include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents.h"
-#include "content/public/browser/web_contents_observer.h"
-#include "content/public/common/frame_navigate_params.h"
 #include "content/public/common/manifest.h"
 #include "jni/ShortcutHelper_jni.h"
-#include "net/base/mime_util.h"
-#include "third_party/WebKit/public/platform/WebScreenOrientationLockType.h"
 #include "ui/gfx/android/java_bitmap.h"
-#include "ui/gfx/codec/png_codec.h"
 #include "ui/gfx/color_analysis.h"
-#include "ui/gfx/favicon_size.h"
-#include "ui/gfx/screen.h"
 #include "url/gurl.h"
 
 using content::Manifest;
 
-// Android's preferred icon size in DP is 48, as defined in
-// http://developer.android.com/design/style/iconography.html
-const int ShortcutHelper::kPreferredIconSizeInDp = 48;
-
 jlong Initialize(JNIEnv* env, jobject obj, jobject java_web_contents) {
   content::WebContents* web_contents =
       content::WebContents::FromJavaWebContents(java_web_contents);
   ShortcutHelper* shortcut_helper = new ShortcutHelper(env, obj, web_contents);
-  shortcut_helper->Initialize();
-
   return reinterpret_cast<intptr_t>(shortcut_helper);
 }
 
 ShortcutHelper::ShortcutHelper(JNIEnv* env,
                                jobject obj,
                                content::WebContents* web_contents)
-    : WebContentsObserver(web_contents),
-      java_ref_(env, obj),
-      shortcut_info_(dom_distiller::url_utils::GetOriginalUrlFromDistillerUrl(
-                     web_contents->GetURL())),
-      add_shortcut_requested_(false),
-      manifest_icon_status_(MANIFEST_ICON_STATUS_NONE),
-      preferred_icon_size_in_px_(kPreferredIconSizeInDp *
-          gfx::Screen::GetScreenFor(web_contents->GetNativeView())->
-              GetPrimaryDisplay().device_scale_factor()),
-      weak_ptr_factory_(this) {
-}
-
-void ShortcutHelper::Initialize() {
-  // Send a message to the renderer to retrieve information about the page.
-  Send(new ChromeViewMsg_GetWebApplicationInfo(routing_id()));
+    : add_shortcut_pending_(false),
+      data_fetcher_(new ShortcutDataFetcher(web_contents, this)) {
+  java_ref_.Reset(env, obj);
 }
 
 ShortcutHelper::~ShortcutHelper() {
+  data_fetcher_->set_weak_observer(nullptr);
+  data_fetcher_ = nullptr;
 }
 
-void ShortcutHelper::OnDidGetWebApplicationInfo(
-    const WebApplicationInfo& received_web_app_info) {
-  // Sanitize received_web_app_info.
-  WebApplicationInfo web_app_info = received_web_app_info;
-  web_app_info.title =
-      web_app_info.title.substr(0, chrome::kMaxMetaTagAttributeLength);
-  web_app_info.description =
-      web_app_info.description.substr(0, chrome::kMaxMetaTagAttributeLength);
-
-  shortcut_info_.title = web_app_info.title.empty() ? web_contents()->GetTitle()
-                                                    : web_app_info.title;
-
-  if (web_app_info.mobile_capable == WebApplicationInfo::MOBILE_CAPABLE ||
-      web_app_info.mobile_capable == WebApplicationInfo::MOBILE_CAPABLE_APPLE) {
-    shortcut_info_.display = content::Manifest::DISPLAY_MODE_STANDALONE;
-  }
-
-  // Record what type of shortcut was added by the user.
-  switch (web_app_info.mobile_capable) {
-    case WebApplicationInfo::MOBILE_CAPABLE:
-      content::RecordAction(
-          base::UserMetricsAction("webapps.AddShortcut.AppShortcut"));
-      break;
-    case WebApplicationInfo::MOBILE_CAPABLE_APPLE:
-      content::RecordAction(
-          base::UserMetricsAction("webapps.AddShortcut.AppShortcutApple"));
-      break;
-    case WebApplicationInfo::MOBILE_CAPABLE_UNSPECIFIED:
-      content::RecordAction(
-          base::UserMetricsAction("webapps.AddShortcut.Bookmark"));
-      break;
-  }
-
-  web_contents()->GetManifest(base::Bind(&ShortcutHelper::OnDidGetManifest,
-                                         weak_ptr_factory_.GetWeakPtr()));
-}
-
-void ShortcutHelper::OnDidGetManifest(const content::Manifest& manifest) {
-  if (!manifest.IsEmpty()) {
-      content::RecordAction(
-          base::UserMetricsAction("webapps.AddShortcut.Manifest"));
-  }
-
-  shortcut_info_.UpdateFromManifest(manifest);
-
-  GURL icon_src = ManifestIconSelector::FindBestMatchingIcon(
-      manifest.icons,
-      kPreferredIconSizeInDp,
-      gfx::Screen::GetScreenFor(web_contents()->GetNativeView()));
-  if (icon_src.is_valid()) {
-    web_contents()->DownloadImage(icon_src,
-                                  false,
-                                  preferred_icon_size_in_px_,
-                                  false,
-                                  base::Bind(&ShortcutHelper::OnDidDownloadIcon,
-                                             weak_ptr_factory_.GetWeakPtr()));
-    manifest_icon_status_ = MANIFEST_ICON_STATUS_FETCHING;
-  }
-
-  // The ShortcutHelper is now able to notify its Java counterpart that it is
-  // initialized. OnInitialized method is not conceptually part of getting the
-  // manifest data but it happens that the initialization is finalized when
-  // these data are available.
+void ShortcutHelper::OnTitleAvailable(const base::string16& title) {
   JNIEnv* env = base::android::AttachCurrentThread();
-  ScopedJavaLocalRef<jobject> j_obj = java_ref_.get(env);
   ScopedJavaLocalRef<jstring> j_title =
-      base::android::ConvertUTF16ToJavaString(env, shortcut_info_.title);
-
-  Java_ShortcutHelper_onInitialized(env, j_obj.obj(), j_title.obj());
+      base::android::ConvertUTF16ToJavaString(env, title);
+  Java_ShortcutHelper_onTitleAvailable(env,
+                                       java_ref_.obj(),
+                                       j_title.obj());
 }
 
-void ShortcutHelper::OnDidDownloadIcon(int id,
-                                       int http_status_code,
-                                       const GURL& url,
-                                       const std::vector<SkBitmap>& bitmaps,
-                                       const std::vector<gfx::Size>& sizes) {
-  // If getting the candidate manifest icon failed, the ShortcutHelper should
-  // fallback to the favicon.
-  // If the user already requested to add the shortcut, it will do so but use
-  // the favicon instead.
-  // Otherwise, it sets the state as if there was no manifest icon pending.
-  if (bitmaps.empty()) {
-    if (add_shortcut_requested_)
-      AddShortcutUsingFavicon();
-    else
-      manifest_icon_status_ = MANIFEST_ICON_STATUS_NONE;
-    return;
-  }
+void ShortcutHelper::OnDataAvailable(const ShortcutInfo& info,
+                                     const SkBitmap& icon) {
+  JNIEnv* env = base::android::AttachCurrentThread();
+  ScopedJavaLocalRef<jobject> java_bitmap;
+  if (icon.getSize())
+    java_bitmap = gfx::ConvertToJavaBitmap(&icon);
 
-  // There might be multiple bitmaps returned. The one to pick is bigger or
-  // equal to the preferred size. |bitmaps| is ordered from bigger to smaller.
-  int preferred_bitmap_index = 0;
-  for (size_t i = 0; i < bitmaps.size(); ++i) {
-    if (bitmaps[i].height() < preferred_icon_size_in_px_)
-      break;
-    preferred_bitmap_index = i;
-  }
+  Java_ShortcutHelper_onIconAvailable(env,
+                                      java_ref_.obj(),
+                                      java_bitmap.obj());
 
-  manifest_icon_ = bitmaps[preferred_bitmap_index];
-  manifest_icon_status_ = MANIFEST_ICON_STATUS_DONE;
-
-  if (add_shortcut_requested_)
-    AddShortcutUsingManifestIcon();
+  if (add_shortcut_pending_)
+    AddShortcut(info, icon);
 }
 
-void ShortcutHelper::TearDown(JNIEnv*, jobject) {
-  Destroy();
-}
-
-void ShortcutHelper::Destroy() {
+void ShortcutHelper::Destroy(JNIEnv* env, jobject obj) {
   delete this;
 }
 
-void ShortcutHelper::AddShortcut(
-    JNIEnv* env,
-    jobject obj,
-    jstring jtitle,
-    jint launcher_large_icon_size) {
-  add_shortcut_requested_ = true;
+SkBitmap ShortcutHelper::FinalizeLauncherIcon(const SkBitmap& bitmap,
+                                              const GURL& url) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
+
+  // Determine a single color to use for the favicon if the favicon that is
+  // returned it is too low quality.
+  SkColor color = color_utils::CalculateKMeanColorOfBitmap(bitmap);
+  int dominant_red = SkColorGetR(color);
+  int dominant_green = SkColorGetG(color);
+  int dominant_blue = SkColorGetB(color);
+
+  // Make the icon acceptable for the Android launcher.
+  JNIEnv* env = base::android::AttachCurrentThread();
+  ScopedJavaLocalRef<jstring> java_url =
+      base::android::ConvertUTF8ToJavaString(env, url.spec());
+  ScopedJavaLocalRef<jobject> java_bitmap;
+  if (bitmap.getSize())
+    java_bitmap = gfx::ConvertToJavaBitmap(&bitmap);
+
+  base::android::ScopedJavaLocalRef<jobject> ref =
+      Java_ShortcutHelper_finalizeLauncherIcon(env,
+                                               java_url.obj(),
+                                               java_bitmap.obj(),
+                                               dominant_red,
+                                               dominant_green,
+                                               dominant_blue);
+  return gfx::CreateSkBitmapFromJavaBitmap(gfx::JavaBitmap(ref.obj()));
+}
+
+void ShortcutHelper::AddShortcut(JNIEnv* env, jobject obj, jstring jtitle) {
+  add_shortcut_pending_ = true;
 
   base::string16 title = base::android::ConvertJavaStringToUTF16(env, jtitle);
   if (!title.empty())
-    shortcut_info_.title = title;
+    data_fetcher_->shortcut_info().title = title;
 
-  switch (manifest_icon_status_) {
-    case MANIFEST_ICON_STATUS_NONE:
-      AddShortcutUsingFavicon();
-      break;
-    case MANIFEST_ICON_STATUS_FETCHING:
-      // ::OnDidDownloadIcon() will call AddShortcutUsingManifestIcon().
-      break;
-    case MANIFEST_ICON_STATUS_DONE:
-      AddShortcutUsingManifestIcon();
-      break;
+  if (data_fetcher_->is_ready()) {
+    // If the fetcher isn't ready yet, the shortcut will be added when it is
+    // via OnDataAvailable();
+    AddShortcut(data_fetcher_->shortcut_info(), data_fetcher_->shortcut_icon());
   }
 }
 
-void ShortcutHelper::AddShortcutUsingManifestIcon() {
+void ShortcutHelper::AddShortcut(const ShortcutInfo& info,
+                                 const SkBitmap& icon) {
+  DCHECK(add_shortcut_pending_);
+  if (!add_shortcut_pending_)
+    return;
+  add_shortcut_pending_ = false;
+
   RecordAddToHomescreen();
 
-  // Stop observing so we don't get destroyed while doing the last steps.
-  Observe(NULL);
-
-  base::WorkerPool::PostTask(
+  content::BrowserThread::PostTask(
+      content::BrowserThread::IO,
       FROM_HERE,
       base::Bind(&ShortcutHelper::AddShortcutInBackgroundWithSkBitmap,
-                 shortcut_info_,
-                 manifest_icon_,
-                 true),
-      true);
-
-  Destroy();
-}
-
-void ShortcutHelper::AddShortcutUsingFavicon() {
-  RecordAddToHomescreen();
-
-  Profile* profile =
-      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
-
-  // Grab the best, largest icon we can find to represent this bookmark.
-  // TODO(dfalcantara): Try combining with the new BookmarksHandler once its
-  //                    rewrite is further along.
-  std::vector<int> icon_types;
-  icon_types.push_back(favicon_base::FAVICON);
-  icon_types.push_back(favicon_base::TOUCH_PRECOMPOSED_ICON |
-                       favicon_base::TOUCH_ICON);
-  favicon::FaviconService* favicon_service =
-      FaviconServiceFactory::GetForProfile(profile,
-                                           ServiceAccessType::EXPLICIT_ACCESS);
-
-  // Using favicon if its size is not smaller than platform required size,
-  // otherwise using the largest icon among all avaliable icons.
-  int threshold_to_get_any_largest_icon = preferred_icon_size_in_px_ - 1;
-  favicon_service->GetLargestRawFaviconForPageURL(
-      shortcut_info_.url,
-      icon_types,
-      threshold_to_get_any_largest_icon,
-      base::Bind(&ShortcutHelper::OnDidGetFavicon,
-                 base::Unretained(this)),
-      &cancelable_task_tracker_);
-}
-
-void ShortcutHelper::OnDidGetFavicon(
-    const favicon_base::FaviconRawBitmapResult& bitmap_result) {
-  // Stop observing so we don't get destroyed while doing the last steps.
-  Observe(NULL);
-
-  base::WorkerPool::PostTask(
-      FROM_HERE,
-      base::Bind(&ShortcutHelper::AddShortcutInBackgroundWithRawBitmap,
-                 shortcut_info_,
-                 bitmap_result),
-      true);
-
-  Destroy();
-}
-
-bool ShortcutHelper::OnMessageReceived(const IPC::Message& message) {
-  bool handled = true;
-
-  IPC_BEGIN_MESSAGE_MAP(ShortcutHelper, message)
-    IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidGetWebApplicationInfo,
-                        OnDidGetWebApplicationInfo)
-    IPC_MESSAGE_UNHANDLED(handled = false)
-  IPC_END_MESSAGE_MAP()
-
-  return handled;
-}
-
-void ShortcutHelper::WebContentsDestroyed() {
-  Destroy();
+                 info,
+                 icon));
 }
 
 bool ShortcutHelper::RegisterShortcutHelper(JNIEnv* env) {
   return RegisterNativesImpl(env);
 }
 
-void ShortcutHelper::AddShortcutInBackgroundWithRawBitmap(
-    const ShortcutInfo& info,
-    const favicon_base::FaviconRawBitmapResult& bitmap_result) {
-  DCHECK(base::WorkerPool::RunsTasksOnCurrentThread());
-
-  SkBitmap icon_bitmap;
-  if (bitmap_result.is_valid()) {
-    gfx::PNGCodec::Decode(bitmap_result.bitmap_data->front(),
-                          bitmap_result.bitmap_data->size(),
-                          &icon_bitmap);
-  }
-
-  AddShortcutInBackgroundWithSkBitmap(info, icon_bitmap, true);
-}
-
+// static
 void ShortcutHelper::AddShortcutInBackgroundWithSkBitmap(
     const ShortcutInfo& info,
-    const SkBitmap& icon_bitmap,
-    const bool return_to_homescreen) {
-  DCHECK(base::WorkerPool::RunsTasksOnCurrentThread());
-
-  SkColor color = color_utils::CalculateKMeanColorOfBitmap(icon_bitmap);
-  int r_value = SkColorGetR(color);
-  int g_value = SkColorGetG(color);
-  int b_value = SkColorGetB(color);
+    const SkBitmap& icon_bitmap) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   // Send the data to the Java side to create the shortcut.
   JNIEnv* env = base::android::AttachCurrentThread();
@@ -337,19 +157,20 @@
       java_url.obj(),
       java_title.obj(),
       java_bitmap.obj(),
-      r_value,
-      g_value,
-      b_value,
       info.display == content::Manifest::DISPLAY_MODE_STANDALONE,
-      info.orientation,
-      return_to_homescreen);
+      info.orientation);
 }
 
 void ShortcutHelper::RecordAddToHomescreen() {
   // Record that the shortcut has been added, so no banners will be shown
   // for this app.
+  content::WebContents* web_contents = data_fetcher_->web_contents();
+  if (!web_contents)
+    return;
+
   AppBannerSettingsHelper::RecordBannerEvent(
-      web_contents(), web_contents()->GetURL(), shortcut_info_.url.spec(),
+      web_contents, web_contents->GetURL(),
+      data_fetcher_->shortcut_info().url.spec(),
       AppBannerSettingsHelper::APP_BANNER_EVENT_DID_ADD_TO_HOMESCREEN,
       base::Time::Now());
 }
diff --git a/chrome/browser/android/shortcut_helper.h b/chrome/browser/android/shortcut_helper.h
index 6200c3c89..9f872f2 100644
--- a/chrome/browser/android/shortcut_helper.h
+++ b/chrome/browser/android/shortcut_helper.h
@@ -8,11 +8,8 @@
 #include "base/android/jni_android.h"
 #include "base/android/jni_weak_ref.h"
 #include "base/basictypes.h"
-#include "base/task/cancelable_task_tracker.h"
+#include "chrome/browser/android/shortcut_data_fetcher.h"
 #include "chrome/browser/android/shortcut_info.h"
-#include "chrome/common/web_application_info.h"
-#include "components/favicon_base/favicon_types.h"
-#include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/manifest.h"
 
 namespace content {
@@ -27,102 +24,50 @@
 
 // ShortcutHelper is the C++ counterpart of org.chromium.chrome.browser's
 // ShortcutHelper in Java. The object is owned by the Java object. It is created
-// from there via a JNI (Initialize) call and can be destroyed from Java too
-// using TearDown. When the Java implementations calls AddShortcut, it gives up
-// the ownership of the object. The instance will then destroy itself when done.
-class ShortcutHelper : public content::WebContentsObserver {
+// from there via a JNI (Initialize) call and MUST BE DESTROYED via Destroy().
+class ShortcutHelper : public ShortcutDataFetcher::Observer {
  public:
   ShortcutHelper(JNIEnv* env,
                  jobject obj,
                  content::WebContents* web_contents);
 
-  // Initialize the helper by requesting the information about the page to the
-  // renderer process. The initialization is asynchronous and
-  // OnDidRetrieveWebappInformation is expected to be called when finished.
-  void Initialize();
-
-  // Called by the Java counter part to let the object knows that it can destroy
-  // itself.
-  void TearDown(JNIEnv* env, jobject obj);
-
-  // IPC message received when the initialization is finished.
-  void OnDidGetWebApplicationInfo(const WebApplicationInfo& web_app_info);
-
-  // Callback run when the Manifest is ready to be used.
-  void OnDidGetManifest(const content::Manifest& manifest);
-
-  // Adds a shortcut to the current URL to the Android home screen.
-  void AddShortcut(JNIEnv* env,
-                   jobject obj,
-                   jstring title,
-                   jint launcher_large_icon_size);
-
-  // Callback run when the requested Manifest icon is ready to be used.
-  void OnDidDownloadIcon(int id,
-                         int http_status_code,
-                         const GURL& url,
-                         const std::vector<SkBitmap>& bitmaps,
-                         const std::vector<gfx::Size>& sizes);
-
-  // Called after AddShortcut() and OnDidDownloadIcon() are run if
-  // OnDidDownloadIcon has a valid icon.
-  void AddShortcutUsingManifestIcon();
-
-  // Use FaviconService to get the best available favicon and create the
-  // shortcut using it. This is used when no Manifest icons are available or
-  // appropriate.
-  void AddShortcutUsingFavicon();
-
-  // Callback run when a favicon is received from GetFavicon() call.
-  void OnDidGetFavicon(
-      const favicon_base::FaviconRawBitmapResult& bitmap_result);
-
-  // WebContentsObserver
-  bool OnMessageReceived(const IPC::Message& message) override;
-  void WebContentsDestroyed() override;
-
-  // Adds a shortcut to the launcher using a FaviconRawBitmapResult.
-  // Must be called from a WorkerPool task.
-  static void AddShortcutInBackgroundWithRawBitmap(
-      const ShortcutInfo& info,
-      const favicon_base::FaviconRawBitmapResult& bitmap_result);
-
-  // Adds a shortcut to the launcher using a SkBitmap.
-  // Must be called from a WorkerPool task.
-  static void AddShortcutInBackgroundWithSkBitmap(
-      const ShortcutInfo& info,
-      const SkBitmap& icon_bitmap,
-      const bool return_to_homescreen);
+  // Called by the Java counterpart to destroy its native half.
+  void Destroy(JNIEnv* env, jobject obj);
 
   // Registers JNI hooks.
   static bool RegisterShortcutHelper(JNIEnv* env);
 
+  // Adds a shortcut to the current URL to the Android home screen.
+  void AddShortcut(JNIEnv* env, jobject obj, jstring title);
+
+  // Adds a shortcut to the launcher using a SkBitmap.
+  // Must be called on the IO thread.
+  static void AddShortcutInBackgroundWithSkBitmap(const ShortcutInfo& info,
+                                                  const SkBitmap& icon_bitmap);
+
+  // ShortcutDataFetcher::Observer
+  void OnTitleAvailable(const base::string16& title) override;
+  void OnDataAvailable(const ShortcutInfo& info, const SkBitmap& icon) override;
+  SkBitmap FinalizeLauncherIcon(const SkBitmap& icon, const GURL& url) override;
+
  private:
-  enum ManifestIconStatus {
-    MANIFEST_ICON_STATUS_NONE,
-    MANIFEST_ICON_STATUS_FETCHING,
-    MANIFEST_ICON_STATUS_DONE
-  };
+  virtual ~ShortcutHelper();
 
-  ~ShortcutHelper() override;
-
-  void Destroy();
+  // Called only when the ShortcutDataFetcher has retrieved all of the
+  // data needed to add the shortcut.
+  void AddShortcut(const ShortcutInfo& info, const SkBitmap& icon);
 
   void RecordAddToHomescreen();
 
-  JavaObjectWeakGlobalRef java_ref_;
+  // Points to the Java object.
+  base::android::ScopedJavaGlobalRef<jobject> java_ref_;
 
-  ShortcutInfo shortcut_info_;
-  SkBitmap manifest_icon_;
-  base::CancelableTaskTracker cancelable_task_tracker_;
+  // Whether the user has requested that a shortcut be added while a fetch was
+  // in progress.
+  bool add_shortcut_pending_;
 
-  bool add_shortcut_requested_;
-
-  ManifestIconStatus manifest_icon_status_;
-  const int preferred_icon_size_in_px_;
-  static const int kPreferredIconSizeInDp;
-
-  base::WeakPtrFactory<ShortcutHelper> weak_ptr_factory_;
+  // Fetches data required to add a shortcut.
+  scoped_refptr<ShortcutDataFetcher> data_fetcher_;
 
   DISALLOW_COPY_AND_ASSIGN(ShortcutHelper);
 };
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index f65bd2a..cf969975 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -161,6 +161,8 @@
       'browser/android/resource_mapper.h',
       'browser/android/seccomp_support_detector.cc',
       'browser/android/seccomp_support_detector.h',
+      'browser/android/shortcut_data_fetcher.cc',
+      'browser/android/shortcut_data_fetcher.h',
       'browser/android/shortcut_helper.cc',
       'browser/android/shortcut_helper.h',
       'browser/android/shortcut_info.cc',