chromium/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/ManageSyncSettings.java

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

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.Lifecycle.State;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;

import org.chromium.base.ContextUtils;
import org.chromium.base.IntentUtils;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.supplier.OneshotSupplier;
import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.AppHooks;
import org.chromium.chrome.browser.SyncFirstSetupCompleteSource;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.password_manager.PasswordManagerHelper;
import org.chromium.chrome.browser.password_manager.account_storage_toggle.AccountStorageToggleFragmentArgs;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
import org.chromium.chrome.browser.settings.ChromeBaseSettingsFragment;
import org.chromium.chrome.browser.settings.ChromeManagedPreferenceDelegate;
import org.chromium.chrome.browser.settings.SettingsLauncherFactory;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.signin.services.ProfileDataCache;
import org.chromium.chrome.browser.signin.services.SigninManager;
import org.chromium.chrome.browser.signin.services.UnifiedConsentServiceBridge;
import org.chromium.chrome.browser.sync.SyncServiceFactory;
import org.chromium.chrome.browser.sync.TrustedVaultClient;
import org.chromium.chrome.browser.sync.settings.SyncSettingsUtils.SyncError;
import org.chromium.chrome.browser.sync.ui.PassphraseCreationDialogFragment;
import org.chromium.chrome.browser.sync.ui.PassphraseDialogFragment;
import org.chromium.chrome.browser.sync.ui.PassphraseTypeDialogFragment;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.browser.ui.signin.SignOutCoordinator;
import org.chromium.chrome.browser.ui.signin.SigninUtils;
import org.chromium.chrome.browser.ui.signin.SignoutButtonPreference;
import org.chromium.chrome.browser.ui.signin.history_sync.HistorySyncHelper;
import org.chromium.chrome.browser.ui.theme.ChromeSemanticColorUtils;
import org.chromium.components.browser_ui.settings.ChromeBaseCheckBoxPreference;
import org.chromium.components.browser_ui.settings.ChromeBasePreference;
import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
import org.chromium.components.browser_ui.settings.SettingsUtils;
import org.chromium.components.search_engines.TemplateUrlService;
import org.chromium.components.signin.AccountManagerFacadeProvider;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.identitymanager.ConsentLevel;
import org.chromium.components.signin.identitymanager.IdentityManager;
import org.chromium.components.signin.identitymanager.PrimaryAccountChangeEvent;
import org.chromium.components.signin.metrics.SignoutReason;
import org.chromium.components.sync.SyncService;
import org.chromium.components.sync.UserSelectableType;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogManagerHolder;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modaldialog.SimpleModalDialogController;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.widget.ButtonCompat;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Settings fragment to customize Sync options (data types, encryption). Corresponds to
 * chrome://settings/syncSetup/advanced and parts of chrome://settings/syncSetup on desktop. This
 * fragment is accessible from the main settings view.
 */
public class ManageSyncSettings extends ChromeBaseSettingsFragment
        implements PassphraseDialogFragment.Delegate,
                PassphraseCreationDialogFragment.Listener,
                PassphraseTypeDialogFragment.Listener,
                Preference.OnPreferenceChangeListener,
                SyncService.SyncStateChangedListener,
                IdentityManager.Observer,
                SyncErrorCardPreference.SyncErrorCardPreferenceListener,
                IdentityErrorCardPreference.Listener {
    private static final String IS_FROM_SIGNIN_SCREEN = "ManageSyncSettings.isFromSigninScreen";

    @VisibleForTesting public static final String FRAGMENT_ENTER_PASSPHRASE = "enter_password";
    @VisibleForTesting public static final String FRAGMENT_CUSTOM_PASSPHRASE = "custom_password";
    @VisibleForTesting public static final String FRAGMENT_PASSPHRASE_TYPE = "password_type";

    @VisibleForTesting
    private static final String PREF_CENTRAL_ACCOUNT_CARD_PREFERENCE = "central_account_card";

    @VisibleForTesting
    public static final String PREF_SYNC_ERROR_CARD_PREFERENCE = "sync_error_card";

    @VisibleForTesting
    public static final String PREF_IDENTITY_ERROR_CARD_PREFERENCE = "identity_error_card";

    @VisibleForTesting public static final String PREF_SYNCING_CATEGORY = "syncing_category";
    @VisibleForTesting public static final String PREF_SYNC_EVERYTHING = "sync_everything";
    @VisibleForTesting public static final String PREF_SYNC_AUTOFILL = "sync_autofill";
    @VisibleForTesting public static final String PREF_SYNC_BOOKMARKS = "sync_bookmarks";

    @VisibleForTesting
    public static final String PREF_SYNC_PAYMENTS_INTEGRATION = "sync_payments_integration";

    @VisibleForTesting public static final String PREF_SYNC_HISTORY = "sync_history";
    @VisibleForTesting public static final String PREF_SYNC_PASSWORDS = "sync_passwords";
    @VisibleForTesting public static final String PREF_SYNC_READING_LIST = "sync_reading_list";
    @VisibleForTesting public static final String PREF_SYNC_RECENT_TABS = "sync_recent_tabs";
    @VisibleForTesting public static final String PREF_SYNC_SETTINGS = "sync_settings";
    @VisibleForTesting public static final String PREF_SYNC_APPS = "sync_apps";
    @VisibleForTesting public static final String PREF_TURN_OFF_SYNC = "turn_off_sync";
    @VisibleForTesting public static final String PREF_SYNC_REVIEW_DATA = "sync_review_data";
    private static final String PREF_ADVANCED_CATEGORY = "advanced_category";

    @VisibleForTesting
    private static final String PREF_SETTINGS_SYNC_DISABLED_BY_ADMINISTRATOR =
            "settings_sync_disabled_by_administrator";

    @VisibleForTesting
    public static final String PREF_BATCH_UPLOAD_CARD_PREFERENCE = "batch_upload_card";

    @VisibleForTesting
    public static final String PREF_ACCOUNT_SECTION_HISTORY_TOGGLE =
            "account_section_history_toggle";

    @VisibleForTesting
    public static final String PREF_ACCOUNT_SECTION_BOOKMARKS_TOGGLE =
            "account_section_bookmarks_toggle";

    @VisibleForTesting
    public static final String PREF_ACCOUNT_SECTION_READING_LIST_TOGGLE =
            "account_section_reading_list_toggle";

    @VisibleForTesting
    public static final String PREF_ACCOUNT_SECTION_ADDRESSES_TOGGLE =
            "account_section_addresses_toggle";

    @VisibleForTesting
    public static final String PREF_ACCOUNT_SECTION_PASSWORDS_TOGGLE =
            "account_section_passwords_toggle";

    @VisibleForTesting
    public static final String PREF_ACCOUNT_SECTION_PAYMENTS_TOGGLE =
            "account_section_payments_toggle";

    @VisibleForTesting
    public static final String PREF_ACCOUNT_SECTION_SETTINGS_TOGGLE =
            "account_section_settings_toggle";

    @VisibleForTesting
    public static final String PREF_GOOGLE_ACTIVITY_CONTROLS = "google_activity_controls";

    @VisibleForTesting public static final String PREF_ENCRYPTION = "encryption";

    @VisibleForTesting
    public static final String PREF_ACCOUNT_DATA_DASHBOARD = "account_data_dashboard";

    @VisibleForTesting
    public static final String PREF_MANAGE_YOUR_GOOGLE_ACCOUNT = "manage_your_google_account";

    @VisibleForTesting
    public static final String PREF_ACCOUNT_ANDROID_DEVICE_ACCOUNTS =
            "account_android_device_accounts";

    @VisibleForTesting
    public static final String PREF_SEARCH_AND_BROWSE_CATEGORY = "search_and_browse_category";

    @VisibleForTesting
    public static final String PREF_URL_KEYED_ANONYMIZED_DATA = "url_keyed_anonymized_data";

    @VisibleForTesting public static final String PREF_SIGN_OUT = "sign_out_button";

    private static final int REQUEST_CODE_TRUSTED_VAULT_KEY_RETRIEVAL = 1;
    private static final int REQUEST_CODE_TRUSTED_VAULT_RECOVERABILITY_DEGRADED = 2;

    private BatchUploadCardPreference mBatchUploadCardPreference;
    private SyncService mSyncService;
    private OneshotSupplier<SnackbarManager> mSnackbarManagerSupplier;

    private boolean mIsFromSigninScreen;
    private boolean mShouldReplaceSyncSettingsWithAccountSettings;

    private SyncErrorCardPreference mSyncErrorCardPreference;
    private PreferenceCategory mSyncingCategory;

    private ChromeSwitchPreference mSyncEverything;

    /**
     * Maps {@link UserSelectableType} to the corresponding preference.
     * `mSyncTypeCheckBoxPreferencesMap` is used for the sync settings, but
     * `mSyncTypeSwitchPreferencesMap` is used for the new account settings panel when
     * mShouldReplaceSyncSettingsWithAccountSettings is true.
     */
    @Nullable private Map<Integer, ChromeBaseCheckBoxPreference> mSyncTypeCheckBoxPreferencesMap;

    @Nullable private Map<Integer, ChromeSwitchPreference> mSyncTypeSwitchPreferencesMap;

    private Preference mGoogleActivityControls;
    private Preference mSyncEncryption;
    private SignoutButtonPreference mSignOutPreference;

    private PreferenceCategory mSearchAndBrowseCategory;
    private ChromeSwitchPreference mUrlKeyedAnonymizedData;

    private SyncService.SyncSetupInProgressHandle mSyncSetupInProgressHandle;

    private final ObservableSupplierImpl<String> mPageTitle = new ObservableSupplierImpl<>();

    /**
     * Creates an argument bundle for this fragment.
     *
     * @param isFromSigninScreen Whether the screen is started from the sign-in screen.
     */
    public static Bundle createArguments(boolean isFromSigninScreen) {
        Bundle result = new Bundle();
        result.putBoolean(IS_FROM_SIGNIN_SCREEN, isFromSigninScreen);
        return result;
    }

    @Override
    public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
        Profile profile = getProfile();
        mSyncService = SyncServiceFactory.getForProfile(profile);

        mIsFromSigninScreen =
                IntentUtils.safeGetBoolean(getArguments(), IS_FROM_SIGNIN_SCREEN, false);

        setHasOptionsMenu(true);

        mShouldReplaceSyncSettingsWithAccountSettings =
                ChromeFeatureList.isEnabled(
                                ChromeFeatureList.REPLACE_SYNC_PROMOS_WITH_SIGN_IN_PROMOS)
                        && !mSyncService.hasSyncConsent();

        if (mShouldReplaceSyncSettingsWithAccountSettings) {
            // Set title with an empty string to have no title on the top of the page.
            mPageTitle.set("");

            SettingsUtils.addPreferencesFromResource(
                    this, R.xml.unified_account_settings_preferences);

            CentralAccountCardPreference centralAccountCardPreference =
                    (CentralAccountCardPreference)
                            findPreference(PREF_CENTRAL_ACCOUNT_CARD_PREFERENCE);
            centralAccountCardPreference.initialize(
                    IdentityServicesProvider.get()
                            .getIdentityManager(getProfile())
                            .getPrimaryAccountInfo(ConsentLevel.SIGNIN),
                    ProfileDataCache.createWithDefaultImageSizeAndNoBadge(getContext()));

            IdentityErrorCardPreference identityErrorCardPreference =
                    (IdentityErrorCardPreference)
                            findPreference(PREF_IDENTITY_ERROR_CARD_PREFERENCE);
            identityErrorCardPreference.initialize(profile, this);

            if (ChromeFeatureList.isEnabled(ChromeFeatureList.ENABLE_BATCH_UPLOAD_FROM_SETTINGS)) {
                mBatchUploadCardPreference =
                        (BatchUploadCardPreference)
                                findPreference(PREF_BATCH_UPLOAD_CARD_PREFERENCE);
                mBatchUploadCardPreference.initialize(
                        getActivity(),
                        profile,
                        ((ModalDialogManagerHolder) getActivity()).getModalDialogManager());
                mBatchUploadCardPreference.setSnackbarManagerSupplier(mSnackbarManagerSupplier);
            }

            if (mSyncService.isSyncDisabledByEnterprisePolicy()) {
                ChromeBasePreference settingsSyncDisabledByAdministrator =
                        (ChromeBasePreference)
                                findPreference(PREF_SETTINGS_SYNC_DISABLED_BY_ADMINISTRATOR);
                settingsSyncDisabledByAdministrator.setDividerAllowedAbove(false);
                settingsSyncDisabledByAdministrator.setVisible(true);
            }

            mSyncTypeSwitchPreferencesMap = new HashMap<>();
            mSyncTypeSwitchPreferencesMap.put(
                    UserSelectableType.AUTOFILL,
                    findPreference(PREF_ACCOUNT_SECTION_ADDRESSES_TOGGLE));
            mSyncTypeSwitchPreferencesMap.put(
                    UserSelectableType.BOOKMARKS,
                    findPreference(PREF_ACCOUNT_SECTION_BOOKMARKS_TOGGLE));
            // HISTORY and TABS are bundled in the same switch in the new settings panel.
            mSyncTypeSwitchPreferencesMap.put(
                    UserSelectableType.HISTORY,
                    findPreference(PREF_ACCOUNT_SECTION_HISTORY_TOGGLE));
            mSyncTypeSwitchPreferencesMap.put(
                    UserSelectableType.TABS, findPreference(PREF_ACCOUNT_SECTION_HISTORY_TOGGLE));
            ChromeSwitchPreference passwordsToggle =
                    (ChromeSwitchPreference) findPreference(PREF_ACCOUNT_SECTION_PASSWORDS_TOGGLE);
            mSyncTypeSwitchPreferencesMap.put(UserSelectableType.PASSWORDS, passwordsToggle);
            if (getArguments() != null
                    && getArguments().getBoolean(AccountStorageToggleFragmentArgs.HIGHLIGHT)) {
                passwordsToggle.setBackgroundColor(
                        ChromeSemanticColorUtils.getIphHighlightColor(getContext()));
            }
            mSyncTypeSwitchPreferencesMap.put(
                    UserSelectableType.PAYMENTS,
                    findPreference(PREF_ACCOUNT_SECTION_PAYMENTS_TOGGLE));
            mSyncTypeSwitchPreferencesMap.put(
                    UserSelectableType.PREFERENCES,
                    findPreference(PREF_ACCOUNT_SECTION_SETTINGS_TOGGLE));
            mSyncTypeSwitchPreferencesMap.put(
                    UserSelectableType.READING_LIST,
                    findPreference(PREF_ACCOUNT_SECTION_READING_LIST_TOGGLE));

            mSyncTypeSwitchPreferencesMap
                    .values()
                    .forEach(pref -> pref.setOnPreferenceChangeListener(this));

            Preference reviewSyncData = findPreference(PREF_ACCOUNT_DATA_DASHBOARD);
            reviewSyncData.setOnPreferenceClickListener(
                    SyncSettingsUtils.toOnClickListener(
                            this, () -> SyncSettingsUtils.openSyncDashboard(getActivity())));

            Preference manageYourGoogleAccount = findPreference(PREF_MANAGE_YOUR_GOOGLE_ACCOUNT);
            manageYourGoogleAccount.setOnPreferenceClickListener(
                    SyncSettingsUtils.toOnClickListener(
                            this,
                            () -> {
                                SyncSettingsUtils.openGoogleMyAccount(getActivity());
                            }));

            Preference manageAccountsOnThisDevice =
                    findPreference(PREF_ACCOUNT_ANDROID_DEVICE_ACCOUNTS);
            manageAccountsOnThisDevice.setOnPreferenceClickListener(
                    SyncSettingsUtils.toOnClickListener(
                            this, () -> SigninUtils.openSettingsForAllAccounts(getActivity())));

            mSignOutPreference = (SignoutButtonPreference) findPreference(PREF_SIGN_OUT);
            if (getProfile().isChild()) {
                mSignOutPreference.setVisible(false);
            } else {
                mSignOutPreference.initialize(
                        requireContext(),
                        getProfile(),
                        getChildFragmentManager(),
                        ((ModalDialogManagerHolder) getActivity()).getModalDialogManager());
            }
            mSignOutPreference.setSnackbarManagerSupplier(mSnackbarManagerSupplier);
        } else {
            mPageTitle.set(getString(R.string.sync_category_title));

            SettingsUtils.addPreferencesFromResource(this, R.xml.manage_sync_preferences);

            mSyncErrorCardPreference =
                    (SyncErrorCardPreference) findPreference(PREF_SYNC_ERROR_CARD_PREFERENCE);
            mSyncErrorCardPreference.initialize(
                    ProfileDataCache.createWithDefaultImageSize(
                            getContext(), R.drawable.ic_sync_badge_error_20dp),
                    profile,
                    this);

            mSyncingCategory = (PreferenceCategory) findPreference(PREF_SYNCING_CATEGORY);

            mSyncEverything = (ChromeSwitchPreference) findPreference(PREF_SYNC_EVERYTHING);
            mSyncEverything.setOnPreferenceChangeListener(this);

            Preference turnOffSync = findPreference(PREF_TURN_OFF_SYNC);

            if (!mIsFromSigninScreen) {
                turnOffSync.setVisible(true);
                if (!profile.isChild()) {
                    // Non-child users have an option to sign out and turn off sync.  This is to
                    // ensure
                    // that revoking consents for sign in and sync does not require more steps than
                    // enabling them.
                    turnOffSync.setIcon(R.drawable.ic_signout_40dp);
                    turnOffSync.setTitle(R.string.sign_out_and_turn_off_sync);
                    turnOffSync.setOnPreferenceClickListener(
                            SyncSettingsUtils.toOnClickListener(
                                    this, this::onSignOutAndTurnOffSyncClicked));
                } else {
                    // Child users are force signed-in, so have an option which only turns off sync.
                    turnOffSync.setIcon(R.drawable.ic_turn_off_sync_48dp);
                    turnOffSync.setTitle(R.string.turn_off_sync);
                    turnOffSync.setOnPreferenceClickListener(
                            SyncSettingsUtils.toOnClickListener(this, this::onTurnOffSyncClicked));
                }

                findPreference(PREF_ADVANCED_CATEGORY).setVisible(true);
            }

            mSyncTypeCheckBoxPreferencesMap = new HashMap<>();
            mSyncTypeCheckBoxPreferencesMap.put(
                    UserSelectableType.AUTOFILL, findPreference(PREF_SYNC_AUTOFILL));
            mSyncTypeCheckBoxPreferencesMap.put(
                    UserSelectableType.BOOKMARKS, findPreference(PREF_SYNC_BOOKMARKS));
            mSyncTypeCheckBoxPreferencesMap.put(
                    UserSelectableType.HISTORY, findPreference(PREF_SYNC_HISTORY));
            mSyncTypeCheckBoxPreferencesMap.put(
                    UserSelectableType.PASSWORDS, findPreference(PREF_SYNC_PASSWORDS));
            mSyncTypeCheckBoxPreferencesMap.put(
                    UserSelectableType.PAYMENTS, findPreference(PREF_SYNC_PAYMENTS_INTEGRATION));
            mSyncTypeCheckBoxPreferencesMap.put(
                    UserSelectableType.PREFERENCES, findPreference(PREF_SYNC_SETTINGS));

            if (ChromeFeatureList.isEnabled(ChromeFeatureList.WEB_APK_BACKUP_AND_RESTORE_BACKEND)) {
                mSyncTypeCheckBoxPreferencesMap.put(
                        UserSelectableType.APPS, findPreference(PREF_SYNC_APPS));
            } else {
                findPreference(PREF_SYNC_APPS).setVisible(false);
            }

            mSyncTypeCheckBoxPreferencesMap.put(
                    UserSelectableType.READING_LIST, findPreference(PREF_SYNC_READING_LIST));
            mSyncTypeCheckBoxPreferencesMap.put(
                    UserSelectableType.TABS, findPreference(PREF_SYNC_RECENT_TABS));

            mSyncTypeCheckBoxPreferencesMap
                    .values()
                    .forEach(pref -> pref.setOnPreferenceChangeListener(this));

            // Prevent sync settings changes from taking effect until the user leaves this screen.
            mSyncSetupInProgressHandle = mSyncService.getSetupInProgressHandle();

            mSearchAndBrowseCategory =
                    (PreferenceCategory) findPreference(PREF_SEARCH_AND_BROWSE_CATEGORY);

            mUrlKeyedAnonymizedData =
                    (ChromeSwitchPreference) findPreference(PREF_URL_KEYED_ANONYMIZED_DATA);
            boolean urlKeyedAnonymizedDataShouldBeEnabled =
                    !UnifiedConsentServiceBridge.isUrlKeyedAnonymizedDataCollectionManaged(profile)
                            || UnifiedConsentServiceBridge
                                    .isUrlKeyedAnonymizedDataCollectionEnabled(profile);
            mUrlKeyedAnonymizedData.setChecked(urlKeyedAnonymizedDataShouldBeEnabled);
            mUrlKeyedAnonymizedData.setManagedPreferenceDelegate(
                    new ChromeManagedPreferenceDelegate(profile) {
                        @Override
                        public boolean isPreferenceControlledByPolicy(Preference preference) {
                            return UnifiedConsentServiceBridge
                                    .isUrlKeyedAnonymizedDataCollectionManaged(profile);
                        }
                    });
            Preference reviewSyncData = findPreference(PREF_SYNC_REVIEW_DATA);
            reviewSyncData.setOnPreferenceClickListener(
                    SyncSettingsUtils.toOnClickListener(
                            this, () -> SyncSettingsUtils.openSyncDashboard(getActivity())));
        }

        mGoogleActivityControls = findPreference(PREF_GOOGLE_ACTIVITY_CONTROLS);
        if (ChromeFeatureList.isEnabled(ChromeFeatureList.LINKED_SERVICES_SETTING)) {
            if (isEeaChoiceCountry()) {
                mGoogleActivityControls.setTitle(
                        R.string.sign_in_personalize_google_services_title_eea);
            } else {
                mGoogleActivityControls.setTitle(
                        R.string.sign_in_personalize_google_services_title);
            }
            mGoogleActivityControls.setSummary(
                    R.string.sign_in_personalize_google_services_summary);
        }

        mSyncEncryption = findPreference(PREF_ENCRYPTION);
        mSyncEncryption.setOnPreferenceClickListener(
                SyncSettingsUtils.toOnClickListener(this, this::onSyncEncryptionClicked));
    }

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

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (!mShouldReplaceSyncSettingsWithAccountSettings) {
            mSyncSetupInProgressHandle.close();
        }
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        menu.clear();
        MenuItem help =
                menu.add(Menu.NONE, R.id.menu_id_targeted_help, Menu.NONE, R.string.menu_help);
        help.setIcon(R.drawable.ic_help_and_feedback);
        if (mIsFromSigninScreen) {
            ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
            assert actionBar != null;
            actionBar.setHomeActionContentDescription(
                    R.string.prefs_manage_sync_settings_content_description);
            RecordUserAction.record("Signin_Signin_ShowAdvancedSyncSettings");
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.menu_id_targeted_help) {
            getHelpAndFeedbackLauncher()
                    .show(getActivity(), getString(R.string.help_context_sync_and_services), null);
            return true;
        }
        if (item.getItemId() == android.R.id.home) {
            return onBackToHome();
        }
        return false;
    }

    @Override
    public View onCreateView(
            LayoutInflater inflater,
            @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
        if (!mIsFromSigninScreen) {
            return super.onCreateView(inflater, container, savedInstanceState);
        }

        // Advanced sync consent flow - add a bottom bar and un-hide relevant preferences.
        ViewGroup result = (ViewGroup) super.onCreateView(inflater, container, savedInstanceState);
        inflater.inflate(R.layout.manage_sync_settings_bottom_bar, result, true);

        ButtonCompat cancelButton = result.findViewById(R.id.cancel_button);
        cancelButton.setOnClickListener(view -> cancelSync());
        ButtonCompat confirmButton = result.findViewById(R.id.confirm_button);
        confirmButton.setOnClickListener(view -> confirmSettings());

        mSearchAndBrowseCategory.setVisible(true);
        mSyncingCategory.setVisible(true);

        return result;
    }

    @Override
    public void onStart() {
        super.onStart();
        mSyncService.addSyncStateChangedListener(this);
        IdentityServicesProvider.get().getIdentityManager(getProfile()).addObserver(this);
    }

    @Override
    public void onStop() {
        super.onStop();
        mSyncService.removeSyncStateChangedListener(this);
        IdentityServicesProvider.get().getIdentityManager(getProfile()).removeObserver(this);
    }

    @Override
    public void onResume() {
        super.onResume();
        // This is necessary to refresh the batch upload card if the user leaves Chrome open on the
        // settings screen, changes their screen lock settings, and then returns to Chrome.
        if (mShouldReplaceSyncSettingsWithAccountSettings
                && ChromeFeatureList.isEnabled(
                        ChromeFeatureList.ENABLE_BATCH_UPLOAD_FROM_SETTINGS)) {
            mBatchUploadCardPreference.hideBatchUploadCardAndUpdate();
        }
        updateSyncPreferences();
    }

    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        if (mSyncService.isUsingExplicitPassphrase()
                && mShouldReplaceSyncSettingsWithAccountSettings
                && preference
                        .getKey()
                        .equals(ManageSyncSettings.PREF_ACCOUNT_SECTION_ADDRESSES_TOGGLE)
                && (Boolean) newValue) {
            // Shows a dialog that warns user that addresses are not encrypted with their custom
            // passphrase.
            showAdressesNotEncryptedDialog(preference);
            // Preference state shouldn't be changed until the user chooses to continue.
            return false;
        }
        if (mShouldReplaceSyncSettingsWithAccountSettings
                && preference
                        .getKey()
                        .equals(ManageSyncSettings.PREF_ACCOUNT_SECTION_HISTORY_TOGGLE)) {
            HistorySyncHelper historySyncHelper = HistorySyncHelper.getForProfile(getProfile());
            if ((Boolean) newValue) {
                // The user opted into syncing history, wipe prefs storing previous declines.
                historySyncHelper.clearHistorySyncDeclinedPrefs();
            } else {
                historySyncHelper.recordHistorySyncDeclinedPrefs();
            }
        }
        // A change to Preference state hasn't been applied yet. Defer
        // updateSyncStateFromSelectedTypes so it gets the updated state from
        // isChecked().
        PostTask.postTask(TaskTraits.UI_DEFAULT, this::updateSyncStateFromSelectedTypes);
        return true;
    }

    /**
     * SyncService.SyncStateChangedListener implementation, listens to sync state changes.
     *
     * <p>If the user has just turned on sync, this listener is needed in order to enable the
     * encryption settings once the engine has initialized.
     */
    @Override
    public void syncStateChanged() {
        // This is invoked synchronously from SyncService.setSelectedTypes, postpone the
        // update to let updateSyncStateFromSelectedTypes finish saving the state.
        PostTask.postTask(TaskTraits.UI_DEFAULT, this::updateSyncPreferences);
    }

    /** IdentityManager.Observer implementation. */
    @Override
    public void onPrimaryAccountChanged(PrimaryAccountChangeEvent eventDetails) {
        if (eventDetails.getEventTypeFor(ConsentLevel.SIGNIN)
                        == PrimaryAccountChangeEvent.Type.CLEARED
                // TODO(b/40066949): Remove this usage of ConsentLevel.SYNC after all android sync
                // users have been migrated.
                || eventDetails.getEventTypeFor(ConsentLevel.SYNC)
                        == PrimaryAccountChangeEvent.Type.CLEARED) {
            if (getActivity() != null) getActivity().finish();
        }
    }

    /** Handles when user clicks home button in menu to get back to home screen. */
    private boolean onBackToHome() {
        if (mIsFromSigninScreen) {
            RecordUserAction.record("Signin_Signin_BackOnAdvancedSyncSettings");
        }
        return false;
    }

    /**
     * Gets the current state of data types from {@link SyncService} and updates UI elements from
     * this state.
     */
    private void updateSyncPreferences() {
        String signedInAccountName =
                CoreAccountInfo.getEmailFrom(
                        IdentityServicesProvider.get()
                                .getIdentityManager(getProfile())
                                .getPrimaryAccountInfo(
                                        mShouldReplaceSyncSettingsWithAccountSettings
                                                ? ConsentLevel.SIGNIN
                                                : ConsentLevel.SYNC));
        // May happen if account is removed from the device while this screen is shown.
        if (signedInAccountName == null) {
            if (getActivity() != null) getActivity().finish();
            return;
        }

        mGoogleActivityControls.setOnPreferenceClickListener(
                SyncSettingsUtils.toOnClickListener(
                        this, () -> onGoogleActivityControlsClicked(signedInAccountName)));

        updateDataTypeState();
        updateEncryptionState();
    }

    /** Gets the state from data type checkboxes and saves this state into {@link SyncService}. */
    private void updateSyncStateFromSelectedTypes() {
        mSyncService.setSelectedTypes(
                mShouldReplaceSyncSettingsWithAccountSettings ? false : mSyncEverything.isChecked(),
                getUserSelectedTypes());

        // Some calls to setSelectedTypes don't trigger syncStateChanged, so schedule update here.
        PostTask.postTask(TaskTraits.UI_DEFAULT, this::updateSyncPreferences);
    }

    /**
     * Update the encryption state.
     *
     * If sync's engine is initialized, the button is enabled and the dialog will present the
     * valid encryption options for the user. Otherwise, any encryption dialogs will be closed
     * and the button will be disabled because the engine is needed in order to know and
     * modify the encryption state.
     */
    private void updateEncryptionState() {
        boolean isEngineInitialized = mSyncService.isEngineInitialized();
        mSyncEncryption.setEnabled(isEngineInitialized);
        mSyncEncryption.setSummary(null);
        if (!isEngineInitialized) {
            // If sync is not initialized, encryption state is unavailable and can't be changed.
            // Leave the button disabled and the summary empty. Additionally, close the dialogs in
            // case they were open when a stop and clear comes.
            closeDialogIfOpen(FRAGMENT_CUSTOM_PASSPHRASE);
            closeDialogIfOpen(FRAGMENT_ENTER_PASSPHRASE);
            return;
        }

        if (mSyncService.isTrustedVaultKeyRequired()) {
            // The user cannot manually enter trusted vault keys, so it needs to gets treated as an
            // error.
            closeDialogIfOpen(FRAGMENT_CUSTOM_PASSPHRASE);
            closeDialogIfOpen(FRAGMENT_ENTER_PASSPHRASE);
            if (mShouldReplaceSyncSettingsWithAccountSettings) {
                // Show the same text as the error card button.
                setEncryptionErrorSummary(R.string.identity_error_card_button_verify);
            } else {
                setEncryptionErrorSummary(
                        mSyncService.isEncryptEverythingEnabled()
                                ? R.string.sync_error_card_title
                                : R.string.password_sync_error_summary);
            }
            return;
        }

        if (!mSyncService.isPassphraseRequiredForPreferredDataTypes()) {
            closeDialogIfOpen(FRAGMENT_ENTER_PASSPHRASE);
        }
        if (mSyncService.isPassphraseRequiredForPreferredDataTypes() && isAdded()) {
            setEncryptionErrorSummary(R.string.sync_need_passphrase);
        }
    }

    private void setEncryptionErrorSummary(@StringRes int stringId) {
        SpannableString summary = new SpannableString(getString(stringId));
        final int errorColor = getContext().getColor(R.color.input_underline_error_color);
        summary.setSpan(new ForegroundColorSpan(errorColor), 0, summary.length(), 0);
        mSyncEncryption.setSummary(summary);
    }

    private Set<Integer> getUserSelectedTypes() {
        return (mShouldReplaceSyncSettingsWithAccountSettings
                        ? mSyncTypeSwitchPreferencesMap
                        : mSyncTypeCheckBoxPreferencesMap)
                .entrySet().stream()
                        .filter(e -> e.getValue().isChecked())
                        .map(Map.Entry::getKey)
                        .collect(Collectors.toSet());
    }

    private void displayPassphraseTypeDialog() {
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        PassphraseTypeDialogFragment dialog =
                PassphraseTypeDialogFragment.create(
                        mSyncService.getPassphraseType(), mSyncService.isCustomPassphraseAllowed());
        dialog.show(ft, FRAGMENT_PASSPHRASE_TYPE);
        dialog.setTargetFragment(this, -1);
    }

    private void displayPassphraseDialog() {
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        PassphraseDialogFragment.newInstance(this).show(ft, FRAGMENT_ENTER_PASSPHRASE);
    }

    private void displayCustomPassphraseDialog() {
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        PassphraseCreationDialogFragment dialog = new PassphraseCreationDialogFragment();
        dialog.setTargetFragment(this, -1);
        dialog.show(ft, FRAGMENT_CUSTOM_PASSPHRASE);
    }

    private void closeDialogIfOpen(String tag) {
        FragmentManager manager = getFragmentManager();
        if (manager == null) {
            // Do nothing if the manager doesn't exist yet; see http://crbug.com/480544.
            return;
        }
        DialogFragment df = (DialogFragment) manager.findFragmentByTag(tag);
        if (df != null) {
            df.dismiss();
        }
    }

    /** Returns whether the passphrase successfully decrypted the pending keys. */
    private boolean handleDecryption(String passphrase) {
        if (passphrase.isEmpty() || !mSyncService.setDecryptionPassphrase(passphrase)) {
            return false;
        }
        // PassphraseDialogFragment doesn't handle closing itself, so do it here. This is not done
        // in updateSyncStateFromAndroidSyncSettings() because that happens onResume and possibly in
        // other cases where the dialog should stay open.
        closeDialogIfOpen(FRAGMENT_ENTER_PASSPHRASE);
        // Update our configuration UI.
        updateSyncPreferences();
        return true;
    }

    /** Callback for PassphraseDialogFragment.Listener */
    @Override
    public boolean onPassphraseEntered(String passphrase) {
        if (!mSyncService.isEngineInitialized()
                || !mSyncService.isPassphraseRequiredForPreferredDataTypes()) {
            // If the engine was shut down since the dialog was opened, or the passphrase isn't
            // required anymore, do nothing.
            return false;
        }
        return handleDecryption(passphrase);
    }

    /** Callback for PassphraseDialogFragment.Listener */
    @Override
    public void onPassphraseCanceled() {}

    /** Callback for PassphraseCreationDialogFragment.Listener */
    @Override
    public void onPassphraseCreated(String passphrase) {
        if (!mSyncService.isEngineInitialized()) {
            // If the engine was shut down since the dialog was opened, do nothing.
            return;
        }
        mSyncService.setEncryptionPassphrase(passphrase);
        // Save the current state of data types - this tells the sync engine to
        // apply our encryption configuration changes.
        updateSyncStateFromSelectedTypes();
    }

    /** Callback for PassphraseTypeDialogFragment.Listener */
    @Override
    public void onChooseCustomPassphraseRequested() {
        if (!mSyncService.isEngineInitialized()) {
            // If the engine was shut down since the dialog was opened, do nothing.
            return;
        }

        // The passphrase type should only ever be selected if the account doesn't have
        // full encryption enabled. Otherwise both options should be disabled.
        assert !mSyncService.isEncryptEverythingEnabled();
        assert !mSyncService.isUsingExplicitPassphrase();
        displayCustomPassphraseDialog();
    }

    public void setSnackbarManagerSupplier(
            OneshotSupplier<SnackbarManager> snackbarManagerSupplier) {
        assert getLifecycle().getCurrentState() == State.INITIALIZED;
        mSnackbarManagerSupplier = snackbarManagerSupplier;
    }

    private void onGoogleActivityControlsClicked(String signedInAccountName) {
        if (ChromeFeatureList.isEnabled(ChromeFeatureList.LINKED_SERVICES_SETTING)
                && isEeaChoiceCountry()) {
            SettingsLauncherFactory.createSettingsLauncher()
                    .launchSettingsActivity(getContext(), PersonalizeGoogleServicesSettings.class);
            RecordUserAction.record("Signin_AccountSettings_PersonalizeGoogleServicesClicked");
        } else {
            AppHooks.get()
                    .createGoogleActivityController()
                    .openWebAndAppActivitySettings(getActivity(), signedInAccountName);
        RecordUserAction.record("Signin_AccountSettings_GoogleActivityControlsClicked");
        }
    }

    private void onSignOutAndTurnOffSyncClicked() {
        assert !getProfile().isChild();
        if (!IdentityServicesProvider.get()
                .getIdentityManager(getProfile())
                .hasPrimaryAccount(ConsentLevel.SYNC)) {
            return;
        }
        SignOutCoordinator.startSignOutFlow(
                requireContext(),
                getProfile(),
                getChildFragmentManager(),
                ((ModalDialogManagerHolder) getActivity()).getModalDialogManager(),
                mSnackbarManagerSupplier.get(),
                SignoutReason.USER_CLICKED_SIGNOUT_SETTINGS,
                /* showConfirmDialog= */ false,
                () -> {});
    }

    private void onTurnOffSyncClicked() {
        assert getProfile().isChild();
        if (!IdentityServicesProvider.get()
                .getIdentityManager(getProfile())
                .hasPrimaryAccount(ConsentLevel.SYNC)) {
            return;
        }
        SignOutCoordinator.startSignOutFlow(
                requireContext(),
                getProfile(),
                getChildFragmentManager(),
                ((ModalDialogManagerHolder) getActivity()).getModalDialogManager(),
                mSnackbarManagerSupplier.get(),
                SignoutReason.USER_CLICKED_REVOKE_SYNC_CONSENT_SETTINGS,
                /* showConfirmDialog= */ false,
                () -> {});
    }

    private void showAdressesNotEncryptedDialog(Preference preference) {
        ModalDialogManager modalDialogManager =
                ((ModalDialogManagerHolder) getActivity()).getModalDialogManager();
        assert modalDialogManager != null;
        ModalDialogProperties.Controller dialogController =
                new SimpleModalDialogController(
                        modalDialogManager,
                        dismissalCause -> {
                            if (dismissalCause == DialogDismissalCause.POSITIVE_BUTTON_CLICKED) {
                                // Preference state should change when the user chooses to continue.
                                ((ChromeSwitchPreference) preference).setChecked(true);
                                PostTask.postTask(
                                        TaskTraits.UI_DEFAULT,
                                        this::updateSyncStateFromSelectedTypes);
                            }
                        });
        PropertyModel dialog =
                new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
                        .with(ModalDialogProperties.CONTROLLER, dialogController)
                        .with(
                                ModalDialogProperties.TITLE,
                                getContext().getString(R.string.sync_addresses_title))
                        .with(
                                ModalDialogProperties.MESSAGE_PARAGRAPH_1,
                                getContext().getString(R.string.sync_addresses_body))
                        .with(
                                ModalDialogProperties.POSITIVE_BUTTON_TEXT,
                                getContext().getString(R.string.sync_addresses_accept))
                        .with(
                                ModalDialogProperties.BUTTON_STYLES,
                                ModalDialogProperties.ButtonStyles.PRIMARY_FILLED_NEGATIVE_OUTLINE)
                        .with(
                                ModalDialogProperties.NEGATIVE_BUTTON_TEXT,
                                getContext().getString(R.string.sync_addresses_cancel))
                        .build();
        modalDialogManager.showDialog(dialog, ModalDialogManager.ModalDialogType.APP);
    }

    private void onSyncEncryptionClicked() {
        if (!mSyncService.isEngineInitialized()) return;

        if (mSyncService.isPassphraseRequiredForPreferredDataTypes()) {
            displayPassphraseDialog();
        } else if (mSyncService.isTrustedVaultKeyRequired()) {
            CoreAccountInfo primaryAccountInfo =
                    IdentityServicesProvider.get()
                            .getIdentityManager(getProfile())
                            .getPrimaryAccountInfo(ConsentLevel.SIGNIN);
            if (primaryAccountInfo != null) {
                SyncSettingsUtils.openTrustedVaultKeyRetrievalDialog(
                        this, primaryAccountInfo, REQUEST_CODE_TRUSTED_VAULT_KEY_RETRIEVAL);
            }
        } else {
            displayPassphraseTypeDialog();
        }
    }

    /** Gets the current state of data types from {@link SyncService} and updates the UI. */
    private void updateDataTypeState() {
        if (mShouldReplaceSyncSettingsWithAccountSettings) {
            Set<Integer> selectedSyncTypes = mSyncService.getSelectedTypes();

            boolean syncDisabledByPolicy = mSyncService.isSyncDisabledByEnterprisePolicy();

            for (Map.Entry<Integer, ChromeSwitchPreference> entry :
                    mSyncTypeSwitchPreferencesMap.entrySet()) {
                @UserSelectableType int type = entry.getKey();
                boolean enabled = !mSyncService.isTypeManagedByCustodian(type);
                boolean checked = selectedSyncTypes.contains(type);
                final boolean managed;

                if (type == UserSelectableType.TABS || type == UserSelectableType.HISTORY) {
                    // PREF_ACCOUNT_SECTION_HISTORY_TOGGLE toggle represents both History and Tabs
                    // in this case.
                    // History and Tabs should usually have the same value, but in some
                    // cases they may not, e.g. if one of them is disabled by policy. In that
                    // case, show the toggle as on if at least one of them is enabled. The
                    // toggle should reflect the value of the non-disabled type.
                    enabled =
                            !mSyncService.isTypeManagedByCustodian(UserSelectableType.TABS)
                                    || !mSyncService.isTypeManagedByCustodian(
                                            UserSelectableType.HISTORY);
                    checked =
                            selectedSyncTypes.contains(UserSelectableType.TABS)
                                    || selectedSyncTypes.contains(UserSelectableType.HISTORY);
                    managed =
                            mSyncService.isTypeManagedByPolicy(UserSelectableType.TABS)
                                    && mSyncService.isTypeManagedByPolicy(
                                            UserSelectableType.HISTORY);
                } else {
                    managed = mSyncService.isTypeManagedByPolicy(type);
                }

                // Disable if sync is disabled by policy.
                // Note that `syncDisabledByPolicy` does not affect `managed` since setting it would
                // otherwise add a disclaimer to all the preferences that it's managed.
                enabled = enabled && !syncDisabledByPolicy;

                ChromeSwitchPreference pref = entry.getValue();
                pref.setEnabled(enabled);
                pref.setChecked(checked);
                pref.setManagedPreferenceDelegate(
                        new ChromeManagedPreferenceDelegate(getProfile()) {
                            @Override
                            public boolean isPreferenceControlledByPolicy(Preference preference) {
                                return managed;
                            }
                        });
            }
            return;
        }
        boolean syncEverything = mSyncService.hasKeepEverythingSynced();
        mSyncEverything.setChecked(syncEverything);

        Set<Integer> selectedSyncTypes = mSyncService.getSelectedTypes();

        for (Map.Entry<Integer, ChromeBaseCheckBoxPreference> entry :
                mSyncTypeCheckBoxPreferencesMap.entrySet()) {
            @UserSelectableType int type = entry.getKey();
            ChromeBaseCheckBoxPreference pref = entry.getValue();
            boolean managed = mSyncService.isTypeManagedByPolicy(type);

            pref.setEnabled(!syncEverything && !mSyncService.isTypeManagedByCustodian(type));
            pref.setChecked(selectedSyncTypes.contains(type));

            pref.setManagedPreferenceDelegate(
                    new ChromeManagedPreferenceDelegate(getProfile()) {
                        @Override
                        public boolean isPreferenceControlledByPolicy(Preference preference) {
                            return managed;
                        }
                    });
        }
    }

    /**
     * Called upon completion of an activity started by a previous call to startActivityForResult()
     * via SyncSettingsUtils.openTrustedVaultKeyRetrievalDialog() or
     * SyncSettingsUtils.openTrustedVaultRecoverabilityDegradedDialog().
     * @param requestCode Request code of the requested intent.
     * @param resultCode Result code of the requested intent.
     * @param data The data returned by the intent.
     */
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        // Upon key retrieval completion, the keys in TrustedVaultClient could have changed. This is
        // done even if the user cancelled the flow (i.e. resultCode != RESULT_OK) because it's
        // harmless to issue a redundant notifyKeysChanged().
        if (requestCode == REQUEST_CODE_TRUSTED_VAULT_KEY_RETRIEVAL) {
            TrustedVaultClient.get().notifyKeysChanged();
        }
        if (requestCode == REQUEST_CODE_TRUSTED_VAULT_RECOVERABILITY_DEGRADED) {
            TrustedVaultClient.get().notifyRecoverabilityChanged();
        }
    }

    // SyncErrorCardPreferenceListener implementation:
    @Override
    public boolean shouldSuppressSyncSetupIncomplete() {
        return mIsFromSigninScreen;
    }

    @Override
    public void onSyncErrorCardPrimaryButtonClicked() {
        assert !mShouldReplaceSyncSettingsWithAccountSettings
                : "Should not show on account settings page";
        assert mSyncService.hasSyncConsent();

        onErrorCardClicked(mSyncErrorCardPreference.getSyncError());
    }

    @Override
    public void onSyncErrorCardSecondaryButtonClicked() {
        assert !mShouldReplaceSyncSettingsWithAccountSettings
                : "Should not show on account settings page";
        assert mSyncService.hasSyncConsent();

        assert mSyncErrorCardPreference.getSyncError() == SyncError.SYNC_SETUP_INCOMPLETE;
        IdentityServicesProvider.get()
                .getSigninManager(getProfile())
                .signOut(SignoutReason.USER_CLICKED_SIGNOUT_SETTINGS);
        getActivity().finish();
    }

    @Override
    public void onIdentityErrorCardButtonClicked(@SyncError int error) {
        assert mShouldReplaceSyncSettingsWithAccountSettings
                : "Should not show on sync settings page";
        assert error != SyncError.SYNC_SETUP_INCOMPLETE : "Invalid error";
        assert error != SyncError.OTHER_ERRORS : "Not an identity error";
        onErrorCardClicked(error);
    }

    private void onErrorCardClicked(@SyncError int error) {
        Profile profile = getProfile();
        final CoreAccountInfo primaryAccountInfo =
                IdentityServicesProvider.get()
                        .getIdentityManager(profile)
                        .getPrimaryAccountInfo(ConsentLevel.SIGNIN);
        assert primaryAccountInfo != null;

        switch (error) {
            case SyncError.AUTH_ERROR:
                AccountManagerFacadeProvider.getInstance()
                        .updateCredentials(
                                CoreAccountInfo.getAndroidAccountFrom(primaryAccountInfo),
                                getActivity(),
                                null);
                return;
            case SyncError.CLIENT_OUT_OF_DATE:
                // Opens the client in play store for update.
                Intent intent = new Intent(Intent.ACTION_VIEW);
                intent.setData(
                        Uri.parse(
                                "market://details?id="
                                        + ContextUtils.getApplicationContext().getPackageName()));
                startActivity(intent);
                return;
            case SyncError.OTHER_ERRORS:
                SignOutCoordinator.startSignOutFlow(
                        requireContext(),
                        profile,
                        getChildFragmentManager(),
                        ((ModalDialogManagerHolder) getActivity()).getModalDialogManager(),
                        mSnackbarManagerSupplier.get(),
                        profile.isChild()
                                ? SignoutReason.USER_CLICKED_REVOKE_SYNC_CONSENT_SETTINGS
                                : SignoutReason.USER_CLICKED_SIGNOUT_SETTINGS,
                        /* showConfirmDialog= */ false,
                        () -> {});
                return;
            case SyncError.PASSPHRASE_REQUIRED:
                displayPassphraseDialog();
                return;
            case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_EVERYTHING:
            case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_PASSWORDS:
                SyncSettingsUtils.openTrustedVaultKeyRetrievalDialog(
                        this, primaryAccountInfo, REQUEST_CODE_TRUSTED_VAULT_KEY_RETRIEVAL);
                return;
            case SyncError.TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_EVERYTHING:
            case SyncError.TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_PASSWORDS:
                SyncSettingsUtils.openTrustedVaultRecoverabilityDegradedDialog(
                        this,
                        primaryAccountInfo,
                        REQUEST_CODE_TRUSTED_VAULT_RECOVERABILITY_DEGRADED);
                return;
            case SyncError.SYNC_SETUP_INCOMPLETE:
                mSyncService.setSyncRequested();
                mSyncService.setInitialSyncFeatureSetupComplete(
                        SyncFirstSetupCompleteSource.ADVANCED_FLOW_INTERRUPTED_TURN_SYNC_ON);
                return;
            case SyncError.UPM_BACKEND_OUTDATED:
                PasswordManagerHelper.launchGmsUpdate(getContext());
                return;
            case SyncError.NO_ERROR:
            default:
        }
    }

    private void confirmSettings() {
        RecordUserAction.record("Signin_Signin_ConfirmAdvancedSyncSettings");
        mSyncService.setInitialSyncFeatureSetupComplete(
                SyncFirstSetupCompleteSource.ADVANCED_FLOW_CONFIRM);

        Profile profile = getProfile();
        UnifiedConsentServiceBridge.setUrlKeyedAnonymizedDataCollectionEnabled(
                profile, mUrlKeyedAnonymizedData.isChecked());
        UnifiedConsentServiceBridge.recordSyncSetupDataTypesHistogram(profile);
        // Settings will be applied when mSyncSetupInProgressHandle is released in onDestroy.
        getActivity().finish();
    }

    private void cancelSync() {
        RecordUserAction.record("Signin_Signin_CancelAdvancedSyncSettings");
        Profile profile = getProfile();
        SigninManager signinManager = IdentityServicesProvider.get().getSigninManager(profile);
        if (profile.isChild()) {
            // Child users cannot sign out, so we revoke the sync consent to return to the
            // previous state. This user won't have started syncing data yet, so there's need
            // need to wipe data before revoking consent.
            signinManager.revokeSyncConsent(
                    SignoutReason.USER_CLICKED_REVOKE_SYNC_CONSENT_SETTINGS, null, false);
        } else {
            signinManager.signOut(SignoutReason.USER_CLICKED_SIGNOUT_SETTINGS);
        }
        getActivity().finish();
    }

    private boolean isEeaChoiceCountry() {
        TemplateUrlService templateUrlService =
                TemplateUrlServiceFactory.getForProfile(getProfile());
        return templateUrlService.isEeaChoiceCountry();
    }
}