blob: 286a86420afce3a68d6eee16c6787fa85c56fb0e [file] [log] [blame]
// Copyright 2018 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.settings.sync;
import android.accounts.Account;
import android.app.Dialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceCategory;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.support.v7.preference.PreferenceGroup;
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.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.BuildInfo;
import org.chromium.base.ContextUtils;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.SyncFirstSetupCompleteSource;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchFieldTrial;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchManager;
import org.chromium.chrome.browser.help.HelpAndFeedback;
import org.chromium.chrome.browser.metrics.UmaSessionStats;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.safe_browsing.SafeBrowsingBridge;
import org.chromium.chrome.browser.settings.ChromeBasePreference;
import org.chromium.chrome.browser.settings.ChromeSwitchPreference;
import org.chromium.chrome.browser.settings.ManagedPreferenceDelegate;
import org.chromium.chrome.browser.settings.ManagedPreferencesUtils;
import org.chromium.chrome.browser.settings.SettingsActivity;
import org.chromium.chrome.browser.settings.SettingsUtils;
import org.chromium.chrome.browser.settings.password.PasswordUIView;
import org.chromium.chrome.browser.settings.privacy.PrivacyPreferencesManager;
import org.chromium.chrome.browser.signin.IdentityServicesProvider;
import org.chromium.chrome.browser.signin.UnifiedConsentServiceBridge;
import org.chromium.chrome.browser.sync.GoogleServiceAuthError;
import org.chromium.chrome.browser.sync.ProfileSyncService;
import org.chromium.chrome.browser.sync.TrustedVaultClient;
import org.chromium.chrome.browser.sync.ui.PassphraseDialogFragment;
import org.chromium.chrome.browser.util.IntentUtils;
import org.chromium.components.signin.AccountManagerFacade;
import org.chromium.components.signin.ChromeSigninController;
import org.chromium.components.signin.metrics.SigninAccessPoint;
import org.chromium.components.signin.metrics.SignoutReason;
import org.chromium.components.sync.AndroidSyncSettings;
import org.chromium.content_public.browser.UiThreadTaskTraits;
import org.chromium.ui.UiUtils;
import org.chromium.ui.widget.ButtonCompat;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Settings fragment to enable Sync and other services that communicate with Google.
*/
public class SyncAndServicesPreferences extends PreferenceFragmentCompat
implements PassphraseDialogFragment.Listener, Preference.OnPreferenceChangeListener,
ProfileSyncService.SyncStateChangedListener,
SettingsActivity.OnBackPressedListener {
private static final String IS_FROM_SIGNIN_SCREEN =
"SyncAndServicesPreferences.isFromSigninScreen";
@VisibleForTesting
public static final String FRAGMENT_ENTER_PASSPHRASE = "enter_password";
private static final String FRAGMENT_CANCEL_SYNC = "cancel_sync_dialog";
private static final String PREF_USER_CATEGORY = "user_category";
private static final String PREF_SIGNIN = "sign_in";
private static final String PREF_MANAGE_YOUR_GOOGLE_ACCOUNT = "manage_your_google_account";
@VisibleForTesting
public static final String PREF_SYNC_CATEGORY = "sync_category";
@VisibleForTesting
public static final String PREF_SYNC_ERROR_CARD = "sync_error_card";
private static final String PREF_SYNC_DISABLED_BY_ADMINISTRATOR =
"sync_disabled_by_administrator";
@VisibleForTesting
public static final String PREF_SYNC_REQUESTED = "sync_requested";
private static final String PREF_MANAGE_SYNC = "manage_sync";
private static final String PREF_SERVICES_CATEGORY = "services_category";
private static final String PREF_SEARCH_SUGGESTIONS = "search_suggestions";
private static final String PREF_NAVIGATION_ERROR = "navigation_error";
private static final String PREF_SAFE_BROWSING = "safe_browsing";
private static final String PREF_PASSWORD_LEAK_DETECTION = "password_leak_detection";
private static final String PREF_SAFE_BROWSING_SCOUT_REPORTING =
"safe_browsing_scout_reporting";
private static final String PREF_USAGE_AND_CRASH_REPORTING = "usage_and_crash_reports";
private static final String PREF_URL_KEYED_ANONYMIZED_DATA = "url_keyed_anonymized_data";
private static final String PREF_CONTEXTUAL_SEARCH = "contextual_search";
@VisibleForTesting
public static final String PREF_AUTOFILL_ASSISTANT = "autofill_assistant";
@IntDef({SyncError.NO_ERROR, SyncError.ANDROID_SYNC_DISABLED, SyncError.AUTH_ERROR,
SyncError.PASSPHRASE_REQUIRED, SyncError.CLIENT_OUT_OF_DATE,
SyncError.SYNC_SETUP_INCOMPLETE, SyncError.OTHER_ERRORS})
@Retention(RetentionPolicy.SOURCE)
private @interface SyncError {
int NO_ERROR = -1;
int ANDROID_SYNC_DISABLED = 0;
int AUTH_ERROR = 1;
int PASSPHRASE_REQUIRED = 2;
int TRUSTED_VAULT_KEY_REQUIRED_FOR_EVERYTHING = 3;
int TRUSTED_VAULT_KEY_REQUIRED_FOR_PASSWORDS = 4;
int CLIENT_OUT_OF_DATE = 5;
int SYNC_SETUP_INCOMPLETE = 6;
int OTHER_ERRORS = 128;
}
private final ProfileSyncService mProfileSyncService = ProfileSyncService.get();
private final PrefServiceBridge mPrefServiceBridge = PrefServiceBridge.getInstance();
private final PrivacyPreferencesManager mPrivacyPrefManager =
PrivacyPreferencesManager.getInstance();
private final ManagedPreferenceDelegate mManagedPreferenceDelegate =
createManagedPreferenceDelegate();
private final SharedPreferencesManager mSharedPreferencesManager =
SharedPreferencesManager.getInstance();
private boolean mIsFromSigninScreen;
private SignInPreference mSigninPreference;
private Preference mManageYourGoogleAccount;
private PreferenceCategory mSyncCategory;
private Preference mSyncErrorCard;
private Preference mSyncDisabledByAdministrator;
private ChromeBasePreference mManageSync;
private ChromeSwitchPreference mSyncRequested;
private ChromeSwitchPreference mSearchSuggestions;
private ChromeSwitchPreference mNavigationError;
private ChromeSwitchPreference mSafeBrowsing;
private @Nullable ChromeSwitchPreference mPasswordLeakDetection;
private ChromeSwitchPreference mSafeBrowsingReporting;
private ChromeSwitchPreference mUsageAndCrashReporting;
private ChromeSwitchPreference mUrlKeyedAnonymizedData;
private @Nullable ChromeSwitchPreference mAutofillAssistant;
private @Nullable Preference mContextualSearch;
private ProfileSyncService.SyncSetupInProgressHandle mSyncSetupInProgressHandle;
private @SyncError int mCurrentSyncError = SyncError.NO_ERROR;
/**
* 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);
mPrivacyPrefManager.migrateNetworkPredictionPreferences();
getActivity().setTitle(R.string.prefs_sync_and_services);
setHasOptionsMenu(true);
if (mIsFromSigninScreen) {
ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
assert actionBar != null;
actionBar.setHomeActionContentDescription(
R.string.prefs_sync_and_services_content_description);
RecordUserAction.record("Signin_Signin_ShowAdvancedSyncSettings");
}
SettingsUtils.addPreferencesFromResource(this, R.xml.sync_and_services_preferences);
mSigninPreference = (SignInPreference) findPreference(PREF_SIGNIN);
mSigninPreference.setPersonalizedPromoEnabled(false);
mManageYourGoogleAccount = findPreference(PREF_MANAGE_YOUR_GOOGLE_ACCOUNT);
mManageYourGoogleAccount.setOnPreferenceClickListener(SyncPreferenceUtils.toOnClickListener(
this, () -> SyncPreferenceUtils.openGoogleMyAccount(getActivity())));
mSyncCategory = (PreferenceCategory) findPreference(PREF_SYNC_CATEGORY);
mSyncErrorCard = findPreference(PREF_SYNC_ERROR_CARD);
mSyncErrorCard.setIcon(UiUtils.getTintedDrawable(
getActivity(), R.drawable.ic_sync_error_40dp, R.color.default_red));
mSyncErrorCard.setOnPreferenceClickListener(
SyncPreferenceUtils.toOnClickListener(this, this::onSyncErrorCardClicked));
mSyncDisabledByAdministrator = findPreference(PREF_SYNC_DISABLED_BY_ADMINISTRATOR);
mSyncDisabledByAdministrator.setIcon(
ManagedPreferencesUtils.getManagedByEnterpriseIconId());
mSyncRequested = (ChromeSwitchPreference) findPreference(PREF_SYNC_REQUESTED);
mSyncRequested.setOnPreferenceChangeListener(this);
mManageSync = (ChromeBasePreference) findPreference(PREF_MANAGE_SYNC);
mSearchSuggestions = (ChromeSwitchPreference) findPreference(PREF_SEARCH_SUGGESTIONS);
mSearchSuggestions.setOnPreferenceChangeListener(this);
mSearchSuggestions.setManagedPreferenceDelegate(mManagedPreferenceDelegate);
mNavigationError = (ChromeSwitchPreference) findPreference(PREF_NAVIGATION_ERROR);
mNavigationError.setOnPreferenceChangeListener(this);
mNavigationError.setManagedPreferenceDelegate(mManagedPreferenceDelegate);
mSafeBrowsing = (ChromeSwitchPreference) findPreference(PREF_SAFE_BROWSING);
mSafeBrowsing.setOnPreferenceChangeListener(this);
mSafeBrowsing.setManagedPreferenceDelegate(mManagedPreferenceDelegate);
PreferenceCategory servicesCategory =
(PreferenceCategory) findPreference(PREF_SERVICES_CATEGORY);
mPasswordLeakDetection =
(ChromeSwitchPreference) findPreference(PREF_PASSWORD_LEAK_DETECTION);
if (ChromeFeatureList.isEnabled(ChromeFeatureList.PASSWORD_LEAK_DETECTION)) {
mPasswordLeakDetection.setOnPreferenceChangeListener(this);
mPasswordLeakDetection.setManagedPreferenceDelegate(mManagedPreferenceDelegate);
} else {
removePreference(servicesCategory, mPasswordLeakDetection);
mPasswordLeakDetection = null;
}
mSafeBrowsingReporting =
(ChromeSwitchPreference) findPreference(PREF_SAFE_BROWSING_SCOUT_REPORTING);
mSafeBrowsingReporting.setOnPreferenceChangeListener(this);
mSafeBrowsingReporting.setManagedPreferenceDelegate(mManagedPreferenceDelegate);
mUsageAndCrashReporting =
(ChromeSwitchPreference) findPreference(PREF_USAGE_AND_CRASH_REPORTING);
mUsageAndCrashReporting.setOnPreferenceChangeListener(this);
mUsageAndCrashReporting.setManagedPreferenceDelegate(mManagedPreferenceDelegate);
mUrlKeyedAnonymizedData =
(ChromeSwitchPreference) findPreference(PREF_URL_KEYED_ANONYMIZED_DATA);
mUrlKeyedAnonymizedData.setOnPreferenceChangeListener(this);
mUrlKeyedAnonymizedData.setManagedPreferenceDelegate(mManagedPreferenceDelegate);
mAutofillAssistant = (ChromeSwitchPreference) findPreference(PREF_AUTOFILL_ASSISTANT);
if (shouldShowAutofillAssistantPreference()) {
mAutofillAssistant.setOnPreferenceChangeListener(this);
mAutofillAssistant.setManagedPreferenceDelegate(mManagedPreferenceDelegate);
} else {
removePreference(servicesCategory, mAutofillAssistant);
mAutofillAssistant = null;
}
mContextualSearch = findPreference(PREF_CONTEXTUAL_SEARCH);
if (!ContextualSearchFieldTrial.isEnabled()) {
removePreference(servicesCategory, mContextualSearch);
mContextualSearch = null;
}
// Prevent sync settings changes from taking effect until the user leaves this screen.
mSyncSetupInProgressHandle = mProfileSyncService.getSetupInProgressHandle();
updatePreferences();
}
@Override
public void onDestroy() {
super.onDestroy();
if (ChromeFeatureList.isEnabled(ChromeFeatureList.SYNC_MANUAL_START_ANDROID)
&& wasSigninFlowInterrupted()) {
// If the setup flow was previously interrupted, and now the user dismissed the page
// without turning sync on, then mark first setup as complete (so that we won't show the
// error again), but turn sync off.
assert !mSyncRequested.isChecked();
SyncPreferenceUtils.enableSync(false);
mProfileSyncService.setFirstSetupComplete(
SyncFirstSetupCompleteSource.ADVANCED_FLOW_INTERRUPTED_LEAVE_SYNC_OFF);
}
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 (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) {
HelpAndFeedback.getInstance().show(getActivity(),
getString(R.string.help_context_sync_and_services),
Profile.getLastUsedProfile(), null);
return true;
}
return false;
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
ViewGroup result = (ViewGroup) super.onCreateView(inflater, container, savedInstanceState);
if (mIsFromSigninScreen) {
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());
}
return result;
}
@Override
public void onStart() {
super.onStart();
mProfileSyncService.addSyncStateChangedListener(this);
mSigninPreference.registerForUpdates();
if (!mIsFromSigninScreen || ChromeSigninController.get().isSignedIn()) {
return;
}
// Don't show CancelSyncDialog and hide bottom bar.
mIsFromSigninScreen = false;
View bottomBarShadow = getView().findViewById(R.id.bottom_bar_shadow);
bottomBarShadow.setVisibility(View.GONE);
View bottomBarButtonContainer = getView().findViewById(R.id.bottom_bar_button_container);
bottomBarButtonContainer.setVisibility(View.GONE);
ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
assert actionBar != null;
// Content description was overridden in onCreate, reset it to the standard one.
actionBar.setHomeActionContentDescription(null);
}
@Override
public void onStop() {
super.onStop();
mSigninPreference.unregisterForUpdates();
mProfileSyncService.removeSyncStateChangedListener(this);
}
@Override
public void onResume() {
super.onResume();
updatePreferences();
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
String key = preference.getKey();
if (PREF_SYNC_REQUESTED.equals(key)) {
assert canDisableSync();
SyncPreferenceUtils.enableSync((boolean) newValue);
if (ChromeFeatureList.isEnabled(ChromeFeatureList.SYNC_MANUAL_START_ANDROID)
&& wasSigninFlowInterrupted()) {
// This flow should only be reached when user toggles sync on.
assert (boolean) newValue;
mProfileSyncService.setFirstSetupComplete(
SyncFirstSetupCompleteSource.ADVANCED_FLOW_INTERRUPTED_TURN_SYNC_ON);
}
PostTask.postTask(UiThreadTaskTraits.DEFAULT, this::updatePreferences);
} else if (PREF_SEARCH_SUGGESTIONS.equals(key)) {
mPrefServiceBridge.setBoolean(Pref.SEARCH_SUGGEST_ENABLED, (boolean) newValue);
} else if (PREF_SAFE_BROWSING.equals(key)) {
mPrefServiceBridge.setBoolean(Pref.SAFE_BROWSING_ENABLED, (boolean) newValue);
// Toggling the safe browsing preference impacts the leak detection and the
// safe browsing reporting preferences as well.
PostTask.postTask(UiThreadTaskTraits.DEFAULT,
this::updateLeakDetectionAndSafeBrowsingReportingPreferences);
} else if (PREF_PASSWORD_LEAK_DETECTION.equals(key)) {
mPrefServiceBridge.setBoolean(
Pref.PASSWORD_MANAGER_LEAK_DETECTION_ENABLED, (boolean) newValue);
} else if (PREF_SAFE_BROWSING_SCOUT_REPORTING.equals(key)) {
SafeBrowsingBridge.setSafeBrowsingExtendedReportingEnabled((boolean) newValue);
} else if (PREF_NAVIGATION_ERROR.equals(key)) {
mPrefServiceBridge.setBoolean(Pref.ALTERNATE_ERROR_PAGES_ENABLED, (boolean) newValue);
} else if (PREF_USAGE_AND_CRASH_REPORTING.equals(key)) {
UmaSessionStats.changeMetricsReportingConsent((boolean) newValue);
} else if (PREF_URL_KEYED_ANONYMIZED_DATA.equals(key)) {
UnifiedConsentServiceBridge.setUrlKeyedAnonymizedDataCollectionEnabled(
(boolean) newValue);
} else if (PREF_AUTOFILL_ASSISTANT.equals(key)) {
setAutofillAssistantSwitchValue((boolean) newValue);
}
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() {
updatePreferences();
}
/** Returns whether Sync can be disabled. */
private boolean canDisableSync() {
return !Profile.getLastUsedProfile().isChild();
}
/** Returns whether user did not complete the sign in flow. */
private boolean wasSigninFlowInterrupted() {
return !mIsFromSigninScreen && !mProfileSyncService.isFirstSetupComplete();
}
private void displayPassphraseDialog() {
FragmentTransaction ft = getFragmentManager().beginTransaction();
PassphraseDialogFragment.newInstance(this).show(ft, FRAGMENT_ENTER_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;
}
updatePreferences();
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() {}
@SyncError
private int getSyncError() {
if (!AndroidSyncSettings.get().isMasterSyncEnabled()) {
return SyncError.ANDROID_SYNC_DISABLED;
}
if (!AndroidSyncSettings.get().isChromeSyncEnabled()) {
return SyncError.NO_ERROR;
}
if (mProfileSyncService.getAuthError()
== GoogleServiceAuthError.State.INVALID_GAIA_CREDENTIALS) {
return SyncError.AUTH_ERROR;
}
if (mProfileSyncService.requiresClientUpgrade()) {
return SyncError.CLIENT_OUT_OF_DATE;
}
if (mProfileSyncService.getAuthError() != GoogleServiceAuthError.State.NONE
|| mProfileSyncService.hasUnrecoverableError()) {
return SyncError.OTHER_ERRORS;
}
if (mProfileSyncService.isEngineInitialized()
&& mProfileSyncService.isPassphraseRequiredForPreferredDataTypes()) {
return SyncError.PASSPHRASE_REQUIRED;
}
if (mProfileSyncService.isEngineInitialized()
&& mProfileSyncService.isTrustedVaultKeyRequiredForPreferredDataTypes()) {
return mProfileSyncService.isEncryptEverythingEnabled()
? SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_EVERYTHING
: SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_PASSWORDS;
}
if (ChromeFeatureList.isEnabled(ChromeFeatureList.SYNC_MANUAL_START_ANDROID)
&& wasSigninFlowInterrupted()) {
return SyncError.SYNC_SETUP_INCOMPLETE;
}
return SyncError.NO_ERROR;
}
/**
* Gets title message for sync error.
* @param error The sync error.
*/
private String getSyncErrorTitle(@SyncError int error) {
switch (error) {
case SyncError.SYNC_SETUP_INCOMPLETE:
assert ChromeFeatureList.isEnabled(ChromeFeatureList.SYNC_MANUAL_START_ANDROID);
return getString(R.string.sync_settings_not_confirmed_title);
case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_EVERYTHING:
return getString(R.string.sync_error_card_title);
case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_PASSWORDS:
return getString(R.string.sync_passwords_error_card_title);
default:
return getString(R.string.sync_error_card_title);
}
}
/**
* Gets hint message to resolve sync error.
* @param error The sync error.
*/
private String getSyncErrorHint(@SyncError int error) {
switch (error) {
case SyncError.ANDROID_SYNC_DISABLED:
return getString(R.string.hint_android_sync_disabled);
case SyncError.AUTH_ERROR:
return getString(R.string.hint_sync_auth_error);
case SyncError.CLIENT_OUT_OF_DATE:
return getString(
R.string.hint_client_out_of_date, BuildInfo.getInstance().hostPackageLabel);
case SyncError.OTHER_ERRORS:
return getString(R.string.hint_other_sync_errors);
case SyncError.PASSPHRASE_REQUIRED:
return getString(R.string.hint_passphrase_required);
case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_EVERYTHING:
case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_PASSWORDS:
return getString(R.string.hint_sync_retrieve_keys);
case SyncError.SYNC_SETUP_INCOMPLETE:
assert ChromeFeatureList.isEnabled(ChromeFeatureList.SYNC_MANUAL_START_ANDROID);
return getString(R.string.hint_sync_settings_not_confirmed_description);
case SyncError.NO_ERROR:
default:
return null;
}
}
private void onSyncErrorCardClicked() {
if (mCurrentSyncError == SyncError.NO_ERROR) {
return;
}
if (mCurrentSyncError == SyncError.ANDROID_SYNC_DISABLED) {
IntentUtils.safeStartActivity(getActivity(), new Intent(Settings.ACTION_SYNC_SETTINGS));
return;
}
if (mCurrentSyncError == SyncError.AUTH_ERROR) {
AccountManagerFacade.get().updateCredentials(
ChromeSigninController.get().getSignedInUser(), getActivity(), null);
return;
}
if (mCurrentSyncError == 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;
}
if (mCurrentSyncError == SyncError.OTHER_ERRORS) {
final Account account = ChromeSigninController.get().getSignedInUser();
// TODO(https://crbug.com/873116): Pass the correct reason for the signout.
IdentityServicesProvider.get().getSigninManager().signOut(
SignoutReason.USER_CLICKED_SIGNOUT_SETTINGS,
()
-> IdentityServicesProvider.get().getSigninManager().signIn(
SigninAccessPoint.SYNC_ERROR_CARD, account, null),
false);
return;
}
if (mCurrentSyncError == SyncError.PASSPHRASE_REQUIRED) {
displayPassphraseDialog();
return;
}
if (mCurrentSyncError == SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_EVERYTHING
|| mCurrentSyncError == SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_PASSWORDS) {
TrustedVaultClient.get().displayKeyRetrievalDialog(getContext());
return;
}
}
private static void removePreference(PreferenceGroup from, Preference preference) {
boolean found = from.removePreference(preference);
assert found : "Don't have such preference! Preference key: " + preference.getKey();
}
private void updatePreferences() {
updateSyncPreferences();
mSearchSuggestions.setChecked(mPrefServiceBridge.getBoolean(Pref.SEARCH_SUGGEST_ENABLED));
mNavigationError.setChecked(
mPrefServiceBridge.getBoolean(Pref.ALTERNATE_ERROR_PAGES_ENABLED));
mSafeBrowsing.setChecked(mPrefServiceBridge.getBoolean(Pref.SAFE_BROWSING_ENABLED));
updateLeakDetectionAndSafeBrowsingReportingPreferences();
mUsageAndCrashReporting.setChecked(
mPrivacyPrefManager.isUsageAndCrashReportingPermittedByUser());
mUrlKeyedAnonymizedData.setChecked(
UnifiedConsentServiceBridge.isUrlKeyedAnonymizedDataCollectionEnabled());
if (mAutofillAssistant != null) {
mAutofillAssistant.setChecked(isAutofillAssistantSwitchOn());
}
if (mContextualSearch != null) {
boolean isContextualSearchEnabled =
!ContextualSearchManager.isContextualSearchDisabled();
mContextualSearch.setSummary(
isContextualSearchEnabled ? R.string.text_on : R.string.text_off);
}
}
private void updateSyncPreferences() {
if (!mProfileSyncService.isEngineInitialized()
|| !mProfileSyncService.isPassphraseRequiredForPreferredDataTypes()) {
closeDialogIfOpen(FRAGMENT_ENTER_PASSPHRASE);
}
if (!ChromeSigninController.get().isSignedIn()) {
getPreferenceScreen().removePreference(mManageYourGoogleAccount);
getPreferenceScreen().removePreference(mSyncCategory);
return;
}
getPreferenceScreen().addPreference(mManageYourGoogleAccount);
getPreferenceScreen().addPreference(mSyncCategory);
if (ProfileSyncService.get().isSyncDisabledByEnterprisePolicy()) {
mSyncCategory.addPreference(mSyncDisabledByAdministrator);
mSyncCategory.removePreference(mSyncErrorCard);
mSyncCategory.removePreference(mSyncRequested);
mSyncCategory.removePreference(mManageSync);
return;
}
mSyncCategory.removePreference(mSyncDisabledByAdministrator);
mSyncCategory.addPreference(mSyncRequested);
mSyncCategory.addPreference(mManageSync);
mCurrentSyncError = getSyncError();
if (mCurrentSyncError == SyncError.NO_ERROR) {
mSyncCategory.removePreference(mSyncErrorCard);
} else {
mSyncErrorCard.setTitle(getSyncErrorTitle(mCurrentSyncError));
mSyncErrorCard.setSummary(getSyncErrorHint(mCurrentSyncError));
mSyncCategory.addPreference(mSyncErrorCard);
}
mSyncRequested.setChecked(AndroidSyncSettings.get().isChromeSyncEnabled());
if (ChromeFeatureList.isEnabled(ChromeFeatureList.SYNC_MANUAL_START_ANDROID)
&& wasSigninFlowInterrupted()) {
// If sync setup was not completed the sync request toggle should be off.
// In this situation, switching it on will trigger a call to setFirstSetupComplete.
mSyncRequested.setChecked(false);
}
mSyncRequested.setEnabled(canDisableSync());
}
/**
* If password leak detection is off and cannot be toggled while safe browsing is disabled, so
* its appearance needs to be updated. The same goes for safe browsing reporting.
*/
private void updateLeakDetectionAndSafeBrowsingReportingPreferences() {
boolean safe_browsing_enabled = mPrefServiceBridge.getBoolean(Pref.SAFE_BROWSING_ENABLED);
mSafeBrowsingReporting.setEnabled(safe_browsing_enabled);
mSafeBrowsingReporting.setChecked(safe_browsing_enabled
&& SafeBrowsingBridge.isSafeBrowsingExtendedReportingEnabled());
if (mPasswordLeakDetection == null) return; // Early exit without leak detection to update.
boolean has_token_for_leak_check = PasswordUIView.hasAccountForLeakCheckRequest();
boolean leak_detection_enabled =
mPrefServiceBridge.getBoolean(Pref.PASSWORD_MANAGER_LEAK_DETECTION_ENABLED);
boolean toggle_enabled = safe_browsing_enabled && has_token_for_leak_check;
mPasswordLeakDetection.setEnabled(toggle_enabled);
mPasswordLeakDetection.setChecked(toggle_enabled && leak_detection_enabled);
if (!safe_browsing_enabled || !leak_detection_enabled || has_token_for_leak_check) {
mPasswordLeakDetection.setSummary(null);
return;
}
mPasswordLeakDetection.setSummary(
R.string.passwords_leak_detection_switch_signed_out_enable_description);
}
private ManagedPreferenceDelegate createManagedPreferenceDelegate() {
return preference -> {
String key = preference.getKey();
if (PREF_NAVIGATION_ERROR.equals(key)) {
return mPrefServiceBridge.isManagedPreference(Pref.ALTERNATE_ERROR_PAGES_ENABLED);
}
if (PREF_SEARCH_SUGGESTIONS.equals(key)) {
return mPrefServiceBridge.isManagedPreference(Pref.SEARCH_SUGGEST_ENABLED);
}
if (PREF_SAFE_BROWSING_SCOUT_REPORTING.equals(key)) {
return SafeBrowsingBridge.isSafeBrowsingExtendedReportingManaged();
}
if (PREF_SAFE_BROWSING.equals(key)) {
return mPrefServiceBridge.isManagedPreference(Pref.SAFE_BROWSING_ENABLED);
}
if (PREF_PASSWORD_LEAK_DETECTION.equals(key)) {
return mPrefServiceBridge.isManagedPreference(
Pref.PASSWORD_MANAGER_LEAK_DETECTION_ENABLED);
}
if (PREF_USAGE_AND_CRASH_REPORTING.equals(key)) {
return PrivacyPreferencesManager.getInstance().isMetricsReportingManaged();
}
if (PREF_URL_KEYED_ANONYMIZED_DATA.equals(key)) {
return UnifiedConsentServiceBridge.isUrlKeyedAnonymizedDataCollectionManaged();
}
return false;
};
}
@Override
public boolean onBackPressed() {
if (!mIsFromSigninScreen) return false; // Let parent activity handle it.
showCancelSyncDialog();
return true;
}
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");
if (ChromeFeatureList.isEnabled(ChromeFeatureList.SYNC_MANUAL_START_ANDROID)) {
ProfileSyncService.get().setFirstSetupComplete(
SyncFirstSetupCompleteSource.ADVANCED_FLOW_CONFIRM);
}
UnifiedConsentServiceBridge.recordSyncSetupDataTypesHistogram();
// Settings will be applied when mSyncSetupInProgressHandle is released in onDestroy.
getActivity().finish();
}
private void cancelSync() {
RecordUserAction.record("Signin_Signin_CancelAdvancedSyncSettings");
IdentityServicesProvider.get().getSigninManager().signOut(
SignoutReason.USER_CLICKED_SIGNOUT_SETTINGS);
getActivity().finish();
}
/**
* The dialog that offers the user to cancel sync. Only shown when
* {@link SyncAndServicesPreferences} 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");
SyncAndServicesPreferences fragment = (SyncAndServicesPreferences) getTargetFragment();
fragment.cancelSync();
}
}
/**
* This checks whether Autofill Assistant is enabled and was shown at least once (only then
* will the AA switch be assigned a value).
*/
private boolean shouldShowAutofillAssistantPreference() {
return ChromeFeatureList.isEnabled(ChromeFeatureList.AUTOFILL_ASSISTANT)
&& mSharedPreferencesManager.contains(
ChromePreferenceKeys.AUTOFILL_ASSISTANT_ENABLED);
}
public boolean isAutofillAssistantSwitchOn() {
return mSharedPreferencesManager.readBoolean(
ChromePreferenceKeys.AUTOFILL_ASSISTANT_ENABLED, false);
}
public void setAutofillAssistantSwitchValue(boolean newValue) {
mSharedPreferencesManager.writeBoolean(
ChromePreferenceKeys.AUTOFILL_ASSISTANT_ENABLED, newValue);
}
}