chromium/chrome/android/javatests/src/org/chromium/chrome/browser/sync/GoogleServicesSettingsTest.java

// Copyright 2020 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.sync;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.RootMatchers.isDialog;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

import android.os.Build;
import android.os.Bundle;

import androidx.annotation.Nullable;
import androidx.test.filters.LargeTest;

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.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.CommandLineFlags;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DoNotBatch;
import org.chromium.base.test.util.Feature;
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.MinAndroidSdkLevel;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.password_manager.PasswordManagerUtilBridge;
import org.chromium.chrome.browser.password_manager.PasswordManagerUtilBridgeJni;
import org.chromium.chrome.browser.password_manager.account_storage_toggle.AccountStorageToggleFragmentArgs;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.price_tracking.PriceTrackingFeatures;
import org.chromium.chrome.browser.price_tracking.PriceTrackingUtilities;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.settings.SettingsActivityTestRule;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.sync.settings.GoogleServicesSettings;
import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.R;
import org.chromium.chrome.test.util.browser.signin.SigninTestRule;
import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.signin.base.AccountInfo;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.identitymanager.ConsentLevel;
import org.chromium.components.signin.test.util.AccountCapabilitiesBuilder;
import org.chromium.components.signin.test.util.FakeAccountManagerFacade;
import org.chromium.components.sync.UserSelectableType;
import org.chromium.components.user_prefs.UserPrefs;

/** Tests for GoogleServicesSettings. */
@RunWith(ChromeJUnit4ClassRunner.class)
@DoNotBatch(reason = "A subset of tests requires adding a new account that could fail if batched.")
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class GoogleServicesSettingsTest {
    @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();

    @Rule public final JniMocker mJniMocker = new JniMocker();

    @Rule public final SigninTestRule mSigninTestRule = new SigninTestRule();

    public final ChromeTabbedActivityTestRule mActivityTestRule =
            new ChromeTabbedActivityTestRule();

    public final SettingsActivityTestRule<GoogleServicesSettings> mSettingsActivityTestRule =
            new SettingsActivityTestRule<>(GoogleServicesSettings.class);

    // SettingsActivity has to be finished before the outer CTA can be finished or trying to finish
    // CTA won't work.
    @Rule
    public final RuleChain mRuleChain =
            RuleChain.outerRule(mActivityTestRule).around(mSettingsActivityTestRule);

    @Mock private PasswordManagerUtilBridge.Natives mMockPasswordManagerUtilBridgeJni;

    @Before
    public void setUp() {
        mJniMocker.mock(PasswordManagerUtilBridgeJni.TEST_HOOKS, mMockPasswordManagerUtilBridgeJni);
        mActivityTestRule.startMainActivityOnBlankPage();
        ThreadUtils.runOnUiThreadBlocking(
                () ->
                        Assert.assertTrue(
                                "SIGNIN_ALLOWED pref should be set by default",
                                UserPrefs.get(ProfileManager.getLastUsedRegularProfile())
                                        .getBoolean(Pref.SIGNIN_ALLOWED)));
    }

    @After
    public void tearDown() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    PrefService prefService =
                            UserPrefs.get(ProfileManager.getLastUsedRegularProfile());
                    prefService.clearPref(Pref.SIGNIN_ALLOWED);
                });
    }

    @Test
    @LargeTest
    public void allowSigninOptionHiddenFromChildUser() {
        mSigninTestRule.addChildTestAccountThenWaitForSignin();
        final Profile profile =
                ThreadUtils.runOnUiThreadBlocking(ProfileManager::getLastUsedRegularProfile);
        CriteriaHelper.pollUiThread(profile::isChild);

        final GoogleServicesSettings googleServicesSettings = startGoogleServicesSettings();
        ChromeSwitchPreference allowChromeSignin =
                (ChromeSwitchPreference)
                        googleServicesSettings.findPreference(
                                GoogleServicesSettings.PREF_ALLOW_SIGNIN);
        Assert.assertFalse(
                "Chrome Signin option should not be visible", allowChromeSignin.isVisible());
    }

    @Test
    @LargeTest
    @DisableFeatures(ChromeFeatureList.REPLACE_SYNC_PROMOS_WITH_SIGN_IN_PROMOS)
    public void signOutUserWithoutShowingSignOutDialog() {
        mSigninTestRule.addTestAccountThenSignin();
        final GoogleServicesSettings googleServicesSettings = startGoogleServicesSettings();
        ChromeSwitchPreference allowChromeSignin =
                (ChromeSwitchPreference)
                        googleServicesSettings.findPreference(
                                GoogleServicesSettings.PREF_ALLOW_SIGNIN);
        Assert.assertTrue("Chrome Signin should be allowed", allowChromeSignin.isChecked());

        onView(withText(R.string.allow_chrome_signin_title)).perform(click());
        ThreadUtils.runOnUiThreadBlocking(
                () ->
                        Assert.assertFalse(
                                "Account should be signed out!",
                                IdentityServicesProvider.get()
                                        .getIdentityManager(
                                                ProfileManager.getLastUsedRegularProfile())
                                        .hasPrimaryAccount(ConsentLevel.SIGNIN)));
        ThreadUtils.runOnUiThreadBlocking(
                () ->
                        Assert.assertFalse(
                                "SIGNIN_ALLOWED pref should be unset",
                                UserPrefs.get(ProfileManager.getLastUsedRegularProfile())
                                        .getBoolean(Pref.SIGNIN_ALLOWED)));
        Assert.assertFalse("Chrome Signin should not be allowed", allowChromeSignin.isChecked());
    }

    @Test
    @LargeTest
    @DisableFeatures(ChromeFeatureList.REPLACE_SYNC_PROMOS_WITH_SIGN_IN_PROMOS)
    public void showSignOutDialogBeforeSigningUserOutLegacy() {
        mSigninTestRule.addTestAccountThenSigninAndEnableSync();
        final GoogleServicesSettings googleServicesSettings = startGoogleServicesSettings();
        ChromeSwitchPreference allowChromeSignin =
                (ChromeSwitchPreference)
                        googleServicesSettings.findPreference(
                                GoogleServicesSettings.PREF_ALLOW_SIGNIN);
        Assert.assertTrue("Chrome Signin should be allowed", allowChromeSignin.isChecked());

        onView(withText(R.string.allow_chrome_signin_title)).perform(click());
        // Accept the sign out Dialog
        onView(withText(R.string.continue_button)).inRoot(isDialog()).perform(click());
        ThreadUtils.runOnUiThreadBlocking(
                () ->
                        Assert.assertFalse(
                                "Accepting the sign-out dialog should set SIGNIN_ALLOWED to false",
                                UserPrefs.get(ProfileManager.getLastUsedRegularProfile())
                                        .getBoolean(Pref.SIGNIN_ALLOWED)));
        Assert.assertFalse("Chrome Signin should not be allowed", allowChromeSignin.isChecked());
    }

    @Test
    @LargeTest
    @EnableFeatures(ChromeFeatureList.REPLACE_SYNC_PROMOS_WITH_SIGN_IN_PROMOS)
    public void showSignOutDialogBeforeSigningUserOut() {
        mSigninTestRule.addAccountThenSignin(SigninTestRule.TEST_ACCOUNT_1);
        final GoogleServicesSettings googleServicesSettings = startGoogleServicesSettings();
        ChromeSwitchPreference allowChromeSignin =
                (ChromeSwitchPreference)
                        googleServicesSettings.findPreference(
                                GoogleServicesSettings.PREF_ALLOW_SIGNIN);
        Assert.assertTrue("Chrome Signin should be allowed", allowChromeSignin.isChecked());

        onView(withText(R.string.allow_chrome_signin_title)).perform(click());
        onView(withText(R.string.sign_out_title)).inRoot(isDialog()).check(matches(isDisplayed()));
        // Accept the sign out Dialog
        onView(withText(R.string.sign_out)).inRoot(isDialog()).perform(click());

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    SnackbarManager snackbarManager =
                            mSettingsActivityTestRule.getActivity().getSnackbarManager();
                    Assert.assertTrue(snackbarManager.isShowing());
                    Snackbar currentSnackbar = snackbarManager.getCurrentSnackbarForTesting();
                    Assert.assertEquals(
                            currentSnackbar.getIdentifierForTesting(), Snackbar.UMA_SIGN_OUT);
                    Assert.assertEquals(
                            currentSnackbar.getTextForTesting(),
                            mActivityTestRule
                                    .getActivity()
                                    .getString(R.string.sign_out_snackbar_message));
                    Assert.assertFalse(
                            "Accepting the sign-out dialog should set SIGNIN_ALLOWED to false",
                            UserPrefs.get(ProfileManager.getLastUsedRegularProfile())
                                    .getBoolean(Pref.SIGNIN_ALLOWED));
                });
        Assert.assertFalse("Chrome Signin should not be allowed", allowChromeSignin.isChecked());
    }

    @Test
    @LargeTest
    @Feature({"Preference"})
    @EnableFeatures({ChromeFeatureList.COMMERCE_PRICE_TRACKING + "<Study"})
    @CommandLineFlags.Add({
        "force-fieldtrials=Study/Group",
        "force-fieldtrial-params=Study.Group:allow_disable_price_annotations/true"
    })
    public void testPriceTrackingAnnotations() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    PriceTrackingFeatures.setPriceTrackingEnabledForTesting(true);
                    PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
                });

        final GoogleServicesSettings googleServicesSettings = startGoogleServicesSettings();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    ChromeSwitchPreference priceAnnotationsSwitch =
                            (ChromeSwitchPreference)
                                    googleServicesSettings.findPreference(
                                            GoogleServicesSettings.PREF_PRICE_TRACKING_ANNOTATIONS);
                    Assert.assertTrue(priceAnnotationsSwitch.isVisible());
                    Assert.assertTrue(priceAnnotationsSwitch.isChecked());

                    priceAnnotationsSwitch.performClick();
                    Assert.assertFalse(
                            PriceTrackingUtilities.isTrackPricesOnTabsEnabled(
                                    ProfileManager.getLastUsedRegularProfile()));
                    priceAnnotationsSwitch.performClick();
                    Assert.assertTrue(
                            PriceTrackingUtilities.isTrackPricesOnTabsEnabled(
                                    ProfileManager.getLastUsedRegularProfile()));
                });
    }

    @Test
    @LargeTest
    @Feature({"Preference"})
    @EnableFeatures({ChromeFeatureList.COMMERCE_PRICE_TRACKING + "<Study"})
    @CommandLineFlags.Add({
        "force-fieldtrials=Study/Group",
        "force-fieldtrial-params=Study.Group:allow_disable_price_annotations/false"
    })
    public void testPriceTrackingAnnotations_FeatureDisabled() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    PriceTrackingFeatures.setPriceTrackingEnabledForTesting(true);
                    PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
                });

        final GoogleServicesSettings googleServicesSettings = startGoogleServicesSettings();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    Assert.assertNull(
                            googleServicesSettings.findPreference(
                                    GoogleServicesSettings.PREF_PRICE_TRACKING_ANNOTATIONS));
                });
    }

    @Test
    @LargeTest
    @Feature({"Preference"})
    @EnableFeatures({ChromeFeatureList.COMMERCE_PRICE_TRACKING + "<Study"})
    @CommandLineFlags.Add({
        "force-fieldtrials=Study/Group",
        "force-fieldtrial-params=Study.Group:allow_disable_price_annotations/true"
    })
    public void testPriceTrackingAnnotations_NotSignedIn() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    PriceTrackingFeatures.setPriceTrackingEnabledForTesting(true);
                    PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(false);
                });

        final GoogleServicesSettings googleServicesSettings = startGoogleServicesSettings();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    Assert.assertNull(
                            googleServicesSettings.findPreference(
                                    GoogleServicesSettings.PREF_PRICE_TRACKING_ANNOTATIONS));
                });
    }

    @Test
    @LargeTest
    @MinAndroidSdkLevel(
            value = Build.VERSION_CODES.Q,
            reason = "Digital Wellbeing is only available from Q.")
    public void testUsageStatsReportingShown() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    PrefService prefService =
                            UserPrefs.get(ProfileManager.getLastUsedRegularProfile());
                    prefService.setBoolean(Pref.USAGE_STATS_ENABLED, true);
                });

        final GoogleServicesSettings googleServicesSettings = startGoogleServicesSettings();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    Assert.assertNotNull(
                            "Usage stats should exist when the flag and pref are set.",
                            googleServicesSettings.findPreference(
                                    GoogleServicesSettings.PREF_USAGE_STATS_REPORTING));
                });
    }

    @Test
    @LargeTest
    @MinAndroidSdkLevel(
            value = Build.VERSION_CODES.Q,
            reason = "Digital Wellbeing is only available from Q.")
    public void testUsageStatsReportingNotShown() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    PrefService prefService =
                            UserPrefs.get(ProfileManager.getLastUsedRegularProfile());
                    prefService.setBoolean(Pref.USAGE_STATS_ENABLED, false);
                });

        final GoogleServicesSettings googleServicesSettings = startGoogleServicesSettings();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    Assert.assertNull(
                            "Usage stats should not exist when the pref is not set.",
                            googleServicesSettings.findPreference(
                                    GoogleServicesSettings.PREF_USAGE_STATS_REPORTING));
                });
    }

    @Test
    @LargeTest
    public void hidePasswordsAccountStorageToggleIfSyncing() {
        mSigninTestRule.addTestAccountThenSigninAndEnableSync();

        startGoogleServicesSettings();

        onView(withText(R.string.passwords_account_storage_toggle_title)).check(doesNotExist());
    }

    @Test
    @LargeTest
    public void hidePasswordsAccountStorageToggleIfSignedOut() {
        Assert.assertEquals(mSigninTestRule.getPrimaryAccount(ConsentLevel.SIGNIN), null);

        startGoogleServicesSettings();

        onView(withText(R.string.passwords_account_storage_toggle_title)).check(doesNotExist());
    }

    @Test
    @LargeTest
    @DisableFeatures({ChromeFeatureList.ENABLE_PASSWORDS_ACCOUNT_STORAGE_FOR_NON_SYNCING_USERS})
    public void hidePasswordsAccountStorageToggleIfSignedInAndFlagDisabled() {
        mSigninTestRule.addTestAccountThenSignin();

        startGoogleServicesSettings();

        onView(withText(R.string.passwords_account_storage_toggle_title)).check(doesNotExist());
    }

    @Test
    @LargeTest
    @EnableFeatures({
        ChromeFeatureList.ENABLE_PASSWORDS_ACCOUNT_STORAGE_FOR_NON_SYNCING_USERS,
        ChromeFeatureList.REPLACE_SYNC_PROMOS_WITH_SIGN_IN_PROMOS
    })
    public void hidePasswordsAccountStorageToggleIfSignedInAndSyncToSigninEnabled() {
        mSigninTestRule.addTestAccountThenSignin();

        startGoogleServicesSettings();

        onView(withText(R.string.passwords_account_storage_toggle_title)).check(doesNotExist());
    }

    @Test
    @LargeTest
    @EnableFeatures({ChromeFeatureList.ENABLE_PASSWORDS_ACCOUNT_STORAGE_FOR_NON_SYNCING_USERS})
    @DisableFeatures({ChromeFeatureList.REPLACE_SYNC_PROMOS_WITH_SIGN_IN_PROMOS})
    public void hidePasswordsAccountStorageToggleIfGmsCoreOutdated() {
        when(mMockPasswordManagerUtilBridgeJni.isGmsCoreUpdateRequired(any(), any()))
                .thenReturn(true);
        mSigninTestRule.addTestAccountThenSignin();

        startGoogleServicesSettings();

        onView(withText(R.string.passwords_account_storage_toggle_title)).check(doesNotExist());
    }

    @Test
    @LargeTest
    @EnableFeatures({ChromeFeatureList.ENABLE_PASSWORDS_ACCOUNT_STORAGE_FOR_NON_SYNCING_USERS})
    @DisableFeatures({ChromeFeatureList.REPLACE_SYNC_PROMOS_WITH_SIGN_IN_PROMOS})
    public void showPasswordsAccountStorageToggleIfSignedInAndFlagEnabled() {
        CoreAccountInfo account = mSigninTestRule.addTestAccountThenSignin();

        GoogleServicesSettings settings = startGoogleServicesSettings();

        onView(withText(R.string.passwords_account_storage_toggle_title))
                .check(matches(isDisplayed()));
        String expectedSummary =
                mSettingsActivityTestRule
                        .getActivity()
                        .getString(
                                R.string.passwords_account_storage_toggle_summary,
                                account.getEmail());
        onView(withText(expectedSummary)).check(matches(isDisplayed()));
        // Note: Using isChecked() with espresso is tricky, since the title view is a sibling of the
        // toggle view. Just resort to findPreference().
        ChromeSwitchPreference toggle =
                (ChromeSwitchPreference)
                        settings.findPreference(
                                GoogleServicesSettings.PREF_PASSWORDS_ACCOUNT_STORAGE);
        Assert.assertTrue(toggle.isChecked());
        Assert.assertNull(toggle.getBackgroundColor());
        Assert.assertTrue(isPasswordSyncEnabled());

        onView(withText(R.string.passwords_account_storage_toggle_title)).perform(click());

        onView(withText(R.string.passwords_account_storage_toggle_title))
                .check(matches(isDisplayed()));
        Assert.assertFalse(toggle.isChecked());
        Assert.assertFalse(isPasswordSyncEnabled());

        onView(withText(R.string.passwords_account_storage_toggle_title)).perform(click());

        onView(withText(R.string.passwords_account_storage_toggle_title))
                .check(matches(isDisplayed()));
        Assert.assertTrue(toggle.isChecked());
        Assert.assertTrue(isPasswordSyncEnabled());
    }

    @Test
    @LargeTest
    @EnableFeatures({ChromeFeatureList.ENABLE_PASSWORDS_ACCOUNT_STORAGE_FOR_NON_SYNCING_USERS})
    @DisableFeatures({ChromeFeatureList.REPLACE_SYNC_PROMOS_WITH_SIGN_IN_PROMOS})
    public void showPasswordsAccountStorageToggleForNonDisplayableEmail() {
        // TODO(b/343378391) Update accountInfo to use
        // AccountManagerTestRule.TEST_ACCOUNT_NON_DISPLAYABLE_EMAIL.
        mSigninTestRule.addAccountThenSignin(
                new AccountInfo.Builder(
                                "[email protected]",
                                FakeAccountManagerFacade.toGaiaId("[email protected]"))
                        .fullName("John Doe")
                        .givenName("John")
                        .accountCapabilities(
                                new AccountCapabilitiesBuilder()
                                        .setIsSubjectToParentalControls(true)
                                        .setCanHaveEmailAddressDisplayed(false)
                                        .build())
                        .build());

        startGoogleServicesSettings();

        onView(withText(R.string.passwords_account_storage_toggle_summary_no_email))
                .check(matches(isDisplayed()));
    }

    @Test
    @LargeTest
    @EnableFeatures({ChromeFeatureList.ENABLE_PASSWORDS_ACCOUNT_STORAGE_FOR_NON_SYNCING_USERS})
    @DisableFeatures({ChromeFeatureList.REPLACE_SYNC_PROMOS_WITH_SIGN_IN_PROMOS})
    public void showPasswordsAccountStorageToggleWithHighlight() {
        mSigninTestRule.addTestAccountThenSignin();

        Bundle args = new Bundle();
        args.putBoolean(AccountStorageToggleFragmentArgs.HIGHLIGHT, true);
        mSettingsActivityTestRule.startSettingsActivity(args);

        ChromeSwitchPreference toggle =
                (ChromeSwitchPreference)
                        mSettingsActivityTestRule
                                .getFragment()
                                .findPreference(
                                        GoogleServicesSettings.PREF_PASSWORDS_ACCOUNT_STORAGE);
        @Nullable Integer backgroundColor = toggle.getBackgroundColor();
        Assert.assertNotNull(backgroundColor);
    }

    private boolean isPasswordSyncEnabled() {
        return ThreadUtils.runOnUiThreadBlocking(
                () ->
                        SyncServiceFactory.getForProfile(ProfileManager.getLastUsedRegularProfile())
                                .getSelectedTypes()
                                .contains(UserSelectableType.PASSWORDS));
    }

    private GoogleServicesSettings startGoogleServicesSettings() {
        mSettingsActivityTestRule.startSettingsActivity();
        return mSettingsActivityTestRule.getFragment();
    }
}