blob: 9e594d7ec075c1528d1f5e8530b07fb968e9c7b0 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.text.Spannable;
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.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
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.ApiCompatibilityUtils;
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.flags.ChromeFeatureList;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileAccountManagementMetrics;
import org.chromium.chrome.browser.settings.ChromeManagedPreferenceDelegate;
import org.chromium.chrome.browser.settings.SettingsActivity;
import org.chromium.chrome.browser.signin.IdentityServicesProvider;
import org.chromium.chrome.browser.signin.SignOutDialogFragment;
import org.chromium.chrome.browser.signin.SigninManager;
import org.chromium.chrome.browser.signin.SigninUtils;
import org.chromium.chrome.browser.signin.UnifiedConsentServiceBridge;
import org.chromium.chrome.browser.sync.ProfileSyncService;
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.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.ModelType;
import org.chromium.components.sync.PassphraseType;
import org.chromium.content_public.browser.UiThreadTaskTraits;
import org.chromium.ui.widget.ButtonCompat;
import java.util.HashSet;
import java.util.Set;
/**
* Settings fragment to customize Sync options (data types, encryption). Can be accessed from
* {@link SyncAndServicesSettings}.
*/
public class ManageSyncSettings extends PreferenceFragmentCompat
implements PassphraseDialogFragment.Listener, PassphraseCreationDialogFragment.Listener,
PassphraseTypeDialogFragment.Listener, Preference.OnPreferenceChangeListener,
ProfileSyncService.SyncStateChangedListener,
SettingsActivity.OnBackPressedListener,
SignOutDialogFragment.SignOutDialogListener,
SyncErrorCardPreference.SyncErrorCardPreferenceListener {
private static final String IS_FROM_SIGNIN_SCREEN = "ManageSyncSettings.isFromSigninScreen";
private static final String FRAGMENT_CANCEL_SYNC = "cancel_sync_dialog";
private static final String CLEAR_DATA_PROGRESS_DIALOG_TAG = "clear_data_progress";
private static final String SIGN_OUT_DIALOG_TAG = "sign_out_dialog_tag";
@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_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_MANAGE_DATA = "sync_manage_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 final ProfileSyncService mProfileSyncService = ProfileSyncService.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 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 mManageSyncData;
private PreferenceCategory mSearchAndBrowseCategory;
private ChromeSwitchPreference mUrlKeyedAnonymizedData;
private ProfileSyncService.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.manage_sync_title);
setHasOptionsMenu(true);
// TODO(https://crbug.com/1063982): Change accessibility text for Advanced Sync Flow.
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);
mSyncRecentTabs = (CheckBoxPreference) findPreference(PREF_SYNC_RECENT_TABS);
mSyncSettings = (CheckBoxPreference) findPreference(PREF_SYNC_SETTINGS);
mTurnOffSync = findPreference(PREF_TURN_OFF_SYNC);
mTurnOffSync.setOnPreferenceClickListener(
SyncSettingsUtils.toOnClickListener(this, this::onTurnOffSyncClicked));
if (ChromeFeatureList.isEnabled(ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY)
&& !mIsFromSigninScreen) {
mTurnOffSync.setVisible(true);
findPreference(PREF_ADVANCED_CATEGORY).setVisible(true);
}
mGoogleActivityControls = findPreference(PREF_GOOGLE_ACTIVITY_CONTROLS);
mSyncEncryption = findPreference(PREF_ENCRYPTION);
mSyncEncryption.setOnPreferenceClickListener(
SyncSettingsUtils.toOnClickListener(this, this::onSyncEncryptionClicked));
mManageSyncData = findPreference(PREF_SYNC_MANAGE_DATA);
mManageSyncData.setOnPreferenceClickListener(SyncSettingsUtils.toOnClickListener(
this, () -> SyncSettingsUtils.openSyncDashboard(getActivity())));
mSyncTypePreferences =
new CheckBoxPreference[] {mSyncAutofill, mSyncBookmarks, mSyncPaymentsIntegration,
mSyncHistory, mSyncPasswords, mSyncRecentTabs, mSyncSettings};
for (CheckBoxPreference type : mSyncTypePreferences) {
type.setOnPreferenceChangeListener(this);
}
Profile profile = Profile.getLastUsedRegularProfile();
if (profile.isChild()) {
mGoogleActivityControls.setSummary(
R.string.sign_in_google_activity_controls_summary_child_account);
}
// Prevent sync settings changes from taking effect until the user leaves this screen.
mSyncSetupInProgressHandle = mProfileSyncService.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);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (ChromeFeatureList.isEnabled(ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY)
&& item.getItemId() == android.R.id.home) {
if (!mIsFromSigninScreen) return false; // Let Settings activity handle it.
showCancelSyncDialog();
return true;
} else 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;
}
return false;
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY)
|| !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.sync_and_services_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();
mProfileSyncService.addSyncStateChangedListener(this);
}
@Override
public void onStop() {
super.onStop();
mProfileSyncService.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
// updateSyncStateFromSelectedModelTypes so it gets the updated state from isChecked().
PostTask.postTask(UiThreadTaskTraits.DEFAULT, this::updateSyncStateFromSelectedModelTypes);
return true;
}
/**
* ProfileSyncService.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 ProfileSyncService.setChosenDataTypes, postpone the
// update to let updateSyncStateFromSelectedModelTypes finish saving the state.
PostTask.postTask(UiThreadTaskTraits.DEFAULT, this::updateSyncPreferences);
}
/**
* Gets the current state of data types from {@link ProfileSyncService} and updates UI elements
* from this state.
*/
private void updateSyncPreferences() {
String signedInAccountName = CoreAccountInfo.getEmailFrom(
IdentityServicesProvider.get()
.getIdentityManager(Profile.getLastUsedRegularProfile())
.getPrimaryAccountInfo(ConsentLevel.SYNC));
if (signedInAccountName == null) {
// May happen if account is removed from the device while this screen is shown.
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 ProfileSyncService}
* and {@link PersonalDataManager}.
*/
private void updateSyncStateFromSelectedModelTypes() {
Set<Integer> selectedModelTypes = getSelectedModelTypes();
mProfileSyncService.setChosenDataTypes(mSyncEverything.isChecked(), selectedModelTypes);
// 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()));
if (ChromeFeatureList.isEnabled(ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY)) {
boolean atLeastOneDataTypeEnabled =
mSyncEverything.isChecked() || selectedModelTypes.size() > 0;
if (mProfileSyncService.isSyncRequested() && !atLeastOneDataTypeEnabled) {
mProfileSyncService.requestStop();
} else if (!mProfileSyncService.isSyncRequested() && atLeastOneDataTypeEnabled) {
mProfileSyncService.requestStart();
}
}
// Some calls to setChosenDataTypes 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 = mProfileSyncService.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 (mProfileSyncService.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);
mSyncEncryption.setSummary(mProfileSyncService.isEncryptEverythingEnabled()
? R.string.sync_error_card_title
: R.string.sync_passwords_error_card_title);
return;
}
if (!mProfileSyncService.isPassphraseRequiredForPreferredDataTypes()) {
closeDialogIfOpen(FRAGMENT_ENTER_PASSPHRASE);
}
if (mProfileSyncService.isPassphraseRequiredForPreferredDataTypes() && isAdded()) {
mSyncEncryption.setSummary(
errorSummary(getString(R.string.sync_need_passphrase), getActivity()));
}
}
/** Applies a span to the given string to give it an error color. */
private static Spannable errorSummary(String string, Context context) {
SpannableString summary = new SpannableString(string);
summary.setSpan(new ForegroundColorSpan(ApiCompatibilityUtils.getColor(
context.getResources(), R.color.input_underline_error_color)),
0, summary.length(), 0);
return summary;
}
private Set<Integer> getSelectedModelTypes() {
Set<Integer> types = new HashSet<>();
if (mSyncAutofill.isChecked()) types.add(ModelType.AUTOFILL);
if (mSyncBookmarks.isChecked()) types.add(ModelType.BOOKMARKS);
if (mSyncHistory.isChecked()) types.add(ModelType.TYPED_URLS);
if (mSyncPasswords.isChecked()) types.add(ModelType.PASSWORDS);
if (mSyncRecentTabs.isChecked()) types.add(ModelType.PROXY_TABS);
if (mSyncSettings.isChecked()) types.add(ModelType.PREFERENCES);
return types;
}
private void displayPassphraseTypeDialog() {
FragmentTransaction ft = getFragmentManager().beginTransaction();
PassphraseTypeDialogFragment dialog =
PassphraseTypeDialogFragment.create(mProfileSyncService.getPassphraseType(),
mProfileSyncService.getExplicitPassphraseTime(),
mProfileSyncService.isEncryptEverythingAllowed());
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() || !mProfileSyncService.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 (!mProfileSyncService.isEngineInitialized()
|| !mProfileSyncService.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 (!mProfileSyncService.isEngineInitialized()) {
// If the engine was shut down since the dialog was opened, do nothing.
return;
}
mProfileSyncService.enableEncryptEverything();
mProfileSyncService.setEncryptionPassphrase(passphrase);
// Save the current state of data types - this tells the sync engine to
// apply our encryption configuration changes.
updateSyncStateFromSelectedModelTypes();
}
/** Callback for PassphraseTypeDialogFragment.Listener */
@Override
public void onPassphraseTypeSelected(@PassphraseType int type) {
if (!mProfileSyncService.isEngineInitialized()) {
// If the engine was shut down since the dialog was opened, do nothing.
return;
}
boolean isAllDataEncrypted = mProfileSyncService.isEncryptEverythingEnabled();
boolean isUsingSecondaryPassphrase = mProfileSyncService.isUsingSecondaryPassphrase();
// The passphrase type should only ever be selected if the account doesn't have
// full encryption enabled. Otherwise both options should be disabled.
assert !isAllDataEncrypted;
assert !isUsingSecondaryPassphrase;
displayCustomPassphraseDialog();
}
private void onGoogleActivityControlsClicked(String signedInAccountName) {
AppHooks.get().createGoogleActivityController().openWebAndAppActivitySettings(
getActivity(), signedInAccountName);
RecordUserAction.record("Signin_AccountSettings_GoogleActivityControlsClicked");
}
private void onTurnOffSyncClicked() {
if (!IdentityServicesProvider.get()
.getIdentityManager(Profile.getLastUsedRegularProfile())
.hasPrimaryAccount()) {
return;
}
SigninUtils.logEvent(ProfileAccountManagementMetrics.TOGGLE_SIGNOUT,
GAIAServiceType.GAIA_SERVICE_TYPE_NONE);
SignOutDialogFragment signOutFragment =
SignOutDialogFragment.create(GAIAServiceType.GAIA_SERVICE_TYPE_NONE);
signOutFragment.setTargetFragment(this, 0);
signOutFragment.show(getParentFragmentManager(), SIGN_OUT_DIALOG_TAG);
}
private void onSyncEncryptionClicked() {
if (!mProfileSyncService.isEngineInitialized()) return;
if (mProfileSyncService.isPassphraseRequiredForPreferredDataTypes()) {
displayPassphraseDialog();
} else if (mProfileSyncService.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 ProfileSyncService} and updates the UI.
*/
private void updateDataTypeState() {
boolean syncEverything = mProfileSyncService.hasKeepEverythingSynced();
mSyncEverything.setChecked(syncEverything);
if (syncEverything) {
for (CheckBoxPreference pref : mSyncTypePreferences) {
pref.setChecked(true);
pref.setEnabled(false);
}
return;
}
Set<Integer> syncTypes = mProfileSyncService.getChosenDataTypes();
mSyncAutofill.setChecked(syncTypes.contains(ModelType.AUTOFILL));
mSyncAutofill.setEnabled(true);
mSyncBookmarks.setChecked(syncTypes.contains(ModelType.BOOKMARKS));
mSyncBookmarks.setEnabled(true);
mSyncHistory.setChecked(syncTypes.contains(ModelType.TYPED_URLS));
mSyncHistory.setEnabled(true);
mSyncPasswords.setChecked(syncTypes.contains(ModelType.PASSWORDS));
mSyncPasswords.setEnabled(true);
mSyncRecentTabs.setChecked(syncTypes.contains(ModelType.PROXY_TABS));
mSyncRecentTabs.setEnabled(true);
mSyncSettings.setChecked(syncTypes.contains(ModelType.PREFERENCES));
mSyncSettings.setEnabled(true);
// Payments integration requires AUTOFILL model type
boolean syncAutofill = syncTypes.contains(ModelType.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().
* @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();
}
}
@Override
public boolean onBackPressed() {
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY)
|| !mIsFromSigninScreen) {
return false; // Let parent activity handle it.
}
showCancelSyncDialog();
return true;
}
// 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.ANDROID_SYNC_DISABLED:
IntentUtils.safeStartActivity(
getActivity(), new Intent(Settings.ACTION_SYNC_SETTINGS));
return;
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:
SignOutDialogFragment signOutFragment =
SignOutDialogFragment.create(GAIAServiceType.GAIA_SERVICE_TYPE_NONE);
signOutFragment.setTargetFragment(this, 0);
signOutFragment.show(getParentFragmentManager(), SIGN_OUT_DIALOG_TAG);
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.SYNC_SETUP_INCOMPLETE:
mProfileSyncService.requestStart();
mProfileSyncService.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 showCancelSyncDialog() {
RecordUserAction.record("Signin_Signin_BackOnAdvancedSyncSettings");
CancelSyncDialog dialog = new CancelSyncDialog();
dialog.setTargetFragment(this, 0);
dialog.show(getFragmentManager(), FRAGMENT_CANCEL_SYNC);
}
private void confirmSettings() {
RecordUserAction.record("Signin_Signin_ConfirmAdvancedSyncSettings");
ProfileSyncService.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");
IdentityServicesProvider.get()
.getSigninManager(Profile.getLastUsedRegularProfile())
.signOut(org.chromium.components.signin.metrics.SignoutReason
.USER_CLICKED_SIGNOUT_SETTINGS);
getActivity().finish();
}
/**
* The dialog that offers the user to cancel sync. Only shown when
* {@link ManageSyncSettings} is opened from the sign-in screen. Shown when the user
* tries to close the settings page without confirming settings.
*/
public static class CancelSyncDialog extends DialogFragment {
public CancelSyncDialog() {
// Fragment must have an empty public constructor
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity(), R.style.Theme_Chromium_AlertDialog)
.setTitle(R.string.cancel_sync_dialog_title)
.setMessage(R.string.cancel_sync_dialog_message)
.setNegativeButton(R.string.back, (dialog, which) -> onBackPressed())
.setPositiveButton(
R.string.cancel_sync_button, (dialog, which) -> onCancelSyncPressed())
.create();
}
private void onBackPressed() {
RecordUserAction.record("Signin_Signin_CancelCancelAdvancedSyncSettings");
dismiss();
}
public void onCancelSyncPressed() {
RecordUserAction.record("Signin_Signin_ConfirmCancelAdvancedSyncSettings");
ManageSyncSettings fragment = (ManageSyncSettings) getTargetFragment();
fragment.cancelSync();
}
}
// SignOutDialogListener implementation:
@Override
public void onSignOutClicked(boolean forceWipeUserData) {
// 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().hasPrimaryAccount()) return;
final DialogFragment clearDataProgressDialog = new ClearDataProgressDialog();
IdentityServicesProvider.get().getSigninManager().signOut(
SignoutReason.USER_CLICKED_SIGNOUT_SETTINGS, new SigninManager.SignOutCallback() {
@Override
public void preWipeData() {
clearDataProgressDialog.show(
getChildFragmentManager(), CLEAR_DATA_PROGRESS_DIALOG_TAG);
}
@Override
public void signOutComplete() {
if (clearDataProgressDialog.isAdded()) {
clearDataProgressDialog.dismissAllowingStateLoss();
}
}
}, forceWipeUserData);
}
}