chromium/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/OmniboxPedalsTest.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.omnibox.suggestions;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.pressKey;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;

import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;

import android.view.KeyEvent;

import androidx.fragment.app.Fragment;
import androidx.test.filters.MediumTest;
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.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.ApplicationTestUtils;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.base.test.util.JniMocker;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.accessibility.settings.AccessibilitySettings;
import org.chromium.chrome.browser.autofill.settings.AutofillPaymentMethodsFragment;
import org.chromium.chrome.browser.browsing_data.ClearBrowsingDataFragmentAdvanced;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.omnibox.suggestions.action.OmniboxPedal;
import org.chromium.chrome.browser.omnibox.suggestions.base.BaseSuggestionView;
import org.chromium.chrome.browser.password_manager.settings.PasswordSettings;
import org.chromium.chrome.browser.safety_check.SafetyCheckSettingsFragment;
import org.chromium.chrome.browser.safety_hub.SafetyHubFragment;
import org.chromium.chrome.browser.settings.MainSettings;
import org.chromium.chrome.browser.settings.SettingsActivity;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.IncognitoTabHostUtils;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.R;
import org.chromium.chrome.test.util.ActivityTestUtils;
import org.chromium.chrome.test.util.OmniboxTestUtils;
import org.chromium.chrome.test.util.OmniboxTestUtils.SuggestionInfo;
import org.chromium.components.browser_ui.site_settings.SiteSettings;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.components.omnibox.AutocompleteMatch;
import org.chromium.components.omnibox.AutocompleteMatchBuilder;
import org.chromium.components.omnibox.AutocompleteResult;
import org.chromium.components.omnibox.OmniboxSuggestionType;
import org.chromium.components.omnibox.action.OmniboxActionJni;
import org.chromium.components.omnibox.action.OmniboxPedalId;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.test.util.GmsCoreVersionRestriction;
import org.chromium.ui.test.util.UiDisableIf;

import java.util.Arrays;
import java.util.List;

/** Tests of the Omnibox Pedals feature. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@Batch(Batch.PER_CLASS)
public class OmniboxPedalsTest {
    public static @ClassRule ChromeTabbedActivityTestRule sActivityTestRule =
            new ChromeTabbedActivityTestRule();
    public @Rule MockitoRule mMockitoRule = MockitoJUnit.rule();
    public @Rule JniMocker mJniMocker = new JniMocker();
    private @Mock OmniboxActionJni mOmniboxActionJni;

    private OmniboxTestUtils mOmniboxUtils;
    private SettingsActivity mTargetActivity;

    @BeforeClass
    public static void beforeClass() {
        sActivityTestRule.startMainActivityOnBlankPage();
        sActivityTestRule.waitForActivityNativeInitializationComplete();
        sActivityTestRule.waitForDeferredStartup();
    }

    @Before
    public void setUp() throws InterruptedException {
        sActivityTestRule.loadUrl("about:blank");
        mOmniboxUtils = new OmniboxTestUtils(sActivityTestRule.getActivity());
        mJniMocker.mock(OmniboxActionJni.TEST_HOOKS, mOmniboxActionJni);
    }

    @After
    public void tearDown() throws Exception {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    IncognitoTabHostUtils.closeAllIncognitoTabs();
                });
        if (mTargetActivity != null) {
            ApplicationTestUtils.finishActivity(mTargetActivity);
        }
        ThreadUtils.runOnUiThreadBlocking(
                () ->
                        sActivityTestRule
                                .getActivity()
                                .getModalDialogManager()
                                .dismissAllDialogs(DialogDismissalCause.NEGATIVE_BUTTON_CLICKED));
        mJniMocker.mock(AutocompleteControllerJni.TEST_HOOKS, null);
        mJniMocker.mock(OmniboxActionJni.TEST_HOOKS, null);
    }

    /**
     * Apply suggestions to the Omnibox. Requires at least one of the suggestions to include at
     * least one OmniboxAction. Verifies that suggestions - and actions - are shown.
     *
     * @param matches the matches to show
     */
    private void setSuggestions(AutocompleteMatch... matches) {
        mOmniboxUtils.requestFocus();
        // Ensure we start from empty suggestions list; don't carry over suggestions from previous
        // run.
        mOmniboxUtils.setSuggestions(AutocompleteResult.fromCache(null, null));

        mOmniboxUtils.setSuggestions(AutocompleteResult.fromCache(Arrays.asList(matches), null));
        mOmniboxUtils.checkSuggestionsShown();
        SuggestionInfo<BaseSuggestionView> info = mOmniboxUtils.findSuggestionWithActionChips();
        Assert.assertNotNull("No suggestions with actions", info);
    }

    private AutocompleteMatch createPedalSuggestion(@OmniboxPedalId int pedalId) {
        return AutocompleteMatchBuilder.searchWithType(OmniboxSuggestionType.SEARCH_SUGGEST)
                .setDisplayText("Suggestion")
                .setActions(
                        List.of(new OmniboxPedal(pedalId, "hint", "accessibilityHint", pedalId)))
                .build();
    }

    /**
     * Click on the pedal to open the SettingsActivity, and confirm Settings activity is shown
     * presenting the expected fragment.
     *
     * @param fragmentType The class type of the displayed settings fragment.
     */
    private void clickOnPedalToSettings(Runnable activate, Class<? extends Fragment> fragmentType) {
        mTargetActivity =
                ActivityTestUtils.waitForActivity(
                        InstrumentationRegistry.getInstrumentation(),
                        SettingsActivity.class,
                        activate);

        CriteriaHelper.pollUiThread(
                () -> {
                    Fragment fragment =
                            mTargetActivity
                                    .getSupportFragmentManager()
                                    .findFragmentById(R.id.content);
                    Criteria.checkThat(fragment, Matchers.instanceOf(fragmentType));
                });
    }

    @Test
    @MediumTest
    @DisableFeatures(ChromeFeatureList.QUICK_DELETE_FOR_ANDROID)
    public void testClearBrowsingData() throws InterruptedException {
        setSuggestions(createPedalSuggestion(OmniboxPedalId.CLEAR_BROWSING_DATA));
        clickOnPedalToSettings(
                () -> mOmniboxUtils.clickOnAction(0, 0), ClearBrowsingDataFragmentAdvanced.class);
        verify(mOmniboxActionJni, times(1))
                .recordActionShown(
                        OmniboxPedalId.CLEAR_BROWSING_DATA,
                        /* position= */ 0,
                        /* executed= */ true);
        verifyNoMoreInteractions(mOmniboxActionJni);
    }

    @Test
    @MediumTest
    @EnableFeatures(ChromeFeatureList.QUICK_DELETE_FOR_ANDROID)
    public void testClearBrowsingData_withQuickDeleteEnabled() throws InterruptedException {
        setSuggestions(createPedalSuggestion(OmniboxPedalId.CLEAR_BROWSING_DATA));
        mOmniboxUtils.clickOnAction(0, 0);

        onViewWaiting(withId(R.id.quick_delete_spinner)).check(matches(isDisplayed()));
        verify(mOmniboxActionJni)
                .recordActionShown(
                        OmniboxPedalId.CLEAR_BROWSING_DATA,
                        /* position= */ 0,
                        /* executed= */ true);
        verifyNoMoreInteractions(mOmniboxActionJni);
    }

    @Test
    @MediumTest
    @DisableIf.Device(type = {UiDisableIf.TABLET}) // https://crbug.com/338976917
    @Restriction(GmsCoreVersionRestriction.RESTRICTION_TYPE_VERSION_GE_22W30)
    public void testManagePasswordsNoUpmFlow() throws InterruptedException {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    PrefService prefService = UserPrefs.get(sActivityTestRule.getProfile(false));
                    prefService.setInteger(
                            "passwords_use_upm_local_and_separate_stores",
                            /*UseUpmLocalAndSeparateStoresState = Off*/ 0);
                });

        setSuggestions(createPedalSuggestion(OmniboxPedalId.MANAGE_PASSWORDS));
        clickOnPedalToSettings(() -> mOmniboxUtils.clickOnAction(0, 0), PasswordSettings.class);
        verify(mOmniboxActionJni, times(1))
                .recordActionShown(
                        OmniboxPedalId.MANAGE_PASSWORDS, /* position= */ 0, /* executed= */ true);
        verifyNoMoreInteractions(mOmniboxActionJni);
    }

    @Test
    @MediumTest
    public void testManagePaymentMethods() throws InterruptedException {
        setSuggestions(createPedalSuggestion(OmniboxPedalId.UPDATE_CREDIT_CARD));
        clickOnPedalToSettings(
                () -> mOmniboxUtils.clickOnAction(0, 0), AutofillPaymentMethodsFragment.class);
        verify(mOmniboxActionJni, times(1))
                .recordActionShown(
                        OmniboxPedalId.UPDATE_CREDIT_CARD, /* position= */ 0, /* executed= */ true);
        verifyNoMoreInteractions(mOmniboxActionJni);
    }

    @Test
    @MediumTest
    public void testOpenIncognitoTab() throws InterruptedException {
        setSuggestions(createPedalSuggestion(OmniboxPedalId.LAUNCH_INCOGNITO));

        mOmniboxUtils.clickOnAction(0, 0);
        CriteriaHelper.pollUiThread(
                () -> {
                    Tab tab = sActivityTestRule.getActivity().getActivityTab();
                    Criteria.checkThat(tab, Matchers.notNullValue());
                    Criteria.checkThat(tab.isIncognito(), Matchers.is(true));
                });

        verify(mOmniboxActionJni, times(1))
                .recordActionShown(
                        OmniboxPedalId.LAUNCH_INCOGNITO, /* position= */ 0, /* executed= */ true);
        verifyNoMoreInteractions(mOmniboxActionJni);
    }

    @Test
    @MediumTest
    @DisableFeatures(ChromeFeatureList.SAFETY_HUB)
    public void testRunChromeSafetyCheck() throws InterruptedException {
        setSuggestions(createPedalSuggestion(OmniboxPedalId.RUN_CHROME_SAFETY_CHECK));

        HistogramWatcher safetyCheckHistogramWatcher =
                HistogramWatcher.newBuilder()
                        .expectAnyRecord("Settings.SafetyCheck.UpdatesResult")
                        .build();
        clickOnPedalToSettings(
                () -> mOmniboxUtils.clickOnAction(0, 0), SafetyCheckSettingsFragment.class);
        verify(mOmniboxActionJni, times(1))
                .recordActionShown(
                        OmniboxPedalId.RUN_CHROME_SAFETY_CHECK,
                        /* position= */ 0,
                        /* executed= */ true);
        // Make sure the safety check was ran.
        safetyCheckHistogramWatcher.pollInstrumentationThreadUntilSatisfied();
        verifyNoMoreInteractions(mOmniboxActionJni);
    }

    @Test
    @MediumTest
    @EnableFeatures(ChromeFeatureList.SAFETY_HUB)
    public void testOpenChromeSafetyHub() throws InterruptedException {
        setSuggestions(createPedalSuggestion(OmniboxPedalId.RUN_CHROME_SAFETY_CHECK));

        clickOnPedalToSettings(() -> mOmniboxUtils.clickOnAction(0, 0), SafetyHubFragment.class);
        verify(mOmniboxActionJni, times(1))
                .recordActionShown(
                        OmniboxPedalId.RUN_CHROME_SAFETY_CHECK,
                        /* position= */ 0,
                        /* executed= */ true);
        verifyNoMoreInteractions(mOmniboxActionJni);
    }

    @Test
    @MediumTest
    public void testManageSiteSettings() throws InterruptedException {
        setSuggestions(createPedalSuggestion(OmniboxPedalId.MANAGE_SITE_SETTINGS));
        clickOnPedalToSettings(() -> mOmniboxUtils.clickOnAction(0, 0), SiteSettings.class);
        verify(mOmniboxActionJni, times(1))
                .recordActionShown(
                        OmniboxPedalId.MANAGE_SITE_SETTINGS,
                        /* position= */ 0,
                        /* executed= */ true);
        verifyNoMoreInteractions(mOmniboxActionJni);
    }

    @Test
    @MediumTest
    public void testManageChromeSettings() throws InterruptedException {
        setSuggestions(createPedalSuggestion(OmniboxPedalId.MANAGE_CHROME_SETTINGS));

        clickOnPedalToSettings(() -> mOmniboxUtils.clickOnAction(0, 0), MainSettings.class);
        verify(mOmniboxActionJni, times(1))
                .recordActionShown(
                        OmniboxPedalId.MANAGE_CHROME_SETTINGS,
                        /* position= */ 0,
                        /* executed= */ true);
        verifyNoMoreInteractions(mOmniboxActionJni);
    }

    @Test
    @MediumTest
    public void testViewYourChromeHistory() throws InterruptedException {
        setSuggestions(createPedalSuggestion(OmniboxPedalId.VIEW_CHROME_HISTORY));

        mOmniboxUtils.clickOnAction(0, 0);
        CriteriaHelper.pollUiThread(
                () -> {
                    Tab tab = sActivityTestRule.getActivity().getActivityTab();
                    Criteria.checkThat(tab, Matchers.notNullValue());
                    Criteria.checkThat(
                            tab.getUrl().getSpec(), Matchers.startsWith(UrlConstants.HISTORY_URL));
                });

        verify(mOmniboxActionJni, times(1))
                .recordActionShown(
                        OmniboxPedalId.VIEW_CHROME_HISTORY,
                        /* position= */ 0,
                        /* executed= */ true);
        verifyNoMoreInteractions(mOmniboxActionJni);
    }

    @Test
    @MediumTest
    public void testManageAccessibilitySettings() throws InterruptedException {
        setSuggestions(createPedalSuggestion(OmniboxPedalId.MANAGE_CHROME_ACCESSIBILITY));
        clickOnPedalToSettings(
                () -> mOmniboxUtils.clickOnAction(0, 0), AccessibilitySettings.class);
        verify(mOmniboxActionJni, times(1))
                .recordActionShown(
                        OmniboxPedalId.MANAGE_CHROME_ACCESSIBILITY,
                        /* position= */ 0,
                        /* executed= */ true);
        verifyNoMoreInteractions(mOmniboxActionJni);
    }

    @Test
    @MediumTest
    public void testPedalsStartedOnTabEnterKeyStroke() throws Exception {
        setSuggestions(createPedalSuggestion(OmniboxPedalId.MANAGE_CHROME_ACCESSIBILITY));

        onView(withId(R.id.url_bar)).perform(pressKey(KeyEvent.KEYCODE_DPAD_DOWN));
        onView(withId(R.id.url_bar)).perform(pressKey(KeyEvent.KEYCODE_TAB));
        clickOnPedalToSettings(
                () -> {
                    onView(withId(R.id.url_bar)).perform(pressKey(KeyEvent.KEYCODE_ENTER));
                },
                AccessibilitySettings.class);

        verify(mOmniboxActionJni, times(1))
                .recordActionShown(
                        OmniboxPedalId.MANAGE_CHROME_ACCESSIBILITY,
                        /* position= */ 0,
                        /* executed= */ true);
        verifyNoMoreInteractions(mOmniboxActionJni);
    }

    @Test
    @MediumTest
    public void testPlayChromeDinoGame() throws InterruptedException {
        setSuggestions(createPedalSuggestion(OmniboxPedalId.PLAY_CHROME_DINO_GAME));

        mOmniboxUtils.clickOnAction(0, 0);
        CriteriaHelper.pollUiThread(
                () -> {
                    Tab tab = sActivityTestRule.getActivity().getActivityTab();
                    Criteria.checkThat(tab, Matchers.notNullValue());
                    Criteria.checkThat(
                            tab.getUrl().getSpec(), Matchers.equalTo(UrlConstants.CHROME_DINO_URL));
                });
        verify(mOmniboxActionJni, times(1))
                .recordActionShown(
                        OmniboxPedalId.PLAY_CHROME_DINO_GAME,
                        /* position= */ 0,
                        /* executed= */ true);
        verifyNoMoreInteractions(mOmniboxActionJni);
    }
}