[M95] Merge fixes for silently redirecting to other browsers

This merges the following 6 changes to the M95 branch:

Handle web intents without specialized handlers in Chrome.
https://chromium-review.googlesource.com/c/chromium/src/+/3166110

Use fallback URL instead of target URL for web links in-browser
https://chromium-review.googlesource.com/c/chromium/src/+/3187533

Ensure user sees chooser dialog when navigating to a different browser
https://chromium-review.googlesource.com/c/chromium/src/+/3198743

Fix crash in ActivityPicker when using resource obfuscation
https://chromium-review.googlesource.com/c/chromium/src/+/3213013

Include ic_launcher drawable for resources excluded from name collapsing
https://chromium-review.googlesource.com/c/chromium/src/+/3217257

Fix showing chooser dialog for specialized http/s URLs
https://chromium-review.googlesource.com/c/chromium/src/+/3225421

PS1 is the changes cherry-picked without resolving any conflicts (so
entirely unchanged from initial review).

Bug: 1249962
Change-Id: Ifa8835ce683e2c522b490c8c5a37656394f46dcd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3232895
Reviewed-by: Colin Blundell <blundell@chromium.org>
Reviewed-by: Yaron Friedman <yfriedman@chromium.org>
Commit-Queue: Michael Thiessen <mthiesse@chromium.org>
Cr-Commit-Position: refs/branch-heads/4638@{#937}
Cr-Branched-From: 159257cab5585bc8421abf347984bb32fdfe9eb9-refs/heads/main@{#920003}
diff --git a/base/android/java/src/org/chromium/base/PackageManagerUtils.java b/base/android/java/src/org/chromium/base/PackageManagerUtils.java
index 0d08cc1..7aa7921e 100644
--- a/base/android/java/src/org/chromium/base/PackageManagerUtils.java
+++ b/base/android/java/src/org/chromium/base/PackageManagerUtils.java
@@ -18,7 +18,14 @@
  */
 public class PackageManagerUtils {
     private static final String TAG = "PackageManagerUtils";
-    private static final String SAMPLE_URL = "http://";
+
+    // This is the intent Android uses internally to detect browser apps.
+    // See
+    // https://cs.android.com/android/_/android/platform/packages/modules/Permission/+/android12-release:PermissionController/src/com/android/permissioncontroller/role/model/BrowserRoleBehavior.java;drc=86fa7d5dfa43f66b170f93ade4f59b9a770be32f;l=50
+    public static final Intent BROWSER_INTENT = new Intent()
+                                                        .setAction(Intent.ACTION_VIEW)
+                                                        .addCategory(Intent.CATEGORY_BROWSABLE)
+                                                        .setData(Uri.fromParts("http", "", null));
 
     /**
      * Retrieve information about the Activity that will handle the given Intent.
@@ -61,14 +68,6 @@
     }
 
     /**
-     * @return Intent to query a list of installed web browsers.
-     */
-    public static Intent getQueryInstalledBrowsersIntent() {
-        return new Intent(Intent.ACTION_VIEW, Uri.parse(SAMPLE_URL))
-                .addCategory(Intent.CATEGORY_BROWSABLE);
-    }
-
-    /**
      * @return Intent to query a list of installed home launchers.
      */
     public static Intent getQueryInstalledHomeLaunchersIntent() {
@@ -79,7 +78,7 @@
      * @return Default ResolveInfo to handle a VIEW intent for a url.
      */
     public static ResolveInfo resolveDefaultWebBrowserActivity() {
-        return resolveActivity(getQueryInstalledBrowsersIntent(), 0);
+        return resolveActivity(BROWSER_INTENT, PackageManager.MATCH_DEFAULT_ONLY);
     }
 
     /**
@@ -87,7 +86,13 @@
      *         may appear twice if it has multiple intent handlers.
      */
     public static List<ResolveInfo> queryAllWebBrowsersInfo() {
-        return queryIntentActivities(getQueryInstalledBrowsersIntent(), PackageManager.MATCH_ALL);
+        // Copying these flags from Android source for detecting the list of installed browsers.
+        // Apparently MATCH_ALL doesn't include MATCH_DIRECT_BOOT_*.
+        // See
+        // https://cs.android.com/android/_/android/platform/packages/modules/Permission/+/android12-release:PermissionController/src/com/android/permissioncontroller/role/model/BrowserRoleBehavior.java;drc=86fa7d5dfa43f66b170f93ade4f59b9a770be32f;l=114
+        int flags = PackageManager.MATCH_ALL | PackageManager.MATCH_DIRECT_BOOT_AWARE
+                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DEFAULT_ONLY;
+        return queryIntentActivities(BROWSER_INTENT, flags);
     }
 
     /**
diff --git a/chrome/android/aapt2.config b/chrome/android/aapt2.config
index a1e0ee9..ea85a3e 100644
--- a/chrome/android/aapt2.config
+++ b/chrome/android/aapt2.config
@@ -1 +1,3 @@
+drawable/ic_launcher#no_collapse
+drawable/ic_launcher_round#no_collapse
 drawable/shortcut_incognito#no_collapse
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/DefaultBrowserInfo.java b/chrome/android/java/src/org/chromium/chrome/browser/DefaultBrowserInfo.java
index 173a1bd3..9d7bbd0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/DefaultBrowserInfo.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/DefaultBrowserInfo.java
@@ -170,7 +170,7 @@
                     List<ResolveInfo> ris = PackageManagerUtils.queryAllWebBrowsersInfo();
                     if (ris != null) {
                         for (ResolveInfo ri : ris) {
-                            String packageName = ri.activityInfo.applicationInfo.packageName;
+                            String packageName = ri.activityInfo.packageName;
                             if (!uniquePackages.add(packageName)) continue;
 
                             if (isSystemPackage(ri)) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java
index dad88fe..96bbbb0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java
@@ -20,7 +20,6 @@
 import androidx.browser.trusted.TrustedWebActivityDisplayMode.ImmersiveMode;
 
 import org.chromium.base.ContextUtils;
-import org.chromium.base.IntentUtils;
 import org.chromium.base.PackageManagerUtils;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.cc.input.BrowserControlsState;
@@ -125,39 +124,25 @@
             // instead.
             boolean isExternalProtocol = !UrlUtilities.isAcceptedScheme(intent.toUri(0));
             boolean hasDefaultHandler = hasDefaultHandler(intent);
-            try {
-                // For a URL chrome can handle and there is no default set, handle it ourselves.
-                if (!hasDefaultHandler) {
-                    if (!TextUtils.isEmpty(mClientPackageName)
-                            && isPackageSpecializedHandler(mClientPackageName, intent)) {
-                        intent.setPackage(mClientPackageName);
-                    } else if (!isExternalProtocol) {
-                        return StartActivityIfNeededResult.HANDLED_WITHOUT_ACTIVITY_START;
-                    }
+            // For a URL chrome can handle and there is no default set, handle it ourselves.
+            if (!hasDefaultHandler) {
+                if (!TextUtils.isEmpty(mClientPackageName)
+                        && isPackageSpecializedHandler(mClientPackageName, intent)) {
+                    // Package and Selector cannot be set at the same time.
+                    intent.setSelector(null);
+                    intent.setPackage(mClientPackageName);
+                } else if (!isExternalProtocol) {
+                    return StartActivityIfNeededResult.HANDLED_WITHOUT_ACTIVITY_START;
                 }
+            }
 
-                if (proxy) {
-                    dispatchAuthenticatedIntent(intent);
-                    mHasActivityStarted = true;
-                    return StartActivityIfNeededResult.HANDLED_WITH_ACTIVITY_START;
-                } else {
-                    // If android fails to find a handler, handle it ourselves.
-                    Context context = getAvailableContext();
-                    if (context instanceof Activity
-                            && ((Activity) context).startActivityIfNeeded(intent, -1)) {
-                        mHasActivityStarted = true;
-                        return StartActivityIfNeededResult.HANDLED_WITH_ACTIVITY_START;
-                    }
-                }
-                return StartActivityIfNeededResult.HANDLED_WITHOUT_ACTIVITY_START;
-            } catch (SecurityException e) {
-                // https://crbug.com/808494: Handle the URL in Chrome if dispatching to another
-                // application fails with a SecurityException. This happens due to malformed
-                // manifests in another app.
-                return StartActivityIfNeededResult.HANDLED_WITHOUT_ACTIVITY_START;
-            } catch (RuntimeException e) {
-                IntentUtils.logTransactionTooLargeOrRethrow(e, intent);
-                return StartActivityIfNeededResult.HANDLED_WITHOUT_ACTIVITY_START;
+            if (proxy) {
+                dispatchAuthenticatedIntent(intent);
+                mHasActivityStarted = true;
+                return StartActivityIfNeededResult.HANDLED_WITH_ACTIVITY_START;
+            } else {
+                // Defer to ExternalNavigationHandler to call startActivityIfNeeded.
+                return StartActivityIfNeededResult.DID_NOT_HANDLE;
             }
         }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
index 291f658..5873f4b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/UrlOverridingTest.java
@@ -7,9 +7,15 @@
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.app.Instrumentation.ActivityMonitor;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.SystemClock;
 import android.support.test.InstrumentationRegistry;
@@ -28,8 +34,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.android.support.PackageManagerWrapper;
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.ContextUtils;
+import org.chromium.base.PackageManagerUtils;
 import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -46,6 +54,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResultType;
 import org.chromium.components.external_intents.InterceptNavigationDelegateImpl;
 import org.chromium.content_public.browser.LoadUrlParams;
@@ -57,6 +66,8 @@
 import org.chromium.ui.base.PageTransition;
 import org.chromium.url.GURL;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
@@ -105,6 +116,10 @@
             BASE_PATH + "navigation_to_file_scheme_via_intent_uri.html";
     private static final String SUBFRAME_REDIRECT_WITH_PLAY_FALLBACK =
             BASE_PATH + "subframe_navigation_with_play_fallback.html";
+    private static final String REDIRECT_TO_OTHER_BROWSER =
+            BASE_PATH + "redirect_to_other_browser.html";
+
+    private static final String OTHER_BROWSER_PACKAGE = "com.other.browser";
 
     private static class TestTabObserver extends EmptyTabObserver {
         private final CallbackHelper mFinishCallback;
@@ -136,8 +151,51 @@
         }
     }
 
+    private static ResolveInfo newResolveInfo(String packageName) {
+        ActivityInfo ai = new ActivityInfo();
+        ai.packageName = packageName;
+        ai.name = "Name: " + packageName;
+        ai.applicationInfo = new ApplicationInfo();
+        ResolveInfo ri = new ResolveInfo();
+        ri.activityInfo = ai;
+        return ri;
+    }
+
+    private static class TestContext extends ContextWrapper {
+        public TestContext(Context baseContext) {
+            super(baseContext);
+        }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return new PackageManagerWrapper(super.getPackageManager()) {
+                @Override
+                public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
+                    if ((intent.getPackage() != null
+                                && intent.getPackage().equals(OTHER_BROWSER_PACKAGE))
+                            || intent.filterEquals(PackageManagerUtils.BROWSER_INTENT)) {
+                        return Arrays.asList(newResolveInfo(OTHER_BROWSER_PACKAGE));
+                    }
+
+                    return TestContext.super.getPackageManager().queryIntentActivities(
+                            intent, flags);
+                }
+
+                @Override
+                public ResolveInfo resolveActivity(Intent intent, int flags) {
+                    if (intent.getPackage() != null
+                            && intent.getPackage().equals(OTHER_BROWSER_PACKAGE)) {
+                        return newResolveInfo(OTHER_BROWSER_PACKAGE);
+                    }
+                    return TestContext.super.getPackageManager().resolveActivity(intent, flags);
+                }
+            };
+        }
+    }
+
     private ActivityMonitor mActivityMonitor;
     private EmbeddedTestServer mTestServer;
+    private Context mContextToRestore;
 
     @Before
     public void setUp() throws Exception {
@@ -146,12 +204,19 @@
         filter.addDataScheme("market");
         mActivityMonitor = InstrumentationRegistry.getInstrumentation().addMonitor(
                 filter, new Instrumentation.ActivityResult(Activity.RESULT_OK, null), true);
-        mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
+        mTestServer = mActivityTestRule.getTestServer();
     }
 
     @After
     public void tearDown() {
-        mTestServer.stopAndDestroyServer();
+        if (mContextToRestore != null) {
+            ContextUtils.initApplicationContextForTests(mContextToRestore);
+        }
+    }
+
+    private void setUpTestContext() {
+        mContextToRestore = ContextUtils.getApplicationContext();
+        ContextUtils.initApplicationContextForTests(new TestContext(mContextToRestore));
     }
 
     private void loadUrlAndWaitForIntentUrl(
@@ -371,10 +436,10 @@
         String fallbackUrl = mTestServer.getURL(FALLBACK_LANDING_PATH);
         String originalUrl = mTestServer.getURL(NAVIGATION_WITH_FALLBACK_URL_PAGE + "?replace_text="
                 + Base64.encodeToString(
-                          ApiCompatibilityUtils.getBytesUtf8("PARAM_FALLBACK_URL"), Base64.URL_SAFE)
+                        ApiCompatibilityUtils.getBytesUtf8("PARAM_FALLBACK_URL"), Base64.URL_SAFE)
                 + ":"
                 + Base64.encodeToString(
-                          ApiCompatibilityUtils.getBytesUtf8(fallbackUrl), Base64.URL_SAFE));
+                        ApiCompatibilityUtils.getBytesUtf8(fallbackUrl), Base64.URL_SAFE));
         loadUrlAndWaitForIntentUrl(originalUrl, true, false, false, fallbackUrl, true);
     }
 
@@ -393,13 +458,10 @@
         byte[] base64FallbackUrl =
                 Base64.encode(ApiCompatibilityUtils.getBytesUtf8(fallbackUrl), Base64.URL_SAFE);
 
-        String originalUrl = mTestServer.getURL(
-                NAVIGATION_WITH_FALLBACK_URL_PARENT_FRAME_PAGE
-                + "?replace_text="
-                + Base64.encodeToString(paramBase64Name, Base64.URL_SAFE) + ":"
+        String originalUrl = mTestServer.getURL(NAVIGATION_WITH_FALLBACK_URL_PARENT_FRAME_PAGE
+                + "?replace_text=" + Base64.encodeToString(paramBase64Name, Base64.URL_SAFE) + ":"
                 + Base64.encodeToString(base64ParamFallbackUrl, Base64.URL_SAFE)
-                + "&replace_text="
-                + Base64.encodeToString(paramBase64Value, Base64.URL_SAFE) + ":"
+                + "&replace_text=" + Base64.encodeToString(paramBase64Value, Base64.URL_SAFE) + ":"
                 + Base64.encodeToString(base64FallbackUrl, Base64.URL_SAFE));
 
         // Fallback URL from a subframe will not trigger main or sub frame navigation.
@@ -557,4 +619,65 @@
         loadUrlAndWaitForIntentUrl(
                 mTestServer.getURL(SUBFRAME_REDIRECT_WITH_PLAY_FALLBACK), false, false);
     }
+
+    private void runRedirectToOtherBrowserTest(Instrumentation.ActivityResult chooserResult) {
+        Context context = ContextUtils.getApplicationContext();
+        Intent intent = new Intent(
+                Intent.ACTION_VIEW, Uri.parse(mTestServer.getURL(REDIRECT_TO_OTHER_BROWSER)));
+        intent.setClassName(context, ChromeLauncherActivity.class.getName());
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PICK_ACTIVITY);
+        Instrumentation.ActivityMonitor monitor =
+                InstrumentationRegistry.getInstrumentation().addMonitor(
+                        filter, chooserResult, true);
+
+        ChromeTabbedActivity activity = ApplicationTestUtils.waitForActivityWithClass(
+                ChromeTabbedActivity.class, Stage.CREATED, () -> context.startActivity(intent));
+        mActivityTestRule.setActivity(activity);
+
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat(monitor.getHits(), Matchers.is(1));
+        }, 10000L, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
+        InstrumentationRegistry.getInstrumentation().removeMonitor(monitor);
+    }
+
+    @Test
+    @LargeTest
+    public void testRedirectToOtherBrowser_ChooseSelf() throws TimeoutException {
+        setUpTestContext();
+        Intent result = new Intent(Intent.ACTION_CREATE_SHORTCUT);
+
+        runRedirectToOtherBrowserTest(
+                new Instrumentation.ActivityResult(Activity.RESULT_OK, result));
+
+        // Wait for the target (data) URL to load in the tab.
+        CriteriaHelper.pollUiThread(() -> {
+            Criteria.checkThat(
+                    mActivityTestRule.getActivity().getActivityTab().getUrl().getScheme(),
+                    Matchers.is(UrlConstants.DATA_SCHEME));
+        });
+    }
+
+    @Test
+    @LargeTest
+    public void testRedirectToOtherBrowser_ChooseOther() throws TimeoutException {
+        setUpTestContext();
+        IntentFilter filter = new IntentFilter(Intent.ACTION_VIEW);
+        filter.addDataScheme(UrlConstants.DATA_SCHEME);
+        filter.addCategory(Intent.CATEGORY_BROWSABLE);
+        Instrumentation.ActivityMonitor monitor =
+                InstrumentationRegistry.getInstrumentation().addMonitor(filter, null, true);
+
+        Intent result = new Intent(Intent.ACTION_VIEW);
+        result.setComponent(new ComponentName(OTHER_BROWSER_PACKAGE, "activity"));
+
+        runRedirectToOtherBrowserTest(
+                new Instrumentation.ActivityResult(Activity.RESULT_OK, result));
+
+        CriteriaHelper.pollUiThread(
+                () -> { Criteria.checkThat(monitor.getHits(), Matchers.is(1)); });
+
+        InstrumentationRegistry.getInstrumentation().removeMonitor(monitor);
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/ChromeActionModeHandlerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/ChromeActionModeHandlerUnitTest.java
index cbfccca..8164f35 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/ChromeActionModeHandlerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/ChromeActionModeHandlerUnitTest.java
@@ -149,8 +149,7 @@
         }
 
         // Mock intent for querying web browsers.
-        packageManager.addResolveInfoForIntent(
-                PackageManagerUtils.getQueryInstalledBrowsersIntent(), browsersList);
+        packageManager.addResolveInfoForIntent(PackageManagerUtils.BROWSER_INTENT, browsersList);
 
         // Mock intent for querying home launchers.
         packageManager.addResolveInfoForIntent(
diff --git a/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtilsTest.java b/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtilsTest.java
index cfbb6b27..073db5e 100644
--- a/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtilsTest.java
+++ b/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtilsTest.java
@@ -81,14 +81,12 @@
 
         DefaultBrowserPromoDeps deps = DefaultBrowserPromoDeps.getInstance();
         infoList.add(createResolveInfo(DefaultBrowserPromoDeps.CHROME_STABLE_PACKAGE_NAME, 1));
-        packageManager.addResolveInfoForIntent(
-                PackageManagerUtils.getQueryInstalledBrowsersIntent(), infoList);
+        packageManager.addResolveInfoForIntent(PackageManagerUtils.BROWSER_INTENT, infoList);
         Assert.assertFalse("Chrome stable should not be counted as a pre-stable channel",
                 deps.isChromePreStableInstalled());
 
         infoList.add(createResolveInfo("com.android.chrome.123", 1));
-        packageManager.addResolveInfoForIntent(
-                PackageManagerUtils.getQueryInstalledBrowsersIntent(), infoList);
+        packageManager.addResolveInfoForIntent(PackageManagerUtils.BROWSER_INTENT, infoList);
         Assert.assertFalse("A random package should not be counted as a pre-stable channel",
                 deps.isChromePreStableInstalled());
 
@@ -96,8 +94,7 @@
             if (name.equals(DefaultBrowserPromoDeps.CHROME_STABLE_PACKAGE_NAME)) continue;
             List<ResolveInfo> list = new ArrayList<>(infoList);
             list.add(createResolveInfo(name, 1));
-            packageManager.addResolveInfoForIntent(
-                    PackageManagerUtils.getQueryInstalledBrowsersIntent(), list);
+            packageManager.addResolveInfoForIntent(PackageManagerUtils.BROWSER_INTENT, list);
             Assert.assertTrue(name + " should be considered as a pre-stable channel",
                     deps.isChromePreStableInstalled());
         }
diff --git a/chrome/test/data/android/url_overriding/redirect_to_other_browser.html b/chrome/test/data/android/url_overriding/redirect_to_other_browser.html
new file mode 100644
index 0000000..b99eefd
--- /dev/null
+++ b/chrome/test/data/android/url_overriding/redirect_to_other_browser.html
@@ -0,0 +1,9 @@
+<html>
+  <head>
+    <meta http-equiv="refresh" content="0;URL='intent:text/plain;base64,SGVsbG8sIHdvcmxk#Intent;action=android.intent.action.VIEW;scheme=data;package=com.other.browser;end'" />
+    <script></script>
+  </head>
+  <body>
+    Redirecting...
+  </body>
+</html>
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
index f4ae1b7..df6e442 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
@@ -13,10 +13,14 @@
 import android.content.DialogInterface.OnCancelListener;
 import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
+import android.content.Intent.ShortcutIconResource;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
 import android.net.Uri;
 import android.os.StrictMode;
 import android.os.SystemClock;
@@ -60,14 +64,17 @@
 import org.chromium.network.mojom.ReferrerPolicy;
 import org.chromium.ui.base.PageTransition;
 import org.chromium.ui.base.PermissionCallback;
+import org.chromium.ui.base.WindowAndroid;
 import org.chromium.url.GURL;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Logic related to the URL overriding/intercepting functionality.
@@ -680,8 +687,8 @@
         // wtai://wp/mc;number
         // number=string(phone-number)
         String phoneNumber = params.getUrl().getSpec().substring(WTAI_MC_URL_PREFIX.length());
-        startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(WebView.SCHEME_TEL + phoneNumber)),
-                false, mDelegate);
+        startActivity(
+                new Intent(Intent.ACTION_VIEW, Uri.parse(WebView.SCHEME_TEL + phoneNumber)), false);
         if (DEBUG) Log.i(TAG, "wtai:// link handled");
         RecordUserAction.record("Android.PhoneIntent");
         return true;
@@ -873,6 +880,74 @@
     }
 
     /**
+     * Returns true if an intent is an ACTION_VIEW intent targeting browsers or browser-like apps
+     * (excluding the embedding app).
+     */
+    private boolean isViewIntentToOtherBrowser(Intent targetIntent, List<ResolveInfo> resolveInfos,
+            boolean isIntentWithSupportedProtocol, boolean hasSpecializedHandler) {
+        // Note that up until at least Android S, an empty action will match any intent filter
+        // with with an action specified. If an intent selector is specified, then don't trust the
+        // action on the intent.
+        if (!TextUtils.isEmpty(targetIntent.getAction())
+                && !targetIntent.getAction().equals(Intent.ACTION_VIEW)
+                && targetIntent.getSelector() == null) {
+            return false;
+        }
+
+        if (targetIntent.getPackage() != null
+                && targetIntent.getPackage().equals(
+                        ContextUtils.getApplicationContext().getPackageName())) {
+            return false;
+        }
+
+        String selfPackageName = mDelegate.getContext().getPackageName();
+        boolean matchesOtherPackage = false;
+        for (ResolveInfo resolveInfo : resolveInfos) {
+            ActivityInfo info = resolveInfo.activityInfo;
+            if (info == null || !selfPackageName.equals(info.packageName)) {
+                matchesOtherPackage = true;
+                break;
+            }
+        }
+        if (!matchesOtherPackage) return false;
+
+        // Shortcut the queryIntentActivities if the scheme is a browser-supported scheme.
+        if (isIntentWithSupportedProtocol && !hasSpecializedHandler) return true;
+
+        // Fall back to querying for browser packages if the intent doesn't obviously match or not
+        // match a browser. This will catch custom URL schemes like googlechrome://.
+        Set<String> browserPackages = getInstalledBrowserPackages();
+
+        if (hasSpecializedHandler) {
+            List<String> specializedPackages = getSpecializedHandlers(resolveInfos);
+            for (String packageName : specializedPackages) {
+                // A non-browser package is specialized, so don't consider it to be targeting a
+                // browser.
+                if (!browserPackages.contains(packageName)) return false;
+            }
+        }
+
+        for (ResolveInfo resolveInfo : resolveInfos) {
+            ActivityInfo info = resolveInfo.activityInfo;
+            if (info != null && browserPackages.contains(info.packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static Set<String> getInstalledBrowserPackages() {
+        List<ResolveInfo> browsers = PackageManagerUtils.queryAllWebBrowsersInfo();
+
+        Set<String> packageNames = new HashSet<>();
+        for (ResolveInfo browser : browsers) {
+            if (browser.activityInfo == null) continue;
+            packageNames.add(browser.activityInfo.packageName);
+        }
+        return packageNames;
+    }
+
+    /**
      * Current URL has at least one specialized handler available. For navigations
      * within the same host, keep the navigation inside the browser unless the set of
      * available apps to handle the new navigation is different. http://crbug.com/463138
@@ -1049,7 +1124,7 @@
         boolean closeTab = params.shouldCloseContentsOnOverrideUrlLoadingAndLaunchIntent();
         if (shouldLaunch) {
             try {
-                startActivity(intent, proxy, mDelegate);
+                startActivity(intent, proxy);
                 if (mDelegate.canCloseTabOnIncognitoIntentLaunch() && closeTab) {
                     mDelegate.closeTab();
                 }
@@ -1116,22 +1191,6 @@
         return false;
     }
 
-    private boolean launchExternalIntent(Intent targetIntent, boolean shouldProxyForInstantApps) {
-        try {
-            if (!startActivityIfNeeded(targetIntent, shouldProxyForInstantApps)) {
-                if (DEBUG) Log.i(TAG, "The current Activity was the only targeted Activity.");
-                return false;
-            }
-        } catch (ActivityNotFoundException e) {
-            // The targeted app must have been uninstalled/disabled since we queried for Activities
-            // to handle this intent.
-            if (DEBUG) Log.i(TAG, "Activity not found.");
-            return false;
-        }
-        if (DEBUG) Log.i(TAG, "startActivityIfNeeded");
-        return true;
-    }
-
     // This will handle external navigations only for intent meant for Autofill Assistant.
     private boolean handleWithAutofillAssistant(
             ExternalNavigationParams params, Intent targetIntent, GURL browserFallbackUrl) {
@@ -1203,7 +1262,12 @@
             return OverrideUrlLoadingResult.forNoOverride();
         }
 
+        GURL intentDataUrl = new GURL(targetIntent.getDataString());
         boolean isExternalProtocol = !UrlUtilities.isAcceptedScheme(params.getUrl());
+        // intent: URLs are considered an external protocol, but may still contain a Data URI that
+        // this app does support, and may still end up launching this app.
+        boolean isIntentWithSupportedProtocol = UrlUtilities.hasIntentScheme(params.getUrl())
+                && UrlUtilities.isAcceptedScheme(intentDataUrl);
 
         if (isInternalPdfDownload(isExternalProtocol, params)) {
             return OverrideUrlLoadingResult.forNoOverride();
@@ -1291,8 +1355,8 @@
             return fallBackToHandlingInApp();
         }
 
-        // From this point on we should only have intents that this app can't handle, or intents for
-        // apps with specialized handlers.
+        // From this point on we should only have URLs from intent URIs, or URLs for
+        // apps with specialized handlers (including custom schemes).
 
         if (shouldStayWithinHost(
                     params, isLink, isFormSubmit, resolvingInfos.get(), isExternalProtocol)) {
@@ -1313,7 +1377,7 @@
         assert intentResolutionMatches(debugIntent, targetIntent);
 
         if (params.isIncognito()) {
-            return handleIncognitoIntent(params, targetIntent, resolvingInfos.get(),
+            return handleIncognitoIntent(params, targetIntent, intentDataUrl, resolvingInfos.get(),
                     browserFallbackUrl, shouldProxyForInstantApps);
         }
 
@@ -1327,24 +1391,29 @@
         } else if (launchWebApkIfSoleIntentHandler(resolvingInfos.get(), targetIntent)) {
             return OverrideUrlLoadingResult.forExternalIntent();
         }
-        if (launchExternalIntent(targetIntent, shouldProxyForInstantApps)) {
-            return OverrideUrlLoadingResult.forExternalIntent();
+
+        boolean requiresIntentChooser = false;
+        if (isViewIntentToOtherBrowser(targetIntent, resolvingInfos.get(),
+                    isIntentWithSupportedProtocol, hasSpecializedHandler)) {
+            RecordHistogram.recordBooleanHistogram("Android.Intent.WebIntentToOtherBrowser", true);
+            requiresIntentChooser = true;
         }
-        return OverrideUrlLoadingResult.forNoOverride();
+
+        return startActivityIfNeeded(targetIntent, shouldProxyForInstantApps, resolvingInfos.get(),
+                requiresIntentChooser, browserFallbackUrl, intentDataUrl, params.getReferrerUrl());
     }
 
     private OverrideUrlLoadingResult handleIncognitoIntent(ExternalNavigationParams params,
-            Intent targetIntent, List<ResolveInfo> resolvingInfos, GURL browserFallbackUrl,
-            boolean shouldProxyForInstantApps) {
+            Intent targetIntent, GURL intentDataUrl, List<ResolveInfo> resolvingInfos,
+            GURL browserFallbackUrl, boolean shouldProxyForInstantApps) {
         boolean intentTargetedToApp = mDelegate.willAppHandleIntent(targetIntent);
 
         GURL fallbackUrl = browserFallbackUrl;
         // If we can handle the intent, then fall back to handling the target URL instead of
         // the fallbackUrl if the user decides not to leave incognito.
         if (resolveInfoContainsSelf(resolvingInfos)) {
-            GURL targetUrl = UrlUtilities.hasIntentScheme(params.getUrl())
-                    ? new GURL(targetIntent.getDataString())
-                    : params.getUrl();
+            GURL targetUrl =
+                    UrlUtilities.hasIntentScheme(params.getUrl()) ? intentDataUrl : params.getUrl();
             // Make sure the browser can handle this URL, in case the Intent targeted a
             // non-browser component for this app.
             if (UrlUtilities.isAcceptedScheme(targetUrl)) fallbackUrl = targetUrl;
@@ -1413,7 +1482,7 @@
             return OverrideUrlLoadingResult.forAsyncAction(
                     OverrideUrlLoadingAsyncActionType.UI_GATING_INTENT_LAUNCH);
         } else {
-            startActivity(intent, false, mDelegate);
+            startActivity(intent, false);
             if (DEBUG) Log.i(TAG, "Intent to Play Store.");
             return OverrideUrlLoadingResult.forExternalIntent();
         }
@@ -1483,7 +1552,7 @@
         Intent webApkIntent = new Intent(targetIntent);
         webApkIntent.setPackage(packageName);
         try {
-            startActivity(webApkIntent, false, mDelegate);
+            startActivity(webApkIntent, false);
             if (DEBUG) Log.i(TAG, "Launched WebAPK");
             return true;
         } catch (ActivityNotFoundException e) {
@@ -1616,16 +1685,15 @@
      * @param proxy Whether we need to proxy the intent through AuthenticatedProxyActivity (this is
      *              used by Instant Apps intents).
      */
-    public static void startActivity(
-            Intent intent, boolean proxy, ExternalNavigationDelegate delegate) {
+    private void startActivity(Intent intent, boolean proxy) {
         try {
             forcePdfViewerAsIntentHandlerIfNeeded(intent);
             if (proxy) {
-                delegate.dispatchAuthenticatedIntent(intent);
+                mDelegate.dispatchAuthenticatedIntent(intent);
             } else {
                 // Start the activity via the current activity if possible, and otherwise as a new
                 // task from the application context.
-                Context context = ContextUtils.activityFromContext(delegate.getContext());
+                Context context = ContextUtils.activityFromContext(mDelegate.getContext());
                 if (context == null) {
                     context = ContextUtils.getApplicationContext();
                     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -1634,10 +1702,10 @@
             }
             recordExternalNavigationDispatched(intent);
         } catch (RuntimeException e) {
-            IntentUtils.logTransactionTooLargeOrRethrow(e, intent);
+            Log.e(TAG, "Could not start Activity for intent " + intent.toString(), e);
         }
 
-        delegate.didStartActivity(intent);
+        mDelegate.didStartActivity(intent);
     }
 
     /**
@@ -1646,69 +1714,191 @@
      * @param intent The intent we want to send.
      * @param proxy Whether we need to proxy the intent through AuthenticatedProxyActivity (this is
      *              used by Instant Apps intents).
-     * @returns whether an activity was started for the intent.
+     * @param resolvingInfos The resolvingInfos |intent| matches against.
+     * @param requiresIntentChooser Whether, for security reasons, the Intent Chooser is required to
+     *                              be shown.
+     * @param browserFallbackUrl The fallback URL if the user chooses not to leave this app.
+     * @param intentDataUrl The URL |intent| is targeting.
+     * @param referrerUrl The referrer for the navigation.
+     * @returns The OverrideUrlLoadingResult for starting (or not starting) the Activity.
      */
-    private boolean startActivityIfNeeded(Intent intent, boolean proxy) {
-        @ExternalNavigationDelegate.StartActivityIfNeededResult
-        int delegateResult = mDelegate.maybeHandleStartActivityIfNeeded(intent, proxy);
+    private OverrideUrlLoadingResult startActivityIfNeeded(Intent intent, boolean proxy,
+            List<ResolveInfo> resolvingInfos, boolean requiresIntentChooser,
+            GURL browserFallbackUrl, GURL intentDataUrl, GURL referrerUrl) {
+        // Only touches disk on Kitkat. See http://crbug.com/617725 for more context.
+        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+        try {
+            boolean withoutPackage = TextUtils.isEmpty(intent.getPackage());
 
-        switch (delegateResult) {
-            case ExternalNavigationDelegate.StartActivityIfNeededResult.HANDLED_WITH_ACTIVITY_START:
-                return true;
-            case ExternalNavigationDelegate.StartActivityIfNeededResult
-                    .HANDLED_WITHOUT_ACTIVITY_START:
-                return false;
-            case ExternalNavigationDelegate.StartActivityIfNeededResult.DID_NOT_HANDLE:
-                return startActivityIfNeededInternal(intent, proxy);
+            // TODO(https://crbug.com/1251722): Rename this delegate function and require
+            // the delegate to defer to the code below to actually launch the Activity.
+            @ExternalNavigationDelegate.StartActivityIfNeededResult
+            int delegateResult = mDelegate.maybeHandleStartActivityIfNeeded(intent, proxy);
+
+            if (withoutPackage
+                    && (!TextUtils.isEmpty(intent.getPackage()) || intent.getComponent() != null)) {
+                // Embedder chose a package for this Intent, we no longer need to use the chooser.
+                requiresIntentChooser = false;
+            }
+            switch (delegateResult) {
+                case ExternalNavigationDelegate.StartActivityIfNeededResult
+                        .HANDLED_WITH_ACTIVITY_START:
+                    return OverrideUrlLoadingResult.forExternalIntent();
+                case ExternalNavigationDelegate.StartActivityIfNeededResult
+                        .HANDLED_WITHOUT_ACTIVITY_START:
+                    return OverrideUrlLoadingResult.forNoOverride();
+                case ExternalNavigationDelegate.StartActivityIfNeededResult.DID_NOT_HANDLE:
+                    return startActivityIfNeededInternal(intent, proxy, resolvingInfos,
+                            requiresIntentChooser, browserFallbackUrl, intentDataUrl, referrerUrl);
+                default:
+                    assert false;
+            }
+        } catch (SecurityException e) {
+            // https://crbug.com/808494: Handle the URL internally if dispatching to another
+            // application fails with a SecurityException. This happens due to malformed
+            // manifests in another app.
+        } catch (ActivityNotFoundException e) {
+            // The targeted app must have been uninstalled/disabled since we queried for Activities
+            // to handle this intent.
+            if (DEBUG) Log.i(TAG, "Activity not found.");
+        } catch (AndroidRuntimeException e) {
+            // https://crbug.com/1226177: Most likely cause of this exception is Android failing
+            // to start the app that we previously detected could handle the Intent.
+            Log.e(TAG, "Could not start Activity for intent " + intent.toString(), e);
+        } catch (RuntimeException e) {
+            IntentUtils.logTransactionTooLargeOrRethrow(e, intent);
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
         }
-
-        assert false;
-        return false;
+        return OverrideUrlLoadingResult.forNoOverride();
     }
 
     /**
      * Implementation of startActivityIfNeeded() that is used when the delegate does not handle the
      * event.
      */
-    private boolean startActivityIfNeededInternal(Intent intent, boolean proxy) {
-        boolean activityWasLaunched;
-        // Only touches disk on Kitkat. See http://crbug.com/617725 for more context.
-        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
-        try {
-            forcePdfViewerAsIntentHandlerIfNeeded(intent);
-            if (proxy) {
-                mDelegate.dispatchAuthenticatedIntent(intent);
-                activityWasLaunched = true;
-            } else {
-                Activity activity = ContextUtils.activityFromContext(mDelegate.getContext());
-                if (activity != null) {
-                    activityWasLaunched = activity.startActivityIfNeeded(intent, -1);
-                } else {
-                    activityWasLaunched = false;
-                }
+    private OverrideUrlLoadingResult startActivityIfNeededInternal(Intent intent, boolean proxy,
+            List<ResolveInfo> resolvingInfos, boolean requiresIntentChooser,
+            GURL browserFallbackUrl, GURL intentDataUrl, GURL referrerUrl) {
+        forcePdfViewerAsIntentHandlerIfNeeded(intent);
+        if (proxy) {
+            mDelegate.dispatchAuthenticatedIntent(intent);
+            recordExternalNavigationDispatched(intent);
+            return OverrideUrlLoadingResult.forExternalIntent();
+        } else {
+            Activity activity = ContextUtils.activityFromContext(mDelegate.getContext());
+            if (activity == null) return OverrideUrlLoadingResult.forNoOverride();
+
+            if (requiresIntentChooser) {
+                return startActivityWithChooser(intent, resolvingInfos, browserFallbackUrl,
+                        intentDataUrl, referrerUrl, activity);
             }
-            if (activityWasLaunched) {
-                recordExternalNavigationDispatched(intent);
-            }
-            return activityWasLaunched;
-        } catch (SecurityException e) {
-            // https://crbug.com/808494: Handle the URL internally if dispatching to another
-            // application fails with a SecurityException. This happens due to malformed manifests
-            // in another app.
-            return false;
-        } catch (AndroidRuntimeException e) {
-            // https://crbug.com/1226177: Most likely cause of this exception is Android failing to
-            // start the app that we previously detected could handle the Intent.
-            Log.e(TAG, "Could not start Activity for intent " + intent.toString(), e);
-            return false;
-        } catch (RuntimeException e) {
-            IntentUtils.logTransactionTooLargeOrRethrow(e, intent);
-            return false;
-        } finally {
-            StrictMode.setThreadPolicy(oldPolicy);
+            return doStartActivityIfNeeded(intent, activity);
         }
     }
 
+    private OverrideUrlLoadingResult doStartActivityIfNeeded(Intent intent, Activity activity) {
+        if (activity.startActivityIfNeeded(intent, -1)) {
+            if (DEBUG) Log.i(TAG, "startActivityIfNeeded");
+            mDelegate.didStartActivity(intent);
+            recordExternalNavigationDispatched(intent);
+            return OverrideUrlLoadingResult.forExternalIntent();
+        } else {
+            if (DEBUG) Log.i(TAG, "The current Activity was the only targeted Activity.");
+            return OverrideUrlLoadingResult.forNoOverride();
+        }
+    }
+
+    @SuppressWarnings("UseCompatLoadingForDrawables")
+    private OverrideUrlLoadingResult startActivityWithChooser(final Intent intent,
+            List<ResolveInfo> resolvingInfos, GURL browserFallbackUrl, GURL intentDataUrl,
+            GURL referrerUrl, Activity activity) {
+        ResolveInfo intentResolveInfo =
+                PackageManagerUtils.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+        // If this is null, then the intent was only previously matching
+        // non-default filters, so just drop it.
+        if (intentResolveInfo == null) return OverrideUrlLoadingResult.forNoOverride();
+
+        // If the |resolvingInfos| from queryIntentActivities don't contain the result of
+        // resolveActivity, it means the intent is resolving to the ResolverActivity, so the user
+        // will already get the option to choose the target app (as there will be multiple options)
+        // and we don't need to do anything. Otherwise we have to make a fake option in the chooser
+        // dialog that loads the URL in the embedding app.
+        if (!resolversSubsetOf(Arrays.asList(intentResolveInfo), resolvingInfos)) {
+            return doStartActivityIfNeeded(intent, activity);
+        }
+
+        Intent pickerIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
+        pickerIntent.putExtra(Intent.EXTRA_INTENT, intent);
+
+        // Add the fake entry for the embedding app. This behavior is not well documented but works
+        // consistently across Android since L (and at least up to S).
+        PackageManager pm = activity.getPackageManager();
+        ArrayList<ShortcutIconResource> icons = new ArrayList<>();
+        ArrayList<String> labels = new ArrayList<>();
+        String packageName = activity.getPackageName();
+        String label = "";
+        ShortcutIconResource resource = new ShortcutIconResource();
+        try {
+            ApplicationInfo applicationInfo =
+                    pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
+            label = (String) pm.getApplicationLabel(applicationInfo);
+            Resources resources = pm.getResourcesForApplication(applicationInfo);
+            resource.packageName = packageName;
+            resource.resourceName = resources.getResourceName(applicationInfo.icon);
+            // This will throw a Resources.NotFoundException if the package uses resource
+            // name collapsing/stripping. The ActivityPicker fails to handle this exception, we have
+            // have to check for it here to avoid crashes.
+            resources.getDrawable(resources.getIdentifier(resource.resourceName, null, null), null);
+        } catch (NameNotFoundException | Resources.NotFoundException e) {
+            Log.w(TAG, "No icon resource found for package: " + packageName);
+            // Most likely the app doesn't have an icon and is just a test
+            // app. Android will just use a blank icon.
+            resource.packageName = "";
+            resource.resourceName = "";
+        }
+        labels.add(label);
+        icons.add(resource);
+        pickerIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, labels);
+        pickerIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icons);
+
+        // Call startActivityForResult on the PICK_ACTIVITY intent, which will set the component of
+        // the data result to the component of the chosen app.
+        mDelegate.getWindowAndroid().showCancelableIntent(
+                pickerIntent, new WindowAndroid.IntentCallback() {
+                    @Override
+                    public void onIntentCompleted(int resultCode, Intent data) {
+                        // If |data| is null, the user backed out of the intent chooser.
+                        if (data == null) return;
+
+                        // Quirk of how we use the ActivityChooser - if the embedding app is
+                        // chosen we get an intent back with ACTION_CREATE_SHORTCUT.
+                        if (data.getAction().equals(Intent.ACTION_CREATE_SHORTCUT)) {
+                            // It's pretty arbitrary whether to prefer the data URL or the fallback
+                            // URL here. We could consider preferring the fallback URL, as the URL
+                            // was probably intending to leave Chrome, but loading the URL the site
+                            // was trying to load in a browser seems like the better choice and
+                            // matches what would have happened had the regular chooser dialog shown
+                            // up and the user selected this app.
+                            if (UrlUtilities.isAcceptedScheme(intentDataUrl)) {
+                                clobberCurrentTab(intentDataUrl, referrerUrl);
+                            } else if (!browserFallbackUrl.isEmpty()) {
+                                clobberCurrentTab(browserFallbackUrl, referrerUrl);
+                            }
+                            return;
+                        }
+
+                        // Set the package for the original intent to the chosen app and start
+                        // it. Note that a selector cannot be set at the same time as a package.
+                        intent.setSelector(null);
+                        intent.setPackage(data.getComponent().getPackageName());
+                        startActivity(intent, false);
+                    }
+                }, null);
+        return OverrideUrlLoadingResult.forAsyncAction(
+                OverrideUrlLoadingAsyncActionType.UI_GATING_INTENT_LAUNCH);
+    }
+
     /**
      * Returns the number of specialized intent handlers in {@params infos}. Specialized intent
      * handlers are intent handlers which handle only a few URLs (e.g. google maps or youtube).
@@ -1787,7 +1977,7 @@
     }
 
     protected boolean resolveInfoContainsSelf(List<ResolveInfo> resolveInfos) {
-        String packageName = ContextUtils.getApplicationContext().getPackageName();
+        String packageName = mDelegate.getContext().getPackageName();
         for (ResolveInfo resolveInfo : resolveInfos) {
             ActivityInfo info = resolveInfo.activityInfo;
             if (info != null && packageName.equals(info.packageName)) {
diff --git a/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java b/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
index 5af25a6..79d741b 100644
--- a/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
+++ b/components/external_intents/android/javatests/src/org/chromium/components/external_intents/ExternalNavigationHandlerTest.java
@@ -89,6 +89,7 @@
     private static final boolean HANDLES_INSTANT_APP_LAUNCHING_INTERNALLY = true;
     private static final boolean INTENT_STARTED_TASK = true;
 
+    private static final String SELF_PACKAGE_NAME = "test.app.name";
     private static final String INTENT_APP_PACKAGE_NAME = "com.imdb.mobile";
     private static final String YOUTUBE_URL = "http://youtube.com/";
     private static final String YOUTUBE_MOBILE_URL = "http://m.youtube.com";
@@ -123,14 +124,14 @@
             "intent:///name/nm0000158#Intent;scheme=imdb;package=com.imdb.mobile;S."
             + ExternalNavigationHandler.EXTRA_MARKET_REFERRER + "=" + ENCODED_MARKET_REFERRER
             + ";end";
-    private static final String INTENT_URL_FOR_CHROME_CUSTOM_TABS = "intent://example.com#Intent;"
-            + "package=org.chromium.chrome;"
+    private static final String INTENT_URL_FOR_SELF_CUSTOM_TABS = "intent://example.com#Intent;"
+            + "package=" + SELF_PACKAGE_NAME + ";"
             + "action=android.intent.action.VIEW;"
             + "scheme=http;"
             + "S.android.support.customtabs.extra.SESSION=;"
             + "end;";
-    private static final String INTENT_URL_FOR_CHROME = "intent://example.com#Intent;"
-            + "package=org.chromium.chrome;"
+    private static final String INTENT_URL_FOR_SELF = "intent://example.com#Intent;"
+            + "package=" + SELF_PACKAGE_NAME + ";"
             + "action=android.intent.action.VIEW;"
             + "scheme=http;"
             + "S." + ExternalNavigationHandler.EXTRA_BROWSER_FALLBACK_URL + "="
@@ -196,6 +197,7 @@
 
         mContext = new TestContext(InstrumentationRegistry.getTargetContext(), mDelegate);
         ContextUtils.initApplicationContextForTests(mContext);
+        mDelegate.setContext(mContext);
 
         NativeLibraryTestUtils.loadNativeLibraryNoBrowserProcess();
     }
@@ -761,7 +763,7 @@
     public void testCCTIntentUriDoesNotFireCCTAndLoadInChrome_InIncognito() throws Exception {
         mUrlHandler.mResolveInfoContainsSelf = true;
         mDelegate.setCanLoadUrlInTab(false);
-        checkUrl(INTENT_URL_FOR_CHROME_CUSTOM_TABS)
+        checkUrl(INTENT_URL_FOR_SELF_CUSTOM_TABS)
                 .withIsIncognito(true)
                 .expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_CLOBBERING_TAB, IGNORE);
         Assert.assertNull(mDelegate.startActivityIntent);
@@ -771,7 +773,7 @@
     @Test
     @SmallTest
     public void testCCTIntentUriFiresCCT_InRegular() throws Exception {
-        checkUrl(INTENT_URL_FOR_CHROME_CUSTOM_TABS)
+        checkUrl(INTENT_URL_FOR_SELF_CUSTOM_TABS)
                 .withIsIncognito(false)
                 .expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
                         START_OTHER_ACTIVITY);
@@ -783,14 +785,14 @@
     public void testChromeIntentUriDoesNotFireAndLoadsInChrome_InIncognito() throws Exception {
         mUrlHandler.mResolveInfoContainsSelf = true;
         mDelegate.setCanLoadUrlInTab(false);
-        checkUrl(INTENT_URL_FOR_CHROME)
+        checkUrl(INTENT_URL_FOR_SELF)
                 .withIsIncognito(true)
                 .expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_CLOBBERING_TAB, IGNORE);
         Assert.assertNull(mDelegate.startActivityIntent);
         Assert.assertEquals("http://example.com/", mUrlHandler.mNewUrlAfterClobbering);
 
         mUrlHandler.mResolveInfoContainsSelf = false;
-        checkUrl(INTENT_URL_FOR_CHROME)
+        checkUrl(INTENT_URL_FOR_SELF)
                 .withIsIncognito(true)
                 .expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_CLOBBERING_TAB, IGNORE);
         Assert.assertNull(mDelegate.startActivityIntent);
@@ -2276,6 +2278,23 @@
                 mUrlHandler.canExternalAppHandleUrl(new GURL(indexOutOfBoundsException)));
     }
 
+    @Test
+    @SmallTest
+    public void testIntentToOtherBrowser() {
+        // This will create a non-specialized ResolveInfo for the target package.
+        mDelegate.setCanResolveActivityForExternalSchemes(true);
+
+        String intent = "intent://example.com#Intent;scheme=https;package=com.other.browser;end";
+
+        // This is a limitation of this testing harness, which doesn't get past
+        // startActivityIfNeeded as that requires an Activity context. This functionality is
+        // tested in ExternalNavigationDelegateImplTest.
+        checkUrl(intent)
+                .withPageTransition(PageTransition.LINK)
+                .expecting(OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT,
+                        START_OTHER_ACTIVITY);
+    }
+
     private static List<ResolveInfo> makeResolveInfos(ResolveInfo... infos) {
         return Arrays.asList(infos);
     }
@@ -2361,7 +2380,7 @@
         @Override
         protected AlertDialog showLeavingIncognitoAlert(Context context,
                 ExternalNavigationParams params, Intent intent, GURL fallbackUrl, boolean proxy) {
-            if (context == null) return mAlertDialog;
+            if (context instanceof TestContext) return mAlertDialog;
             mShownIncognitoAlertDialog =
                     super.showLeavingIncognitoAlert(context, params, intent, fallbackUrl, proxy);
             return mShownIncognitoAlertDialog;
@@ -2447,6 +2466,11 @@
             return list;
         }
 
+        public ResolveInfo resolveActivity(Intent intent) {
+            List<ResolveInfo> list = queryIntentActivities(intent);
+            return list.size() > 0 ? list.get(0) : null;
+        }
+
         @Override
         public Context getContext() {
             return mContext;
@@ -2883,6 +2907,11 @@
         public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
             return mDelegate.queryIntentActivities(intent);
         }
+
+        @Override
+        public ResolveInfo resolveActivity(Intent intent, int flags) {
+            return mDelegate.resolveActivity(intent);
+        }
     }
 
     private static class TestContext extends ContextWrapper {
@@ -2905,7 +2934,7 @@
 
         @Override
         public String getPackageName() {
-            return "test.app.name";
+            return SELF_PACKAGE_NAME;
         }
 
         @Override
@@ -2914,8 +2943,7 @@
         }
 
         @Override
-        public void startActivity(Intent intent) {
-        }
+        public void startActivity(Intent intent) {}
 
         @Override
         public void startActivity(Intent intent, Bundle options) {
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index c584249..b85d96e 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -1553,6 +1553,16 @@
   </summary>
 </histogram>
 
+<histogram name="Android.Intent.WebIntentToOtherBrowser" enum="Boolean"
+    expires_after="M98">
+  <owner>mthiesse@chromium.org</owner>
+  <owner>yfriedman@chromium.org</owner>
+  <summary>
+    When a site attempts to navigate to a supported URL with an intent URL that
+    targets other browser-like apps.
+  </summary>
+</histogram>
+
 <histogram name="Android.IsLastSharedAppInfoRetrieved"
     enum="BooleanIsLastSharedAppInfoRetrieved" expires_after="M77">
   <owner>tedchoc@chromium.org</owner>
diff --git a/weblayer/browser/android/javatests/BUILD.gn b/weblayer/browser/android/javatests/BUILD.gn
index 81a33e7..82f31eea 100644
--- a/weblayer/browser/android/javatests/BUILD.gn
+++ b/weblayer/browser/android/javatests/BUILD.gn
@@ -57,6 +57,7 @@
     "//net/android:net_java_test_support",
     "//third_party/android_deps:android_support_v4_java",
     "//third_party/android_deps:espresso_java",
+    "//third_party/android_sdk:android_support_chromium_java",
     "//third_party/android_support_test_runner:rules_java",
     "//third_party/android_support_test_runner:runner_java",
     "//third_party/androidx:androidx_activity_activity_java",
diff --git a/weblayer/browser/android/javatests/skew/expectations.txt b/weblayer/browser/android/javatests/skew/expectations.txt
index ab44db6..ff80cfa 100644
--- a/weblayer/browser/android/javatests/skew/expectations.txt
+++ b/weblayer/browser/android/javatests/skew/expectations.txt
@@ -5,7 +5,7 @@
 # with versions less than or equal to $VERSION of the implementation.
 #
 # These lines are not comments! They define the set of known tags and other information.
-# tags: [ client_lte_91 client_lte_94 ]
+# tags: [ client_lte_91 client_lte_94 impl_lte_94 ]
 # 'all' disables the test from any skew test.
 # tags: [ all ]
 # results: [ Skip ]
@@ -35,6 +35,45 @@
 crbug.com/1238481 [ client_lte_94 ] org.chromium.weblayer.test.TabTest#testRotationDoesntChangeVisibility [ Skip ]
 crbug.com/1239028 [ client_lte_94 ] org.chromium.weblayer.test.MediaSessionTest#basic [ Skip ]
 
+# Chrome changes broke some test infrastructure, so tests are only artificially
+# broken - the behavior under test has not changed.
+crbug.com/1249962 [ client_lte_94] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentAfterRedirectInBackgroundTabLaunchedWhenBackgroundLaunchesAllowed [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentAfterRedirectLaunched [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentInNewTabLaunchedOnLinkClick [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentInSameTabLaunchedOnLinkClick [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentNavigationParamSetOnIntentLaunchViaLinkClick [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentNavigationParamSetOnNavigationsToIntents [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentViaOnLoadLaunched [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithFallbackUrlAfterRedirectLaunched [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectInBackgroundTabLaunchedWhenBackgroundLaunchesAllowed [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectInBrowserStartupInIncognitoBlockedWhenBackgroundLaunchesAllowedAndUserForbids [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectInBrowserStartupInIncognitoLaunchedWhenBackgroundLaunchesAllowedAndUserConsents [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectInBrowserStartupInIncognitoWithEmbedderPresentingWarningDialogBlockedWhenBackgroundLaunchesAllowedAndUserForbids [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectInBrowserStartupInIncognitoWithEmbedderPresentingWarningDialogLaunchedWhenBackgroundLaunchesAllowedAndUserConsents [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectInBrowserStartupLaunchedWhenBackgroundLaunchesAllowed [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectLaunched [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testUserClicksLinkToPageWithExternalIntentLaunchedViaOnLoad [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testUserDecidingExternalIntentNavigationParamSetOnNavigationsToIntentsInIncognito [ Skip ]
+crbug.com/1249962 [ client_lte_94 ] org.chromium.weblayer.test.NavigationTest#testIsKnownProtocol [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentAfterRedirectInBackgroundTabLaunchedWhenBackgroundLaunchesAllowed [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentAfterRedirectLaunched [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentInNewTabLaunchedOnLinkClick [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentInSameTabLaunchedOnLinkClick [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentNavigationParamSetOnIntentLaunchViaLinkClick [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentNavigationParamSetOnNavigationsToIntents [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentViaOnLoadLaunched [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithFallbackUrlAfterRedirectLaunched [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectInBackgroundTabLaunchedWhenBackgroundLaunchesAllowed [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectInBrowserStartupInIncognitoBlockedWhenBackgroundLaunchesAllowedAndUserForbids [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectInBrowserStartupInIncognitoLaunchedWhenBackgroundLaunchesAllowedAndUserConsents [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectInBrowserStartupInIncognitoWithEmbedderPresentingWarningDialogBlockedWhenBackgroundLaunchesAllowedAndUserForbids [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectInBrowserStartupInIncognitoWithEmbedderPresentingWarningDialogLaunchedWhenBackgroundLaunchesAllowedAndUserConsents [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectInBrowserStartupLaunchedWhenBackgroundLaunchesAllowed [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testExternalIntentWithNoRedirectLaunched [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testUserClicksLinkToPageWithExternalIntentLaunchedViaOnLoad [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.ExternalNavigationTest#testUserDecidingExternalIntentNavigationParamSetOnNavigationsToIntentsInIncognito [ Skip ]
+crbug.com/1249962 [ impl_lte_94 ] org.chromium.weblayer.test.NavigationTest#testIsKnownProtocol [ Skip ]
+
 # Bulk disable to get bot green.
 crbug.com/1191751 [ all ] org.chromium.weblayer.test.InputTypesTest* [ Skip ]
 crbug.com/1191751 [ all ] org.chromium.weblayer.test.TabCallbackTest#testScrollNotificationDirectionChange [ Skip ]
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java
index 8d9df69..0900568 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/ExternalNavigationTest.java
@@ -12,11 +12,11 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
-import android.app.Activity;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
 
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
@@ -49,17 +49,6 @@
     public InstrumentationActivityTestRule mActivityTestRule =
             new InstrumentationActivityTestRule();
 
-    /**
-     * A dummy activity that claims to handle "weblayer://weblayertest".
-     */
-    public static class DummyActivityForSpecialScheme extends Activity {
-        @Override
-        protected void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            finish();
-        }
-    }
-
     private static final boolean EXPECT_NAVIGATION_COMPLETION = true;
     private static final boolean EXPECT_NAVIGATION_FAILURE = false;
     private static final boolean RESULTS_IN_EXTERNAL_INTENT = true;
@@ -77,20 +66,21 @@
     // The package is not specified in the intent that gets created when navigating to the special
     // scheme.
     private static final String INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_PACKAGE = null;
-    private static final String INTENT_TO_CHROME_DATA_CONTENT =
+    private static final String INTENT_TO_SELF_DATA_CONTENT =
             "play.google.com/store/apps/details?id=com.facebook.katana/";
-    private static final String INTENT_TO_CHROME_SCHEME = "https";
-    private static final String INTENT_TO_CHROME_DATA_STRING =
-            INTENT_TO_CHROME_SCHEME + "://" + INTENT_TO_CHROME_DATA_CONTENT;
-    private static final String INTENT_TO_CHROME_ACTION = "android.intent.action.VIEW";
-    private static final String INTENT_TO_CHROME_PACKAGE = "com.android.chrome";
+    private static final String INTENT_TO_SELF_SCHEME = "https";
+    private static final String INTENT_TO_SELF_DATA_STRING =
+            INTENT_TO_SELF_SCHEME + "://" + INTENT_TO_SELF_DATA_CONTENT;
+    private static final String INTENT_TO_SELF_ACTION = "android.intent.action.VIEW";
+    private static final String INTENT_TO_SELF_PACKAGE =
+            InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName();
 
-    // An intent that opens Chrome to view a specified URL. Note that the "end" is left off to allow
-    // appending extras when constructing URLs.
-    private static final String INTENT_TO_CHROME = "intent://" + INTENT_TO_CHROME_DATA_CONTENT
-            + "#Intent;scheme=" + INTENT_TO_CHROME_SCHEME + ";action=" + INTENT_TO_CHROME_ACTION
-            + ";package=" + INTENT_TO_CHROME_PACKAGE + ";";
-    private static final String INTENT_TO_CHROME_URL = INTENT_TO_CHROME + "end";
+    // An intent that opens the test app to view a specified URL. Note that the "end" is left off to
+    // allow appending extras when constructing URLs.
+    private static final String INTENT_TO_SELF = "intent://" + INTENT_TO_SELF_DATA_CONTENT
+            + "#Intent;scheme=" + INTENT_TO_SELF_SCHEME + ";action=" + INTENT_TO_SELF_ACTION
+            + ";package=" + INTENT_TO_SELF_PACKAGE + ";";
+    private static final String INTENT_TO_SELF_URL = INTENT_TO_SELF + "end";
 
     // An intent URL that gets rejected as malformed.
     private static final String MALFORMED_INTENT_URL = "intent://garbage;end";
@@ -100,33 +90,33 @@
     private static final String NON_RESOLVABLE_INTENT =
             "intent://dummy.com/#Intent;scheme=https;action=android.intent.action.VIEW;package=com.missing.app;";
 
-    private static final String LINK_WITH_INTENT_TO_CHROME_IN_SAME_TAB_FILE =
-            "link_with_intent_to_chrome_in_same_tab.html";
-    private static final String LINK_WITH_INTENT_TO_CHROME_IN_NEW_TAB_FILE =
-            "link_with_intent_to_chrome_in_new_tab.html";
+    private static final String LINK_WITH_INTENT_TO_SELF_IN_SAME_TAB_FILE =
+            "link_with_intent_to_package_in_same_tab.html#" + INTENT_TO_SELF_PACKAGE;
+    private static final String LINK_WITH_INTENT_TO_SELF_IN_NEW_TAB_FILE =
+            "link_with_intent_to_package_in_new_tab.html#" + INTENT_TO_SELF_PACKAGE;
     private static final String PAGE_THAT_INTENTS_TO_CHROME_ON_LOAD_FILE =
-            "page_that_intents_to_chrome_on_load.html";
+            "page_that_intents_to_package_on_load.html#" + INTENT_TO_SELF_PACKAGE;
     private static final String LINK_TO_PAGE_THAT_INTENTS_TO_CHROME_ON_LOAD_FILE =
-            "link_to_page_that_intents_to_chrome_on_load.html";
+            "link_to_page_that_intents_to_package_on_load.html#" + INTENT_TO_SELF_PACKAGE;
 
     // The test server handles "echo" with a response containing "Echo" :).
     private final String mTestServerSiteUrl = mActivityTestRule.getTestServer().getURL("/echo");
 
     private final String mTestServerSiteFallbackUrlExtra =
             "S.browser_fallback_url=" + android.net.Uri.encode(mTestServerSiteUrl) + ";";
-    private final String mIntentToChromeWithFallbackUrl =
-            INTENT_TO_CHROME + mTestServerSiteFallbackUrlExtra + "end";
+    private final String mIntentToSelfWithFallbackUrl =
+            INTENT_TO_SELF + mTestServerSiteFallbackUrlExtra + "end";
     private final String mNonResolvableIntentWithFallbackUrl =
             NON_RESOLVABLE_INTENT + mTestServerSiteFallbackUrlExtra + "end";
 
     private final String mRedirectToCustomSchemeUrlWithDefaultExternalHandler =
             mActivityTestRule.getTestServer().getURL(
                     "/server-redirect?" + CUSTOM_SCHEME_URL_WITH_DEFAULT_EXTERNAL_HANDLER);
-    private final String mRedirectToIntentToChromeURL =
-            mActivityTestRule.getTestServer().getURL("/server-redirect?" + INTENT_TO_CHROME_URL);
+    private final String mRedirectToIntentToSelfURL =
+            mActivityTestRule.getTestServer().getURL("/server-redirect?" + INTENT_TO_SELF_URL);
     private final String mNonResolvableIntentWithFallbackUrlThatLaunchesIntent =
             NON_RESOLVABLE_INTENT + "S.browser_fallback_url="
-            + android.net.Uri.encode(mRedirectToIntentToChromeURL) + ";end";
+            + android.net.Uri.encode(mRedirectToIntentToSelfURL) + ";end";
 
     private class IntentInterceptor implements InstrumentationActivity.IntentInterceptor {
         public Intent mLastIntent;
@@ -276,10 +266,10 @@
 
         // Navigate directly to an intent in the background and verify that the intent is not
         // launched.
-        NavigationWaiter waiter = new NavigationWaiter(INTENT_TO_CHROME_URL, backgroundTab,
+        NavigationWaiter waiter = new NavigationWaiter(INTENT_TO_SELF_URL, backgroundTab,
                 /*expectFailure=*/true, /*waitForPaint=*/false);
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            backgroundTab.getNavigationController().navigate(Uri.parse(INTENT_TO_CHROME_URL));
+            backgroundTab.getNavigationController().navigate(Uri.parse(INTENT_TO_SELF_URL));
         });
 
         waiter.waitForNavigation();
@@ -321,7 +311,7 @@
             NavigateParams.Builder navigateParamsBuilder = new NavigateParams.Builder();
             navigateParamsBuilder.allowIntentLaunchesInBackground();
             backgroundTab.getNavigationController().navigate(
-                    Uri.parse(INTENT_TO_CHROME_URL), navigateParamsBuilder.build());
+                    Uri.parse(INTENT_TO_SELF_URL), navigateParamsBuilder.build());
         });
 
         intentInterceptor.waitForIntent();
@@ -330,9 +320,9 @@
         // navigation in the background tab.
         Intent intent = intentInterceptor.mLastIntent;
         Assert.assertNotNull(intent);
-        Assert.assertEquals(INTENT_TO_CHROME_PACKAGE, intent.getPackage());
-        Assert.assertEquals(INTENT_TO_CHROME_ACTION, intent.getAction());
-        Assert.assertEquals(INTENT_TO_CHROME_DATA_STRING, intent.getDataString());
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
 
         int numNavigationsInBackgroundTab = TestThreadUtils.runOnUiThreadBlocking(
                 () -> { return backgroundTab.getNavigationController().getNavigationListSize(); });
@@ -357,11 +347,10 @@
 
         // Perform a navigation that redirects to an intent in the background and verify that the
         // intent is not launched.
-        NavigationWaiter waiter = new NavigationWaiter(INTENT_TO_CHROME_URL, backgroundTab,
+        NavigationWaiter waiter = new NavigationWaiter(INTENT_TO_SELF_URL, backgroundTab,
                 /*expectFailure=*/true, /*waitForPaint=*/false);
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            backgroundTab.getNavigationController().navigate(
-                    Uri.parse(mRedirectToIntentToChromeURL));
+            backgroundTab.getNavigationController().navigate(Uri.parse(mRedirectToIntentToSelfURL));
         });
 
         waiter.waitForNavigation();
@@ -403,7 +392,7 @@
             NavigateParams.Builder navigateParamsBuilder = new NavigateParams.Builder();
             navigateParamsBuilder.allowIntentLaunchesInBackground();
             backgroundTab.getNavigationController().navigate(
-                    Uri.parse(mRedirectToIntentToChromeURL), navigateParamsBuilder.build());
+                    Uri.parse(mRedirectToIntentToSelfURL), navigateParamsBuilder.build());
         });
 
         intentInterceptor.waitForIntent();
@@ -412,9 +401,9 @@
         // navigation in the background tab.
         Intent intent = intentInterceptor.mLastIntent;
         Assert.assertNotNull(intent);
-        Assert.assertEquals(INTENT_TO_CHROME_PACKAGE, intent.getPackage());
-        Assert.assertEquals(INTENT_TO_CHROME_ACTION, intent.getAction());
-        Assert.assertEquals(INTENT_TO_CHROME_DATA_STRING, intent.getDataString());
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
 
         int numNavigationsInBackgroundTab = TestThreadUtils.runOnUiThreadBlocking(
                 () -> { return backgroundTab.getNavigationController().getNavigationListSize(); });
@@ -434,7 +423,7 @@
         NavigationCallback navigationCallback = new NavigationCallback() {
             @Override
             public void onNavigationFailed(Navigation navigation) {
-                if (navigation.getUri().toString().equals(INTENT_TO_CHROME_URL)) {
+                if (navigation.getUri().toString().equals(INTENT_TO_SELF_URL)) {
                     onNavigationFailedCallbackHelper.notifyCalled();
                 }
             }
@@ -451,7 +440,7 @@
                         browser.getActiveTab().getNavigationController().registerNavigationCallback(
                                 navigationCallback);
                         browser.getActiveTab().getNavigationController().navigate(
-                                Uri.parse(INTENT_TO_CHROME_URL));
+                                Uri.parse(INTENT_TO_SELF_URL));
                     }
                 });
 
@@ -508,7 +497,7 @@
                         NavigateParams.Builder navigateParamsBuilder = new NavigateParams.Builder();
                         navigateParamsBuilder.allowIntentLaunchesInBackground();
                         browser.getActiveTab().getNavigationController().navigate(
-                                Uri.parse(INTENT_TO_CHROME_URL), navigateParamsBuilder.build());
+                                Uri.parse(INTENT_TO_SELF_URL), navigateParamsBuilder.build());
                     }
                 });
 
@@ -518,9 +507,9 @@
         intentInterceptor.waitForIntent();
         Intent intent = intentInterceptor.mLastIntent;
         Assert.assertNotNull(intent);
-        Assert.assertEquals(INTENT_TO_CHROME_PACKAGE, intent.getPackage());
-        Assert.assertEquals(INTENT_TO_CHROME_ACTION, intent.getAction());
-        Assert.assertEquals(INTENT_TO_CHROME_DATA_STRING, intent.getDataString());
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
 
         // ...the tab created for the initial navigation should be closed...
         onTabRemovedCallbackHelper.waitForFirst();
@@ -548,7 +537,7 @@
         NavigationCallback navigationCallback = new NavigationCallback() {
             @Override
             public void onNavigationFailed(Navigation navigation) {
-                if (navigation.getUri().toString().equals(INTENT_TO_CHROME_URL)) {
+                if (navigation.getUri().toString().equals(INTENT_TO_SELF_URL)) {
                     onNavigationFailedCallbackHelper.notifyCalled();
                 }
             }
@@ -564,7 +553,7 @@
                         browser.getActiveTab().getNavigationController().registerNavigationCallback(
                                 navigationCallback);
                         browser.getActiveTab().getNavigationController().navigate(
-                                Uri.parse(INTENT_TO_CHROME_URL));
+                                Uri.parse(INTENT_TO_SELF_URL));
                     }
                 });
 
@@ -626,7 +615,7 @@
                         NavigateParams.Builder navigateParamsBuilder = new NavigateParams.Builder();
                         navigateParamsBuilder.allowIntentLaunchesInBackground();
                         browser.getActiveTab().getNavigationController().navigate(
-                                Uri.parse(INTENT_TO_CHROME_URL), navigateParamsBuilder.build());
+                                Uri.parse(INTENT_TO_SELF_URL), navigateParamsBuilder.build());
                     }
                 });
 
@@ -645,9 +634,9 @@
         intentInterceptor.waitForIntent();
         Intent intent = intentInterceptor.mLastIntent;
         Assert.assertNotNull(intent);
-        Assert.assertEquals(INTENT_TO_CHROME_PACKAGE, intent.getPackage());
-        Assert.assertEquals(INTENT_TO_CHROME_ACTION, intent.getAction());
-        Assert.assertEquals(INTENT_TO_CHROME_DATA_STRING, intent.getDataString());
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
 
         // ...the tab created for the initial navigation should be closed...
         onTabRemovedCallbackHelper.waitForFirst();
@@ -680,11 +669,13 @@
             @Override
             public void onNavigationStarted(Navigation navigation) {
                 // There should be no additional navigations after the initial one.
-                Assert.assertEquals(INTENT_TO_CHROME_URL, navigation.getUri().toString());
+                Assert.assertEquals(INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_DATA_STRING,
+                        navigation.getUri().toString());
             }
             @Override
             public void onNavigationFailed(Navigation navigation) {
-                if (navigation.getUri().toString().equals(INTENT_TO_CHROME_URL)) {
+                if (navigation.getUri().toString().equals(
+                            INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_DATA_STRING)) {
                     onNavigationToIntentFailedCallbackHelper.notifyCalled();
                 }
             }
@@ -703,7 +694,8 @@
                         NavigateParams.Builder navigateParamsBuilder = new NavigateParams.Builder();
                         navigateParamsBuilder.allowIntentLaunchesInBackground();
                         browser.getActiveTab().getNavigationController().navigate(
-                                Uri.parse(INTENT_TO_CHROME_URL), navigateParamsBuilder.build());
+                                Uri.parse(INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_DATA_STRING),
+                                navigateParamsBuilder.build());
                     }
                 });
 
@@ -775,7 +767,7 @@
                         NavigateParams.Builder navigateParamsBuilder = new NavigateParams.Builder();
                         navigateParamsBuilder.allowIntentLaunchesInBackground();
                         browser.getActiveTab().getNavigationController().navigate(
-                                Uri.parse(INTENT_TO_CHROME_URL), navigateParamsBuilder.build());
+                                Uri.parse(INTENT_TO_SELF_URL), navigateParamsBuilder.build());
                     }
                 });
 
@@ -798,9 +790,9 @@
         intentInterceptor.waitForIntent();
         Intent intent = intentInterceptor.mLastIntent;
         Assert.assertNotNull(intent);
-        Assert.assertEquals(INTENT_TO_CHROME_PACKAGE, intent.getPackage());
-        Assert.assertEquals(INTENT_TO_CHROME_ACTION, intent.getAction());
-        Assert.assertEquals(INTENT_TO_CHROME_DATA_STRING, intent.getDataString());
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
 
         // ...the tab created for the initial navigation should be closed...
         onTabRemovedCallbackHelper.waitForFirst();
@@ -833,11 +825,13 @@
             @Override
             public void onNavigationStarted(Navigation navigation) {
                 // There should be no additional navigations after the initial one.
-                Assert.assertEquals(INTENT_TO_CHROME_URL, navigation.getUri().toString());
+                Assert.assertEquals(INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_DATA_STRING,
+                        navigation.getUri().toString());
             }
             @Override
             public void onNavigationFailed(Navigation navigation) {
-                if (navigation.getUri().toString().equals(INTENT_TO_CHROME_URL)) {
+                if (navigation.getUri().toString().equals(
+                            INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_DATA_STRING)) {
                     onNavigationToIntentFailedCallbackHelper.notifyCalled();
                 }
             }
@@ -860,7 +854,8 @@
                         NavigateParams.Builder navigateParamsBuilder = new NavigateParams.Builder();
                         navigateParamsBuilder.allowIntentLaunchesInBackground();
                         browser.getActiveTab().getNavigationController().navigate(
-                                Uri.parse(INTENT_TO_CHROME_URL), navigateParamsBuilder.build());
+                                Uri.parse(INTENT_TO_DUMMY_ACTIVITY_FOR_SPECIAL_SCHEME_DATA_STRING),
+                                navigateParamsBuilder.build());
                     }
                 });
 
@@ -913,7 +908,7 @@
 
         Tab tab = mActivityTestRule.getActivity().getTab();
         TestThreadUtils.runOnUiThreadBlocking(
-                () -> { tab.getNavigationController().navigate(Uri.parse(INTENT_TO_CHROME_URL)); });
+                () -> { tab.getNavigationController().navigate(Uri.parse(INTENT_TO_SELF_URL)); });
 
         intentInterceptor.waitForIntent();
 
@@ -921,9 +916,9 @@
         Assert.assertEquals(ABOUT_BLANK_URL, mActivityTestRule.getCurrentDisplayUrl());
         Intent intent = intentInterceptor.mLastIntent;
         Assert.assertNotNull(intent);
-        Assert.assertEquals(INTENT_TO_CHROME_PACKAGE, intent.getPackage());
-        Assert.assertEquals(INTENT_TO_CHROME_ACTION, intent.getAction());
-        Assert.assertEquals(INTENT_TO_CHROME_DATA_STRING, intent.getDataString());
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
     }
 
     /**
@@ -957,12 +952,12 @@
         IntentInterceptor intentInterceptor = new IntentInterceptor();
         activity.setIntentInterceptor(intentInterceptor);
 
-        navigateAndCheckExternalIntentParams(INTENT_TO_CHROME_URL, EXPECT_NAVIGATION_FAILURE,
+        navigateAndCheckExternalIntentParams(INTENT_TO_SELF_URL, EXPECT_NAVIGATION_FAILURE,
                 RESULTS_IN_EXTERNAL_INTENT, DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
-        navigateAndCheckExternalIntentParams(mIntentToChromeWithFallbackUrl,
+        navigateAndCheckExternalIntentParams(mIntentToSelfWithFallbackUrl,
                 EXPECT_NAVIGATION_FAILURE, RESULTS_IN_EXTERNAL_INTENT,
                 DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
-        navigateAndCheckExternalIntentParams(mRedirectToIntentToChromeURL, INTENT_TO_CHROME_URL,
+        navigateAndCheckExternalIntentParams(mRedirectToIntentToSelfURL, INTENT_TO_SELF_URL,
                 EXPECT_NAVIGATION_FAILURE, RESULTS_IN_EXTERNAL_INTENT,
                 DOESNT_RESULT_IN_USER_DECIDING_EXTERNAL_INTENT);
         navigateAndCheckExternalIntentParams(mRedirectToCustomSchemeUrlWithDefaultExternalHandler,
@@ -994,7 +989,7 @@
         extras.putBoolean(InstrumentationActivity.EXTRA_IS_INCOGNITO, true);
         mActivityTestRule.launchShellWithUrl(ABOUT_BLANK_URL, extras);
 
-        navigateAndCheckExternalIntentParams(INTENT_TO_CHROME_URL, EXPECT_NAVIGATION_FAILURE,
+        navigateAndCheckExternalIntentParams(INTENT_TO_SELF_URL, EXPECT_NAVIGATION_FAILURE,
                 DOESNT_RESULT_IN_EXTERNAL_INTENT, RESULTS_IN_USER_DECIDING_EXTERNAL_INTENT);
     }
 
@@ -1017,7 +1012,7 @@
         NavigationCallback navigationCallback = new NavigationCallback() {
             @Override
             public void onNavigationFailed(Navigation navigation) {
-                Assert.assertEquals(INTENT_TO_CHROME_URL, navigation.getUri().toString());
+                Assert.assertEquals(INTENT_TO_SELF_URL, navigation.getUri().toString());
                 Assert.assertEquals(true, navigation.wasIntentLaunched());
                 Assert.assertEquals(false, navigation.isUserDecidingIntentLaunch());
 
@@ -1030,7 +1025,7 @@
 
         // Navigate to a URL that has a link to an intent, click on the link, and verify via the
         // callback that the navigation to the intent fails with the expected state set.
-        String url = mActivityTestRule.getTestDataURL(LINK_WITH_INTENT_TO_CHROME_IN_SAME_TAB_FILE);
+        String url = mActivityTestRule.getTestDataURL(LINK_WITH_INTENT_TO_SELF_IN_SAME_TAB_FILE);
         mActivityTestRule.navigateAndWait(url);
         mActivityTestRule.executeScriptSync(
                 "document.onclick = function() {document.getElementById('link').click()}",
@@ -1059,7 +1054,7 @@
 
         Tab tab = mActivityTestRule.getActivity().getTab();
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            tab.getNavigationController().navigate(Uri.parse(mRedirectToIntentToChromeURL));
+            tab.getNavigationController().navigate(Uri.parse(mRedirectToIntentToSelfURL));
         });
 
         intentInterceptor.waitForIntent();
@@ -1068,9 +1063,9 @@
         Assert.assertEquals(ABOUT_BLANK_URL, mActivityTestRule.getCurrentDisplayUrl());
         Intent intent = intentInterceptor.mLastIntent;
         Assert.assertNotNull(intent);
-        Assert.assertEquals(INTENT_TO_CHROME_PACKAGE, intent.getPackage());
-        Assert.assertEquals(INTENT_TO_CHROME_ACTION, intent.getAction());
-        Assert.assertEquals(INTENT_TO_CHROME_DATA_STRING, intent.getDataString());
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
     }
 
     /**
@@ -1118,7 +1113,7 @@
         IntentInterceptor intentInterceptor = new IntentInterceptor();
         activity.setIntentInterceptor(intentInterceptor);
 
-        String url = mActivityTestRule.getTestDataURL(LINK_WITH_INTENT_TO_CHROME_IN_SAME_TAB_FILE);
+        String url = mActivityTestRule.getTestDataURL(LINK_WITH_INTENT_TO_SELF_IN_SAME_TAB_FILE);
 
         mActivityTestRule.navigateAndWait(url);
 
@@ -1134,9 +1129,9 @@
         Assert.assertEquals(url, mActivityTestRule.getCurrentDisplayUrl());
         Intent intent = intentInterceptor.mLastIntent;
         Assert.assertNotNull(intent);
-        Assert.assertEquals(INTENT_TO_CHROME_PACKAGE, intent.getPackage());
-        Assert.assertEquals(INTENT_TO_CHROME_ACTION, intent.getAction());
-        Assert.assertEquals(INTENT_TO_CHROME_DATA_STRING, intent.getDataString());
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
     }
 
     /**
@@ -1153,7 +1148,7 @@
         IntentInterceptor intentInterceptor = new IntentInterceptor();
         activity.setIntentInterceptor(intentInterceptor);
 
-        String url = mActivityTestRule.getTestDataURL(LINK_WITH_INTENT_TO_CHROME_IN_NEW_TAB_FILE);
+        String url = mActivityTestRule.getTestDataURL(LINK_WITH_INTENT_TO_SELF_IN_NEW_TAB_FILE);
 
         mActivityTestRule.navigateAndWait(url);
 
@@ -1191,9 +1186,9 @@
         intentInterceptor.waitForIntent();
         Intent intent = intentInterceptor.mLastIntent;
         Assert.assertNotNull(intent);
-        Assert.assertEquals(INTENT_TO_CHROME_PACKAGE, intent.getPackage());
-        Assert.assertEquals(INTENT_TO_CHROME_ACTION, intent.getAction());
-        Assert.assertEquals(INTENT_TO_CHROME_DATA_STRING, intent.getDataString());
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
 
         // (3) And finally the new tab should be closed.
         onTabRemovedCallbackHelper.waitForFirst();
@@ -1224,7 +1219,7 @@
         activity.setIntentInterceptor(intentInterceptor);
 
         String url = mActivityTestRule.getTestServer().getURL(
-                "/server-redirect?" + mIntentToChromeWithFallbackUrl);
+                "/server-redirect?" + mIntentToSelfWithFallbackUrl);
 
         Tab tab = mActivityTestRule.getActivity().getTab();
         TestThreadUtils.runOnUiThreadBlocking(
@@ -1236,9 +1231,9 @@
         Assert.assertEquals(ABOUT_BLANK_URL, mActivityTestRule.getCurrentDisplayUrl());
         Intent intent = intentInterceptor.mLastIntent;
         Assert.assertNotNull(intent);
-        Assert.assertEquals(INTENT_TO_CHROME_PACKAGE, intent.getPackage());
-        Assert.assertEquals(INTENT_TO_CHROME_ACTION, intent.getAction());
-        Assert.assertEquals(INTENT_TO_CHROME_DATA_STRING, intent.getDataString());
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
     }
 
     /**
@@ -1324,7 +1319,7 @@
                 () -> { tab.getNavigationController().navigate(Uri.parse(url)); });
 
         NavigationWaiter waiter = new NavigationWaiter(
-                INTENT_TO_CHROME_URL, tab, /*expectFailure=*/true, /*waitForPaint=*/false);
+                INTENT_TO_SELF_URL, tab, /*expectFailure=*/true, /*waitForPaint=*/false);
         waiter.waitForNavigation();
 
         Assert.assertNull(intentInterceptor.mLastIntent);
@@ -1365,9 +1360,9 @@
         Assert.assertEquals(initialUrl, mActivityTestRule.getCurrentDisplayUrl());
         Intent intent = intentInterceptor.mLastIntent;
         Assert.assertNotNull(intent);
-        Assert.assertEquals(INTENT_TO_CHROME_PACKAGE, intent.getPackage());
-        Assert.assertEquals(INTENT_TO_CHROME_ACTION, intent.getAction());
-        Assert.assertEquals(INTENT_TO_CHROME_DATA_STRING, intent.getDataString());
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
     }
 
     /**
@@ -1416,9 +1411,9 @@
         Assert.assertEquals(url, mActivityTestRule.getCurrentDisplayUrl());
         Intent intent = intentInterceptor.mLastIntent;
         Assert.assertNotNull(intent);
-        Assert.assertEquals(INTENT_TO_CHROME_PACKAGE, intent.getPackage());
-        Assert.assertEquals(INTENT_TO_CHROME_ACTION, intent.getAction());
-        Assert.assertEquals(INTENT_TO_CHROME_DATA_STRING, intent.getDataString());
+        Assert.assertEquals(INTENT_TO_SELF_PACKAGE, intent.getPackage());
+        Assert.assertEquals(INTENT_TO_SELF_ACTION, intent.getAction());
+        Assert.assertEquals(INTENT_TO_SELF_DATA_STRING, intent.getDataString());
     }
 
     /**
@@ -1442,7 +1437,7 @@
         });
 
         NavigationWaiter waiter = new NavigationWaiter(
-                INTENT_TO_CHROME_URL, tab, /*expectFailure=*/true, /*waitForPaint=*/false);
+                INTENT_TO_SELF_URL, tab, /*expectFailure=*/true, /*waitForPaint=*/false);
         waiter.waitForNavigation();
 
         Assert.assertNull(intentInterceptor.mLastIntent);
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java
index 86317ab..8d01e771 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java
@@ -84,9 +84,10 @@
     // A URL with a custom scheme/host that is handled by WebLayer Shell.
     private static final String CUSTOM_SCHEME_URL_WITH_DEFAULT_EXTERNAL_HANDLER =
             "weblayer://weblayertest/intent";
-    // An intent that opens Chrome to view a specified URL.
-    private static final String INTENT_TO_CHROME_URL =
-            "intent://play.google.com/store/apps/details?id=com.facebook.katana/#Intent;scheme=https;action=android.intent.action.VIEW;package=com.android.chrome;end";
+    // An intent that sends an url with a custom scheme that is handled by WebLayer Shell.
+    private static final String INTENT_TO_CUSTOM_SCHEME_URL =
+            "intent://weblayertest/intent#Intent;scheme=weblayer;"
+            + "action=android.intent.action.VIEW;end";
 
     // An IntentInterceptor that simply drops intents to ensure that intent launches don't interfere
     // with running of tests.
@@ -762,7 +763,7 @@
         assertEquals(true, mCallback.onCompletedCallback.isKnownProtocol());
 
         // Test external protocol cases.
-        mActivityTestRule.navigateAndWaitForFailure(activity.getTab(), INTENT_TO_CHROME_URL,
+        mActivityTestRule.navigateAndWaitForFailure(activity.getTab(), INTENT_TO_CUSTOM_SCHEME_URL,
                 /*waitForPaint=*/false);
         assertEquals(false, mCallback.onStartedCallback.isKnownProtocol());
         assertEquals(false, mCallback.onFailedCallback.isKnownProtocol());
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java
index 6717c9d..967dab2 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/ExternalNavigationDelegateImpl.java
@@ -14,7 +14,6 @@
 import org.chromium.base.Callback;
 import org.chromium.base.Function;
 import org.chromium.base.PackageManagerUtils;
-import org.chromium.components.embedder_support.util.UrlUtilities;
 import org.chromium.components.external_intents.ExternalNavigationDelegate;
 import org.chromium.components.external_intents.ExternalNavigationDelegate.StartActivityIfNeededResult;
 import org.chromium.components.external_intents.ExternalNavigationParams;
@@ -78,17 +77,7 @@
         assert !proxy
             : "|proxy| should be true only for instant apps, which WebLayer doesn't handle";
 
-        boolean isExternalProtocol = !UrlUtilities.isAcceptedScheme(intent.toUri(0));
-        boolean hasDefaultHandler = hasDefaultHandler(intent);
-
-        // Match CCT's custom behavior of keeping http(s) URLs with no default handler in the app.
-        // TODO(blundell): If/when CCT eliminates its special handling of this case, eliminate it
-        // from WebLayer as well.
-        if (!isExternalProtocol && !hasDefaultHandler) {
-            return StartActivityIfNeededResult.HANDLED_WITHOUT_ACTIVITY_START;
-        }
-
-        // Otherwise defer to ExternalNavigationHandler's default logic.
+        // Defer to ExternalNavigationHandler's default logic.
         return StartActivityIfNeededResult.DID_NOT_HANDLE;
     }
 
diff --git a/weblayer/shell/android/shell_apk/AndroidManifest.xml b/weblayer/shell/android/shell_apk/AndroidManifest.xml
index 5739c36..35457b3 100644
--- a/weblayer/shell/android/shell_apk/AndroidManifest.xml
+++ b/weblayer/shell/android/shell_apk/AndroidManifest.xml
@@ -48,14 +48,26 @@
                   android:theme="@android:style/Theme.Holo.Light.NoActionBar">
         </activity>
         <activity android:name="org.chromium.weblayer.test.ExternalNavigationTest$DummyActivityForSpecialScheme"
-          android:exported="true" >
-          <intent-filter>
-              <action android:name="android.intent.action.VIEW" />
-              <category android:name="android.intent.category.DEFAULT" />
-              <category android:name="android.intent.category.BROWSABLE" />
-              <data android:host="weblayertest" android:scheme="weblayer" />
-          </intent-filter>
-      </activity>
+                  android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:host="weblayertest" android:scheme="weblayer" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="org.chromium.weblayer.test.ExternalNavigationTest$DummyActivity"
+                  android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="http" />
+                <data android:scheme="https" />
+                <data android:scheme="about" />
+                <category android:name="android.intent.category.BROWSABLE" />
+            </intent-filter>
+        </activity>
 
       <!-- Enable ADB logging of js logs (console.log). This is disabled by default because some
            websites log PII. -->
diff --git a/weblayer/test/data/link_to_page_that_intents_to_chrome_on_load.html b/weblayer/test/data/link_to_page_that_intents_to_chrome_on_load.html
deleted file mode 100644
index d5e024b..0000000
--- a/weblayer/test/data/link_to_page_that_intents_to_chrome_on_load.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta name="viewport"
-    content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
-</head>
-<body>
-  <a id='link' href='/weblayer/test/data/page_that_intents_to_chrome_on_load.html'>
-  Click to go to page that intents to Chrome on load
-</a>
-</body>
-</html>
diff --git a/weblayer/test/data/link_to_page_that_intents_to_package_on_load.html b/weblayer/test/data/link_to_page_that_intents_to_package_on_load.html
new file mode 100644
index 0000000..e31ef486
--- /dev/null
+++ b/weblayer/test/data/link_to_page_that_intents_to_package_on_load.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta name="viewport"
+    content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
+</head>
+<body>
+  <a id='link'>
+  <script type="text/javascript">
+    document.getElementById('link').href = '/weblayer/test/data/page_that_intents_to_package_on_load.html' + window.location.hash;
+  </script>
+  Click to go to page that intents to the package specified in the URL hash on load
+</a>
+</body>
+</html>
diff --git a/weblayer/test/data/link_with_intent_to_chrome_in_new_tab.html b/weblayer/test/data/link_with_intent_to_chrome_in_new_tab.html
deleted file mode 100644
index 44994a17..0000000
--- a/weblayer/test/data/link_with_intent_to_chrome_in_new_tab.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta name="viewport"
-    content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
-</head>
-<body>
-  <a id='link' target='_blank' href='intent://play.google.com/store/apps/details?id=com.facebook.katana/#Intent;scheme=https;action=android.intent.action.VIEW;package=com.android.chrome;end'>
-  Click to intent to production Chrome in a new tab
-</a>
-</body>
-</html>
diff --git a/weblayer/test/data/link_with_intent_to_chrome_in_same_tab.html b/weblayer/test/data/link_with_intent_to_chrome_in_same_tab.html
deleted file mode 100644
index e442dc92..0000000
--- a/weblayer/test/data/link_with_intent_to_chrome_in_same_tab.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta name="viewport"
-    content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
-</head>
-<body>
-  <a id='link' href='intent://play.google.com/store/apps/details?id=com.facebook.katana/#Intent;scheme=https;action=android.intent.action.VIEW;package=com.android.chrome;end'>
-  Click to intent to production Chrome in this tab
-</a>
-</body>
-</html>
diff --git a/weblayer/test/data/link_with_intent_to_package_in_new_tab.html b/weblayer/test/data/link_with_intent_to_package_in_new_tab.html
new file mode 100644
index 0000000..5fc9711
--- /dev/null
+++ b/weblayer/test/data/link_with_intent_to_package_in_new_tab.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta name="viewport"
+    content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
+</head>
+<body>
+  <a id='link' target='_blank'>
+    Click to intent to the package specified in the URL hash in a new tab
+  </a>
+  <script type="text/javascript">
+    document.getElementById('link').href = 'intent://play.google.com/store/apps/details?id=com.facebook.katana/#Intent;scheme=https;action=android.intent.action.VIEW;package=' + window.location.href.split('#')[1] + ';end';
+  </script>
+</body>
+</html>
diff --git a/weblayer/test/data/link_with_intent_to_package_in_same_tab.html b/weblayer/test/data/link_with_intent_to_package_in_same_tab.html
new file mode 100644
index 0000000..0a9a240
--- /dev/null
+++ b/weblayer/test/data/link_with_intent_to_package_in_same_tab.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta name="viewport"
+    content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
+</head>
+<body>
+  <a id='link'>
+    Click to intent to the package specified in the URL hash in this tab
+  </a>
+  <script type="text/javascript">
+    document.getElementById('link').href = 'intent://play.google.com/store/apps/details?id=com.facebook.katana/#Intent;scheme=https;action=android.intent.action.VIEW;package=' + window.location.href.split('#')[1] + ';end';
+  </script>
+</body>
+</html>
diff --git a/weblayer/test/data/page_that_intents_to_chrome_on_load.html b/weblayer/test/data/page_that_intents_to_chrome_on_load.html
deleted file mode 100644
index 253dfbe..0000000
--- a/weblayer/test/data/page_that_intents_to_chrome_on_load.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <meta name="viewport"
-    content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
-</head>
-<body>
-  <a id='link' href='intent://play.google.com/store/apps/details?id=com.facebook.katana/#Intent;scheme=https;action=android.intent.action.VIEW;package=com.android.chrome;end'>
-  Click to intent to production Chrome in this tab
-</a>
-<script>
-    window.addEventListener("load", function() {
-      document.getElementById("link").click();
-    }, false);
-</script>
-</body>
-</html>
diff --git a/weblayer/test/data/page_that_intents_to_package_on_load.html b/weblayer/test/data/page_that_intents_to_package_on_load.html
new file mode 100644
index 0000000..4875c03
--- /dev/null
+++ b/weblayer/test/data/page_that_intents_to_package_on_load.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta name="viewport"
+    content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
+</head>
+<body>
+  <a id='link'>
+    Click to intent to the package specified in the URL hash in this tab
+  </a>
+<script>
+    document.getElementById('link').href = 'intent://play.google.com/store/apps/details?id=com.facebook.katana/#Intent;scheme=https;action=android.intent.action.VIEW;package=' + window.location.href.split('#')[1] + ';end';
+    window.addEventListener("load", function() {
+      document.getElementById("link").click();
+    }, false);
+</script>
+</body>
+</html>