Update WebAPKs even if the WebAPK start URL has no Web Manifest part 2/3

The class name ManifestUpgradeDetector does not make sense in the context of
the new ManifestUpgradeDetector#Callback. The new callback can be called up to
twice:
- Once after the initial page load. (Regardless of whether the page uses the
correct Web Manifest)
- Once after a page which uses the correct Web Manifest has finished loading.
If a Web Developer removes the Web Manifest from their site, the second call is
never done.

This CL:
- moves the check for "whether the fetched Web Manifest requires a WebAPK
upgrade" into WebApkUpdateManager.
- merges the manifest fetching parts of ManifestUpgradeDetector and
ManifestUpgradeDetectorFetcher into a new class WebApkUpdateDataFetcher

BUG=639536

Review-Url: https://codereview.chromium.org/2460253002
Cr-Commit-Position: refs/heads/master@{#437641}
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 1a7d07e..3489d39 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -397,6 +397,7 @@
     "//base:base_java",
     "//base:base_java_test_support",
     "//chrome/android:chrome_java",
+    "//chrome/android/webapk/libs/client:client_java",
     "//chrome/android/webapk/libs/common:common_java",
     "//chrome/android/webapk/libs/runtime_library:webapk_service_aidl_java",
     "//chrome/test/android:chrome_java_test_support",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetector.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetector.java
deleted file mode 100644
index e7f78fe..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetector.java
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright 2016 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.
-
-package org.chromium.chrome.browser.webapps;
-
-import android.text.TextUtils;
-
-import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.util.UrlUtilities;
-
-import java.util.Map;
-
-/**
- * This class checks whether the WebAPK needs to be re-installed and sends a request to re-install
- * the WebAPK if it needs to be re-installed.
- */
-public class ManifestUpgradeDetector implements ManifestUpgradeDetectorFetcher.Callback {
-    /** ManifestUpgradeDetector callback. */
-    public interface Callback {
-        /**
-         * Called when the Web Manifest for the initial URL load has been fetched (successfully or
-         * unsuccessfully).
-         * TODO(pkotwicz): Add calls to {@link #onFinishedFetchingWebManifestForInitialUrl()}.
-         * @param needsUpgrade Whether the WebAPK should be updated because the Web Manifest has
-         *                     changed. False if the Web Manifest could not be fetched.
-         * @param info         The fetched Web Manifest data. Null if the initial URL does not point
-         *                     to a Web Manifest.
-         * @param bestIconUrl  The icon URL in {@link WebApkInfo#iconUrlToMurmur2HashMap()} best
-         *                     suited for use as the launcher icon on this device.
-         */
-        void onFinishedFetchingWebManifestForInitialUrl(
-                boolean needsUpgrade, WebApkInfo info, String bestIconUrl);
-
-        /**
-         * Called when the Web Manifest has been successfully fetched (including on the initial URL
-         * load).
-         * @param needsUpgrade Whether the WebAPK should be updated because the Web Manifest has
-         *                     changed.
-         * @param info         The fetched Web Manifest data.
-         * @param bestIconUrl  The icon URL in {@link WebApkInfo#iconUrlToMurmur2HashMap()} best
-         *                     suited for use as the launcher icon on this device.
-         */
-        void onGotManifestData(boolean needsUpgrade, WebApkInfo info, String bestIconUrl);
-    }
-
-    private static final String TAG = "cr_UpgradeDetector";
-
-    /** The WebAPK's tab. */
-    private final Tab mTab;
-
-    /**
-     * Web Manifest data at time that the WebAPK was generated.
-     */
-    private WebApkInfo mInfo;
-
-    /**
-     * Fetches the WebAPK's Web Manifest from the web.
-     */
-    private ManifestUpgradeDetectorFetcher mFetcher;
-    private Callback mCallback;
-
-    /**
-     * Creates an instance of {@link ManifestUpgradeDetector}.
-     *
-     * @param tab WebAPK's tab.
-     * @param webappInfo Parameters used for generating the WebAPK. Extracted from WebAPK's Android
-     *                   manifest.
-     * @param info       Web Manifest data at the time that the WebAPK was generated.
-     * @param callback   Called once it has been determined whether the WebAPK needs to be upgraded.
-     */
-    public ManifestUpgradeDetector(Tab tab, WebApkInfo info, Callback callback) {
-        mTab = tab;
-        mInfo = info;
-        mCallback = callback;
-    }
-
-    /**
-     * Starts fetching the web manifest resources.
-     */
-    public boolean start() {
-        if (mFetcher != null) return false;
-
-        mFetcher = createFetcher();
-        return mFetcher.start(mTab, mInfo, this);
-    }
-
-    /**
-     * Creates ManifestUpgradeDataFetcher.
-     */
-    protected ManifestUpgradeDetectorFetcher createFetcher() {
-        return new ManifestUpgradeDetectorFetcher();
-    }
-
-    /**
-     * Puts the object in a state where it is safe to be destroyed.
-     */
-    public void destroy() {
-        if (mFetcher != null) {
-            mFetcher.destroy();
-        }
-        mFetcher = null;
-    }
-
-    /**
-     * Called when the updated Web Manifest has been fetched.
-     */
-    @Override
-    public void onGotManifestData(WebApkInfo fetchedInfo, String bestIconUrl) {
-        mFetcher.destroy();
-        mFetcher = null;
-
-        // TODO(hanxi): crbug.com/627824. Validate whether the new fetched data is
-        // WebAPK-compatible.
-        boolean upgrade = needsUpgrade(fetchedInfo, bestIconUrl);
-        mCallback.onGotManifestData(upgrade, fetchedInfo, bestIconUrl);
-    }
-
-    /**
-     * Checks whether the WebAPK needs to be upgraded provided the fetched manifest data.
-     */
-    private boolean needsUpgrade(WebApkInfo fetchedInfo, String bestIconUrl) {
-        // We should have computed the Murmur2 hash for the bitmap at the best icon URL for
-        // {@link fetchedInfo} (but not the other icon URLs.)
-        String fetchedBestIconMurmur2Hash = fetchedInfo.iconUrlToMurmur2HashMap().get(bestIconUrl);
-        String bestIconMurmur2Hash =
-                findMurmur2HashForUrlIgnoringFragment(mInfo.iconUrlToMurmur2HashMap(), bestIconUrl);
-
-        return !TextUtils.equals(bestIconMurmur2Hash, fetchedBestIconMurmur2Hash)
-                || !urlsMatchIgnoringFragments(
-                           mInfo.scopeUri().toString(), fetchedInfo.scopeUri().toString())
-                || !urlsMatchIgnoringFragments(
-                           mInfo.manifestStartUrl(), fetchedInfo.manifestStartUrl())
-                || !TextUtils.equals(mInfo.shortName(), fetchedInfo.shortName())
-                || !TextUtils.equals(mInfo.name(), fetchedInfo.name())
-                || mInfo.backgroundColor() != fetchedInfo.backgroundColor()
-                || mInfo.themeColor() != fetchedInfo.themeColor()
-                || mInfo.orientation() != fetchedInfo.orientation()
-                || mInfo.displayMode() != fetchedInfo.displayMode();
-    }
-
-    /**
-     * Returns the Murmur2 hash for entry in {@link iconUrlToMurmur2HashMap} whose canonical
-     * representation, ignoring fragments, matches {@link iconUrlToMatch}.
-     */
-    private String findMurmur2HashForUrlIgnoringFragment(
-            Map<String, String> iconUrlToMurmur2HashMap, String iconUrlToMatch) {
-        for (Map.Entry<String, String> entry : iconUrlToMurmur2HashMap.entrySet()) {
-            if (urlsMatchIgnoringFragments(entry.getKey(), iconUrlToMatch)) {
-                return entry.getValue();
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns whether the URLs match ignoring fragments. Canonicalizes the URLs prior to doing the
-     * comparison.
-     */
-    protected boolean urlsMatchIgnoringFragments(String url1, String url2) {
-        return UrlUtilities.urlsMatchIgnoringFragments(url1, url2);
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorFetcher.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcher.java
similarity index 60%
rename from chrome/android/java/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorFetcher.java
rename to chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcher.java
index f6f58954..12f827d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorFetcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcher.java
@@ -18,50 +18,56 @@
  * Downloads the Web Manifest if the web site still uses the {@link manifestUrl} passed to the
  * constructor.
  */
-public class ManifestUpgradeDetectorFetcher extends EmptyTabObserver {
-
-    /**
-     * Called once the Web Manifest has been downloaded.
-     */
-    public interface Callback {
+public class WebApkUpdateDataFetcher extends EmptyTabObserver {
+    /** Observes fetching of the Web Manifest. */
+    public interface Observer {
         /**
-         * @param fetchedInfo The fetched Web Manifest data.
-         * @param bestIconUrl Icon URL in {@link data} which is best suited for use as the launcher
-         *                    icon on this device.
+         * Called when the Web Manifest for the initial URL load has been fetched (successfully or
+         * unsuccessfully).
+         * TODO(pkotwicz): Add calls to {@link #onFinishedFetchingWebManifestForInitialUrl()}.
+         * @param fetchedInfo  The fetched Web Manifest data. Null if the initial URL does not point
+         *                     to a Web Manifest.
+         * @param bestIconUrl  The icon URL in {@link fetchedInfo#iconUrlToMurmur2HashMap()} best
+         *                     suited for use as the launcher icon on this device.
+         */
+        void onFinishedFetchingWebManifestForInitialUrl(WebApkInfo fetchedInfo, String bestIconUrl);
+
+        /**
+         * Called when the Web Manifest has been successfully fetched (including on the initial URL
+         * load).
+         * @param fetchedInfo  The fetched Web Manifest data.
+         * @param bestIconUrl  The icon URL in {@link fetchedInfo#iconUrlToMurmur2HashMap()} best
+         *                     suited for use as the launcher icon on this device.
          */
         void onGotManifestData(WebApkInfo fetchedInfo, String bestIconUrl);
     }
 
     /**
-     * Pointer to the native side ManifestUpgradeDetectorFetcher. The Java side owns the native side
-     * ManifestUpgradeDetectorFetcher.
+     * Pointer to the native side WebApkUpdateDataFetcher. The Java side owns the native side
+     * WebApkUpdateDataFetcher.
      */
     private long mNativePointer;
 
     /** The tab that is being observed. */
     private Tab mTab;
 
-    /**
-     * Web Manifest data at time that the WebAPK was generated.
-     */
+    /** Web Manifest data at the time that the WebAPK was generated. */
     private WebApkInfo mOldInfo;
 
-    private Callback mCallback;
+    private Observer mObserver;
 
-    /**
-     * Starts fetching the web manifest resources.
-     * @param callback Called once the Web Manifest has been downloaded.
-     */
-    public boolean start(Tab tab, WebApkInfo oldInfo, Callback callback) {
+    /** Starts observing page loads in order to fetch the Web Manifest after each page load. */
+    public boolean start(Tab tab, WebApkInfo oldInfo, Observer observer) {
         if (tab.getWebContents() == null || TextUtils.isEmpty(oldInfo.manifestUrl())) {
             return false;
         }
 
         mTab = tab;
         mOldInfo = oldInfo;
-        mNativePointer = nativeInitialize(mOldInfo.scopeUri().toString(), mOldInfo.manifestUrl());
-        mCallback = callback;
+        mObserver = observer;
+
         mTab.addObserver(this);
+        mNativePointer = nativeInitialize(mOldInfo.scopeUri().toString(), mOldInfo.manifestUrl());
         nativeStart(mNativePointer, mTab.getWebContents());
         return true;
     }
@@ -87,7 +93,7 @@
     }
 
     /**
-     * Updates which WebContents the native ManifestUpgradeDetectorFetcher is monitoring.
+     * Updates which WebContents the native WebApkUpdateDataFetcher is monitoring.
      */
     private void updatePointers() {
         nativeReplaceWebContents(mNativePointer, mTab.getWebContents());
@@ -112,13 +118,12 @@
                 mOldInfo.source(), themeColor, backgroundColor, mOldInfo.webApkPackageName(),
                 mOldInfo.shellApkVersion(), mOldInfo.manifestUrl(), startUrl,
                 iconUrlToMurmur2HashMap);
-        mCallback.onGotManifestData(info, bestIconUrl);
+        mObserver.onGotManifestData(info, bestIconUrl);
     }
 
     private native long nativeInitialize(String scope, String webManifestUrl);
     private native void nativeReplaceWebContents(
-            long nativeManifestUpgradeDetectorFetcher, WebContents webContents);
-    private native void nativeDestroy(long nativeManifestUpgradeDetectorFetcher);
-    private native void nativeStart(
-            long nativeManifestUpgradeDetectorFetcher, WebContents webContents);
+            long nativeWebApkUpdateDataFetcher, WebContents webContents);
+    private native void nativeDestroy(long nativeWebApkUpdateDataFetcher);
+    private native void nativeStart(long nativeWebApkUpdateDataFetcher, WebContents webContents);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
index 8aacf80..66c0bc5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
@@ -9,6 +9,7 @@
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.provider.Settings;
+import android.text.TextUtils;
 
 import org.chromium.base.CommandLine;
 import org.chromium.base.ContextUtils;
@@ -16,15 +17,17 @@
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.util.UrlUtilities;
 import org.chromium.webapk.lib.client.WebApkVersion;
 
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 /**
  * WebApkUpdateManager manages when to check for updates to the WebAPK's Web Manifest, and sends
  * an update request to the WebAPK Server when an update is needed.
  */
-public class WebApkUpdateManager implements ManifestUpgradeDetector.Callback {
+public class WebApkUpdateManager implements WebApkUpdateDataFetcher.Observer {
     private static final String TAG = "WebApkUpdateManager";
 
     /** Number of milliseconds between checks for whether the WebAPK's Web Manifest has changed. */
@@ -44,7 +47,7 @@
      */
     private boolean mPreviousUpdateSucceeded;
 
-    private ManifestUpgradeDetector mUpgradeDetector;
+    private WebApkUpdateDataFetcher mFetcher;
 
     /**
      * Checks whether the WebAPK's Web Manifest has changed. Requests an updated WebAPK if the
@@ -60,27 +63,28 @@
 
         if (!shouldCheckIfWebManifestUpdated(storage, mInfo, mPreviousUpdateSucceeded)) return;
 
-        mUpgradeDetector = buildManifestUpgradeDetector(tab, mInfo);
-        mUpgradeDetector.start();
+        mFetcher = buildFetcher();
+        mFetcher.start(tab, mInfo, this);
     }
 
     public void destroy() {
-        destroyUpgradeDetector();
+        destroyFetcher();
     }
 
     @Override
     public void onFinishedFetchingWebManifestForInitialUrl(
-            boolean needsUpgrade, WebApkInfo info, String bestIconUrl) {
-        onGotManifestData(needsUpgrade, info, bestIconUrl);
+            WebApkInfo fetchedInfo, String bestIconUrl) {
+        onGotManifestData(fetchedInfo, bestIconUrl);
     }
 
     @Override
-    public void onGotManifestData(boolean needsUpgrade, WebApkInfo info, String bestIconUrl) {
+    public void onGotManifestData(WebApkInfo fetchedInfo, String bestIconUrl) {
         WebappDataStorage storage = WebappRegistry.getInstance().getWebappDataStorage(mInfo.id());
         storage.updateTimeOfLastCheckForUpdatedWebManifest();
 
-        boolean gotManifest = (info != null);
-        needsUpgrade |= isShellApkVersionOutOfDate(mInfo);
+        boolean gotManifest = (fetchedInfo != null);
+        boolean needsUpgrade = isShellApkVersionOutOfDate(mInfo)
+                || (gotManifest && needsUpdate(mInfo, fetchedInfo, bestIconUrl));
         Log.v(TAG, "Got Manifest: " + gotManifest);
         Log.v(TAG, "WebAPK upgrade needed: " + needsUpgrade);
 
@@ -97,7 +101,7 @@
         // Web Manifests as the user navigates. For instance, the WebAPK's start_url might not
         // point to a Web Manifest because start_url redirects to the WebAPK's main page.
         if (gotManifest || needsUpgrade) {
-            destroyUpgradeDetector();
+            destroyFetcher();
         }
 
         if (!needsUpgrade) {
@@ -111,8 +115,8 @@
         // {@link onBuiltWebApk} being called.
         recordUpdate(storage, false);
 
-        if (info != null) {
-            updateAsync(info, bestIconUrl);
+        if (fetchedInfo != null) {
+            updateAsync(fetchedInfo, bestIconUrl);
             return;
         }
 
@@ -122,10 +126,10 @@
     }
 
     /**
-     * Builds {@link ManifestUpgradeDetector}. In a separate function for the sake of tests.
+     * Builds {@link WebApkUpdateDataFetcher}. In a separate function for the sake of tests.
      */
-    protected ManifestUpgradeDetector buildManifestUpgradeDetector(Tab tab, WebApkInfo info) {
-        return new ManifestUpgradeDetector(tab, info, this);
+    protected WebApkUpdateDataFetcher buildFetcher() {
+        return new WebApkUpdateDataFetcher();
     }
 
     /**
@@ -142,13 +146,13 @@
     }
 
     /**
-     * Destroys {@link mUpgradeDetector}. In a separate function for the sake of tests.
+     * Destroys {@link mFetcher}. In a separate function for the sake of tests.
      */
-    protected void destroyUpgradeDetector() {
-        if (mUpgradeDetector == null) return;
+    protected void destroyFetcher() {
+        if (mFetcher == null) return;
 
-        mUpgradeDetector.destroy();
-        mUpgradeDetector = null;
+        mFetcher.destroy();
+        mFetcher = null;
     }
 
     /** Returns the current time. In a separate function for the sake of testing. */
@@ -201,6 +205,11 @@
      */
     private boolean shouldCheckIfWebManifestUpdated(
             WebappDataStorage storage, WebApkInfo info, boolean previousUpdateSucceeded) {
+        if (CommandLine.getInstance().hasSwitch(
+                    ChromeSwitches.CHECK_FOR_WEB_MANIFEST_UPDATE_ON_STARTUP)) {
+            return true;
+        }
+
         // Updating WebAPKs requires "installation from unknown sources" to be enabled. It is
         // confusing for a user to see a dialog asking them to enable "installation from unknown
         // sources" when they are in the middle of using the WebAPK (as opposed to after requesting
@@ -209,11 +218,6 @@
             return false;
         }
 
-        if (CommandLine.getInstance().hasSwitch(
-                    ChromeSwitches.CHECK_FOR_WEB_MANIFEST_UPDATE_ON_STARTUP)) {
-            return true;
-        }
-
         if (isShellApkVersionOutOfDate(info)) return true;
 
         long now = currentTimeMillis();
@@ -253,6 +257,55 @@
     }
 
     /**
+     * Checks whether the WebAPK needs to be updated.
+     * @param info               Meta data from WebAPK's Android Manifest.
+     * @param fetchedInfo        Fetched data for Web Manifest.
+     * @param bestFetchedIconUrl The icon URL in {@link fetchedInfo#iconUrlToMurmur2HashMap()} best
+     *                           suited for use as the launcher icon on this device.
+     */
+    private boolean needsUpdate(WebApkInfo info, WebApkInfo fetchedInfo, String bestIconUrl) {
+        // We should have computed the Murmur2 hash for the bitmap at the best icon URL for
+        // {@link fetchedInfo} (but not the other icon URLs.)
+        String fetchedBestIconMurmur2Hash = fetchedInfo.iconUrlToMurmur2HashMap().get(bestIconUrl);
+        String bestIconMurmur2Hash =
+                findMurmur2HashForUrlIgnoringFragment(mInfo.iconUrlToMurmur2HashMap(), bestIconUrl);
+
+        return !TextUtils.equals(bestIconMurmur2Hash, fetchedBestIconMurmur2Hash)
+                || !urlsMatchIgnoringFragments(
+                           mInfo.scopeUri().toString(), fetchedInfo.scopeUri().toString())
+                || !urlsMatchIgnoringFragments(
+                           mInfo.manifestStartUrl(), fetchedInfo.manifestStartUrl())
+                || !TextUtils.equals(mInfo.shortName(), fetchedInfo.shortName())
+                || !TextUtils.equals(mInfo.name(), fetchedInfo.name())
+                || mInfo.backgroundColor() != fetchedInfo.backgroundColor()
+                || mInfo.themeColor() != fetchedInfo.themeColor()
+                || mInfo.orientation() != fetchedInfo.orientation()
+                || mInfo.displayMode() != fetchedInfo.displayMode();
+    }
+
+    /**
+     * Returns the Murmur2 hash for entry in {@link iconUrlToMurmur2HashMap} whose canonical
+     * representation, ignoring fragments, matches {@link iconUrlToMatch}.
+     */
+    private String findMurmur2HashForUrlIgnoringFragment(
+            Map<String, String> iconUrlToMurmur2HashMap, String iconUrlToMatch) {
+        for (Map.Entry<String, String> entry : iconUrlToMurmur2HashMap.entrySet()) {
+            if (urlsMatchIgnoringFragments(entry.getKey(), iconUrlToMatch)) {
+                return entry.getValue();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns whether the urls match ignoring fragments. Canonicalizes the URLs prior to doing the
+     * comparison.
+     */
+    protected boolean urlsMatchIgnoringFragments(String url1, String url2) {
+        return UrlUtilities.urlsMatchIgnoringFragments(url1, url2);
+    }
+
+    /**
      * Called after either a request to update the WebAPK has been sent or the update process
      * fails.
      */
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index dbe2eb09c..bc673e58 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -1030,8 +1030,6 @@
   "java/src/org/chromium/chrome/browser/webapps/ChromeWebApkHost.java",
   "java/src/org/chromium/chrome/browser/webapps/FullScreenActivity.java",
   "java/src/org/chromium/chrome/browser/webapps/FullScreenDelegateFactory.java",
-  "java/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetector.java",
-  "java/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorFetcher.java",
   "java/src/org/chromium/chrome/browser/webapps/WebApkActivity.java",
   "java/src/org/chromium/chrome/browser/webapps/WebApkActivity0.java",
   "java/src/org/chromium/chrome/browser/webapps/WebApkActivity1.java",
@@ -1046,6 +1044,7 @@
   "java/src/org/chromium/chrome/browser/webapps/WebApkInfo.java",
   "java/src/org/chromium/chrome/browser/webapps/WebApkInstaller.java",
   "java/src/org/chromium/chrome/browser/webapps/WebApkManagedActivity.java",
+  "java/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcher.java",
   "java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java",
   "java/src/org/chromium/chrome/browser/webapps/WebApkVersionManager.java",
   "java/src/org/chromium/chrome/browser/webapps/WebappActivity.java",
@@ -1442,8 +1441,8 @@
   "javatests/src/org/chromium/chrome/browser/webapps/AddToHomescreenDialogTest.java",
   "javatests/src/org/chromium/chrome/browser/webapps/AddToHomescreenManagerTest.java",
   "javatests/src/org/chromium/chrome/browser/webapps/TestFetchStorageCallback.java",
-  "javatests/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorFetcherTest.java",
-  "javatests/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorTest.java",
+  "javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcherTest.java",
+  "javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java",
   "javatests/src/org/chromium/chrome/browser/webapps/WebappActivityTestBase.java",
   "javatests/src/org/chromium/chrome/browser/webapps/WebappAuthenticatorTest.java",
   "javatests/src/org/chromium/chrome/browser/webapps/WebappDirectoryManagerTest.java",
@@ -1520,7 +1519,6 @@
   "junit/src/org/chromium/chrome/browser/snackbar/SnackbarCollectionUnitTest.java",
   "junit/src/org/chromium/chrome/browser/superviseduser/SupervisedUserContentProviderUnitTest.java",
   "junit/src/org/chromium/chrome/browser/tabstate/TabStateUnitTest.java",
-  "junit/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorTest.java",
   "junit/src/org/chromium/chrome/browser/webapps/WebappDataStorageTest.java",
   "junit/src/org/chromium/chrome/browser/webapps/WebappRegistryTest.java",
   "junit/src/org/chromium/chrome/browser/webapps/WebApkInfoTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorFetcherTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcherTest.java
similarity index 77%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorFetcherTest.java
rename to chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcherTest.java
index 2e5d912..0472215 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorFetcherTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcherTest.java
@@ -19,9 +19,9 @@
 import java.util.HashMap;
 
 /**
- * Tests the ManifestUpgradeDetectorFetcher.
+ * Tests the WebApkUpdateDataFetcher.
  */
-public class ManifestUpgradeDetectorFetcherTest extends ChromeTabbedActivityTestBase {
+public class WebApkUpdateDataFetcherTest extends ChromeTabbedActivityTestBase {
 
     private static final String WEB_MANIFEST_URL1 = "/chrome/test/data/banners/manifest.json";
     // Name for Web Manifest at {@link WEB_MANIFEST_URL1}.
@@ -48,11 +48,20 @@
     // CallbackHelper which blocks until the {@link ManifestUpgradeDetectorFetcher.Callback}
     // callback is called.
     private static class CallbackWaiter
-            extends CallbackHelper implements ManifestUpgradeDetectorFetcher.Callback {
+            extends CallbackHelper implements WebApkUpdateDataFetcher.Observer {
         private String mName;
         private String mBestIconMurmur2Hash;
 
         @Override
+        public void onFinishedFetchingWebManifestForInitialUrl(
+                WebApkInfo fetchedInfo, String bestIconUrl) {
+            assertNull(mName);
+            mName = fetchedInfo.name();
+            mBestIconMurmur2Hash = fetchedInfo.iconUrlToMurmur2HashMap().get(bestIconUrl);
+            notifyCalled();
+        }
+
+        @Override
         public void onGotManifestData(WebApkInfo fetchedInfo, String bestIconUrl) {
             assertNull(mName);
             mName = fetchedInfo.name();
@@ -89,31 +98,30 @@
     }
 
     /**
-     * Starts a ManifestUpgradeDetectorFetcher. Calls {@link callback} once the fetcher is done.
+     * Starts a WebApkUpdateDataFetcher. Calls {@link callback} once the fetcher is done.
      */
-    private void startManifestUpgradeDetectorFetcher(final String scopeUrl,
-            final String manifestUrl, final ManifestUpgradeDetectorFetcher.Callback callback) {
-        final ManifestUpgradeDetectorFetcher fetcher = new ManifestUpgradeDetectorFetcher();
+    private void startWebApkUpdateDataFetcher(final String scopeUrl,
+            final String manifestUrl, final WebApkUpdateDataFetcher.Observer observer) {
+        final WebApkUpdateDataFetcher fetcher = new WebApkUpdateDataFetcher();
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
                 WebApkInfo oldInfo = WebApkInfo.create("", "", scopeUrl, null, null, null, -1, -1,
                         -1, -1, -1, "random.package", -1, manifestUrl, null,
                         new HashMap<String, String>());
-                fetcher.start(mTab, oldInfo, callback);
+                fetcher.start(mTab, oldInfo, observer);
             }
         });
     }
 
     /**
-     * Test starting ManifestUpgradeDetectorFetcher while a page with the desired manifest URL is
-     * loading.
+     * Test starting WebApkUpdateDataFetcher while a page with the desired manifest URL is loading.
      */
     @MediumTest
     @Feature({"WebApk"})
     public void testLaunchWithDesiredManifestUrl() throws Exception {
         CallbackWaiter waiter = new CallbackWaiter();
-        startManifestUpgradeDetectorFetcher(mTestServer.getURL(WEB_MANIFEST_SCOPE),
+        startWebApkUpdateDataFetcher(mTestServer.getURL(WEB_MANIFEST_SCOPE),
                 mTestServer.getURL(WEB_MANIFEST_URL1), waiter);
 
         WebappTestPage.navigateToPageWithServiceWorkerAndManifest(
@@ -124,16 +132,16 @@
     }
 
     /**
-     * Test starting ManifestUpgradeDetectorFetcher on page which uses a different manifest URL than
-     * the ManifestUpgradeDetectorFetcher is looking for. Check that the callback is only called
-     * once the user navigates to a page which uses the desired manifest URL.
+     * Test starting WebApkUpdateDataFetcher on page which uses a different manifest URL than the
+     * ManifestUpgradeDetectorFetcher is looking for. Check that the callback is only called once
+     * the user navigates to a page which uses the desired manifest URL.
      */
     @MediumTest
     @Feature({"Webapps"})
     @RetryOnFailure
     public void testLaunchWithDifferentManifestUrl() throws Exception {
         CallbackWaiter waiter = new CallbackWaiter();
-        startManifestUpgradeDetectorFetcher(mTestServer.getURL(WEB_MANIFEST_SCOPE),
+        startWebApkUpdateDataFetcher(mTestServer.getURL(WEB_MANIFEST_SCOPE),
                 mTestServer.getURL(WEB_MANIFEST_URL2), waiter);
 
         WebappTestPage.navigateToPageWithServiceWorkerAndManifest(
@@ -153,7 +161,7 @@
     @Feature({"Webapps"})
     public void testLargeIconMurmur2Hash() throws Exception {
         CallbackWaiter waiter = new CallbackWaiter();
-        startManifestUpgradeDetectorFetcher(mTestServer.getURL(WEB_MANIFEST_SCOPE),
+        startWebApkUpdateDataFetcher(mTestServer.getURL(WEB_MANIFEST_SCOPE),
                 mTestServer.getURL(WEB_MANIFEST_WITH_LONG_ICON_MURMUR2_HASH), waiter);
         WebappTestPage.navigateToPageWithServiceWorkerAndManifest(
                 mTestServer, mTab, WEB_MANIFEST_WITH_LONG_ICON_MURMUR2_HASH);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java
similarity index 66%
rename from chrome/android/javatests/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorTest.java
rename to chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java
index eeadabf..8f83831 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java
@@ -9,22 +9,28 @@
 
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.blink_public.platform.WebDisplayMode;
+import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeTabbedActivityTestBase;
 import org.chromium.chrome.test.util.browser.WebappTestPage;
 import org.chromium.content_public.common.ScreenOrientationValues;
 import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.webapk.lib.client.WebApkVersion;
 
 import java.util.HashMap;
 import java.util.Map;
 
 /**
- * Tests ManifestUpgradeDetector. This class contains tests which cannot be done as JUnit tests.
+ * Tests WebApkUpdateManager. This class contains tests which cannot be done as JUnit tests.
  */
-public class ManifestUpgradeDetectorTest extends ChromeTabbedActivityTestBase {
[email protected](ChromeSwitches.CHECK_FOR_WEB_MANIFEST_UPDATE_ON_STARTUP)
+public class WebApkUpdateManagerTest extends ChromeTabbedActivityTestBase {
 
+    private static final String WEBAPK_PACKAGE = "test.package";
+    private static final String WEBAPK_ID = "webapk_id";
     private static final String WEBAPK_MANIFEST_URL =
             "/chrome/test/data/banners/manifest_one_icon.json";
 
@@ -44,29 +50,38 @@
     private EmbeddedTestServer mTestServer;
     private Tab mTab;
 
-    // CallbackHelper which blocks until the {@link ManifestUpgradeDetector.Callback} callback is
-    // called.
-    private static class CallbackWaiter
-            extends CallbackHelper implements ManifestUpgradeDetector.Callback {
-        private String mName;
-        private boolean mNeedsUpgrade;
+    /**
+     * Subclass of {@link WebApkUpdateManager} which notifies the {@link CallbackHelper} passed to
+     * the constructor when it has been determined whether an update is needed.
+     */
+    private static class TestWebApkUpdateManager extends WebApkUpdateManager {
+        private CallbackHelper mWaiter;
+        private boolean mNeedsUpdate = false;
+
+        public TestWebApkUpdateManager(CallbackHelper waiter) {
+            mWaiter = waiter;
+        }
 
         @Override
         public void onFinishedFetchingWebManifestForInitialUrl(
-                boolean needsUpgrade, WebApkInfo info, String bestIconUrl) {}
-
-        public void onGotManifestData(boolean needsUpgrade, WebApkInfo info, String bestIconUrl) {
-            mName = info.name();
-            mNeedsUpgrade = needsUpgrade;
-            notifyCalled();
+                WebApkInfo fetchedInfo, String bestIconUrl) {
+            super.onFinishedFetchingWebManifestForInitialUrl(fetchedInfo, bestIconUrl);
+            mWaiter.notifyCalled();
         }
 
-        public String name() {
-            return mName;
+        @Override
+        public void onGotManifestData(WebApkInfo fetchedInfo, String bestIconUrl) {
+            super.onGotManifestData(fetchedInfo, bestIconUrl);
+            mWaiter.notifyCalled();
         }
 
-        public boolean needsUpgrade() {
-            return mNeedsUpgrade;
+        @Override
+        protected void updateAsync(WebApkInfo fetchedInfo, String bestIconUrl) {
+            mNeedsUpdate = true;
+        }
+
+        public boolean needsUpdate() {
+            return mNeedsUpdate;
         }
     }
 
@@ -108,6 +123,10 @@
         Context context = getInstrumentation().getTargetContext();
         mTestServer = EmbeddedTestServer.createAndStartServer(context);
         mTab = getActivity().getActivityTab();
+
+        TestFetchStorageCallback callback = new TestFetchStorageCallback();
+        WebappRegistry.getInstance().register(WEBAPK_ID, callback);
+        callback.waitForCallback(0);
     }
 
     @Override
@@ -121,24 +140,26 @@
         startMainActivityOnBlankPage();
     }
 
-    /**
-     * Starts a ManifestUpgradeDetector. Calls {@link callback} once the detector has fetched the
-     * Web Manifest and determined whether the WebAPK needs to be upgraded.
-     */
-    private void startManifestUpgradeDetector(
-            CreationData creationData, final ManifestUpgradeDetector.Callback callback) {
-        WebApkInfo info = WebApkInfo.create("", "", creationData.scope, null, creationData.name,
-                creationData.shortName, creationData.displayMode, creationData.orientation, 0,
-                creationData.themeColor, creationData.backgroundColor, "", 0,
-                creationData.manifestUrl, creationData.startUrl,
-                creationData.iconUrlToMurmur2HashMap);
-        final ManifestUpgradeDetector detector = new ManifestUpgradeDetector(mTab, info, callback);
+     /** Checks whether a WebAPK update is needed. */
+    private boolean checkUpdateNeeded(final CreationData creationData) throws Exception {
+        CallbackHelper waiter = new CallbackHelper();
+        final TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(waiter);
+
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                detector.start();
+                WebApkInfo info = WebApkInfo.create(WEBAPK_ID, "", creationData.scope, null,
+                        creationData.name, creationData.shortName, creationData.displayMode,
+                        creationData.orientation, 0, creationData.themeColor,
+                        creationData.backgroundColor, "", WebApkVersion.CURRENT_SHELL_APK_VERSION,
+                        creationData.manifestUrl, creationData.startUrl,
+                        creationData.iconUrlToMurmur2HashMap);
+                updateManager.updateIfNeeded(mTab, info);
             }
         });
+        waiter.waitForCallback(0);
+
+        return updateManager.needsUpdate();
     }
 
     /**
@@ -150,20 +171,14 @@
     @MediumTest
     @Feature({"WebApk"})
     public void testCanonicalUrlsIdenticalShouldNotUpgrade() throws Exception {
-        CallbackWaiter waiter = new CallbackWaiter();
-
         // URL canonicalization should replace "%74" with 't'.
         CreationData creationData = defaultCreationData(mTestServer);
         creationData.startUrl = mTestServer.getURL(
                 "/chrome/test/data/banners/manifest_%74est_page.html");
-        startManifestUpgradeDetector(creationData, waiter);
 
         WebappTestPage.navigateToPageWithServiceWorkerAndManifest(
                 mTestServer, mTab, WEBAPK_MANIFEST_URL);
-        waiter.waitForCallback(0);
-
-        assertEquals(WEBAPK_NAME, waiter.name());
-        assertFalse(waiter.needsUpgrade());
+        assertFalse(checkUpdateNeeded(creationData));
     }
 
     /**
@@ -172,19 +187,13 @@
     @MediumTest
     @Feature({"WebApk"})
     public void testCanonicalUrlsDifferentShouldUpgrade() throws Exception {
-        CallbackWaiter waiter = new CallbackWaiter();
-
         // URL canonicalization should replace "%62" with 'b'.
         CreationData creationData = defaultCreationData(mTestServer);
         creationData.startUrl = mTestServer.getURL(
                 "/chrome/test/data/banners/manifest_%62est_page.html");
-        startManifestUpgradeDetector(creationData, waiter);
 
         WebappTestPage.navigateToPageWithServiceWorkerAndManifest(
                 mTestServer, mTab, WEBAPK_MANIFEST_URL);
-        waiter.waitForCallback(0);
-
-        assertEquals(WEBAPK_NAME, waiter.name());
-        assertTrue(waiter.needsUpgrade());
+        assertTrue(checkUpdateNeeded(creationData));
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java
index 0105256..aba336d1 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java
@@ -8,7 +8,12 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.os.Bundle;
 import android.provider.Settings;
+import android.text.TextUtils;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -16,19 +21,22 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowBitmap;
 
 import org.chromium.base.CommandLine;
 import org.chromium.base.ContextUtils;
 import org.chromium.blink_public.platform.WebDisplayMode;
 import org.chromium.chrome.browser.ShortcutHelper;
-import org.chromium.chrome.browser.ShortcutSource;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.content_public.common.ScreenOrientationValues;
 import org.chromium.testing.local.LocalRobolectricTestRunner;
 import org.chromium.webapk.lib.client.WebApkVersion;
+import org.chromium.webapk.lib.common.WebApkConstants;
+import org.chromium.webapk.lib.common.WebApkMetaDataKeys;
 import org.chromium.webapk.test.WebApkTestHelper;
 
 import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Unit tests for WebApkUpdateManager.
@@ -37,13 +45,25 @@
 @Config(manifest = Config.NONE)
 public class WebApkUpdateManagerTest {
     /** WebAPK's id in {@link WebAppDataStorage}. */
-    private static final String WEBAPK_ID = "id";
+    private static final String WEBAPK_ID =
+            WebApkConstants.WEBAPK_ID_PREFIX + WebApkTestHelper.WEBAPK_PACKAGE_NAME;
 
-    /** Value of the "name" <meta-data> tag in the Android Manifest. */
-    private static final String ANDROID_MANIFEST_NAME = "Android Manifest name";
+    /** Web Manifest URL */
+    private static final String WEB_MANIFEST_URL = "manifest.json";
 
-    /** Value of the "name" property in the Web Manifest. */
-    private static final String WEB_MANIFEST_NAME = "Web Manifest name";
+    private static final String START_URL = "/start_url.html";
+    private static final String SCOPE_URL = "/";
+    private static final String NAME = "Long Name";
+    private static final String SHORT_NAME = "Short Name";
+    private static final String ICON_URL = "/icon.png";
+    private static final String ICON_MURMUR2_HASH = "3";
+    private static final int DISPLAY_MODE = WebDisplayMode.Undefined;
+    private static final int ORIENTATION = ScreenOrientationValues.DEFAULT;
+    private static final long THEME_COLOR = 1L;
+    private static final long BACKGROUND_COLOR = 2L;
+
+    /** Different name than the one used in {@link defaultManifestData()}. */
+    private static final String DIFFERENT_NAME = "Different Name";
 
     /** {@link WebappDataStorage#Clock} subclass which enables time to be manually advanced. */
     private static class MockClock extends WebappDataStorage.Clock {
@@ -60,21 +80,16 @@
         }
     }
 
-    /** Mock {@link ManifestUpgradeDetector}. */
-    private static class TestManifestUpgradeDetector extends ManifestUpgradeDetector {
+    /** Mock {@link WebApkUpdateDataFetcher}. */
+    private static class TestWebApkUpdateDataFetcher extends WebApkUpdateDataFetcher {
         private boolean mStarted;
 
-        public TestManifestUpgradeDetector(
-                Tab tab, WebApkInfo info, ManifestUpgradeDetector.Callback callback) {
-            super(tab, info, callback);
-        }
-
         public boolean wasStarted() {
             return mStarted;
         }
 
         @Override
-        public boolean start() {
+        public boolean start(Tab tab, WebApkInfo oldInfo, Observer observer) {
             mStarted = true;
             return true;
         }
@@ -82,10 +97,10 @@
 
     private static class TestWebApkUpdateManager extends WebApkUpdateManager {
         private WebappDataStorage.Clock mClock;
-        private TestManifestUpgradeDetector mUpgradeDetector;
-        private int mNumUpdatesRequested;
+        private TestWebApkUpdateDataFetcher mFetcher;
+        private boolean mUpdateRequested;
         private String mUpdateName;
-        private boolean mDestroyedManifestUpgradeDetector;
+        private boolean mDestroyedFetcher;
 
         public TestWebApkUpdateManager(WebappDataStorage.Clock clock) {
             mClock = clock;
@@ -95,21 +110,14 @@
          * Returns whether the is-update-needed check has been triggered.
          */
         public boolean updateCheckStarted() {
-            return mUpgradeDetector != null && mUpgradeDetector.wasStarted();
+            return mFetcher != null && mFetcher.wasStarted();
         }
 
         /**
          * Returns whether an update has been requested.
          */
         public boolean updateRequested() {
-            return mNumUpdatesRequested > 0;
-        }
-
-        /**
-         * Returns the number of updates which have been requested.
-         */
-        public int numUpdatesRequested() {
-            return mNumUpdatesRequested;
+            return mUpdateRequested;
         }
 
         /**
@@ -119,68 +127,165 @@
             return mUpdateName;
         }
 
-        public boolean destroyedManifestUpgradeDetector() {
-            return mDestroyedManifestUpgradeDetector;
+        public boolean destroyedFetcher() {
+            return mDestroyedFetcher;
         }
 
         @Override
-        protected ManifestUpgradeDetector buildManifestUpgradeDetector(Tab tab, WebApkInfo info) {
-            mUpgradeDetector = new TestManifestUpgradeDetector(tab, info, this);
-            return mUpgradeDetector;
+        protected WebApkUpdateDataFetcher buildFetcher() {
+            mFetcher = new TestWebApkUpdateDataFetcher();
+            return mFetcher;
         }
 
         @Override
         protected void updateAsync(WebApkInfo info, String bestIconUrl) {
-            ++mNumUpdatesRequested;
+            mUpdateRequested = true;
             mUpdateName = info.name();
         }
 
         @Override
-        protected void destroyUpgradeDetector() {
-            mUpgradeDetector = null;
-            mDestroyedManifestUpgradeDetector = true;
+        protected void destroyFetcher() {
+            mFetcher = null;
+            mDestroyedFetcher = true;
         }
 
         @Override
         protected long currentTimeMillis() {
             return mClock.currentTimeMillis();
         }
+
+        // Stubbed out because real implementation uses native.
+        @Override
+        protected boolean urlsMatchIgnoringFragments(String url1, String url2) {
+            return TextUtils.equals(url1, url2);
+        }
+    }
+
+    private static class ManifestData {
+        public String startUrl;
+        public String scopeUrl;
+        public String name;
+        public String shortName;
+        public Map<String, String> iconUrlToMurmur2HashMap;
+        public String bestIconUrl;
+        public Bitmap bestIcon;
+        public int displayMode;
+        public int orientation;
+        public long themeColor;
+        public long backgroundColor;
     }
 
     private MockClock mClock;
-    private int mShellApkVersion;
-
-    /** Sets the version of the code in //chrome/android/webapk/shell_apk. */
-    public void setShellApkVersion(int shellApkVersion) {
-        mShellApkVersion = shellApkVersion;
-    }
 
     private WebappDataStorage getStorage() {
         return WebappRegistry.getInstance().getWebappDataStorage(WEBAPK_ID);
     }
 
-    private WebApkInfo infoWithName(String name) {
-        return WebApkInfo.create(WEBAPK_ID, "", "", null, name, "", WebDisplayMode.Standalone,
-                ScreenOrientationValues.DEFAULT, ShortcutSource.UNKNOWN,
-                ShortcutHelper.MANIFEST_COLOR_INVALID_OR_MISSING,
-                ShortcutHelper.MANIFEST_COLOR_INVALID_OR_MISSING,
-                WebApkTestHelper.WEBAPK_PACKAGE_NAME, mShellApkVersion, "", "",
-                new HashMap<String, String>());
+    /**
+     * Registers WebAPK with default package name. Overwrites previous registrations.
+     * @param manifestData        <meta-data> values for WebAPK's Android Manifest.
+     * @param shellApkVersionCode WebAPK's version of the //chrome/android/webapk/shell_apk code.
+     */
+    private void registerWebApk(ManifestData manifestData, int shellApkVersionCode) {
+        Bundle metaData = new Bundle();
+        metaData.putInt(
+                WebApkMetaDataKeys.SHELL_APK_VERSION, shellApkVersionCode);
+        metaData.putString(WebApkMetaDataKeys.START_URL, manifestData.startUrl);
+        metaData.putString(WebApkMetaDataKeys.SCOPE, manifestData.scopeUrl);
+        metaData.putString(WebApkMetaDataKeys.NAME, manifestData.name);
+        metaData.putString(WebApkMetaDataKeys.SHORT_NAME, manifestData.shortName);
+        metaData.putString(WebApkMetaDataKeys.THEME_COLOR, manifestData.themeColor + "L");
+        metaData.putString(WebApkMetaDataKeys.BACKGROUND_COLOR, manifestData.backgroundColor + "L");
+        metaData.putString(WebApkMetaDataKeys.WEB_MANIFEST_URL, WEB_MANIFEST_URL);
+
+        String iconUrlsAndIconMurmur2Hashes = "";
+        for (Map.Entry<String, String> mapEntry : manifestData.iconUrlToMurmur2HashMap.entrySet()) {
+            String murmur2Hash = mapEntry.getValue();
+            if (murmur2Hash == null) {
+                murmur2Hash = "0";
+            }
+            iconUrlsAndIconMurmur2Hashes += " " + mapEntry.getKey() + " " + murmur2Hash;
+        }
+        iconUrlsAndIconMurmur2Hashes = iconUrlsAndIconMurmur2Hashes.trim();
+        metaData.putString(WebApkMetaDataKeys.ICON_URLS_AND_ICON_MURMUR2_HASHES,
+                iconUrlsAndIconMurmur2Hashes);
+
+        WebApkTestHelper.registerWebApkWithMetaData(metaData);
     }
 
-    private WebApkInfo fetchedWebApkInfo() {
-        return infoWithName(WEB_MANIFEST_NAME);
+    private static ManifestData defaultManifestData() {
+        ManifestData manifestData = new ManifestData();
+        manifestData.startUrl = START_URL;
+        manifestData.scopeUrl = SCOPE_URL;
+        manifestData.name = NAME;
+        manifestData.shortName = SHORT_NAME;
+
+        manifestData.iconUrlToMurmur2HashMap = new HashMap<String, String>();
+        manifestData.iconUrlToMurmur2HashMap.put(ICON_URL, ICON_MURMUR2_HASH);
+
+        manifestData.bestIconUrl = ICON_URL;
+        manifestData.bestIcon = createBitmap(Color.GREEN);
+        manifestData.displayMode = DISPLAY_MODE;
+        manifestData.orientation = ORIENTATION;
+        manifestData.themeColor = THEME_COLOR;
+        manifestData.backgroundColor = BACKGROUND_COLOR;
+        return manifestData;
     }
 
-    private void updateIfNeeded(WebApkUpdateManager updateManager) {
-        WebApkInfo info = infoWithName(ANDROID_MANIFEST_NAME);
+    private static WebApkInfo infoFromManifestData(ManifestData manifestData) {
+        if (manifestData == null) return null;
+
+        return WebApkInfo.create("", "", manifestData.scopeUrl,
+                new WebApkInfo.Icon(manifestData.bestIcon), manifestData.name,
+                manifestData.shortName, manifestData.displayMode, manifestData.orientation, -1,
+                manifestData.themeColor, manifestData.backgroundColor,
+                WebApkTestHelper.WEBAPK_PACKAGE_NAME, -1, WEB_MANIFEST_URL,
+                manifestData.startUrl, manifestData.iconUrlToMurmur2HashMap);
+    }
+
+    /**
+     * Creates 1x1 bitmap.
+     * @param color The bitmap color.
+     */
+    private static Bitmap createBitmap(int color) {
+        int colors[] = { color };
+        return ShadowBitmap.createBitmap(colors, 1, 1, Bitmap.Config.ALPHA_8);
+    }
+
+    private static void updateIfNeeded(WebApkUpdateManager updateManager) {
+        // Use the intent version of {@link WebApkInfo#create()} in order to test default values
+        // set by the intent version of {@link WebApkInfo#create()}.
+        Intent intent = new Intent();
+        intent.putExtra(ShortcutHelper.EXTRA_URL, "");
+        intent.putExtra(
+                ShortcutHelper.EXTRA_WEBAPK_PACKAGE_NAME, WebApkTestHelper.WEBAPK_PACKAGE_NAME);
+        WebApkInfo info = WebApkInfo.create(intent);
+
         updateManager.updateIfNeeded(null, info);
     }
 
-    private void onGotWebApkCompatibleWebManifestForInitialUrl(
-            WebApkUpdateManager updateManager, boolean needsUpdate) {
+    private static void onGotUnchangedWebManifestForInitialUrl(WebApkUpdateManager updateManager) {
+        onFinishedFetchingWebManifestForInitialUrl(updateManager, defaultManifestData());
+    }
+
+    private static void onFinishedFetchingWebManifestForInitialUrl(
+            WebApkUpdateManager updateManager, ManifestData fetchedManifestData) {
+        String bestIconUrl = randomIconUrl(fetchedManifestData);
         updateManager.onFinishedFetchingWebManifestForInitialUrl(
-                needsUpdate, fetchedWebApkInfo(), null);
+                infoFromManifestData(fetchedManifestData), bestIconUrl);
+    }
+
+    private static void onGotManifestData(WebApkUpdateManager updateManager,
+            ManifestData fetchedManifestData) {
+        String bestIconUrl = randomIconUrl(fetchedManifestData);
+        updateManager.onGotManifestData(infoFromManifestData(fetchedManifestData), bestIconUrl);
+    }
+
+    private static String randomIconUrl(ManifestData fetchedManifestData) {
+        if (fetchedManifestData == null || fetchedManifestData.iconUrlToMurmur2HashMap.isEmpty()) {
+            return null;
+        }
+        return fetchedManifestData.iconUrlToMurmur2HashMap.keySet().iterator().next();
     }
 
     /**
@@ -193,19 +298,35 @@
         return updateManager.updateCheckStarted();
     }
 
+    /**
+     * Checks whether the WebAPK is updated given data from the WebAPK's Android Manifest and data
+     * from the fetched Web Manifest.
+     */
+    private boolean checkUpdateNeededForFetchedManifest(
+            ManifestData androidManifestData, ManifestData fetchedManifestData) {
+        registerWebApk(androidManifestData, WebApkVersion.CURRENT_SHELL_APK_VERSION);
+        mClock.advance(WebApkUpdateManager.FULL_CHECK_UPDATE_INTERVAL);
+
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock);
+        updateIfNeeded(updateManager);
+        assertTrue(updateManager.updateCheckStarted());
+        updateManager.onFinishedFetchingWebManifestForInitialUrl(
+                infoFromManifestData(fetchedManifestData), fetchedManifestData.bestIconUrl);
+        return updateManager.updateRequested();
+    }
+
     @Before
     public void setUp() {
         ContextUtils.initApplicationContextForTests(RuntimeEnvironment.application);
         CommandLine.init(null);
 
+        registerWebApk(defaultManifestData(), WebApkVersion.CURRENT_SHELL_APK_VERSION);
         Settings.Secure.putInt(RuntimeEnvironment.application.getContentResolver(),
                 Settings.Secure.INSTALL_NON_MARKET_APPS, 1);
 
         mClock = new MockClock();
         WebappDataStorage.setClockForTests(mClock);
 
-        mShellApkVersion = WebApkVersion.CURRENT_SHELL_APK_VERSION;
-
         WebappRegistry.getInstance().register(
                 WEBAPK_ID, new WebappRegistry.FetchWebappDataStorageCallback() {
                     @Override
@@ -292,7 +413,7 @@
             TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock);
             updateIfNeeded(updateManager);
             assertTrue(updateManager.updateCheckStarted());
-            onGotWebApkCompatibleWebManifestForInitialUrl(updateManager, false);
+            onGotUnchangedWebManifestForInitialUrl(updateManager);
         }
 
         {
@@ -317,7 +438,7 @@
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock);
         updateIfNeeded(updateManager);
         assertTrue(updateManager.updateCheckStarted());
-        onGotWebApkCompatibleWebManifestForInitialUrl(updateManager, false);
+        onGotUnchangedWebManifestForInitialUrl(updateManager);
         assertFalse(updateManager.updateRequested());
 
         WebappDataStorage storage = getStorage();
@@ -340,7 +461,7 @@
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock);
         updateIfNeeded(updateManager);
         assertTrue(updateManager.updateCheckStarted());
-        onGotWebApkCompatibleWebManifestForInitialUrl(updateManager, false);
+        onGotUnchangedWebManifestForInitialUrl(updateManager);
         assertFalse(updateManager.updateRequested());
 
         assertTrue(storage.getDidLastWebApkUpdateRequestSucceed());
@@ -359,7 +480,9 @@
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock);
         updateIfNeeded(updateManager);
         assertTrue(updateManager.updateCheckStarted());
-        onGotWebApkCompatibleWebManifestForInitialUrl(updateManager, true);
+        ManifestData manifestData = defaultManifestData();
+        manifestData.name = DIFFERENT_NAME;
+        onFinishedFetchingWebManifestForInitialUrl(updateManager, manifestData);
         assertTrue(updateManager.updateRequested());
 
         // Chrome is killed. {@link WebApkUpdateManager#onBuiltWebApk} is never called.
@@ -382,21 +505,21 @@
      */
     @Test
     public void testShellApkOutOfDateNoWebManifest() {
-        setShellApkVersion(WebApkVersion.CURRENT_SHELL_APK_VERSION - 1);
+        registerWebApk(defaultManifestData(), WebApkVersion.CURRENT_SHELL_APK_VERSION - 1);
         mClock.advance(WebApkUpdateManager.FULL_CHECK_UPDATE_INTERVAL);
 
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock);
         updateIfNeeded(updateManager);
         assertTrue(updateManager.updateCheckStarted());
 
-        updateManager.onFinishedFetchingWebManifestForInitialUrl(false, null, null);
+        onFinishedFetchingWebManifestForInitialUrl(updateManager, null);
         assertTrue(updateManager.updateRequested());
-        assertEquals(ANDROID_MANIFEST_NAME, updateManager.requestedUpdateName());
+        assertEquals(NAME, updateManager.requestedUpdateName());
 
         // Check that the {@link ManifestUpgradeDetector} has been destroyed. This prevents
         // {@link #onFinishedFetchingWebManifestForInitialUrl()} and {@link #onGotManifestData()}
         // from getting called.
-        assertTrue(updateManager.destroyedManifestUpgradeDetector());
+        assertTrue(updateManager.destroyedFetcher());
     }
 
     /**
@@ -405,18 +528,18 @@
      */
     @Test
     public void testShellApkOutOfDateStillHasWebManifest() {
-        setShellApkVersion(WebApkVersion.CURRENT_SHELL_APK_VERSION - 1);
+        registerWebApk(defaultManifestData(), WebApkVersion.CURRENT_SHELL_APK_VERSION - 1);
         mClock.advance(WebApkUpdateManager.FULL_CHECK_UPDATE_INTERVAL);
 
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock);
         updateIfNeeded(updateManager);
         assertTrue(updateManager.updateCheckStarted());
 
-        updateManager.onFinishedFetchingWebManifestForInitialUrl(false, fetchedWebApkInfo(), null);
+        onFinishedFetchingWebManifestForInitialUrl(updateManager, defaultManifestData());
         assertTrue(updateManager.updateRequested());
-        assertEquals(WEB_MANIFEST_NAME, updateManager.requestedUpdateName());
+        assertEquals(NAME, updateManager.requestedUpdateName());
 
-        assertTrue(updateManager.destroyedManifestUpgradeDetector());
+        assertTrue(updateManager.destroyedFetcher());
     }
 
     /**
@@ -438,23 +561,25 @@
         assertTrue(updateManager.updateCheckStarted());
 
         // start_url does not have a Web Manifest. No update should be requested.
-        updateManager.onFinishedFetchingWebManifestForInitialUrl(false, null, null);
+        onFinishedFetchingWebManifestForInitialUrl(updateManager, null);
         assertFalse(updateManager.updateRequested());
         // {@link ManifestUpgradeDetector} should still be alive so that it can get
         // {@link #onGotManifestData} when page with the Web Manifest finishes loading.
-        assertFalse(updateManager.destroyedManifestUpgradeDetector());
+        assertFalse(updateManager.destroyedFetcher());
 
         // start_url redirects to page with Web Manifest.
 
-        updateManager.onGotManifestData(true, fetchedWebApkInfo(), null);
+        ManifestData manifestData = defaultManifestData();
+        manifestData.name = DIFFERENT_NAME;
+        onGotManifestData(updateManager, manifestData);
         assertTrue(updateManager.updateRequested());
-        assertEquals(WEB_MANIFEST_NAME, updateManager.requestedUpdateName());
+        assertEquals(DIFFERENT_NAME, updateManager.requestedUpdateName());
 
-        assertTrue(updateManager.destroyedManifestUpgradeDetector());
+        assertTrue(updateManager.destroyedFetcher());
     }
 
     /**
-     * Test than an update is not requested if:
+     * Test that an update is not requested if:
      * - start_url does not refer to a Web Manifest.
      * AND
      * - The user eventually navigates to a page pointing to a Web Manifest with the correct URL.
@@ -467,12 +592,123 @@
 
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mClock);
         updateIfNeeded(updateManager);
-        updateManager.onFinishedFetchingWebManifestForInitialUrl(false, null, null);
-        updateManager.onGotManifestData(false, fetchedWebApkInfo(), null);
+        onFinishedFetchingWebManifestForInitialUrl(updateManager, null);
+        onGotManifestData(updateManager, defaultManifestData());
         assertFalse(updateManager.updateRequested());
 
         // We got the Web Manifest. The {@link ManifestUpgradeDetector} should be destroyed to stop
         // it from fetching the Web Manifest for subsequent page loads.
-        assertTrue(updateManager.destroyedManifestUpgradeDetector());
+        assertTrue(updateManager.destroyedFetcher());
+    }
+
+    @Test
+    public void testManifestDoesNotUpgrade() {
+        assertFalse(
+                checkUpdateNeededForFetchedManifest(defaultManifestData(), defaultManifestData()));
+    }
+
+    /**
+     * Test that an upgrade is not requested when the Web Manifest did not change and the Web
+     * Manifest scope is empty.
+     */
+    @Test
+    public void testManifestEmptyScopeShouldNotUpgrade() {
+        ManifestData oldData = defaultManifestData();
+        // webapk_installer.cc sets the scope to the default scope if the scope is empty.
+        oldData.scopeUrl = ShortcutHelper.getScopeFromUrl(oldData.startUrl);
+        ManifestData fetchedData = defaultManifestData();
+        fetchedData.scopeUrl = "";
+        assertTrue(!oldData.scopeUrl.equals(fetchedData.scopeUrl));
+        assertFalse(checkUpdateNeededForFetchedManifest(oldData, fetchedData));
+    }
+
+    /**
+     * Test that an upgrade is requested when the Web Manifest is updated from using a non-empty
+     * scope to an empty scope.
+     */
+    @Test
+    public void testManifestNonEmptyScopeToEmptyScopeShouldUpgrade() {
+        ManifestData oldData = defaultManifestData();
+        oldData.startUrl = "/fancy/scope/special/snowflake.html";
+        oldData.scopeUrl = "/fancy/scope/";
+        assertTrue(
+                !oldData.scopeUrl.equals(ShortcutHelper.getScopeFromUrl(oldData.startUrl)));
+        ManifestData fetchedData = defaultManifestData();
+        fetchedData.startUrl = "/fancy/scope/special/snowflake.html";
+        fetchedData.scopeUrl = "";
+
+        assertTrue(checkUpdateNeededForFetchedManifest(oldData, fetchedData));
+    }
+
+    /**
+     * Test that an upgrade is requested when:
+     * - WebAPK was generated using icon at {@link ICON_URL} from Web Manifest.
+     * - Bitmap at {@link ICON_URL} has changed.
+     */
+    @Test
+    public void testHomescreenIconChangeShouldUpgrade() {
+        ManifestData fetchedData = defaultManifestData();
+        fetchedData.iconUrlToMurmur2HashMap.put(fetchedData.bestIconUrl, ICON_MURMUR2_HASH + "1");
+        fetchedData.bestIcon = createBitmap(Color.BLUE);
+        assertTrue(checkUpdateNeededForFetchedManifest(defaultManifestData(), fetchedData));
+    }
+
+    /**
+     * Test that an upgrade is requested when:
+     * - WebAPK is generated using icon at {@link ICON_URL} from Web Manifest.
+     * - Web Manifest is updated to refer to different icon.
+     */
+    @Test
+    public void testHomescreenBestIconUrlChangeShouldUpgrade() {
+        ManifestData fetchedData = defaultManifestData();
+        fetchedData.iconUrlToMurmur2HashMap.clear();
+        fetchedData.iconUrlToMurmur2HashMap.put("/icon2.png", "22");
+        fetchedData.bestIconUrl = "/icon2.png";
+        assertTrue(checkUpdateNeededForFetchedManifest(defaultManifestData(), fetchedData));
+    }
+
+    /**
+     * Test that an upgrade is not requested if:
+     * - icon URL is added to the Web Manifest
+     * AND
+     * - "best" icon URL for the launcher icon did not change.
+     */
+    @Test
+    public void testIconUrlsChangeShouldNotUpgradeIfTheBestIconUrlDoesNotChange() {
+        ManifestData fetchedData = defaultManifestData();
+        fetchedData.iconUrlToMurmur2HashMap.clear();
+        fetchedData.iconUrlToMurmur2HashMap.put(ICON_URL, ICON_MURMUR2_HASH);
+        fetchedData.iconUrlToMurmur2HashMap.put("/icon2.png", null);
+        assertFalse(checkUpdateNeededForFetchedManifest(defaultManifestData(), fetchedData));
+    }
+
+    /**
+     * Test than upgrade is requested if:
+     * - the WebAPK's meta data has murmur2 hashes for all of the icons.
+     * AND
+     * - the Web Manifest has not changed
+     * AND
+     * - the computed best icon URL is different from the one stored in the WebAPK's meta data.
+     */
+    @Test
+    public void testWebManifestSameButBestIconUrlChangedShouldNotUpgrade() {
+        String iconUrl1 = "/icon1.png";
+        String iconUrl2 = "/icon2.png";
+        String hash1 = "11";
+        String hash2 = "22";
+
+        ManifestData oldData = defaultManifestData();
+        oldData.bestIconUrl = iconUrl1;
+        oldData.iconUrlToMurmur2HashMap.clear();
+        oldData.iconUrlToMurmur2HashMap.put(iconUrl1, hash1);
+        oldData.iconUrlToMurmur2HashMap.put(iconUrl2, hash2);
+
+        ManifestData fetchedData = defaultManifestData();
+        fetchedData.bestIconUrl = iconUrl2;
+        fetchedData.iconUrlToMurmur2HashMap.clear();
+        fetchedData.iconUrlToMurmur2HashMap.put(iconUrl1, null);
+        fetchedData.iconUrlToMurmur2HashMap.put(iconUrl2, hash2);
+
+        assertFalse(checkUpdateNeededForFetchedManifest(oldData, fetchedData));
     }
 }
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 01395a3..4e14096f7 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3231,14 +3231,14 @@
       "android/web_contents_factory.h",
       "android/webapk/chrome_webapk_host.cc",
       "android/webapk/chrome_webapk_host.h",
-      "android/webapk/manifest_upgrade_detector_fetcher.cc",
-      "android/webapk/manifest_upgrade_detector_fetcher.h",
       "android/webapk/webapk_icon_hasher.cc",
       "android/webapk/webapk_icon_hasher.h",
       "android/webapk/webapk_installer.cc",
       "android/webapk/webapk_installer.h",
       "android/webapk/webapk_metrics.cc",
       "android/webapk/webapk_metrics.h",
+      "android/webapk/webapk_update_data_fetcher.cc",
+      "android/webapk/webapk_update_data_fetcher.h",
       "android/webapk/webapk_update_manager.cc",
       "android/webapk/webapk_update_manager.h",
       "android/webapk/webapk_web_manifest_checker.cc",
@@ -4028,8 +4028,8 @@
       "../android/java/src/org/chromium/chrome/browser/util/UrlUtilities.java",
       "../android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenManager.java",
       "../android/java/src/org/chromium/chrome/browser/webapps/ChromeWebApkHost.java",
-      "../android/java/src/org/chromium/chrome/browser/webapps/ManifestUpgradeDetectorFetcher.java",
       "../android/java/src/org/chromium/chrome/browser/webapps/WebApkInstaller.java",
+      "../android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcher.java",
       "../android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java",
       "../android/java/src/org/chromium/chrome/browser/webapps/WebappRegistry.java",
     ]
diff --git a/chrome/browser/android/chrome_jni_registrar.cc b/chrome/browser/android/chrome_jni_registrar.cc
index 5a33cd54..595bde4 100644
--- a/chrome/browser/android/chrome_jni_registrar.cc
+++ b/chrome/browser/android/chrome_jni_registrar.cc
@@ -102,8 +102,8 @@
 #include "chrome/browser/android/voice_search_tab_helper.h"
 #include "chrome/browser/android/warmup_manager.h"
 #include "chrome/browser/android/web_contents_factory.h"
-#include "chrome/browser/android/webapk/manifest_upgrade_detector_fetcher.h"
 #include "chrome/browser/android/webapk/webapk_installer.h"
+#include "chrome/browser/android/webapk/webapk_update_data_fetcher.h"
 #include "chrome/browser/android/webapk/webapk_update_manager.h"
 #include "chrome/browser/android/webapps/add_to_homescreen_manager.h"
 #include "chrome/browser/autofill/android/personal_data_manager_android.h"
@@ -311,8 +311,6 @@
     {"LayerTitleCache", RegisterLayerTitleCache},
     {"SpecialLocaleHandler", RegisterSpecialLocaleHandler},
     {"LogoBridge", RegisterLogoBridge},
-    {"ManifestUpgradeDetectorFetcher",
-     ManifestUpgradeDetectorFetcher::Register},
     {"MediaDrmCredentialManager",
      MediaDrmCredentialManager::RegisterMediaDrmCredentialManager},
     {"MostVisitedSites", MostVisitedSitesBridge::Register},
@@ -417,6 +415,7 @@
     {"WarmupManager", RegisterWarmupManager},
     {"WebApkInstaller", WebApkInstaller::Register},
     {"WebApkUpdateManager", WebApkUpdateManager::Register},
+    {"WebApkUpdateDataFetcher", WebApkUpdateDataFetcher::Register},
     {"WebContentsFactory", RegisterWebContentsFactory},
     {"WebsitePreferenceBridge", RegisterWebsitePreferenceBridge},
     {"WebsiteSettingsPopupAndroid",
diff --git a/chrome/browser/android/webapk/manifest_upgrade_detector_fetcher.cc b/chrome/browser/android/webapk/webapk_update_data_fetcher.cc
similarity index 84%
rename from chrome/browser/android/webapk/manifest_upgrade_detector_fetcher.cc
rename to chrome/browser/android/webapk/webapk_update_data_fetcher.cc
index 492240b..a5a20efe 100644
--- a/chrome/browser/android/webapk/manifest_upgrade_detector_fetcher.cc
+++ b/chrome/browser/android/webapk/webapk_update_data_fetcher.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/android/webapk/manifest_upgrade_detector_fetcher.h"
+#include "chrome/browser/android/webapk/webapk_update_data_fetcher.h"
 
 #include <jni.h>
 #include <vector>
@@ -16,7 +16,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/manifest.h"
-#include "jni/ManifestUpgradeDetectorFetcher_jni.h"
+#include "jni/WebApkUpdateDataFetcher_jni.h"
 #include "third_party/smhasher/src/MurmurHash2.h"
 #include "ui/gfx/android/java_bitmap.h"
 #include "ui/gfx/codec/png_codec.h"
@@ -42,12 +42,12 @@
   GURL scope(base::android::ConvertJavaStringToUTF8(env, java_scope_url));
   GURL web_manifest_url(base::android::ConvertJavaStringToUTF8(
       env, java_web_manifest_url));
-  ManifestUpgradeDetectorFetcher* fetcher =
-      new ManifestUpgradeDetectorFetcher(env, obj, scope, web_manifest_url);
+  WebApkUpdateDataFetcher* fetcher =
+      new WebApkUpdateDataFetcher(env, obj, scope, web_manifest_url);
   return reinterpret_cast<intptr_t>(fetcher);
 }
 
-ManifestUpgradeDetectorFetcher::ManifestUpgradeDetectorFetcher(
+WebApkUpdateDataFetcher::WebApkUpdateDataFetcher(
     JNIEnv* env,
     jobject obj,
     const GURL& scope,
@@ -60,15 +60,15 @@
   java_ref_.Reset(env, obj);
 }
 
-ManifestUpgradeDetectorFetcher::~ManifestUpgradeDetectorFetcher() {
+WebApkUpdateDataFetcher::~WebApkUpdateDataFetcher() {
 }
 
 // static
-bool ManifestUpgradeDetectorFetcher::Register(JNIEnv* env) {
+bool WebApkUpdateDataFetcher::Register(JNIEnv* env) {
   return RegisterNativesImpl(env);
 }
 
-void ManifestUpgradeDetectorFetcher::ReplaceWebContents(
+void WebApkUpdateDataFetcher::ReplaceWebContents(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj,
     const JavaParamRef<jobject>& java_web_contents) {
@@ -77,12 +77,12 @@
   content::WebContentsObserver::Observe(web_contents);
 }
 
-void ManifestUpgradeDetectorFetcher::Destroy(JNIEnv* env,
-                                             const JavaParamRef<jobject>& obj) {
+void WebApkUpdateDataFetcher::Destroy(JNIEnv* env,
+                                      const JavaParamRef<jobject>& obj) {
   delete this;
 }
 
-void ManifestUpgradeDetectorFetcher::Start(
+void WebApkUpdateDataFetcher::Start(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj,
     const JavaParamRef<jobject>& java_web_contents) {
@@ -91,11 +91,11 @@
     FetchInstallableData();
 }
 
-void ManifestUpgradeDetectorFetcher::DidStopLoading() {
+void WebApkUpdateDataFetcher::DidStopLoading() {
   FetchInstallableData();
 }
 
-void ManifestUpgradeDetectorFetcher::FetchInstallableData() {
+void WebApkUpdateDataFetcher::FetchInstallableData() {
   GURL url = web_contents()->GetLastCommittedURL();
 
   // DidStopLoading() can be called multiple times for a single URL. Only fetch
@@ -119,11 +119,11 @@
       InstallableManager::FromWebContents(web_contents());
   installable_manager->GetData(
       params,
-      base::Bind(&ManifestUpgradeDetectorFetcher::OnDidGetInstallableData,
+      base::Bind(&WebApkUpdateDataFetcher::OnDidGetInstallableData,
                  weak_ptr_factory_.GetWeakPtr()));
 }
 
-void ManifestUpgradeDetectorFetcher::OnDidGetInstallableData(
+void WebApkUpdateDataFetcher::OnDidGetInstallableData(
     const InstallableData& data) {
   // If the manifest is empty, it means the current WebContents doesn't
   // associate with a Web Manifest. In such case, we ignore the empty manifest
@@ -154,11 +154,11 @@
   icon_hasher_->DownloadAndComputeMurmur2Hash(
       profile->GetRequestContext(),
       data.icon_url,
-      base::Bind(&ManifestUpgradeDetectorFetcher::OnGotIconMurmur2Hash,
+      base::Bind(&WebApkUpdateDataFetcher::OnGotIconMurmur2Hash,
                  weak_ptr_factory_.GetWeakPtr()));
 }
 
-void ManifestUpgradeDetectorFetcher::OnGotIconMurmur2Hash(
+void WebApkUpdateDataFetcher::OnGotIconMurmur2Hash(
     const std::string& best_icon_murmur2_hash) {
   icon_hasher_.reset();
 
@@ -171,7 +171,7 @@
   OnDataAvailable(info_, best_icon_murmur2_hash, best_icon_);
 }
 
-void ManifestUpgradeDetectorFetcher::OnDataAvailable(
+void WebApkUpdateDataFetcher::OnDataAvailable(
     const ShortcutInfo& info,
     const std::string& best_icon_murmur2_hash,
     const SkBitmap& best_icon_bitmap) {
@@ -195,7 +195,7 @@
   ScopedJavaLocalRef<jobjectArray> java_icon_urls =
       base::android::ToJavaArrayOfStrings(env, info.icon_urls);
 
-  Java_ManifestUpgradeDetectorFetcher_onDataAvailable(
+  Java_WebApkUpdateDataFetcher_onDataAvailable(
       env, java_ref_, java_url, java_scope, java_name, java_short_name,
       java_best_icon_url, java_best_icon_murmur2_hash, java_best_bitmap,
       java_icon_urls, info.display, info.orientation, info.theme_color,
diff --git a/chrome/browser/android/webapk/manifest_upgrade_detector_fetcher.h b/chrome/browser/android/webapk/webapk_update_data_fetcher.h
similarity index 72%
rename from chrome/browser/android/webapk/manifest_upgrade_detector_fetcher.h
rename to chrome/browser/android/webapk/webapk_update_data_fetcher.h
index 5d3849e..6bc7885 100644
--- a/chrome/browser/android/webapk/manifest_upgrade_detector_fetcher.h
+++ b/chrome/browser/android/webapk/webapk_update_data_fetcher.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_ANDROID_WEBAPK_MANIFEST_UPGRADE_DETECTOR_FETCHER_H_
-#define CHROME_BROWSER_ANDROID_WEBAPK_MANIFEST_UPGRADE_DETECTOR_FETCHER_H_
+#ifndef CHROME_BROWSER_ANDROID_WEBAPK_WEBAPK_UPDATE_DATA_FETCHER_H_
+#define CHROME_BROWSER_ANDROID_WEBAPK_WEBAPK_UPDATE_DATA_FETCHER_H_
 
 #include "base/android/jni_android.h"
 #include "base/android/jni_weak_ref.h"
@@ -22,15 +22,15 @@
 struct InstallableData;
 class WebApkIconHasher;
 
-// ManifestUpgradeDetectorFetcher is the C++ counterpart of
-// org.chromium.chrome.browser's ManifestUpgradeDetectorFetcher in Java. It is
-// created via a JNI (Initialize) call and MUST BE DESTROYED via Destroy().
-class ManifestUpgradeDetectorFetcher : public content::WebContentsObserver {
+// WebApkUpdateDataFetcher is the C++ counterpart of
+// org.chromium.chrome.browser's WebApkUpdateDataFetcher in Java. It is created
+// via a JNI (Initialize) call and MUST BE DESTROYED via Destroy().
+class WebApkUpdateDataFetcher : public content::WebContentsObserver {
  public:
-  ManifestUpgradeDetectorFetcher(JNIEnv* env,
-                                 jobject obj,
-                                 const GURL& scope,
-                                 const GURL& web_manifest_url);
+  WebApkUpdateDataFetcher(JNIEnv* env,
+                          jobject obj,
+                          const GURL& scope,
+                          const GURL& web_manifest_url);
 
   // Replaces the WebContents that is being observed.
   void ReplaceWebContents(
@@ -50,7 +50,7 @@
   static bool Register(JNIEnv* env);
 
  private:
-  ~ManifestUpgradeDetectorFetcher() override;
+  ~WebApkUpdateDataFetcher() override;
 
   // content::WebContentsObserver:
   void DidStopLoading() override;
@@ -87,9 +87,9 @@
   ShortcutInfo info_;
   SkBitmap best_icon_;
 
-  base::WeakPtrFactory<ManifestUpgradeDetectorFetcher> weak_ptr_factory_;
+  base::WeakPtrFactory<WebApkUpdateDataFetcher> weak_ptr_factory_;
 
-  DISALLOW_COPY_AND_ASSIGN(ManifestUpgradeDetectorFetcher);
+  DISALLOW_COPY_AND_ASSIGN(WebApkUpdateDataFetcher);
 };
 
-#endif  // CHROME_BROWSER_ANDROID_WEBAPK_MANIFEST_UPGRADE_DETECTOR_FETCHER_H_
+#endif  // CHROME_BROWSER_ANDROID_WEBAPK_WEBAPK_UPDATE_DATA_FETCHER_H_