chromium/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabExternalNavigationTest.java

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.customtabs;

import static org.junit.Assert.assertTrue;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;

import androidx.browser.auth.AuthTabIntent;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.Features;
import org.chromium.chrome.browser.browserservices.TrustedWebActivityTestUtil;
import org.chromium.chrome.browser.browserservices.ui.controller.CurrentPageVerifier.VerificationStatus;
import org.chromium.chrome.browser.customtabs.CustomTabDelegateFactory.CustomTabNavigationDelegate;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabDelegateFactory;
import org.chromium.chrome.browser.tab.TabTestUtils;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.external_intents.ExternalNavigationHandler;
import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResult;
import org.chromium.components.external_intents.ExternalNavigationHandler.OverrideUrlLoadingResultType;
import org.chromium.components.external_intents.ExternalNavigationParams;
import org.chromium.components.external_intents.RedirectHandler;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.ui.base.PageTransition;
import org.chromium.url.GURL;

import java.util.concurrent.TimeoutException;

/** Instrumentation test for external navigation handling of a Custom Tab. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class CustomTabExternalNavigationTest {
    @Rule
    public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule();

    /** A dummy activity that claims to handle "customtab://customtabtest". */
    public static class DummyActivityForSpecialScheme extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            finish();
        }
    }

    /** A dummy activity that claims to handle "http://customtabtest.com". */
    public static class DummyActivityForHttp extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            finish();
        }
    }

    private static final String TWA_PACKAGE_NAME = "com.foo.bar";
    private static final String TEST_PATH = "/chrome/test/data/android/google.html";
    private static final String CUSTOM_SCHEME = "myscheme";
    private static final String REDIRECT_URL = "myscheme://auth?token=secret";
    private CustomTabNavigationDelegate mNavigationDelegate;
    private EmbeddedTestServer mTestServer;
    private ExternalNavigationHandler mUrlHandler;

    @Before
    public void setUp() throws Exception {
        mCustomTabActivityTestRule.getEmbeddedTestServerRule().setServerUsesHttps(true);
        mTestServer = mCustomTabActivityTestRule.getTestServer();
    }

    private void setUpTwa() throws TimeoutException {
        launchTwa(TWA_PACKAGE_NAME, mTestServer.getURL(TEST_PATH));
        finishSetUp();
    }

    private void setUpAuthTab() throws TimeoutException {
        launchAuthTab(mTestServer.getURL(TEST_PATH));
        finishSetUp();
    }

    private void finishSetUp() {
        Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab();
        TabDelegateFactory delegateFactory = TabTestUtils.getDelegateFactory(tab);
        assertTrue(delegateFactory instanceof CustomTabDelegateFactory);
        CustomTabDelegateFactory customTabDelegateFactory =
                ((CustomTabDelegateFactory) delegateFactory);
        mUrlHandler =
                ThreadUtils.runOnUiThreadBlocking(
                        () -> customTabDelegateFactory.createExternalNavigationHandler(tab));
        assertTrue(
                customTabDelegateFactory.getExternalNavigationDelegate()
                        instanceof CustomTabNavigationDelegate);
        mNavigationDelegate =
                (CustomTabNavigationDelegate)
                        customTabDelegateFactory.getExternalNavigationDelegate();
    }

    private void launchTwa(String twaPackageName, String url) throws TimeoutException {
        Intent intent = TrustedWebActivityTestUtil.createTrustedWebActivityIntent(url);
        TrustedWebActivityTestUtil.spoofVerification(twaPackageName, url);
        TrustedWebActivityTestUtil.createSession(intent, twaPackageName);
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
    }

    private void launchAuthTab(String url) throws TimeoutException {
        Context context = ApplicationProvider.getApplicationContext();
        Intent intent =
                CustomTabsIntentTestUtils.createMinimalCustomTabIntent(context, url)
                        .putExtra(AuthTabIntent.EXTRA_LAUNCH_AUTH_TAB, true)
                        .putExtra(AuthTabIntent.EXTRA_REDIRECT_SCHEME, CUSTOM_SCHEME);
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
    }

    private OverrideUrlLoadingResult getOverrideUrlLoadingResult(String url) {
        ExternalNavigationHandler.sAllowIntentsToSelfForTesting = true;
        final GURL testUrl = new GURL(url);
        RedirectHandler redirectHandler = RedirectHandler.create();
        redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0, false, true);
        ExternalNavigationParams params =
                new ExternalNavigationParams.Builder(testUrl, false)
                        .setIsMainFrame(true)
                        .setIsRendererInitiated(true)
                        .setRedirectHandler(redirectHandler)
                        .build();
        return mUrlHandler.shouldOverrideUrlLoading(params);
    }

    /**
     * For urls with special schemes and hosts, and there is exactly one activity having a matching
     * intent filter, the framework will make that activity the default handler of the special url.
     * This test tests whether chrome is able to start the default external handler.
     */
    @Test
    @SmallTest
    public void testExternalActivityStartedForDefaultUrl() throws TimeoutException {
        setUpTwa();
        OverrideUrlLoadingResult result =
                getOverrideUrlLoadingResult("customtab://customtabtest/intent");
        Assert.assertEquals(
                OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT, result.getResultType());
    }

    @Test
    @SmallTest
    @Features.EnableFeatures(ChromeFeatureList.CCT_AUTH_TAB)
    public void testAuthTabShouldReturnAsActivityResult() throws TimeoutException {
        setUpAuthTab();
        OverrideUrlLoadingResult result = getOverrideUrlLoadingResult(REDIRECT_URL);

        // AuthTab does not launch an external intent for a custom scheme URL, but passes
        // the result back to the calling app and closes itself.
        Assert.assertEquals(OverrideUrlLoadingResultType.NO_OVERRIDE, result.getResultType());
        Assert.assertTrue(mCustomTabActivityTestRule.getActivity().isFinishing());
    }

    /**
     * When loading a normal http url that chrome is able to handle, an intent picker should never
     * be shown, even if other activities such as {@link DummyActivityForHttp} claim to handle it.
     */
    @Test
    @SmallTest
    @DisableIf.Build(
            supported_abis_includes = "x86",
            sdk_is_greater_than = VERSION_CODES.O_MR1,
            sdk_is_less_than = VERSION_CODES.Q,
            message = "crbug.com/1188920")
    public void testIntentPickerNotShownForNormalUrl() throws TimeoutException {
        setUpTwa();
        final GURL testUrl = new GURL("http://customtabtest.com");
        RedirectHandler redirectHandler = RedirectHandler.create();
        redirectHandler.updateNewUrlLoading(PageTransition.LINK, false, true, 0, 0, false, true);
        ExternalNavigationParams params =
                new ExternalNavigationParams.Builder(testUrl, false)
                        .setRedirectHandler(redirectHandler)
                        .build();
        OverrideUrlLoadingResult result = mUrlHandler.shouldOverrideUrlLoading(params);
        Assert.assertEquals(OverrideUrlLoadingResultType.NO_OVERRIDE, result.getResultType());
    }

    private @VerificationStatus int getCurrentPageVerifierStatus() {
        CustomTabActivity customTabActivity = mCustomTabActivityTestRule.getActivity();
        return customTabActivity.getComponent().resolveCurrentPageVerifier().getState().status;
    }

    /**
     * Tests that {@link CustomTabNavigationDelegate#shouldDisableExternalIntentRequestsForUrl()}
     * disables forwarding URL requests to external intents for navigations within the TWA's origin.
     */
    @Test
    @SmallTest
    public void testShouldDisableExternalIntentRequestsForUrl() throws TimeoutException {
        setUpTwa();
        GURL insideVerifiedOriginUrl =
                new GURL(mTestServer.getURL("/chrome/test/data/android/simple.html"));
        GURL outsideVerifiedOriginUrl = new GURL("https://example.com/test.html");

        TrustedWebActivityTestUtil.waitForCurrentPageVerifierToFinish(
                mCustomTabActivityTestRule.getActivity());
        Assert.assertEquals(VerificationStatus.SUCCESS, getCurrentPageVerifierStatus());

        assertTrue(
                mNavigationDelegate.shouldDisableExternalIntentRequestsForUrl(
                        insideVerifiedOriginUrl));
        Assert.assertFalse(
                mNavigationDelegate.shouldDisableExternalIntentRequestsForUrl(
                        outsideVerifiedOriginUrl));
    }
}