| // 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.preference.CheckBoxPreference; |
| import androidx.preference.Preference; |
| import androidx.preference.PreferenceCategory; |
| import androidx.preference.PreferenceFragmentCompat; |
| |
| import org.chromium.base.ContextUtils; |
| import org.chromium.base.IntentUtils; |
| import org.chromium.base.metrics.RecordUserAction; |
| import org.chromium.base.task.PostTask; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.AppHooks; |
| import org.chromium.chrome.browser.SyncFirstSetupCompleteSource; |
| import org.chromium.chrome.browser.autofill.PersonalDataManager; |
| import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncherImpl; |
| import org.chromium.chrome.browser.profiles.Profile; |
| import org.chromium.chrome.browser.settings.ChromeManagedPreferenceDelegate; |
| import org.chromium.chrome.browser.settings.SettingsActivity; |
| import org.chromium.chrome.browser.signin.services.IdentityServicesProvider; |
| import org.chromium.chrome.browser.signin.services.SigninManager; |
| import org.chromium.chrome.browser.signin.services.UnifiedConsentServiceBridge; |
| import org.chromium.chrome.browser.sync.SyncService; |
| 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.signin.SignOutDialogCoordinator; |
| import org.chromium.chrome.browser.ui.signin.SignOutDialogCoordinator.Listener; |
| import org.chromium.components.browser_ui.settings.ChromeSwitchPreference; |
| import org.chromium.components.browser_ui.settings.SettingsUtils; |
| import org.chromium.components.signin.AccountManagerFacadeProvider; |
| import org.chromium.components.signin.GAIAServiceType; |
| import org.chromium.components.signin.base.CoreAccountInfo; |
| import org.chromium.components.signin.identitymanager.ConsentLevel; |
| import org.chromium.components.signin.metrics.SignoutReason; |
| import org.chromium.components.sync.UserSelectableType; |
| import org.chromium.content_public.browser.UiThreadTaskTraits; |
| import org.chromium.ui.modaldialog.ModalDialogManagerHolder; |
| import org.chromium.ui.widget.ButtonCompat; |
| |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| /** |
| * 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 PreferenceFragmentCompat |
| implements PassphraseDialogFragment.Listener, PassphraseCreationDialogFragment.Listener, |
| PassphraseTypeDialogFragment.Listener, Preference.OnPreferenceChangeListener, |
| SyncService.SyncStateChangedListener, SettingsActivity.OnBackPressedListener, |
| Listener, SyncErrorCardPreference.SyncErrorCardPreferenceListener { |
| private static final String IS_FROM_SIGNIN_SCREEN = "ManageSyncSettings.isFromSigninScreen"; |
| private static final String CLEAR_DATA_PROGRESS_DIALOG_TAG = "clear_data_progress"; |
| |
| @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 |
| public static final String PREF_SYNC_ERROR_CARD_PREFERENCE = "sync_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_TURN_OFF_SYNC = "turn_off_sync"; |
| private static final String PREF_ADVANCED_CATEGORY = "advanced_category"; |
| @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_SYNC_REVIEW_DATA = "sync_review_data"; |
| @VisibleForTesting |
| public static final String PREF_SEARCH_AND_BROWSE_CATEGORY = "search_and_browse_category"; |
| |
| private static final String PREF_URL_KEYED_ANONYMIZED_DATA = "url_keyed_anonymized_data"; |
| |
| private static final int REQUEST_CODE_TRUSTED_VAULT_KEY_RETRIEVAL = 1; |
| private static final int REQUEST_CODE_TRUSTED_VAULT_RECOVERABILITY_DEGRADED = 2; |
| |
| private final SyncService mSyncService = SyncService.get(); |
| |
| private boolean mIsFromSigninScreen; |
| |
| private SyncErrorCardPreference mSyncErrorCardPreference; |
| private PreferenceCategory mSyncingCategory; |
| |
| private ChromeSwitchPreference mSyncEverything; |
| private CheckBoxPreference mSyncAutofill; |
| private CheckBoxPreference mSyncBookmarks; |
| private CheckBoxPreference mSyncPaymentsIntegration; |
| private CheckBoxPreference mSyncHistory; |
| private CheckBoxPreference mSyncPasswords; |
| private CheckBoxPreference mSyncReadingList; |
| private CheckBoxPreference mSyncRecentTabs; |
| private CheckBoxPreference mSyncSettings; |
| // Contains preferences for all sync data types. |
| private CheckBoxPreference[] mSyncTypePreferences; |
| |
| private Preference mTurnOffSync; |
| private Preference mGoogleActivityControls; |
| private Preference mSyncEncryption; |
| private Preference mReviewSyncData; |
| |
| private PreferenceCategory mSearchAndBrowseCategory; |
| private ChromeSwitchPreference mUrlKeyedAnonymizedData; |
| |
| private SyncService.SyncSetupInProgressHandle mSyncSetupInProgressHandle; |
| |
| /** |
| * 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) { |
| mIsFromSigninScreen = |
| IntentUtils.safeGetBoolean(getArguments(), IS_FROM_SIGNIN_SCREEN, false); |
| |
| getActivity().setTitle(R.string.sync_category_title); |
| setHasOptionsMenu(true); |
| |
| SettingsUtils.addPreferencesFromResource(this, R.xml.manage_sync_preferences); |
| |
| mSyncErrorCardPreference = |
| (SyncErrorCardPreference) findPreference(PREF_SYNC_ERROR_CARD_PREFERENCE); |
| mSyncErrorCardPreference.setSyncErrorCardPreferenceListener(this); |
| |
| mSyncingCategory = (PreferenceCategory) findPreference(PREF_SYNCING_CATEGORY); |
| |
| mSyncEverything = (ChromeSwitchPreference) findPreference(PREF_SYNC_EVERYTHING); |
| mSyncEverything.setOnPreferenceChangeListener(this); |
| |
| mSyncAutofill = (CheckBoxPreference) findPreference(PREF_SYNC_AUTOFILL); |
| mSyncBookmarks = (CheckBoxPreference) findPreference(PREF_SYNC_BOOKMARKS); |
| mSyncPaymentsIntegration = |
| (CheckBoxPreference) findPreference(PREF_SYNC_PAYMENTS_INTEGRATION); |
| mSyncHistory = (CheckBoxPreference) findPreference(PREF_SYNC_HISTORY); |
| mSyncPasswords = (CheckBoxPreference) findPreference(PREF_SYNC_PASSWORDS); |
| mSyncReadingList = (CheckBoxPreference) findPreference(PREF_SYNC_READING_LIST); |
| mSyncRecentTabs = (CheckBoxPreference) findPreference(PREF_SYNC_RECENT_TABS); |
| mSyncSettings = (CheckBoxPreference) findPreference(PREF_SYNC_SETTINGS); |
| |
| mTurnOffSync = findPreference(PREF_TURN_OFF_SYNC); |
| |
| Profile profile = Profile.getLastUsedRegularProfile(); |
| if (!mIsFromSigninScreen) { |
| mTurnOffSync.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. |
| mTurnOffSync.setIcon(R.drawable.ic_signout_40dp); |
| mTurnOffSync.setTitle(R.string.sign_out_and_turn_off_sync); |
| mTurnOffSync.setOnPreferenceClickListener(SyncSettingsUtils.toOnClickListener( |
| this, this::onSignOutAndTurnOffSyncClicked)); |
| } else { |
| // Child users are force signed-in, so have an option which only turns off sync. |
| mTurnOffSync.setIcon(R.drawable.ic_turn_off_sync_48dp); |
| mTurnOffSync.setTitle(R.string.turn_off_sync); |
| mTurnOffSync.setOnPreferenceClickListener( |
| SyncSettingsUtils.toOnClickListener(this, this::onTurnOffSyncClicked)); |
| } |
| |
| findPreference(PREF_ADVANCED_CATEGORY).setVisible(true); |
| } |
| |
| mGoogleActivityControls = findPreference(PREF_GOOGLE_ACTIVITY_CONTROLS); |
| mSyncEncryption = findPreference(PREF_ENCRYPTION); |
| mSyncEncryption.setOnPreferenceClickListener( |
| SyncSettingsUtils.toOnClickListener(this, this::onSyncEncryptionClicked)); |
| mReviewSyncData = findPreference(PREF_SYNC_REVIEW_DATA); |
| mReviewSyncData.setOnPreferenceClickListener(SyncSettingsUtils.toOnClickListener( |
| this, () -> SyncSettingsUtils.openSyncDashboard(getActivity()))); |
| |
| mSyncTypePreferences = new CheckBoxPreference[] {mSyncAutofill, mSyncBookmarks, |
| mSyncPaymentsIntegration, mSyncHistory, mSyncPasswords, mSyncReadingList, |
| mSyncRecentTabs, mSyncSettings}; |
| for (CheckBoxPreference type : mSyncTypePreferences) { |
| type.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); |
| mUrlKeyedAnonymizedData.setChecked( |
| UnifiedConsentServiceBridge.isUrlKeyedAnonymizedDataCollectionEnabled(profile)); |
| mUrlKeyedAnonymizedData.setOnPreferenceChangeListener((preference, newValue) -> { |
| UnifiedConsentServiceBridge.setUrlKeyedAnonymizedDataCollectionEnabled( |
| profile, (boolean) newValue); |
| return true; |
| }); |
| mUrlKeyedAnonymizedData.setManagedPreferenceDelegate(( |
| ChromeManagedPreferenceDelegate) (preference |
| -> UnifiedConsentServiceBridge.isUrlKeyedAnonymizedDataCollectionManaged(profile))); |
| } |
| |
| @Override |
| public void onDestroy() { |
| super.onDestroy(); |
| 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) { |
| HelpAndFeedbackLauncherImpl.getInstance().show(getActivity(), |
| getString(R.string.help_context_sync_and_services), |
| Profile.getLastUsedRegularProfile(), null); |
| return true; |
| } |
| if (item.getItemId() == android.R.id.home) { |
| return onBackPressed(); |
| } |
| 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); |
| } |
| |
| @Override |
| public void onStop() { |
| super.onStop(); |
| mSyncService.removeSyncStateChangedListener(this); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| updateSyncPreferences(); |
| } |
| |
| @Override |
| public boolean onPreferenceChange(Preference preference, Object newValue) { |
| // A change to Preference state hasn't been applied yet. Defer |
| // updateSyncStateFromSelectedTypes so it gets the updated state from |
| // isChecked(). |
| PostTask.postTask(UiThreadTaskTraits.DEFAULT, this::updateSyncStateFromSelectedTypes); |
| return true; |
| } |
| |
| /** |
| * SyncService.SyncStateChangedListener implementation, listens to sync state changes. |
| * |
| * 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(UiThreadTaskTraits.DEFAULT, this::updateSyncPreferences); |
| } |
| |
| /** |
| * 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(Profile.getLastUsedRegularProfile()) |
| .getPrimaryAccountInfo(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} |
| * and {@link PersonalDataManager}. |
| */ |
| private void updateSyncStateFromSelectedTypes() { |
| mSyncService.setSelectedTypes(mSyncEverything.isChecked(), getUserSelectedTypes()); |
| // Note: mSyncPaymentsIntegration should be checked if mSyncEverything is checked, but if |
| // mSyncEverything was just enabled, then that state may not have propagated to |
| // mSyncPaymentsIntegration yet. See crbug.com/972863. |
| PersonalDataManager.setPaymentsIntegrationEnabled(mSyncEverything.isChecked() |
| || (mSyncPaymentsIntegration.isChecked() && mSyncAutofill.isChecked())); |
| |
| // Some calls to setSelectedTypes don't trigger syncStateChanged, so schedule update here. |
| PostTask.postTask(UiThreadTaskTraits.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); |
| 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() { |
| Set<Integer> types = new HashSet<>(); |
| if (mSyncAutofill.isChecked()) types.add(UserSelectableType.AUTOFILL); |
| if (mSyncBookmarks.isChecked()) types.add(UserSelectableType.BOOKMARKS); |
| if (mSyncHistory.isChecked()) types.add(UserSelectableType.HISTORY); |
| if (mSyncPasswords.isChecked()) types.add(UserSelectableType.PASSWORDS); |
| if (mSyncReadingList.isChecked()) types.add(UserSelectableType.READING_LIST); |
| if (mSyncRecentTabs.isChecked()) types.add(UserSelectableType.TABS); |
| if (mSyncSettings.isChecked()) types.add(UserSelectableType.PREFERENCES); |
| return types; |
| } |
| |
| 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(); |
| } |
| |
| private void onGoogleActivityControlsClicked(String signedInAccountName) { |
| AppHooks.get().createGoogleActivityController().openWebAndAppActivitySettings( |
| getActivity(), signedInAccountName); |
| RecordUserAction.record("Signin_AccountSettings_GoogleActivityControlsClicked"); |
| } |
| |
| private void onSignOutAndTurnOffSyncClicked() { |
| if (!IdentityServicesProvider.get() |
| .getIdentityManager(Profile.getLastUsedRegularProfile()) |
| .hasPrimaryAccount(ConsentLevel.SYNC)) { |
| return; |
| } |
| SignOutDialogCoordinator.show(requireContext(), |
| ((ModalDialogManagerHolder) getActivity()).getModalDialogManager(), this, |
| SignOutDialogCoordinator.ActionType.CLEAR_PRIMARY_ACCOUNT, |
| GAIAServiceType.GAIA_SERVICE_TYPE_NONE); |
| } |
| |
| private void onTurnOffSyncClicked() { |
| if (!IdentityServicesProvider.get() |
| .getIdentityManager(Profile.getLastUsedRegularProfile()) |
| .hasPrimaryAccount(ConsentLevel.SYNC)) { |
| return; |
| } |
| SignOutDialogCoordinator.show(requireContext(), |
| ((ModalDialogManagerHolder) getActivity()).getModalDialogManager(), this, |
| SignOutDialogCoordinator.ActionType.REVOKE_SYNC_CONSENT, |
| GAIAServiceType.GAIA_SERVICE_TYPE_NONE); |
| } |
| |
| private void onSyncEncryptionClicked() { |
| if (!mSyncService.isEngineInitialized()) return; |
| |
| if (mSyncService.isPassphraseRequiredForPreferredDataTypes()) { |
| displayPassphraseDialog(); |
| } else if (mSyncService.isTrustedVaultKeyRequired()) { |
| CoreAccountInfo primaryAccountInfo = |
| IdentityServicesProvider.get() |
| .getIdentityManager(Profile.getLastUsedRegularProfile()) |
| .getPrimaryAccountInfo(ConsentLevel.SYNC); |
| 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() { |
| boolean syncEverything = mSyncService.hasKeepEverythingSynced(); |
| mSyncEverything.setChecked(syncEverything); |
| if (syncEverything) { |
| for (CheckBoxPreference pref : mSyncTypePreferences) { |
| pref.setChecked(true); |
| pref.setEnabled(false); |
| } |
| return; |
| } |
| |
| Set<Integer> syncTypes = mSyncService.getSelectedTypes(); |
| mSyncAutofill.setChecked(syncTypes.contains(UserSelectableType.AUTOFILL)); |
| mSyncAutofill.setEnabled(true); |
| mSyncBookmarks.setChecked(syncTypes.contains(UserSelectableType.BOOKMARKS)); |
| mSyncBookmarks.setEnabled(true); |
| mSyncHistory.setChecked(syncTypes.contains(UserSelectableType.HISTORY)); |
| mSyncHistory.setEnabled(true); |
| mSyncPasswords.setChecked(syncTypes.contains(UserSelectableType.PASSWORDS)); |
| mSyncPasswords.setEnabled(true); |
| mSyncReadingList.setChecked(syncTypes.contains(UserSelectableType.READING_LIST)); |
| mSyncReadingList.setEnabled(true); |
| mSyncRecentTabs.setChecked(syncTypes.contains(UserSelectableType.TABS)); |
| mSyncRecentTabs.setEnabled(true); |
| mSyncSettings.setChecked(syncTypes.contains(UserSelectableType.PREFERENCES)); |
| mSyncSettings.setEnabled(true); |
| |
| // Payments integration requires AUTOFILL user selectable type |
| boolean syncAutofill = syncTypes.contains(UserSelectableType.AUTOFILL); |
| mSyncPaymentsIntegration.setChecked( |
| syncAutofill && PersonalDataManager.isPaymentsIntegrationEnabled()); |
| mSyncPaymentsIntegration.setEnabled(syncAutofill); |
| } |
| |
| /** |
| * 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(); |
| } |
| } |
| |
| @Override |
| public boolean onBackPressed() { |
| if (mIsFromSigninScreen) { |
| RecordUserAction.record("Signin_Signin_BackOnAdvancedSyncSettings"); |
| } |
| return false; |
| } |
| |
| // SyncErrorCardPreferenceListener implementation: |
| @Override |
| public boolean shouldSuppressSyncSetupIncomplete() { |
| return mIsFromSigninScreen; |
| } |
| |
| @Override |
| public void onSyncErrorCardPrimaryButtonClicked() { |
| @SyncError |
| int syncError = mSyncErrorCardPreference.getSyncError(); |
| Profile profile = Profile.getLastUsedRegularProfile(); |
| final CoreAccountInfo primaryAccountInfo = |
| IdentityServicesProvider.get().getIdentityManager(profile).getPrimaryAccountInfo( |
| ConsentLevel.SYNC); |
| assert primaryAccountInfo != null; |
| |
| switch (syncError) { |
| 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: |
| SignOutDialogCoordinator.show(requireContext(), |
| ((ModalDialogManagerHolder) getActivity()).getModalDialogManager(), this, |
| profile.isChild() |
| ? SignOutDialogCoordinator.ActionType.REVOKE_SYNC_CONSENT |
| : SignOutDialogCoordinator.ActionType.CLEAR_PRIMARY_ACCOUNT, |
| GAIAServiceType.GAIA_SERVICE_TYPE_NONE); |
| 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(true); |
| mSyncService.setFirstSetupComplete( |
| SyncFirstSetupCompleteSource.ADVANCED_FLOW_INTERRUPTED_TURN_SYNC_ON); |
| return; |
| case SyncError.NO_ERROR: |
| default: |
| return; |
| } |
| } |
| |
| @Override |
| public void onSyncErrorCardSecondaryButtonClicked() { |
| assert mSyncErrorCardPreference.getSyncError() == SyncError.SYNC_SETUP_INCOMPLETE; |
| IdentityServicesProvider.get() |
| .getSigninManager(Profile.getLastUsedRegularProfile()) |
| .signOut(SignoutReason.USER_CLICKED_SIGNOUT_SETTINGS); |
| getActivity().finish(); |
| } |
| |
| private void confirmSettings() { |
| RecordUserAction.record("Signin_Signin_ConfirmAdvancedSyncSettings"); |
| SyncService.get().setFirstSetupComplete(SyncFirstSetupCompleteSource.ADVANCED_FLOW_CONFIRM); |
| UnifiedConsentServiceBridge.recordSyncSetupDataTypesHistogram( |
| Profile.getLastUsedRegularProfile()); |
| // Settings will be applied when mSyncSetupInProgressHandle is released in onDestroy. |
| getActivity().finish(); |
| } |
| |
| private void cancelSync() { |
| RecordUserAction.record("Signin_Signin_CancelAdvancedSyncSettings"); |
| Profile profile = Profile.getLastUsedRegularProfile(); |
| 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(); |
| } |
| |
| // SignOutDialogListener implementation: |
| @Override |
| public void onSignOutClicked(boolean forceWipeUserData) { |
| final Profile profile = Profile.getLastUsedRegularProfile(); |
| // In case sign-out happened while the dialog was displayed, we guard the sign out so |
| // we do not hit a native crash. |
| if (!IdentityServicesProvider.get().getIdentityManager(profile).hasPrimaryAccount( |
| ConsentLevel.SYNC)) { |
| return; |
| } |
| |
| final DialogFragment clearDataProgressDialog = new ClearDataProgressDialog(); |
| SigninManager.SignOutCallback dataWipeCallback = new SigninManager.SignOutCallback() { |
| @Override |
| public void preWipeData() { |
| clearDataProgressDialog.show( |
| getChildFragmentManager(), CLEAR_DATA_PROGRESS_DIALOG_TAG); |
| } |
| |
| @Override |
| public void signOutComplete() { |
| // TODO(crbug.com/1313527): deal with both the following edge cases (currently |
| // this code only deals with 1): |
| // |
| // 1) The parent activity showing the dialog is dismissed before signout completes. |
| // 2) The signout completes before the dialog is added. |
| if (clearDataProgressDialog.isAdded()) { |
| clearDataProgressDialog.dismissAllowingStateLoss(); |
| } |
| } |
| }; |
| |
| if (profile.isChild()) { |
| // Call through to PrimaryAccountMutatorImpl::RevokeSyncConsent(). |
| IdentityServicesProvider.get().getSigninManager(profile).revokeSyncConsent( |
| SignoutReason.USER_CLICKED_REVOKE_SYNC_CONSENT_SETTINGS, dataWipeCallback, |
| forceWipeUserData); |
| } else { |
| IdentityServicesProvider.get().getSigninManager(profile).signOut( |
| SignoutReason.USER_CLICKED_SIGNOUT_SETTINGS, dataWipeCallback, |
| forceWipeUserData); |
| } |
| } |
| } |