// Copyright 2015 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.ui;
import android.accounts.Account;
import android.app.DialogFragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceFragment;
import android.preference.SwitchPreference;
import android.provider.Settings;
import android.support.annotation.IntDef;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.BuildInfo;
import org.chromium.base.Callback;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.autofill.PersonalDataManager;
import org.chromium.chrome.browser.childaccounts.ChildAccountService;
import org.chromium.chrome.browser.invalidation.InvalidationController;
import org.chromium.chrome.browser.preferences.ChromeSwitchPreference;
import org.chromium.chrome.browser.preferences.SyncedAccountPreference;
import org.chromium.chrome.browser.signin.SigninManager;
import org.chromium.chrome.browser.sync.GoogleServiceAuthError;
import org.chromium.chrome.browser.sync.ProfileSyncService;
import org.chromium.chrome.browser.sync.SyncAccountSwitcher;
import org.chromium.components.signin.AccountManagerHelper;
import org.chromium.components.signin.ChromeSigninController;
import org.chromium.components.sync.AndroidSyncSettings;
import org.chromium.components.sync.ModelType;
import org.chromium.components.sync.PassphraseType;
import org.chromium.components.sync.ProtocolErrorClientAction;
import org.chromium.components.sync.StopSource;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashSet;
import java.util.Set;
/**
* Settings fragment to customize Sync options (data types, encryption).
*/
public class SyncCustomizationFragment extends PreferenceFragment
implements PassphraseDialogFragment.Listener, PassphraseCreationDialogFragment.Listener,
PassphraseTypeDialogFragment.Listener, OnPreferenceClickListener,
OnPreferenceChangeListener, ProfileSyncService.SyncStateChangedListener {
private static final String TAG = "SyncCustomizationFragment";
@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 PREFERENCE_SYNC_EVERYTHING = "sync_everything";
@VisibleForTesting
public static final String PREFERENCE_SYNC_AUTOFILL = "sync_autofill";
@VisibleForTesting
public static final String PREFERENCE_SYNC_BOOKMARKS = "sync_bookmarks";
@VisibleForTesting
public static final String PREFERENCE_SYNC_OMNIBOX = "sync_omnibox";
@VisibleForTesting
public static final String PREFERENCE_SYNC_PASSWORDS = "sync_passwords";
@VisibleForTesting
public static final String PREFERENCE_SYNC_RECENT_TABS = "sync_recent_tabs";
@VisibleForTesting
public static final String PREFERENCE_SYNC_SETTINGS = "sync_settings";
@VisibleForTesting
public static final String PREFERENCE_PAYMENTS_INTEGRATION = "payments_integration";
@VisibleForTesting
public static final String PREFERENCE_ENCRYPTION = "encryption";
@VisibleForTesting
public static final String PREF_SYNC_SWITCH = "sync_switch";
@VisibleForTesting
public static final String PREFERENCE_SYNC_MANAGE_DATA = "sync_manage_data";
@VisibleForTesting
public static final String PREFERENCE_SYNC_ACCOUNT_LIST = "synced_account";
public static final String PREFERENCE_SYNC_ERROR_CARD = "sync_error_card";
@Retention(RetentionPolicy.SOURCE)
@IntDef({SYNC_NO_ERROR, SYNC_ANDROID_SYNC_DISABLED, SYNC_AUTH_ERROR, SYNC_PASSPHRASE_REQUIRED,
SYNC_CLIENT_OUT_OF_DATE, SYNC_OTHER_ERRORS})
private @interface SyncError {}
private static final int SYNC_NO_ERROR = -1;
private static final int SYNC_ANDROID_SYNC_DISABLED = 0;
private static final int SYNC_AUTH_ERROR = 1;
private static final int SYNC_PASSPHRASE_REQUIRED = 2;
private static final int SYNC_CLIENT_OUT_OF_DATE = 3;
private static final int SYNC_OTHER_ERRORS = 128;
public static final String ARGUMENT_ACCOUNT = "account";
private ChromeSwitchPreference mSyncSwitchPreference;
private boolean mIsBackendInitialized;
private boolean mIsPassphraseRequired;
@VisibleForTesting
public static final String[] PREFS_TO_SAVE = {
PREFERENCE_SYNC_EVERYTHING,
PREFERENCE_SYNC_AUTOFILL,
PREFERENCE_SYNC_BOOKMARKS,
PREFERENCE_SYNC_OMNIBOX,
PREFERENCE_SYNC_PASSWORDS,
PREFERENCE_SYNC_RECENT_TABS,
PREFERENCE_SYNC_SETTINGS,
PREFERENCE_PAYMENTS_INTEGRATION
};
private static final String DASHBOARD_URL = "https://www.google.com/settings/chrome/sync";
private SwitchPreference mSyncEverything;
private CheckBoxPreference mSyncAutofill;
private CheckBoxPreference mSyncBookmarks;
private CheckBoxPreference mSyncOmnibox;
private CheckBoxPreference mSyncPasswords;
private CheckBoxPreference mSyncRecentTabs;
private CheckBoxPreference mSyncSettings;
private CheckBoxPreference mPaymentsIntegration;
private Preference mSyncEncryption;
private Preference mManageSyncData;
private Preference mSyncErrorCard;
private CheckBoxPreference[] mAllTypes;
private SyncedAccountPreference mSyncedAccountPreference;
private ProfileSyncService mProfileSyncService;
@SyncError private int mCurrentSyncError = SYNC_NO_ERROR;
@Override
public View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mProfileSyncService = ProfileSyncService.get();
assert mProfileSyncService != null;
mIsBackendInitialized = mProfileSyncService.isBackendInitialized();
mIsPassphraseRequired =
mIsBackendInitialized && mProfileSyncService.isPassphraseRequiredForDecryption();
getActivity().setTitle(R.string.sign_in_sync);
View view = super.onCreateView(inflater, container, savedInstanceState);
addPreferencesFromResource(R.xml.sync_customization_preferences);
mSyncEverything = (SwitchPreference) findPreference(PREFERENCE_SYNC_EVERYTHING);
mSyncAutofill = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_AUTOFILL);
mSyncBookmarks = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_BOOKMARKS);
mSyncOmnibox = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_OMNIBOX);
mSyncPasswords = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_PASSWORDS);
mSyncRecentTabs = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_RECENT_TABS);
mSyncSettings = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_SETTINGS);
mPaymentsIntegration = (CheckBoxPreference) findPreference(PREFERENCE_PAYMENTS_INTEGRATION);
mSyncEncryption = findPreference(PREFERENCE_ENCRYPTION);
mSyncEncryption.setOnPreferenceClickListener(this);
mManageSyncData = findPreference(PREFERENCE_SYNC_MANAGE_DATA);
mManageSyncData.setOnPreferenceClickListener(this);
mSyncErrorCard = findPreference(PREFERENCE_SYNC_ERROR_CARD);
mSyncErrorCard.setOnPreferenceClickListener(this);
mAllTypes = new CheckBoxPreference[] {
mSyncAutofill, mSyncBookmarks, mSyncOmnibox, mSyncPasswords,
mSyncRecentTabs, mSyncSettings, mPaymentsIntegration
};
mSyncEverything.setOnPreferenceChangeListener(this);
for (CheckBoxPreference type : mAllTypes) {
type.setOnPreferenceChangeListener(this);
}
mSyncSwitchPreference = (ChromeSwitchPreference) findPreference(PREF_SYNC_SWITCH);
mSyncSwitchPreference.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
assert canDisableSync();
if ((boolean) newValue) {
mProfileSyncService.requestStart();
} else {
stopSync();
}
// Must be done asynchronously because the switch state isn't updated
// until after this function exits.
new Handler().post(new Runnable() {
@Override
public void run() {
updateSyncStateFromSwitch();
}
});
return true;
}
});
mSyncedAccountPreference =
(SyncedAccountPreference) findPreference(PREFERENCE_SYNC_ACCOUNT_LIST);
mSyncedAccountPreference.setOnPreferenceChangeListener(
new SyncAccountSwitcher(getActivity(), mSyncedAccountPreference));
return view;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference == mSyncEverything) {
new Handler().post(new Runnable() {
@Override
public void run() {
updateDataTypeState();
}
});
return true;
}
if (isSyncTypePreference(preference)) {
final boolean syncAutofillToggled = preference == mSyncAutofill;
final boolean preferenceChecked = (boolean) newValue;
new Handler().post(new Runnable() {
@Override
public void run() {
if (syncAutofillToggled) {
// If the user checks the autofill sync checkbox, then enable and check the
// payments integration checkbox.
//
// If the user unchecks the autofill sync checkbox, then disable and uncheck
// the payments integration checkbox.
mPaymentsIntegration.setEnabled(preferenceChecked);
mPaymentsIntegration.setChecked(preferenceChecked);
}
maybeDisableSync();
}
});
return true;
}
return false;
}
/**
* @return Whether Sync can be disabled.
*/
private boolean canDisableSync() {
return !ChildAccountService.isChildAccount();
}
private boolean isSyncTypePreference(Preference preference) {
for (Preference pref : mAllTypes) {
if (pref == preference) return true;
}
return false;
}
/**
* Returns the sync action bar switch to enable/disable sync.
*
* @return the mActionBarSwitch
*/
@VisibleForTesting
public ChromeSwitchPreference getSyncSwitchPreference() {
return mSyncSwitchPreference;
}
@Override
public void onStart() {
super.onStart();
// The current account may have been switched on a different screen so ensure the synced
// account preference displays the correct signed in account.
mSyncedAccountPreference.update();
mIsBackendInitialized = mProfileSyncService.isBackendInitialized();
mIsPassphraseRequired =
mIsBackendInitialized && mProfileSyncService.isPassphraseRequiredForDecryption();
// This prevents sync from actually syncing until the dialog is closed.
mProfileSyncService.setSetupInProgress(true);
mProfileSyncService.addSyncStateChangedListener(this);
updateSyncState();
}
@Override
public void onStop() {
super.onStop();
mProfileSyncService.removeSyncStateChangedListener(this);
// If this activity is closing, apply configuration changes and tell sync that
// the user is done configuring sync.
if (!getActivity().isChangingConfigurations()) {
// Only save state if the switch and external state match. If a stop and clear comes
// while the dialog is open, this will be false and settings won't be saved.
if (mSyncSwitchPreference.isChecked()
&& AndroidSyncSettings.isSyncEnabled(getActivity())) {
// Save the new data type state.
configureSyncDataTypes();
// Inform sync that the user has finished setting up sync at least once.
mProfileSyncService.setFirstSetupComplete();
}
PersonalDataManager.setPaymentsIntegrationEnabled(mPaymentsIntegration.isChecked());
// Setup is done. This was preventing sync from turning on even if it was enabled.
// TODO(crbug/557784): This needs to be set only when we think the user is done with
// setting up. This means: 1) If the user leaves the Sync Settings screen (via back)
// or, 2) If the user leaves the screen by tapping on "Manage Synced Data"
mProfileSyncService.setSetupInProgress(false);
}
}
/**
* Update the state of all settings from sync.
*
* This sets the state of the sync switch from external sync state and then calls
* updateSyncStateFromSwitch, which uses that as its source of truth.
*/
private void updateSyncState() {
boolean isSyncEnabled = AndroidSyncSettings.isSyncEnabled(getActivity());
mSyncSwitchPreference.setChecked(isSyncEnabled);
mSyncSwitchPreference.setEnabled(canDisableSync());
updateSyncStateFromSwitch();
}
private void updateSyncAccountsListState() {
SyncedAccountPreference accountList =
(SyncedAccountPreference) findPreference(PREFERENCE_SYNC_ACCOUNT_LIST);
// We remove the the SyncedAccountPreference if there's only 1 account on the device, so
// it's possible for accountList to be null
if (accountList != null) {
Account[] accounts = AccountManagerHelper.get(getActivity()).getGoogleAccounts();
if (accounts.length <= 1) {
getPreferenceScreen().removePreference(accountList);
} else {
accountList.setEnabled(mSyncSwitchPreference.isChecked());
}
}
}
/**
* Update the state of settings using the switch state to determine if sync is enabled.
*/
private void updateSyncStateFromSwitch() {
updateSyncEverythingState();
updateDataTypeState();
updateEncryptionState();
updateSyncAccountsListState();
updateSyncErrorCard();
}
/**
* Update the encryption state.
*
* If sync's backend 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 backend is needed in order to know and
* modify the encryption state.
*/
private void updateEncryptionState() {
boolean isSyncEnabled = mSyncSwitchPreference.isChecked();
boolean isBackendInitialized = mProfileSyncService.isBackendInitialized();
mSyncEncryption.setEnabled(isSyncEnabled && isBackendInitialized);
mSyncEncryption.setSummary(null);
if (!isBackendInitialized) {
// 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.isPassphraseRequiredForDecryption()) {
closeDialogIfOpen(FRAGMENT_ENTER_PASSPHRASE);
}
if (mProfileSyncService.isPassphraseRequiredForDecryption() && 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 void configureSyncDataTypes() {
if (maybeDisableSync()) return;
boolean syncEverything = mSyncEverything.isChecked();
mProfileSyncService.setPreferredDataTypes(syncEverything, getSelectedModelTypes());
// Update the invalidation listener with the set of types we are enabling.
InvalidationController invController = InvalidationController.get(getActivity());
invController.ensureStartedAndUpdateRegisteredTypes();
}
private Set<Integer> getSelectedModelTypes() {
Set<Integer> types = new HashSet<Integer>();
if (mSyncAutofill.isChecked()) types.add(ModelType.AUTOFILL);
if (mSyncBookmarks.isChecked()) types.add(ModelType.BOOKMARKS);
if (mSyncOmnibox.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();
}
}
private void configureEncryption(String passphrase) {
if (mProfileSyncService.isBackendInitialized()) {
mProfileSyncService.enableEncryptEverything();
mProfileSyncService.setEncryptionPassphrase(passphrase);
// Configure the current set of data types - this tells the sync engine to
// apply our encryption configuration changes.
configureSyncDataTypes();
// Re-display our config UI to properly reflect the new state.
updateSyncState();
}
}
/**
* @return whether the passphrase successfully decrypted the pending keys.
*/
private boolean handleDecryption(String passphrase) {
if (!passphrase.isEmpty() && mProfileSyncService.setDecryptionPassphrase(passphrase)) {
// PassphraseDialogFragment doesn't handle closing itself, so do it here. This is
// not done in updateSyncState() because that happens onResume and possibly in other
// cases where the dialog should stay open.
closeDialogIfOpen(FRAGMENT_ENTER_PASSPHRASE);
// Update our configuration UI.
updateSyncState();
return true;
}
return false;
}
/**
* Callback for PassphraseDialogFragment.Listener
*/
@Override
public boolean onPassphraseEntered(String passphrase) {
if (!mProfileSyncService.isBackendInitialized()) {
// If the backend was shut down since the dialog was opened, 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.isBackendInitialized()) {
// If the backend was shut down since the dialog was opened, do nothing.
return;
}
configureEncryption(passphrase);
}
/**
* Callback for PassphraseTypeDialogFragment.Listener
*/
@Override
public void onPassphraseTypeSelected(PassphraseType type) {
if (!mProfileSyncService.isBackendInitialized()) {
// If the backend 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();
}
/**
* Callback for OnPreferenceClickListener
*/
@Override
public boolean onPreferenceClick(Preference preference) {
if (!isResumed()) {
// This event could come in after onPause if the user clicks back and the preference at
// roughly the same time. See http://b/5983282
return false;
}
if (preference == mSyncEncryption && mProfileSyncService.isBackendInitialized()) {
if (mProfileSyncService.isPassphraseRequiredForDecryption()) {
displayPassphraseDialog();
} else {
displayPassphraseTypeDialog();
return true;
}
} else if (preference == mManageSyncData) {
openDashboardTabInNewActivityStack();
return true;
} else if (preference == mSyncErrorCard) {
onSyncErrorCardClicked();
return true;
}
return false;
}
/**
* Opens the Google Dashboard where the user can control the data stored for the account.
*/
private void openDashboardTabInNewActivityStack() {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(DASHBOARD_URL));
intent.setPackage(getActivity().getPackageName());
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
/**
* Update the state of the sync everything switch.
*
* If sync is on, load the pref from native. Otherwise display sync everything as on but
* disable the switch.
*/
private void updateSyncEverythingState() {
boolean isSyncEnabled = mSyncSwitchPreference.isChecked();
mSyncEverything.setEnabled(isSyncEnabled);
mSyncEverything.setChecked(!isSyncEnabled
|| mProfileSyncService.hasKeepEverythingSynced());
}
/**
* Update the data type switch state.
*
* If sync is on, load the prefs from native. Otherwise, all data types are disabled and
* checked. Note that the Password data type will be shown as disabled and unchecked between
* sync being turned on and the backend initialization completing.
*/
private void updateDataTypeState() {
boolean isSyncEnabled = mSyncSwitchPreference.isChecked();
boolean syncEverything = mSyncEverything.isChecked();
boolean passwordSyncConfigurable = mProfileSyncService.isBackendInitialized()
&& mProfileSyncService.isCryptographerReady();
Set<Integer> syncTypes = mProfileSyncService.getPreferredDataTypes();
boolean syncAutofill = syncTypes.contains(ModelType.AUTOFILL);
for (CheckBoxPreference pref : mAllTypes) {
boolean canSyncType = true;
if (pref == mSyncPasswords) canSyncType = passwordSyncConfigurable;
if (pref == mPaymentsIntegration) {
canSyncType = syncAutofill || syncEverything;
}
if (!isSyncEnabled) {
pref.setChecked(true);
} else if (syncEverything) {
pref.setChecked(canSyncType);
}
pref.setEnabled(isSyncEnabled && !syncEverything && canSyncType);
}
if (isSyncEnabled && !syncEverything) {
mSyncAutofill.setChecked(syncAutofill);
mSyncBookmarks.setChecked(syncTypes.contains(ModelType.BOOKMARKS));
mSyncOmnibox.setChecked(syncTypes.contains(ModelType.TYPED_URLS));
mSyncPasswords.setChecked(passwordSyncConfigurable
&& syncTypes.contains(ModelType.PASSWORDS));
mSyncRecentTabs.setChecked(syncTypes.contains(ModelType.PROXY_TABS));
// TODO(zea): Switch this to PREFERENCE once that datatype is
// supported on Android.
mSyncSettings.setChecked(syncTypes.contains(ModelType.PRIORITY_PREFERENCES));
mPaymentsIntegration.setChecked(
syncAutofill && PersonalDataManager.isPaymentsIntegrationEnabled());
}
}
private void updateSyncErrorCard() {
mCurrentSyncError = getSyncError();
if (mCurrentSyncError != SYNC_NO_ERROR) {
String summary = getSyncErrorHint(mCurrentSyncError);
mSyncErrorCard.setSummary(summary);
getPreferenceScreen().addPreference(mSyncErrorCard);
} else {
getPreferenceScreen().removePreference(mSyncErrorCard);
}
}
@SyncError
private int getSyncError() {
if (!AndroidSyncSettings.isMasterSyncEnabled(getActivity())) {
return SYNC_ANDROID_SYNC_DISABLED;
}
if (!mSyncSwitchPreference.isChecked()) {
return SYNC_NO_ERROR;
}
if (mProfileSyncService.getAuthError()
== GoogleServiceAuthError.State.INVALID_GAIA_CREDENTIALS) {
return SYNC_AUTH_ERROR;
}
if (mProfileSyncService.getProtocolErrorClientAction()
== ProtocolErrorClientAction.UPGRADE_CLIENT) {
return SYNC_CLIENT_OUT_OF_DATE;
}
if (mProfileSyncService.getAuthError() != GoogleServiceAuthError.State.NONE
|| mProfileSyncService.hasUnrecoverableError()) {
return SYNC_OTHER_ERRORS;
}
if (mProfileSyncService.isSyncActive()
&& mProfileSyncService.isPassphraseRequiredForDecryption()) {
return SYNC_PASSPHRASE_REQUIRED;
}
return SYNC_NO_ERROR;
}
/**
* Gets hint message to resolve sync error.
* @param error The sync error.
*/
private String getSyncErrorHint(@SyncError int error) {
Resources res = getActivity().getResources();
switch (error) {
case SYNC_ANDROID_SYNC_DISABLED:
return res.getString(R.string.hint_android_sync_disabled);
case SYNC_AUTH_ERROR:
return res.getString(R.string.hint_sync_auth_error);
case SYNC_CLIENT_OUT_OF_DATE:
return res.getString(
R.string.hint_client_out_of_date, BuildInfo.getPackageLabel(getActivity()));
case SYNC_OTHER_ERRORS:
return res.getString(R.string.hint_other_sync_errors);
case SYNC_PASSPHRASE_REQUIRED:
return res.getString(R.string.hint_passphrase_required);
case SYNC_NO_ERROR:
default:
return null;
}
}
private void onSyncErrorCardClicked() {
if (mCurrentSyncError == SYNC_NO_ERROR) {
return;
}
if (mCurrentSyncError == SYNC_ANDROID_SYNC_DISABLED) {
// TODO(crbug.com/557784): This needs to actually take the user to a specific account
// settings page. There doesn't seem to be an obvious way to do that at the moment, but
// should update this when we figure that out.
Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
intent.putExtra(Settings.EXTRA_ACCOUNT_TYPES, new String[] {"com.google"});
if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
getActivity().startActivity(intent);
}
return;
}
if (mCurrentSyncError == SYNC_AUTH_ERROR) {
AccountManagerHelper.get(getActivity())
.updateCredentials(ChromeSigninController.get(getActivity()).getSignedInUser(),
getActivity(), new Callback<Boolean>() {
@Override
public void onResult(Boolean result) {}
});
return;
}
if (mCurrentSyncError == SYNC_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=" + BuildInfo.getPackageName(getActivity())));
startActivity(intent);
return;
}
if (mCurrentSyncError == SYNC_OTHER_ERRORS) {
final Account account = ChromeSigninController.get(getActivity()).getSignedInUser();
SigninManager.get(getActivity()).signOut(new Runnable() {
@Override
public void run() {
SigninManager.get(getActivity()).signIn(account, null, null);
}
});
return;
}
if (mCurrentSyncError == SYNC_PASSPHRASE_REQUIRED) {
displayPassphraseDialog();
return;
}
}
/**
* Listen 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 backend has initialized.
*/
@Override
public void syncStateChanged() {
boolean wasSyncInitialized = mIsBackendInitialized;
boolean wasPassphraseRequired = mIsPassphraseRequired;
mIsBackendInitialized = mProfileSyncService.isBackendInitialized();
mIsPassphraseRequired =
mIsBackendInitialized && mProfileSyncService.isPassphraseRequiredForDecryption();
if (mIsBackendInitialized != wasSyncInitialized
|| mIsPassphraseRequired != wasPassphraseRequired) {
// Update all because Password syncability is also affected by the backend.
updateSyncStateFromSwitch();
} else {
updateSyncErrorCard();
}
}
/**
* Disables Sync if all data types have been disabled.
*
* @return true if Sync has been disabled, false otherwise.
*/
private boolean maybeDisableSync() {
if (mSyncEverything.isChecked()
|| !getSelectedModelTypes().isEmpty()
|| !canDisableSync()) {
return false;
}
stopSync();
mSyncSwitchPreference.setChecked(false);
// setChecked doesn't trigger the callback, so update manually.
updateSyncStateFromSwitch();
return true;
}
private void stopSync() {
if (mProfileSyncService.isSyncRequested()) {
RecordHistogram.recordEnumeratedHistogram("Sync.StopSource",
StopSource.CHROME_SYNC_SETTINGS, StopSource.STOP_SOURCE_LIMIT);
mProfileSyncService.requestStop();
}
}
}