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

// Copyright 2021 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 androidx.test.espresso.matcher.ViewMatchers.assertThat;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;

import android.app.Activity;
import android.app.Instrumentation;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;

import androidx.browser.customtabs.CustomTabsCallback;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsSession;
import androidx.core.os.BuildCompat;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;

import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import org.chromium.base.ContextUtils;
import org.chromium.base.IntentUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.JniMocker;
import org.chromium.base.test.util.PackageManagerWrapper;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.IntentHandler;
import org.chromium.chrome.browser.app.metrics.LaunchCauseMetrics;
import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.CustomTabsUiType;
import org.chromium.chrome.browser.customtabs.content.CustomTabIntentHandler;
import org.chromium.chrome.browser.customtabs.dependency_injection.BaseCustomTabActivityModule;
import org.chromium.chrome.browser.dependency_injection.ModuleOverridesRule;
import org.chromium.chrome.browser.document.ChromeLauncherActivity;
import org.chromium.chrome.browser.firstrun.FirstRunStatus;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthSettingUtils;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.chrome.browser.theme.TopUiThemeColorProvider;
import org.chromium.chrome.browser.translate.TranslateBridge;
import org.chromium.chrome.browser.translate.TranslateBridgeJni;
import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator;
import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
import org.chromium.chrome.browser.ui.appmenu.AppMenuItemProperties;
import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.ui.appmenu.AppMenuTestSupport;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.R;
import org.chromium.chrome.test.util.ChromeTabUtils;
import org.chromium.components.webapps.WebappsUtils;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.test.util.DeviceRestriction;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;

/** Integration tests for the Custom Tab App Menu. */
@RunWith(ChromeJUnit4ClassRunner.class)
@EnableFeatures({ChromeFeatureList.APP_SPECIFIC_HISTORY})
public class CustomTabActivityAppMenuTest {
    private static final int MAX_MENU_CUSTOM_ITEMS = 7;
    private static final int NUM_CHROME_MENU_ITEMS = 6;
    private static final int NUM_CHROME_MENU_ITEMS_WITH_DIVIDER = 7;
    private static final String TEST_PAGE = "/chrome/test/data/android/google.html";
    private static final String TEST_MENU_TITLE = "testMenuTitle";

    @Rule public JniMocker jniMocker = new JniMocker();
    @Mock private TranslateBridge.Natives mTranslateBridgeJniMock;

    public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule();

    private final TestRule mModuleOverridesRule =
            new ModuleOverridesRule()
                    .setOverride(
                            BaseCustomTabActivityModule.Factory.class,
                            (BrowserServicesIntentDataProvider intentDataProvider,
                                    CustomTabNightModeStateController nightModeController,
                                    CustomTabIntentHandler.IntentIgnoringCriterion
                                            intentIgnoringCriterion,
                                    TopUiThemeColorProvider topUiThemeColorProvider,
                                    DefaultBrowserProviderImpl customTabDefaultBrowserProvider) ->
                                    new BaseCustomTabActivityModule(
                                            intentDataProvider,
                                            nightModeController,
                                            intentIgnoringCriterion,
                                            topUiThemeColorProvider,
                                            new FakeDefaultBrowserProviderImpl()));

    @Rule
    public RuleChain mRuleChain =
            RuleChain.emptyRuleChain()
                    .around(mCustomTabActivityTestRule)
                    .around(mModuleOverridesRule);

    private String mTestPage;

    private class TestContext extends ContextWrapper {
        public TestContext(Context baseContext) {
            super(baseContext);
        }

        @Override
        public PackageManager getPackageManager() {
            return new PackageManagerWrapper(super.getPackageManager()) {
                @Override
                public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int filters) {
                    return new ArrayList<ResolveInfo>();
                }
            };
        }

        @Override
        public Object getSystemService(String name) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
                if (name.equals(Context.SHORTCUT_SERVICE)) {
                    return null;
                }
            }
            return super.getSystemService(name);
        }
    }

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        // Mock translate bridge so "Translate..." menu item doesn't unexpectedly show up.
        jniMocker.mock(
                org.chromium.chrome.browser.translate.TranslateBridgeJni.TEST_HOOKS,
                mTranslateBridgeJniMock);
        jniMocker.mock(TranslateBridgeJni.TEST_HOOKS, mTranslateBridgeJniMock);

        ThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(true));
        mTestPage = mCustomTabActivityTestRule.getTestServer().getURL(TEST_PAGE);
        WebappsUtils.setAddToHomeIntentSupportedForTesting(true);
        LibraryLoader.getInstance().ensureInitialized();
    }

    @After
    public void tearDown() {
        ThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(false));

        // finish() is called on a non-UI thread by the testing harness. Must hide the menu
        // first, otherwise the UI is manipulated on a non-UI thread.
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    if (mCustomTabActivityTestRule.getActivity() == null) return;
                    AppMenuCoordinator coordinator =
                            mCustomTabActivityTestRule.getAppMenuCoordinator();
                    // CCT doesn't always have a menu (ex. in the media viewer).
                    if (coordinator == null) return;
                    AppMenuHandler handler = coordinator.getAppMenuHandler();
                    if (handler != null) handler.hideAppMenu();
                });

        WebappsUtils.setAddToHomeIntentSupportedForTesting(null);
    }

    private Intent createMinimalCustomTabIntent() {
        return CustomTabsIntentTestUtils.createMinimalCustomTabIntent(
                ApplicationProvider.getApplicationContext(), mTestPage);
    }

    private CustomTabIntentDataProvider getCustomTabIntentDataProvider() {
        return (CustomTabIntentDataProvider)
                mCustomTabActivityTestRule.getActivity().getIntentDataProvider();
    }

    private void openAppMenuAndAssertMenuShown() {
        CustomTabsTestUtils.openAppMenuAndAssertMenuShown(mCustomTabActivityTestRule.getActivity());
    }

    private static int adjustMenuSize(int expectedMenuSize) {
        // history menu won't be shown on pre-U devices. Decrease the expected size by 1.
        return BuildCompat.isAtLeastU() ? expectedMenuSize : expectedMenuSize - 1;
    }

    private void assertHistoryMenuVisibility() {
        var historyMenu =
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(),
                        R.id.open_history_menu_id);
        if (BuildCompat.isAtLeastU()) {
            Assert.assertNotNull(historyMenu);
        } else {
            Assert.assertNull(historyMenu);
        }
    }

    private void assertHistoryMenuIsNotShown() {
        openAppMenuAndAssertMenuShown();

        Assert.assertNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(),
                        R.id.open_history_menu_id));

        ModelList menuItemsModelList =
                AppMenuTestSupport.getMenuModelList(
                        mCustomTabActivityTestRule.getAppMenuCoordinator());
        final int expectedMenuSize = NUM_CHROME_MENU_ITEMS - 1;
        CustomTabsTestUtils.assertMenuSize(menuItemsModelList, expectedMenuSize);
    }

    /** Test the entries in the app menu. */
    @Test
    @SmallTest
    public void testAppMenu() throws Exception {
        Intent intent = createMinimalCustomTabIntent();
        int numMenuEntries = 1;
        CustomTabsIntentTestUtils.addMenuEntriesToIntent(intent, numMenuEntries, TEST_MENU_TITLE);
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
        CustomTabAppMenuPropertiesDelegate propertiesDelegate =
                (CustomTabAppMenuPropertiesDelegate)
                        mCustomTabActivityTestRule
                                .getAppMenuCoordinator()
                                .getAppMenuPropertiesDelegate();
        propertiesDelegate.setHasClientPackageForTesting(true);

        openAppMenuAndAssertMenuShown();
        ModelList menuItemsModelList =
                AppMenuTestSupport.getMenuModelList(
                        mCustomTabActivityTestRule.getAppMenuCoordinator());
        final int expectedMenuSize = numMenuEntries + NUM_CHROME_MENU_ITEMS_WITH_DIVIDER;

        Assert.assertNotNull("App menu is not initialized: ", menuItemsModelList);
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.forward_menu_id));
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(),
                        R.id.bookmark_this_page_id));
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.offline_page_id));
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.info_menu_id));
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.reload_menu_id));
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(),
                        R.id.open_in_browser_id));
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.find_in_page_id));
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(),
                        R.id.universal_install));
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(),
                        R.id.request_desktop_site_row_menu_id));
        Assert.assertNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(),
                        R.id.share_row_menu_id));

        assertHistoryMenuVisibility();

        // Assert the divider line is displayed in the correct position.
        int dividerLine =
                AppMenuTestSupport.findIndexOfMenuItemById(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.divider_line_id);
        int expectedPos = numMenuEntries + 1; // Add 1 to account for app menu icon row.
        Assert.assertEquals("Divider line at incorrect index.", expectedPos, dividerLine);

        CustomTabsTestUtils.assertMenuSize(menuItemsModelList, adjustMenuSize(expectedMenuSize));
    }

    @Test
    @SmallTest
    public void testAppMenuNoCustomEntries() throws Exception {
        Intent intent = createMinimalCustomTabIntent();
        int numMenuEntries = 0;
        CustomTabsIntentTestUtils.addMenuEntriesToIntent(intent, numMenuEntries, TEST_MENU_TITLE);
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
        CustomTabAppMenuPropertiesDelegate propertiesDelegate =
                (CustomTabAppMenuPropertiesDelegate)
                        mCustomTabActivityTestRule
                                .getAppMenuCoordinator()
                                .getAppMenuPropertiesDelegate();
        propertiesDelegate.setHasClientPackageForTesting(true);
        openAppMenuAndAssertMenuShown();
        ModelList menuItemsModelList =
                AppMenuTestSupport.getMenuModelList(
                        mCustomTabActivityTestRule.getAppMenuCoordinator());
        final int expectedMenuSize = numMenuEntries + NUM_CHROME_MENU_ITEMS;

        Assert.assertNotNull("App menu is not initialized: ", menuItemsModelList);

        // Assert the divider line is not displayed.
        int dividerLine =
                AppMenuTestSupport.findIndexOfMenuItemById(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.divider_line_id);
        int expectedPos = -1; // No custom menu entries, not expecting a divider line.
        Assert.assertEquals("Divider present when it shouldn't be.", expectedPos, dividerLine);

        CustomTabsTestUtils.assertMenuSize(menuItemsModelList, adjustMenuSize(expectedMenuSize));
    }

    /** Test the App Menu does not show for media viewer. */
    @Test
    @SmallTest
    public void testAppMenuForMediaViewer() {
        Intent intent = createMinimalCustomTabIntent();
        intent.putExtra(CustomTabIntentDataProvider.EXTRA_UI_TYPE, CustomTabsUiType.MEDIA_VIEWER);
        IntentUtils.addTrustedIntentExtras(intent);
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);

        PostTask.runOrPostTask(
                TaskTraits.UI_DEFAULT,
                () -> {
                    mCustomTabActivityTestRule
                            .getActivity()
                            .onMenuOrKeyboardAction(R.id.show_menu, false);
                    Assert.assertNull(mCustomTabActivityTestRule.getAppMenuCoordinator());
                });
    }

    /** Test the entries in app menu for Reader Mode. */
    @Test
    @SmallTest
    public void testAppMenuForReaderMode() throws Exception {
        Intent intent = createMinimalCustomTabIntent();
        intent.putExtra(CustomTabIntentDataProvider.EXTRA_UI_TYPE, CustomTabsUiType.READER_MODE);
        IntentUtils.addTrustedIntentExtras(intent);
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);

        openAppMenuAndAssertMenuShown();
        ModelList menuItemsModelList =
                AppMenuTestSupport.getMenuModelList(
                        mCustomTabActivityTestRule.getAppMenuCoordinator());
        final int expectedMenuSize = 2;

        Assert.assertNotNull("App menu is not initialized: ", menuItemsModelList);

        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.find_in_page_id));
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(),
                        R.id.reader_mode_prefs_id));

        CustomTabsTestUtils.assertMenuSize(menuItemsModelList, expectedMenuSize);
    }

    /** Test the entries in app menu for media viewer. */
    @Test
    @SmallTest
    public void testAppMenuForOfflinePage() throws Exception {
        Intent intent = createMinimalCustomTabIntent();
        intent.putExtra(
                CustomTabIntentDataProvider.EXTRA_UI_TYPE,
                CustomTabIntentDataProvider.CustomTabsUiType.OFFLINE_PAGE);
        IntentUtils.addTrustedIntentExtras(intent);
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);

        openAppMenuAndAssertMenuShown();
        ModelList menuItemsModelList =
                AppMenuTestSupport.getMenuModelList(
                        mCustomTabActivityTestRule.getAppMenuCoordinator());
        final int expectedMenuSize = 3;

        Assert.assertNotNull("App menu is not initialized: ", menuItemsModelList);

        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.find_in_page_id));
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(),
                        R.id.request_desktop_site_row_menu_id));

        ModelList iconRowModelList =
                AppMenuTestSupport.getMenuItemPropertyModel(
                                mCustomTabActivityTestRule.getAppMenuCoordinator(),
                                R.id.icon_row_menu_id)
                        .get(AppMenuItemProperties.SUBMENU);
        final int expectedIconMenuSize = 4;
        assertEquals(expectedIconMenuSize, iconRowModelList.size());
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.forward_menu_id));
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(),
                        R.id.bookmark_this_page_id));
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.info_menu_id));
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.reload_menu_id));

        CustomTabsTestUtils.assertMenuSize(menuItemsModelList, expectedMenuSize);
    }

    @Test
    @SmallTest
    public void testAppMenuBeforeFirstRun() throws Exception {
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent());
        // Mark the first run as not completed. This has to be done after we start the intent,
        // otherwise we are going to hit the FRE.
        ThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(false));

        openAppMenuAndAssertMenuShown();
        ModelList menuItemsModelList =
                AppMenuTestSupport.getMenuModelList(
                        mCustomTabActivityTestRule.getAppMenuCoordinator());
        final int expectedMenuSize = 3;

        Assert.assertNotNull("App menu is not initialized: ", menuItemsModelList);

        // Checks the first row (icons).
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.forward_menu_id));
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.info_menu_id));
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.reload_menu_id));
        Assert.assertNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.offline_page_id));
        Assert.assertNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(),
                        R.id.bookmark_this_page_id));

        // Following rows.
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.find_in_page_id));
        Assert.assertNotNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(),
                        R.id.request_desktop_site_row_menu_id));
        Assert.assertNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(),
                        R.id.open_in_browser_id));
        Assert.assertNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(),
                        R.id.universal_install));
        Assert.assertNull(
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(),
                        R.id.open_history_menu_id));

        CustomTabsTestUtils.assertMenuSize(menuItemsModelList, expectedMenuSize);
    }

    /** Tests if the default share item can be shown in the app menu. */
    @Test
    @SmallTest
    @Restriction(DeviceRestriction.RESTRICTION_TYPE_NON_AUTO)
    public void testShareMenuItem() throws Exception {
        Intent intent = createMinimalCustomTabIntent();
        intent.putExtra(CustomTabsIntent.EXTRA_DEFAULT_SHARE_MENU_ITEM, true);
        intent.putExtra(CustomTabsIntent.EXTRA_SHARE_STATE, CustomTabsIntent.SHARE_STATE_OFF);
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);

        openAppMenuAndAssertMenuShown();
        PropertyModel sharePropertyModel =
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.share_menu_id);
        Assert.assertNotNull(sharePropertyModel);
        Assert.assertTrue(sharePropertyModel.get(AppMenuItemProperties.ENABLED));
    }

    /**
     * Tests that the Add to Home screen item is not shown in the menu if there is no pin to home
     * screen capability
     */
    @Test
    @SmallTest
    public void testAddToHomeScreenMenuItemNoHomeScreen() throws Exception {
        // Clear default setting from #setUp.
        WebappsUtils.setAddToHomeIntentSupportedForTesting(null);
        Context contextToRestore = ContextUtils.getApplicationContext();
        TestContext testContext = new TestContext(contextToRestore);
        ContextUtils.initApplicationContextForTests(testContext);
        Intent intent = createMinimalCustomTabIntent();
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);

        openAppMenuAndAssertMenuShown();
        PropertyModel addToHomeScreenPropertyModel =
                AppMenuTestSupport.getMenuItemPropertyModel(
                        mCustomTabActivityTestRule.getAppMenuCoordinator(), R.id.universal_install);

        Assert.assertNull(addToHomeScreenPropertyModel);

        ContextUtils.initApplicationContextForTests(contextToRestore);
    }

    /** Test that only up to 7 entries are added to the custom menu. */
    @Test
    @SmallTest
    public void testMaxMenuItems() throws Exception {
        Intent intent = createMinimalCustomTabIntent();
        int numMenuEntries = 9;
        Assert.assertTrue(MAX_MENU_CUSTOM_ITEMS < numMenuEntries);
        CustomTabsIntentTestUtils.addMenuEntriesToIntent(intent, numMenuEntries, TEST_MENU_TITLE);
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
        CustomTabAppMenuPropertiesDelegate propertiesDelegate =
                (CustomTabAppMenuPropertiesDelegate)
                        mCustomTabActivityTestRule
                                .getAppMenuCoordinator()
                                .getAppMenuPropertiesDelegate();
        propertiesDelegate.setHasClientPackageForTesting(true);

        openAppMenuAndAssertMenuShown();
        ModelList menuItemsModelList =
                AppMenuTestSupport.getMenuModelList(
                        mCustomTabActivityTestRule.getAppMenuCoordinator());
        final int expectedMenuSize = MAX_MENU_CUSTOM_ITEMS + NUM_CHROME_MENU_ITEMS_WITH_DIVIDER;
        Assert.assertNotNull("App menu is not initialized: ", menuItemsModelList);
        CustomTabsTestUtils.assertMenuSize(menuItemsModelList, adjustMenuSize(expectedMenuSize));
    }

    /**
     * Test whether the custom menu is correctly shown and clicking it sends the right {@link
     * PendingIntent}.
     */
    // TODO(crbug.com/40896028): Re-enable this test after fixing/diagnosing flakiness.
    @Test
    @SmallTest
    @DisabledTest(message = "https://crbug.com/1420991")
    public void testCustomMenuEntry() throws TimeoutException {
        Intent customTabIntent = createMinimalCustomTabIntent();
        Intent baseCallbackIntent = new Intent();
        baseCallbackIntent.putExtra("FOO", 42);
        final PendingIntent pi =
                CustomTabsIntentTestUtils.addMenuEntriesToIntent(
                        customTabIntent, 1, baseCallbackIntent, TEST_MENU_TITLE);
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(customTabIntent);

        final CustomTabsIntentTestUtils.OnFinishedForTest onFinished =
                new CustomTabsIntentTestUtils.OnFinishedForTest(pi);
        getCustomTabIntentDataProvider().setPendingIntentOnFinishedForTesting(onFinished);

        openAppMenuAndAssertMenuShown();
        PostTask.runOrPostTask(
                TaskTraits.UI_DEFAULT,
                () -> {
                    int itemId =
                            ((CustomTabAppMenuPropertiesDelegate)
                                            AppMenuTestSupport.getAppMenuPropertiesDelegate(
                                                    mCustomTabActivityTestRule
                                                            .getAppMenuCoordinator()))
                                    .getItemIdForTitle(TEST_MENU_TITLE);
                    Assert.assertNotEquals(AppMenuPropertiesDelegate.INVALID_ITEM_ID, itemId);
                    AppMenuTestSupport.onOptionsItemSelected(
                            mCustomTabActivityTestRule.getAppMenuCoordinator(), itemId);
                });

        onFinished.waitForCallback("Pending Intent was not sent.");
        Intent callbackIntent = onFinished.getCallbackIntent();
        assertThat(callbackIntent.getDataString(), equalTo(mTestPage));

        // Verify that the callback intent has the page title as the subject, but other extras are
        // kept intact.
        assertThat(callbackIntent.getStringExtra(Intent.EXTRA_SUBJECT), equalTo("The Google"));
        assertThat(callbackIntent.getIntExtra("FOO", 0), equalTo(42));
    }

    /**
     * Test whether clicking "Open in Chrome" takes us to a chrome normal tab, loading the same url.
     */
    @Test
    @SmallTest
    @DisabledTest(message = "https://crbug.com/361629264")
    public void testOpenInBrowser() throws Exception {
        // Augment the CustomTabsSession to catch the callback.
        CallbackHelper callbackTriggered = new CallbackHelper();
        CustomTabsSession session =
                CustomTabsTestUtils.bindWithCallback(
                                new CustomTabsCallback() {
                                    @Override
                                    public void extraCallback(String callbackName, Bundle args) {
                                        if (callbackName.equals(
                                                CustomTabsConnection.OPEN_IN_BROWSER_CALLBACK)) {
                                            callbackTriggered.notifyCalled();
                                        }
                                    }
                                })
                        .session;

        Intent intent = new CustomTabsIntent.Builder(session).build().intent;
        intent.setData(Uri.parse(mTestPage));
        intent.setComponent(
                new ComponentName(
                        ApplicationProvider.getApplicationContext(), ChromeLauncherActivity.class));
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        IntentUtils.addTrustedIntentExtras(intent);

        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
        assertEquals(
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        LaunchCauseMetrics.LAUNCH_CAUSE_HISTOGRAM,
                        LaunchCauseMetrics.LaunchCause.CUSTOM_TAB));

        IntentFilter filter = new IntentFilter(Intent.ACTION_VIEW);
        filter.addDataScheme(
                Uri.parse(mCustomTabActivityTestRule.getTestServer().getURL("/")).getScheme());
        final Instrumentation.ActivityMonitor monitor =
                InstrumentationRegistry.getInstrumentation().addMonitor(filter, null, false);
        openAppMenuAndAssertMenuShown();
        PostTask.runOrPostTask(
                TaskTraits.UI_DEFAULT,
                () -> {
                    Assert.assertNotNull(
                            AppMenuTestSupport.getMenuItemPropertyModel(
                                    mCustomTabActivityTestRule.getAppMenuCoordinator(),
                                    R.id.open_in_browser_id));
                    mCustomTabActivityTestRule
                            .getActivity()
                            .onMenuOrKeyboardAction(R.id.open_in_browser_id, false);
                });
        final Activity activity =
                monitor.waitForActivityWithTimeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL);

        callbackTriggered.waitForCallback(0);

        CriteriaHelper.pollInstrumentationThread(
                () -> {
                    Criteria.checkThat(
                            RecordHistogram.getHistogramValueCountForTesting(
                                    LaunchCauseMetrics.LAUNCH_CAUSE_HISTOGRAM,
                                    LaunchCauseMetrics.LaunchCause.OPEN_IN_BROWSER_FROM_MENU),
                            Matchers.is(1));
                },
                5000L,
                CriteriaHelper.DEFAULT_POLLING_INTERVAL);
        activity.finish();
    }

    /**
     * Test whether clicking "Open in Incognito Chrome" takes us to a new chrome incognito tab,
     * loading the same url.
     */
    @Test
    @SmallTest
    @EnableFeatures(ChromeFeatureList.CCT_EPHEMERAL_MODE)
    public void testOpenInIncognitoBrowser() throws Exception {
        IncognitoReauthSettingUtils.setIsDeviceScreenLockEnabledForTesting(false);
        // Augment the CustomTabsSession to catch the callback.
        CallbackHelper callbackTriggered = new CallbackHelper();
        CustomTabsSession session =
                CustomTabsTestUtils.bindWithCallback(
                                new CustomTabsCallback() {
                                    @Override
                                    public void extraCallback(String callbackName, Bundle args) {
                                        if (callbackName.equals(
                                                CustomTabsConnection.OPEN_IN_BROWSER_CALLBACK)) {
                                            callbackTriggered.notifyCalled();
                                        }
                                    }
                                })
                        .session;

        Intent intent = new CustomTabsIntent.Builder(session).build().intent;
        // Set up an OTR custom tab to trigger "Open in Chrome Incognito".
        intent.putExtra(IntentHandler.EXTRA_ENABLE_EPHEMERAL_BROWSING, true);
        intent.setData(Uri.parse(mTestPage));
        intent.setComponent(
                new ComponentName(
                        ApplicationProvider.getApplicationContext(), ChromeLauncherActivity.class));
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        IntentUtils.addTrustedIntentExtras(intent);

        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
        assertEquals(
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        LaunchCauseMetrics.LAUNCH_CAUSE_HISTOGRAM,
                        LaunchCauseMetrics.LaunchCause.CUSTOM_TAB));

        final Instrumentation.ActivityMonitor monitor =
                InstrumentationRegistry.getInstrumentation()
                        .addMonitor(ChromeTabbedActivity.class.getName(), null, false);
        openAppMenuAndAssertMenuShown();
        PostTask.runOrPostTask(
                TaskTraits.UI_DEFAULT,
                () -> {
                    Assert.assertNotNull(
                            AppMenuTestSupport.getMenuItemPropertyModel(
                                    mCustomTabActivityTestRule.getAppMenuCoordinator(),
                                    R.id.open_in_browser_id));
                    mCustomTabActivityTestRule
                            .getActivity()
                            .onMenuOrKeyboardAction(R.id.open_in_browser_id, false);
                });
        final ChromeTabbedActivity tabbedActivity =
                (ChromeTabbedActivity)
                        monitor.waitForActivityWithTimeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL);

        callbackTriggered.waitForCallback(0);

        CriteriaHelper.pollInstrumentationThread(
                () -> {
                    Criteria.checkThat(
                            RecordHistogram.getHistogramValueCountForTesting(
                                    LaunchCauseMetrics.LAUNCH_CAUSE_HISTOGRAM,
                                    LaunchCauseMetrics.LaunchCause.OPEN_IN_BROWSER_FROM_MENU),
                            is(1));

                    TabModel tabModel = tabbedActivity.getCurrentTabModel();
                    Criteria.checkThat(
                            "Incognito tab model not selected",
                            tabModel.isIncognitoBranded(),
                            is(true));

                    Tab tab = TabModelUtils.getCurrentTab(tabModel);
                    Criteria.checkThat("Tab is null", tab, Matchers.notNullValue());
                    Criteria.checkThat(
                            "Incognito tab not selected", tab.isIncognitoBranded(), is(true));
                    Criteria.checkThat(
                            "Wrong URL loaded in incognito tab",
                            ChromeTabUtils.getUrlStringOnUiThread(tab),
                            is(mTestPage));
                },
                5000L,
                CriteriaHelper.DEFAULT_POLLING_INTERVAL);

        tabbedActivity.finish();
    }

    @Test
    @SmallTest
    public void testOpenHistory() throws Exception {
        Intent intent = new CustomTabsIntent.Builder().build().intent;
        intent.setData(Uri.parse(mTestPage));
        intent.setComponent(
                new ComponentName(
                        ApplicationProvider.getApplicationContext(), ChromeLauncherActivity.class));
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        IntentUtils.addTrustedIntentExtras(intent);

        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
        CustomTabAppMenuPropertiesDelegate propertiesDelegate =
                (CustomTabAppMenuPropertiesDelegate)
                        mCustomTabActivityTestRule
                                .getAppMenuCoordinator()
                                .getAppMenuPropertiesDelegate();
        propertiesDelegate.setHasClientPackageForTesting(true);
        assertEquals(
                1,
                RecordHistogram.getHistogramValueCountForTesting(
                        LaunchCauseMetrics.LAUNCH_CAUSE_HISTOGRAM,
                        LaunchCauseMetrics.LaunchCause.CUSTOM_TAB));

        openAppMenuAndAssertMenuShown();
        assertHistoryMenuVisibility();
    }

    @Test
    @SmallTest
    @DisableFeatures({ChromeFeatureList.APP_SPECIFIC_HISTORY})
    public void testNoHistoryItem_FeatureDisabled() throws Exception {
        Intent intent = createMinimalCustomTabIntent();
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);

        // History menu won't show, since the feature flag is disabled.
        assertHistoryMenuIsNotShown();
    }

    @Test
    @SmallTest
    @EnableFeatures({ChromeFeatureList.APP_SPECIFIC_HISTORY})
    public void testNoHistoryItem_NoClientPackage() throws Exception {
        Intent intent = createMinimalCustomTabIntent();
        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);

        CustomTabAppMenuPropertiesDelegate propertiesDelegate =
                (CustomTabAppMenuPropertiesDelegate)
                        mCustomTabActivityTestRule
                                .getAppMenuCoordinator()
                                .getAppMenuPropertiesDelegate();
        propertiesDelegate.setHasClientPackageForTesting(false);

        // History menu won't show, since package name is not set.
        assertHistoryMenuIsNotShown();
    }
}