chromium/chrome/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettings.java

// Copyright 2014 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.password_manager.settings;

import static org.chromium.chrome.browser.password_manager.PasswordMetricsUtil.PASSWORD_SETTINGS_EXPORT_METRICS_ID;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;

import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;

import org.chromium.base.BuildInfo;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.supplier.OneshotSupplier;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.password_check.PasswordCheck;
import org.chromium.chrome.browser.password_check.PasswordCheckFactory;
import org.chromium.chrome.browser.password_manager.ManagePasswordsReferrer;
import org.chromium.chrome.browser.password_manager.PasswordCheckReferrer;
import org.chromium.chrome.browser.password_manager.PasswordManagerHelper;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.settings.ChromeBaseSettingsFragment;
import org.chromium.chrome.browser.settings.ChromeManagedPreferenceDelegate;
import org.chromium.chrome.browser.sync.SyncServiceFactory;
import org.chromium.chrome.browser.sync.settings.SyncSettingsUtils;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
import org.chromium.components.browser_ui.settings.ChromeBasePreference;
import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
import org.chromium.components.browser_ui.settings.SearchUtils;
import org.chromium.components.browser_ui.settings.TextMessagePreference;
import org.chromium.components.browser_ui.styles.SemanticColorUtils;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.sync.PassphraseType;
import org.chromium.components.sync.SyncService;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.ui.text.SpanApplier;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;

/**
 * The "Passwords" screen in Settings, which allows the user to enable or disable password saving,
 * to view saved passwords (just the username and URL), and to delete saved passwords.
 */
public class PasswordSettings extends ChromeBaseSettingsFragment
        implements PasswordListObserver,
                Preference.OnPreferenceClickListener,
                SyncService.SyncStateChangedListener {
    @IntDef({
        TrustedVaultBannerState.NOT_SHOWN,
        TrustedVaultBannerState.OFFER_OPT_IN,
        TrustedVaultBannerState.OPTED_IN
    })
    @Retention(RetentionPolicy.SOURCE)
    private @interface TrustedVaultBannerState {
        int NOT_SHOWN = 0;
        int OFFER_OPT_IN = 1;
        int OPTED_IN = 2;
    }

    // Keys for name/password dictionaries.
    public static final String PASSWORD_LIST_URL = "url";
    public static final String PASSWORD_LIST_NAME = "name";
    public static final String PASSWORD_LIST_PASSWORD = "password";

    // Used to pass the password id into a new activity.
    public static final String PASSWORD_LIST_ID = "id";

    // The key for saving |mSearchQuery| to instance bundle.
    private static final String SAVED_STATE_SEARCH_QUERY = "saved-state-search-query";

    public static final String PREF_SAVE_PASSWORDS_SWITCH = "save_passwords_switch";
    public static final String PREF_AUTOSIGNIN_SWITCH = "autosignin_switch";
    public static final String PREF_CHECK_PASSWORDS = "check_passwords";
    public static final String PREF_TRUSTED_VAULT_BANNER = "trusted_vault_banner";
    public static final String PREF_KEY_MANAGE_ACCOUNT_LINK = "manage_account_link";

    private static final String PREF_KEY_CATEGORY_SAVED_PASSWORDS = "saved_passwords";
    private static final String PREF_KEY_CATEGORY_EXCEPTIONS = "exceptions";
    private static final String PREF_KEY_SAVED_PASSWORDS_NO_TEXT = "saved_passwords_no_text";

    private static final int ORDER_SWITCH = 0;
    private static final int ORDER_AUTO_SIGNIN_CHECKBOX = 1;
    private static final int ORDER_CHECK_PASSWORDS = 2;
    private static final int ORDER_TRUSTED_VAULT_BANNER = 3;
    private static final int ORDER_MANAGE_ACCOUNT_LINK = 4;
    private static final int ORDER_SAVED_PASSWORDS = 6;
    private static final int ORDER_EXCEPTIONS = 7;
    private static final int ORDER_SAVED_PASSWORDS_NO_TEXT = 8;

    // This request code is not actually consumed today in onActivityResult() but is defined here to
    // avoid bugs in the future if the request code is reused.
    private static final int REQUEST_CODE_TRUSTED_VAULT_OPT_IN = 1;

    // Unique request code for the password exporting activity.
    private static final int PASSWORD_EXPORT_INTENT_REQUEST_CODE = 3485764;

    private boolean mNoPasswords;
    private boolean mNoPasswordExceptions;
    private @TrustedVaultBannerState int mTrustedVaultBannerState =
            TrustedVaultBannerState.NOT_SHOWN;

    private MenuItem mHelpItem;
    private MenuItem mSearchItem;

    private String mSearchQuery;
    private Preference mLinkPref;
    private Menu mMenu;

    private @Nullable PasswordCheck mPasswordCheck;
    private @ManagePasswordsReferrer int mManagePasswordsReferrer;
    private OneshotSupplier<BottomSheetController> mBottomSheetControllerSupplier;
    private final ObservableSupplierImpl<String> mPageTitle = new ObservableSupplierImpl<>();

    /** For controlling the UX flow of exporting passwords. */
    private ExportFlow mExportFlow = new ExportFlow();

    public ExportFlow getExportFlowForTesting() {
        return mExportFlow;
    }

    @Override
    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
        mExportFlow.onCreate(
                savedInstanceState,
                new ExportFlow.Delegate() {
                    @Override
                    public Activity getActivity() {
                        return PasswordSettings.this.getActivity();
                    }

                    @Override
                    public FragmentManager getFragmentManager() {
                        return PasswordSettings.this.getFragmentManager();
                    }

                    @Override
                    public int getViewId() {
                        return getView().getId();
                    }

                    @Override
                    public void runCreateFileOnDiskIntent(Intent intent) {
                        startActivityForResult(intent, PASSWORD_EXPORT_INTENT_REQUEST_CODE);
                    }

                    @Override
                    public Profile getProfile() {
                        return PasswordSettings.this.getProfile();
                    }
                },
                PASSWORD_SETTINGS_EXPORT_METRICS_ID);
        mPageTitle.set(getString(R.string.password_manager_settings_title));
        setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getStyledContext()));
        PasswordManagerHandlerProvider.getForProfile(getProfile()).addObserver(this);

        if (SyncServiceFactory.getForProfile(getProfile()) != null) {
            SyncServiceFactory.getForProfile(getProfile()).addSyncStateChangedListener(this);
        }

        setHasOptionsMenu(true); // Password Export might be optional but Search is always present.

        mManagePasswordsReferrer = getReferrerFromInstanceStateOrLaunchBundle(savedInstanceState);

        if (savedInstanceState == null) return;

        if (savedInstanceState.containsKey(SAVED_STATE_SEARCH_QUERY)) {
            mSearchQuery = savedInstanceState.getString(SAVED_STATE_SEARCH_QUERY);
        }
    }

    @Override
    public ObservableSupplier<String> getPageTitle() {
        return mPageTitle;
    }

    private @ManagePasswordsReferrer int getReferrerFromInstanceStateOrLaunchBundle(
            Bundle savedInstanceState) {
        if (savedInstanceState != null
                && savedInstanceState.containsKey(
                        PasswordManagerHelper.MANAGE_PASSWORDS_REFERRER)) {
            return savedInstanceState.getInt(PasswordManagerHelper.MANAGE_PASSWORDS_REFERRER);
        }
        Bundle extras = getArguments();
        assert extras.containsKey(PasswordManagerHelper.MANAGE_PASSWORDS_REFERRER)
                : "PasswordSettings must be launched with a manage-passwords-referrer fragment"
                        + "argument, but none was provided.";
        return extras.getInt(PasswordManagerHelper.MANAGE_PASSWORDS_REFERRER);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPasswordCheck = PasswordCheckFactory.getOrCreate();
        computeTrustedVaultBannerState();
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        // Disable animations of preference changes.
        getListView().setItemAnimator(null);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        menu.clear();
        mMenu = menu;
        inflater.inflate(R.menu.save_password_preferences_action_bar_menu, menu);
        menu.findItem(R.id.export_passwords).setVisible(ExportFlow.providesPasswordExport());
        menu.findItem(R.id.export_passwords).setEnabled(false);
        mSearchItem = menu.findItem(R.id.menu_id_search);
        mSearchItem.setVisible(true);
        mHelpItem = menu.findItem(R.id.menu_id_targeted_help);
        SearchUtils.initializeSearchView(
                mSearchItem, mSearchQuery, getActivity(), this::filterPasswords);
    }

    @Override
    public void onPrepareOptionsMenu(Menu menu) {
        menu.findItem(R.id.export_passwords).setEnabled(!mNoPasswords && !mExportFlow.isActive());
        super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.export_passwords) {
            RecordHistogram.recordEnumeratedHistogram(
                    mExportFlow.getExportEventHistogramName(),
                    ExportFlow.PasswordExportEvent.EXPORT_OPTION_SELECTED,
                    ExportFlow.PasswordExportEvent.COUNT);
            mExportFlow.startExporting();
            return true;
        }
        if (SearchUtils.handleSearchNavigation(item, mSearchItem, mSearchQuery, getActivity())) {
            filterPasswords(null);
            return true;
        }
        if (id == R.id.menu_id_targeted_help) {
            getHelpAndFeedbackLauncher()
                    .show(getActivity(), getString(R.string.help_context_passwords), null);
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    private void filterPasswords(String query) {
        mSearchQuery = query;
        mHelpItem.setShowAsAction(
                mSearchQuery == null
                        ? MenuItem.SHOW_AS_ACTION_IF_ROOM
                        : MenuItem.SHOW_AS_ACTION_NEVER);
        rebuildPasswordLists();
    }

    /** Empty screen message when no passwords or exceptions are stored. */
    private void displayEmptyScreenMessage() {
        TextMessagePreference emptyView = new TextMessagePreference(getStyledContext(), null);
        emptyView.setSummary(R.string.saved_passwords_none_text);
        emptyView.setKey(PREF_KEY_SAVED_PASSWORDS_NO_TEXT);
        emptyView.setOrder(ORDER_SAVED_PASSWORDS_NO_TEXT);
        emptyView.setDividerAllowedAbove(false);
        emptyView.setDividerAllowedBelow(false);
        getPreferenceScreen().addPreference(emptyView);
    }

    /** Include a message when there's no match. */
    private void displayPasswordNoResultScreenMessage() {
        Preference noResultView = new Preference(getStyledContext());
        noResultView.setLayoutResource(R.layout.password_no_result);
        noResultView.setSelectable(false);
        getPreferenceScreen().addPreference(noResultView);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        ReauthenticationManager.resetLastReauth();
    }

    void rebuildPasswordLists() {
        mNoPasswords = false;
        mNoPasswordExceptions = false;
        getPreferenceScreen().removeAll();
        if (mSearchQuery != null) {
            // Only the filtered passwords and exceptions should be shown.
            PasswordManagerHandlerProvider.getForProfile(getProfile())
                    .getPasswordManagerHandler()
                    .updatePasswordLists();
            return;
        }

        createSavePasswordsSwitch();
        if (shouldShowAutoSigninOption()) {
            createAutoSignInCheckbox();
        }
        if (mPasswordCheck != null) {
            createCheckPasswords();
        }

        if (mTrustedVaultBannerState == TrustedVaultBannerState.OPTED_IN) {
            createTrustedVaultBanner(
                    R.string.android_trusted_vault_banner_sub_label_opted_in,
                    this::openTrustedVaultInfoPage);
        } else if (mTrustedVaultBannerState == TrustedVaultBannerState.OFFER_OPT_IN) {
            createTrustedVaultBanner(
                    R.string.android_trusted_vault_banner_sub_label_offer_opt_in,
                    this::openTrustedVaultOptInDialog);
        }
        PasswordManagerHandlerProvider.getForProfile(getProfile())
                .getPasswordManagerHandler()
                .updatePasswordLists();
    }

    private boolean shouldShowAutoSigninOption() {
        return !BuildInfo.getInstance().isAutomotive;
    }

    /**
     * Removes the UI displaying the list of saved passwords or exceptions.
     * @param preferenceCategoryKey The key string identifying the PreferenceCategory to be removed.
     */
    private void resetList(String preferenceCategoryKey) {
        PreferenceCategory profileCategory =
                (PreferenceCategory) getPreferenceScreen().findPreference(preferenceCategoryKey);
        if (profileCategory != null) {
            profileCategory.removeAll();
            getPreferenceScreen().removePreference(profileCategory);
        }
    }

    /** Removes the message informing the user that there are no saved entries to display. */
    private void resetNoEntriesTextMessage() {
        Preference message = getPreferenceScreen().findPreference(PREF_KEY_SAVED_PASSWORDS_NO_TEXT);
        if (message != null) {
            getPreferenceScreen().removePreference(message);
        }
    }

    @Override
    public void passwordListAvailable(int count) {
        resetList(PREF_KEY_CATEGORY_SAVED_PASSWORDS);
        resetNoEntriesTextMessage();

        mNoPasswords = count == 0;
        if (mNoPasswords) {
            if (mNoPasswordExceptions) displayEmptyScreenMessage();
            return;
        }

        displayManageAccountLink();

        PreferenceGroup passwordParent;
        if (mSearchQuery == null) {
            PreferenceCategory profileCategory = new PreferenceCategory(getStyledContext());
            profileCategory.setKey(PREF_KEY_CATEGORY_SAVED_PASSWORDS);
            profileCategory.setTitle(R.string.password_list_title);
            profileCategory.setOrder(ORDER_SAVED_PASSWORDS);
            getPreferenceScreen().addPreference(profileCategory);
            passwordParent = profileCategory;
        } else {
            passwordParent = getPreferenceScreen();
        }
        for (int i = 0; i < count; i++) {
            SavedPasswordEntry saved =
                    PasswordManagerHandlerProvider.getForProfile(getProfile())
                            .getPasswordManagerHandler()
                            .getSavedPasswordEntry(i);
            String url = saved.getUrl();
            String name = saved.getUserName();
            String password = saved.getPassword();
            if (shouldBeFiltered(url, name)) {
                continue; // The current password won't show with the active filter, try the next.
            }
            Preference preference = new Preference(getStyledContext());
            preference.setTitle(url);
            preference.setOnPreferenceClickListener(this);
            preference.setSummary(name);
            Bundle args = preference.getExtras();
            args.putString(PASSWORD_LIST_NAME, name);
            args.putString(PASSWORD_LIST_URL, url);
            args.putString(PASSWORD_LIST_PASSWORD, password);
            args.putInt(PASSWORD_LIST_ID, i);
            passwordParent.addPreference(preference);
        }
        mNoPasswords = passwordParent.getPreferenceCount() == 0;
        if (mMenu != null) {
            mMenu.findItem(R.id.export_passwords)
                    .setEnabled(!mNoPasswords && !mExportFlow.isActive());
        }
        if (mNoPasswords) {
            if (count == 0) displayEmptyScreenMessage(); // Show if the list was already empty.
            if (mSearchQuery == null) {
                // If not searching, the category needs to be removed again.
                getPreferenceScreen().removePreference(passwordParent);
            } else {
                displayPasswordNoResultScreenMessage();
                getView()
                        .announceForAccessibility(
                                getString(R.string.accessible_find_in_page_no_results));
            }
        }

        if (!mNoPasswords) {
            PasswordManagerHandlerProvider.getForProfile(getProfile())
                    .getPasswordManagerHandler()
                    .showMigrationWarning(getActivity(), mBottomSheetControllerSupplier.get());
        }
    }

    /**
     * Returns true if there is a search query that requires the exclusion of an entry based on
     * the passed url or name.
     * @param url the visible URL of the entry to check. May be empty but must not be null.
     * @param name the visible user name of the entry to check. May be empty but must not be null.
     * @return Returns whether the entry with the passed url and name should be filtered.
     */
    private boolean shouldBeFiltered(final String url, final String name) {
        if (mSearchQuery == null) {
            return false;
        }
        return !url.toLowerCase(Locale.ENGLISH).contains(mSearchQuery.toLowerCase(Locale.ENGLISH))
                && !name.toLowerCase(Locale.getDefault())
                        .contains(mSearchQuery.toLowerCase(Locale.getDefault()));
    }

    @Override
    public void passwordExceptionListAvailable(int count) {
        if (mSearchQuery != null) return; // Don't show exceptions if a search is ongoing.
        resetList(PREF_KEY_CATEGORY_EXCEPTIONS);
        resetNoEntriesTextMessage();

        mNoPasswordExceptions = count == 0;
        if (mNoPasswordExceptions) {
            if (mNoPasswords) displayEmptyScreenMessage();
            return;
        }

        displayManageAccountLink();

        PreferenceCategory profileCategory = new PreferenceCategory(getStyledContext());
        profileCategory.setKey(PREF_KEY_CATEGORY_EXCEPTIONS);
        profileCategory.setTitle(R.string.section_saved_passwords_exceptions);
        profileCategory.setOrder(ORDER_EXCEPTIONS);
        getPreferenceScreen().addPreference(profileCategory);
        for (int i = 0; i < count; i++) {
            String exception =
                    PasswordManagerHandlerProvider.getForProfile(getProfile())
                            .getPasswordManagerHandler()
                            .getSavedPasswordException(i);
            Preference preference = new Preference(getStyledContext());
            preference.setTitle(exception);
            preference.setOnPreferenceClickListener(this);
            Bundle args = preference.getExtras();
            args.putString(PASSWORD_LIST_URL, exception);
            args.putInt(PASSWORD_LIST_ID, i);
            profileCategory.addPreference(preference);
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        mExportFlow.onResume();
        rebuildPasswordLists();
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        if (requestCode != PASSWORD_EXPORT_INTENT_REQUEST_CODE) return;
        if (resultCode != Activity.RESULT_OK) return;
        if (intent == null || intent.getData() == null) return;

        mExportFlow.savePasswordsToDownloads(intent.getData());
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mExportFlow.onSaveInstanceState(outState);
        if (mSearchQuery != null) {
            outState.putString(SAVED_STATE_SEARCH_QUERY, mSearchQuery);
        }
        outState.putInt(PasswordManagerHelper.MANAGE_PASSWORDS_REFERRER, mManagePasswordsReferrer);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if (SyncServiceFactory.getForProfile(getProfile()) != null) {
            SyncServiceFactory.getForProfile(getProfile()).removeSyncStateChangedListener(this);
        }
        // The component should only be destroyed when the activity has been closed by the user
        // (e.g. by pressing on the back button) and not when the activity is temporarily destroyed
        // by the system.
        if (getActivity().isFinishing()) {
            PasswordManagerHandlerProvider.getForProfile(getProfile()).removeObserver(this);
            if (mPasswordCheck != null
                    && mManagePasswordsReferrer != ManagePasswordsReferrer.CHROME_SETTINGS) {
                PasswordCheckFactory.destroy();
            }
        }
    }

    /**
     *  Preference was clicked. Either navigate to manage account site or launch the PasswordEditor
     *  depending on which preference it was.
     */
    @Override
    public boolean onPreferenceClick(Preference preference) {
        if (preference == mLinkPref) {
            Intent intent =
                    new Intent(
                            Intent.ACTION_VIEW, Uri.parse(PasswordUIView.getAccountDashboardURL()));
            intent.setPackage(getActivity().getPackageName());
            getActivity().startActivity(intent);
        } else {
            boolean isBlockedCredential =
                    !preference.getExtras().containsKey(PasswordSettings.PASSWORD_LIST_NAME);
            PasswordManagerHandlerProvider.getForProfile(getProfile())
                    .getPasswordManagerHandler()
                    .showPasswordEntryEditingView(
                            getActivity(),
                            preference.getExtras().getInt(PasswordSettings.PASSWORD_LIST_ID),
                            isBlockedCredential);
        }
        return true;
    }

    private void createSavePasswordsSwitch() {
        ChromeSwitchPreference savePasswordsSwitch =
                new ChromeSwitchPreference(getStyledContext(), null);
        savePasswordsSwitch.setKey(PREF_SAVE_PASSWORDS_SWITCH);
        savePasswordsSwitch.setTitle(R.string.password_settings_save_passwords);
        savePasswordsSwitch.setOrder(ORDER_SWITCH);
        savePasswordsSwitch.setSummaryOn(R.string.text_on);
        savePasswordsSwitch.setSummaryOff(R.string.text_off);
        savePasswordsSwitch.setOnPreferenceChangeListener(
                (preference, newValue) -> {
                    getPrefService()
                            .setBoolean(Pref.CREDENTIALS_ENABLE_SERVICE, (boolean) newValue);
                    RecordHistogram.recordBooleanHistogram(
                            "PasswordManager.Settings.ToggleOfferToSavePasswords",
                            (boolean) newValue);
                    // TODO(http://crbug.com/1371422): Remove method and manage evictions from
                    // native code as this is covered by chrome://password-manager-internals page.
                    if ((boolean) newValue) {
                        PasswordManagerHelper.getForProfile(getProfile()).resetUpmUnenrollment();
                    }
                    return true;
                });
        savePasswordsSwitch.setManagedPreferenceDelegate(
                new ChromeManagedPreferenceDelegate(getProfile()) {
                    @Override
                    public boolean isPreferenceControlledByPolicy(Preference preference) {
                        return getPrefService()
                                .isManagedPreference(Pref.CREDENTIALS_ENABLE_SERVICE);
                    }
                });

        getPreferenceScreen().addPreference(savePasswordsSwitch);

        // Note: setting the switch state before the preference is added to the screen results in
        // some odd behavior where the switch state doesn't always match the internal enabled state
        // (e.g. the switch will say "On" when save passwords is really turned off), so
        // .setChecked() should be called after .addPreference()
        savePasswordsSwitch.setChecked(
                getPrefService().getBoolean(Pref.CREDENTIALS_ENABLE_SERVICE));
    }

    private void createAutoSignInCheckbox() {
        ChromeSwitchPreference autoSignInSwitch =
                new ChromeSwitchPreference(getStyledContext(), null);
        autoSignInSwitch.setKey(PREF_AUTOSIGNIN_SWITCH);
        autoSignInSwitch.setTitle(R.string.passwords_auto_signin_title);
        autoSignInSwitch.setOrder(ORDER_AUTO_SIGNIN_CHECKBOX);
        autoSignInSwitch.setSummary(R.string.passwords_auto_signin_description);
        autoSignInSwitch.setOnPreferenceChangeListener(
                (preference, newValue) -> {
                    getPrefService()
                            .setBoolean(Pref.CREDENTIALS_ENABLE_AUTOSIGNIN, (boolean) newValue);
                    RecordHistogram.recordBooleanHistogram(
                            "PasswordManager.Settings.ToggleAutoSignIn", (boolean) newValue);
                    return true;
                });
        autoSignInSwitch.setManagedPreferenceDelegate(
                new ChromeManagedPreferenceDelegate(getProfile()) {
                    @Override
                    public boolean isPreferenceControlledByPolicy(Preference preference) {
                        return getPrefService()
                                .isManagedPreference(Pref.CREDENTIALS_ENABLE_AUTOSIGNIN);
                    }
                });
        getPreferenceScreen().addPreference(autoSignInSwitch);
        autoSignInSwitch.setChecked(
                getPrefService().getBoolean(Pref.CREDENTIALS_ENABLE_AUTOSIGNIN));
    }

    private void createCheckPasswords() {
        ChromeBasePreference checkPasswords = new ChromeBasePreference(getStyledContext());
        checkPasswords.setKey(PREF_CHECK_PASSWORDS);
        checkPasswords.setTitle(R.string.passwords_check_title);
        checkPasswords.setOrder(ORDER_CHECK_PASSWORDS);
        checkPasswords.setSummary(R.string.passwords_check_description);
        // Add a listener which launches a settings page for the leak password check
        checkPasswords.setOnPreferenceClickListener(
                preference -> {
                    PasswordCheck passwordCheck = PasswordCheckFactory.getOrCreate();
                    passwordCheck.showUi(
                            getStyledContext(), PasswordCheckReferrer.PASSWORD_SETTINGS);
                    // Return true to notify the click was handled.
                    return true;
                });
        getPreferenceScreen().addPreference(checkPasswords);
    }

    private void createTrustedVaultBanner(
            @StringRes int subLabel, Preference.OnPreferenceClickListener listener) {
        ChromeBasePreference trustedVaultBanner = new ChromeBasePreference(getStyledContext());
        trustedVaultBanner.setKey(PREF_TRUSTED_VAULT_BANNER);
        trustedVaultBanner.setTitle(R.string.android_trusted_vault_banner_label);
        trustedVaultBanner.setOrder(ORDER_TRUSTED_VAULT_BANNER);
        trustedVaultBanner.setSummary(subLabel);
        trustedVaultBanner.setOnPreferenceClickListener(listener);
        getPreferenceScreen().addPreference(trustedVaultBanner);
    }

    private void displayManageAccountLink() {
        SyncService syncService = SyncServiceFactory.getForProfile(getProfile());
        if (syncService == null || !syncService.isEngineInitialized()) {
            return;
        }
        if (!PasswordManagerHelper.isSyncingPasswordsWithNoCustomPassphrase(syncService)) {
            return;
        }
        if (mSearchQuery != null && !mNoPasswords) {
            return; // Don't add the Manage Account link if there is a search going on.
        }
        if (getPreferenceScreen().findPreference(PREF_KEY_MANAGE_ACCOUNT_LINK) != null) {
            return; // Don't add the Manage Account link if it's present.
        }
        if (mLinkPref != null) {
            // If we created the link before, reuse it.
            getPreferenceScreen().addPreference(mLinkPref);
            return;
        }
        ForegroundColorSpan colorSpan =
                new ForegroundColorSpan(SemanticColorUtils.getDefaultTextColorLink(getContext()));
        SpannableString title =
                SpanApplier.applySpans(
                        getString(R.string.manage_passwords_text),
                        new SpanApplier.SpanInfo("<link>", "</link>", colorSpan));
        mLinkPref = new ChromeBasePreference(getStyledContext());
        mLinkPref.setKey(PREF_KEY_MANAGE_ACCOUNT_LINK);
        mLinkPref.setTitle(title);
        mLinkPref.setOnPreferenceClickListener(this);
        mLinkPref.setOrder(ORDER_MANAGE_ACCOUNT_LINK);
        getPreferenceScreen().addPreference(mLinkPref);
    }

    private Context getStyledContext() {
        return getPreferenceManager().getContext();
    }

    private PrefService getPrefService() {
        return UserPrefs.get(getProfile());
    }

    @Override
    public void syncStateChanged() {
        final @TrustedVaultBannerState int oldTrustedVaultBannerState = mTrustedVaultBannerState;
        computeTrustedVaultBannerState();
        if (oldTrustedVaultBannerState != mTrustedVaultBannerState) {
            rebuildPasswordLists();
        }
    }

    private void computeTrustedVaultBannerState() {
        final SyncService syncService = SyncServiceFactory.getForProfile(getProfile());
        if (syncService == null) {
            mTrustedVaultBannerState = TrustedVaultBannerState.NOT_SHOWN;
            return;
        }
        if (!syncService.isEngineInitialized()) {
            // Can't call getPassphraseType() yet.
            mTrustedVaultBannerState = TrustedVaultBannerState.NOT_SHOWN;
            return;
        }
        if (syncService.getPassphraseType() == PassphraseType.TRUSTED_VAULT_PASSPHRASE) {
            mTrustedVaultBannerState = TrustedVaultBannerState.OPTED_IN;
            return;
        }
        if (syncService.shouldOfferTrustedVaultOptIn()) {
            mTrustedVaultBannerState = TrustedVaultBannerState.OFFER_OPT_IN;
            return;
        }
        mTrustedVaultBannerState = TrustedVaultBannerState.NOT_SHOWN;
    }

    private boolean openTrustedVaultOptInDialog(Preference unused) {
        assert SyncServiceFactory.getForProfile(getProfile()) != null;
        CoreAccountInfo accountInfo =
                SyncServiceFactory.getForProfile(getProfile()).getAccountInfo();
        assert accountInfo != null;
        SyncSettingsUtils.openTrustedVaultOptInDialog(
                this, accountInfo, REQUEST_CODE_TRUSTED_VAULT_OPT_IN);
        // Return true to notify the click was handled.
        return true;
    }

    private boolean openTrustedVaultInfoPage(Preference unused) {
        Intent intent =
                new Intent(
                        Intent.ACTION_VIEW,
                        Uri.parse(PasswordUIView.getTrustedVaultLearnMoreURL()));
        intent.setPackage(getActivity().getPackageName());
        getActivity().startActivity(intent);
        // Return true to notify the click was handled.
        return true;
    }

    public void setBottomSheetControllerSupplier(
            OneshotSupplier<BottomSheetController> bottomSheetControllerSupplier) {
        mBottomSheetControllerSupplier = bottomSheetControllerSupplier;
    }

    Menu getMenuForTesting() {
        return mMenu;
    }

    Toolbar getToolbarForTesting() {
        return getActivity().findViewById(R.id.action_bar);
    }
}