// 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.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.Browser;
import android.text.TextUtils;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import org.chromium.base.BuildInfo;
import org.chromium.base.IntentUtils;
import org.chromium.base.Log;
import org.chromium.base.Promise;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeStringConstants;
import org.chromium.chrome.browser.LaunchIntentDispatcher;
import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.CustomTabsUiType;
import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
import org.chromium.chrome.browser.password_manager.PasswordManagerUtilBridge;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.services.DisplayableProfileData;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.sync.SyncServiceFactory;
import org.chromium.chrome.browser.sync.TrustedVaultClient;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.base.GoogleServiceAuthError;
import org.chromium.components.signin.identitymanager.ConsentLevel;
import org.chromium.components.sync.SyncService;
import org.chromium.components.sync.TrustedVaultUserActionTriggerForUMA;
import org.chromium.components.sync.UserSelectableType;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.ui.widget.Toast;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** Helper methods for sync settings. */
public class SyncSettingsUtils {
private static final String MY_ACCOUNT_URL = "https://myaccount.google.com/smartlink/home";
private static final String TAG = "SyncSettingsUtils";
@IntDef({TitlePreference.FULL_NAME, TitlePreference.EMAIL})
@Retention(RetentionPolicy.SOURCE)
public @interface TitlePreference {
int FULL_NAME = 0;
int EMAIL = 1;
}
// Keep in sync with SyncErrorReason variant in sync/histograms.xml and signin/histograms.xml.
@IntDef({
SyncError.NO_ERROR,
SyncError.AUTH_ERROR,
SyncError.PASSPHRASE_REQUIRED,
SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_EVERYTHING,
SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_PASSWORDS,
SyncError.TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_EVERYTHING,
SyncError.TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_PASSWORDS,
SyncError.CLIENT_OUT_OF_DATE,
SyncError.SYNC_SETUP_INCOMPLETE,
SyncError.UPM_BACKEND_OUTDATED,
SyncError.OTHER_ERRORS
})
@Retention(RetentionPolicy.SOURCE)
public @interface SyncError {
int NO_ERROR = -1;
int AUTH_ERROR = 0;
int PASSPHRASE_REQUIRED = 1;
int TRUSTED_VAULT_KEY_REQUIRED_FOR_EVERYTHING = 2;
int TRUSTED_VAULT_KEY_REQUIRED_FOR_PASSWORDS = 3;
int TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_EVERYTHING = 4;
int TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_PASSWORDS = 5;
int CLIENT_OUT_OF_DATE = 6;
int SYNC_SETUP_INCOMPLETE = 7;
int UPM_BACKEND_OUTDATED = 8;
int OTHER_ERRORS = 128;
}
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// These are the actions users can taken on error cards, messages, and notifications.
// Keep in sync with SyncErrorUiAction enum in sync/enums.xml, and SyncErrorPromptUIAction enum
// in signin/enums.xml.
// LINT.IfChange(SyncErrorUiAction)
@IntDef({
ErrorUiAction.SHOWN,
ErrorUiAction.DISMISSED,
ErrorUiAction.BUTTON_CLICKED,
ErrorUiAction.NUM_ENTRIES
})
@Retention(RetentionPolicy.SOURCE)
public @interface ErrorUiAction {
int SHOWN = 0;
int DISMISSED = 1;
int BUTTON_CLICKED = 2;
int NUM_ENTRIES = 3;
}
// LINT.ThenChange(/tools/metrics/histograms/metadata/sync/enums.xml:SyncErrorUiAction)
// Class to wrap the details of an error card.
public static class ErrorCardDetails {
public @StringRes int message;
public @StringRes int buttonLabel;
public ErrorCardDetails(@StringRes int message, @StringRes int buttonLabel) {
this.message = message;
this.buttonLabel = buttonLabel;
}
}
/** Returns the type of the sync error, for syncing users. */
public static @SyncError int getSyncError(Profile profile) {
assert profile != null;
SyncService syncService = SyncServiceFactory.getForProfile(profile);
if (syncService == null) {
return SyncError.NO_ERROR;
}
if (!syncService.hasSyncConsent()) {
return SyncError.NO_ERROR;
}
if (!syncService.isInitialSyncFeatureSetupComplete()) {
return SyncError.SYNC_SETUP_INCOMPLETE;
}
return getCommonError(profile);
}
/**
* Gets hint message to resolve sync error.
*
* @param context The application context.
* @param error The sync error.
*/
public static String getSyncErrorHint(Context context, @SyncError int error) {
switch (error) {
case SyncError.AUTH_ERROR:
return context.getString(R.string.hint_sync_auth_error_modern);
case SyncError.CLIENT_OUT_OF_DATE:
return context.getString(
R.string.hint_client_out_of_date, BuildInfo.getInstance().hostPackageLabel);
case SyncError.OTHER_ERRORS:
return context.getString(R.string.hint_other_sync_errors);
case SyncError.PASSPHRASE_REQUIRED:
return context.getString(R.string.hint_passphrase_required);
case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_EVERYTHING:
return context.getString(R.string.hint_sync_retrieve_keys_for_everything);
case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_PASSWORDS:
return context.getString(R.string.hint_sync_retrieve_keys_for_passwords);
case SyncError.TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_EVERYTHING:
return context.getString(R.string.hint_sync_recoverability_degraded_for_everything);
case SyncError.TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_PASSWORDS:
return context.getString(R.string.hint_sync_recoverability_degraded_for_passwords);
case SyncError.SYNC_SETUP_INCOMPLETE:
return context.getString(R.string.hint_sync_settings_not_confirmed_description);
case SyncError.UPM_BACKEND_OUTDATED:
return context.getString(R.string.sync_error_card_outdated_gms);
case SyncError.NO_ERROR:
default:
return null;
}
}
/**
* Gets the title for a sync error.
* @param context The application context.
* @param error The sync error.
*/
public static String getSyncErrorCardTitle(Context context, @SyncError int error) {
switch (error) {
case SyncError.AUTH_ERROR:
case SyncError.CLIENT_OUT_OF_DATE:
case SyncError.OTHER_ERRORS:
case SyncError.PASSPHRASE_REQUIRED:
case SyncError.SYNC_SETUP_INCOMPLETE:
case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_EVERYTHING:
return context.getString(R.string.sync_error_card_title);
case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_PASSWORDS:
return context.getString(R.string.password_sync_error_summary);
case SyncError.TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_EVERYTHING:
case SyncError.TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_PASSWORDS:
return context.getString(R.string.sync_needs_verification_title);
case SyncError.UPM_BACKEND_OUTDATED:
return context.getString(R.string.sync_error_outdated_gms);
case SyncError.NO_ERROR:
default:
return null;
}
}
public static @Nullable String getSyncErrorCardButtonLabel(
Context context, @SyncError int error) {
switch (error) {
case SyncError.AUTH_ERROR:
case SyncError.OTHER_ERRORS:
// Both these errors should be resolved by signing the user again.
return context.getString(R.string.auth_error_card_button);
case SyncError.CLIENT_OUT_OF_DATE:
return context.getString(
R.string.client_out_of_date_error_card_button,
BuildInfo.getInstance().hostPackageLabel);
case SyncError.PASSPHRASE_REQUIRED:
return context.getString(R.string.passphrase_required_error_card_button);
case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_EVERYTHING:
case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_PASSWORDS:
case SyncError.TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_EVERYTHING:
case SyncError.TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_PASSWORDS:
return context.getString(R.string.trusted_vault_error_card_button);
case SyncError.SYNC_SETUP_INCOMPLETE:
return context.getString(R.string.sync_promo_turn_on_sync);
case SyncError.UPM_BACKEND_OUTDATED:
return context.getString(R.string.password_manager_outdated_gms_positive_button);
case SyncError.NO_ERROR:
default:
return null;
}
}
/** Return a short summary of the current sync status. */
public static String getSyncStatusSummary(Context context, Profile profile) {
SyncService syncService = SyncServiceFactory.getForProfile(profile);
if (syncService == null) {
return context.getString(R.string.sync_off);
}
if (!syncService.hasSyncConsent()) {
// There is no account with sync consent available.
return context.getString(R.string.sync_off);
}
if (syncService.isSyncDisabledByEnterprisePolicy()) {
return context.getString(R.string.sync_is_disabled_by_administrator);
}
if (!syncService.isInitialSyncFeatureSetupComplete()) {
return context.getString(R.string.sync_settings_not_confirmed);
}
if (syncService.getAuthError() != GoogleServiceAuthError.State.NONE) {
return getSyncStatusSummaryForAuthError(context, syncService.getAuthError());
}
if (syncService.requiresClientUpgrade()) {
return context.getString(
R.string.sync_error_upgrade_client, BuildInfo.getInstance().hostPackageLabel);
}
if (syncService.hasUnrecoverableError()) {
return context.getString(R.string.sync_error_generic);
}
if (syncService.getSelectedTypes().isEmpty()) {
return context.getString(R.string.sync_data_types_off);
}
if (!syncService.isSyncFeatureActive()) {
return context.getString(R.string.sync_setup_progress);
}
if (syncService.isPassphraseRequiredForPreferredDataTypes()) {
return context.getString(R.string.sync_need_passphrase);
}
if (syncService.isTrustedVaultKeyRequiredForPreferredDataTypes()) {
return syncService.isEncryptEverythingEnabled()
? context.getString(R.string.sync_error_card_title)
: context.getString(R.string.password_sync_error_summary);
}
if (syncService.isTrustedVaultRecoverabilityDegraded()) {
return context.getString(R.string.sync_needs_verification_title);
}
if (syncService.getSelectedTypes().contains(UserSelectableType.PASSWORDS)
&& PasswordManagerUtilBridge.isGmsCoreUpdateRequired(
UserPrefs.get(profile), syncService)) {
return context.getString(R.string.sync_error_outdated_gms);
}
return context.getString(R.string.sync_on);
}
/**
* Gets the sync status summary for a given {@link GoogleServiceAuthError.State}.
* @param context The application context, used by the method to get string resources.
* @param state Must not be GoogleServiceAuthError.State.None.
*/
private static String getSyncStatusSummaryForAuthError(
Context context, @GoogleServiceAuthError.State int state) {
switch (state) {
case GoogleServiceAuthError.State.INVALID_GAIA_CREDENTIALS:
return context.getString(R.string.sync_error_ga);
case GoogleServiceAuthError.State.CONNECTION_FAILED:
return context.getString(R.string.sync_error_connection);
case GoogleServiceAuthError.State.SERVICE_UNAVAILABLE:
return context.getString(R.string.sync_error_service_unavailable);
case GoogleServiceAuthError.State.REQUEST_CANCELED:
case GoogleServiceAuthError.State.UNEXPECTED_SERVICE_RESPONSE:
case GoogleServiceAuthError.State.SERVICE_ERROR:
return context.getString(R.string.sync_error_generic);
case GoogleServiceAuthError.State.NONE:
assert false : "No summary if there's no auth error";
return "";
default:
assert false : "Unknown auth error state";
return "";
}
}
/** Returns an icon that represents the current sync state. */
public static @Nullable Drawable getSyncStatusIcon(Context context, Profile profile) {
SyncService syncService = SyncServiceFactory.getForProfile(profile);
if (syncService == null
|| !syncService.hasSyncConsent()
|| syncService.getSelectedTypes().isEmpty()
|| syncService.isSyncDisabledByEnterprisePolicy()) {
return AppCompatResources.getDrawable(context, R.drawable.ic_sync_off_48dp);
}
if (getSyncError(profile) != SyncError.NO_ERROR) {
return AppCompatResources.getDrawable(context, R.drawable.ic_sync_error_48dp);
}
return AppCompatResources.getDrawable(context, R.drawable.ic_sync_on_48dp);
}
/**
* Creates a wrapper around {@link Runnable} that calls the runnable only if
* {@link PreferenceFragmentCompat} is still in resumed state. Click events that arrive after
* the fragment has been paused will be ignored. See http://b/5983282.
* @param fragment The fragment that hosts the preference.
* @param runnable The runnable to call from {@link Preference.OnPreferenceClickListener}.
*/
static Preference.OnPreferenceClickListener toOnClickListener(
PreferenceFragmentCompat fragment, Runnable runnable) {
return preference -> {
if (!fragment.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;
}
runnable.run();
return false;
};
}
/**
* Opens web dashboard to specified url in a custom tab.
* @param activity The activity to use for starting the intent.
* @param url The url link to open in the custom tab.
*/
private static void openCustomTabWithURL(Activity activity, String url) {
CustomTabsIntent customTabIntent =
new CustomTabsIntent.Builder().setShowTitle(false).build();
customTabIntent.intent.setData(Uri.parse(url));
Intent intent =
LaunchIntentDispatcher.createCustomTabActivityIntent(
activity, customTabIntent.intent);
intent.setPackage(activity.getPackageName());
intent.putExtra(CustomTabIntentDataProvider.EXTRA_UI_TYPE, CustomTabsUiType.DEFAULT);
intent.putExtra(Browser.EXTRA_APPLICATION_ID, activity.getPackageName());
IntentUtils.addTrustedIntentExtras(intent);
IntentUtils.safeStartActivity(activity, intent);
}
/**
* Opens web dashboard to manage sync in a custom tab.
*
* @param activity The activity to use for starting the intent.
*/
public static void openSyncDashboard(Activity activity) {
// TODO(crbug.com/41450409): Create a builder for custom tab intents.
openCustomTabWithURL(activity, ChromeStringConstants.SYNC_DASHBOARD_URL);
}
/**
* Opens web dashboard to manage google account in a custom tab.
*
* <p>Callers should ensure the current account has sync consent prior to calling.
*
* @param activity The activity to use for starting the intent.
*/
public static void openGoogleMyAccount(Activity activity) {
RecordUserAction.record("SyncPreferences_ManageGoogleAccountClicked");
openCustomTabWithURL(activity, MY_ACCOUNT_URL);
}
/**
* Upon promise completion, opens a dialog by starting the intent representing a user action
* required for managing a trusted vault.
*
* @param fragment Fragment to use when starting the dialog.
* @param accountInfo Account representing the user.
* @param requestCode Arbitrary request code that upon completion will be passed back via
* Fragment.onActivityResult().
* @param pendingIntentPromise promise that provides the intent to be started.
*/
private static void openTrustedVaultDialogForPendingIntent(
Fragment fragment,
CoreAccountInfo accountInfo,
int requestCode,
Promise<PendingIntent> pendingIntentPromise) {
pendingIntentPromise.then(
(pendingIntent) -> {
try {
// startIntentSenderForResult() will fail if the fragment is
// already gone, see crbug.com/1362141.
if (!fragment.isAdded()) {
return;
}
fragment.startIntentSenderForResult(
pendingIntent.getIntentSender(),
requestCode,
/* fillInIntent= */ null,
/* flagsMask= */ 0,
/* flagsValues= */ 0,
/* extraFlags= */ 0,
/* options= */ null);
} catch (IntentSender.SendIntentException exception) {
Log.w(
TAG,
"Error sending trusted vault intent for code ",
requestCode,
": ",
exception);
}
},
(exception) -> {
Log.e(
TAG,
"Error opening trusted vault dialog for code ",
requestCode,
": ",
exception);
});
}
/**
* Displays a UI that allows the user to reauthenticate and retrieve the sync encryption keys
* from a trusted vault.
*
* @param fragment Fragment to use when starting the dialog.
* @param accountInfo Account representing the user.
* @param requestCode Arbitrary request code that upon completion will be passed back via
* Fragment.onActivityResult().
*/
public static void openTrustedVaultKeyRetrievalDialog(
Fragment fragment, CoreAccountInfo accountInfo, int requestCode) {
TrustedVaultClient.get()
.recordKeyRetrievalTrigger(TrustedVaultUserActionTriggerForUMA.SETTINGS);
openTrustedVaultDialogForPendingIntent(
fragment,
accountInfo,
requestCode,
TrustedVaultClient.get().createKeyRetrievalIntent(accountInfo));
}
/**
* Displays a UI that allows the user to improve recoverability of the trusted vault data,
* typically involving reauthentication.
*
* @param fragment Fragment to use when starting the dialog.
* @param accountInfo Account representing the user.
* @param requestCode Arbitrary request code that upon completion will be passed back via
* Fragment.onActivityResult().
*/
public static void openTrustedVaultRecoverabilityDegradedDialog(
Fragment fragment, CoreAccountInfo accountInfo, int requestCode) {
TrustedVaultClient.get()
.recordRecoverabilityDegradedFixTrigger(
TrustedVaultUserActionTriggerForUMA.SETTINGS);
openTrustedVaultDialogForPendingIntent(
fragment,
accountInfo,
requestCode,
TrustedVaultClient.get().createRecoverabilityDegradedIntent(accountInfo));
}
/**
* Displays a UI that allows the user to opt in into the trusted vault passphrase type.
*
* @param fragment Fragment to use when starting the dialog.
* @param accountInfo Account representing the user.
* @param requestCode Arbitrary request code that upon completion will be passed back via
* Fragment.onActivityResult().
*/
public static void openTrustedVaultOptInDialog(
Fragment fragment, CoreAccountInfo accountInfo, int requestCode) {
openTrustedVaultDialogForPendingIntent(
fragment,
accountInfo,
requestCode,
TrustedVaultClient.get().createOptInIntent(accountInfo));
}
/**
* Shows a toast indicating that sync is disabled for the account by the system administrator.
*
* @param context The context where the toast will be shown.
*/
public static void showSyncDisabledByAdministratorToast(Context context) {
Toast.makeText(
context,
context.getString(R.string.sync_is_disabled_by_administrator),
Toast.LENGTH_LONG)
.show();
}
/**
* Returns either the full name or the email address of a DisplayableProfileData according
* to preference. If the preferred string is not displayable, returns the other displayable
* string, or fallback to default string.
*
* This method is used by {@link Preference#setTitle(CharSequence)} callers.
*
* @param profileData DisplayableProfileData containing the user's full name and email address.
* @param context The context where the returned string is passed to setTitle(CharSequence).
* @param preference Whether the full name or the email is preferred.
*/
public static String getDisplayableFullNameOrEmailWithPreference(
DisplayableProfileData profileData, Context context, @TitlePreference int preference) {
final String fullName = profileData.getFullName();
final String accountEmail = profileData.getAccountEmail();
final String defaultString = context.getString(R.string.default_google_account_username);
final boolean canShowFullName = !TextUtils.isEmpty(fullName);
final boolean canShowEmailAddress = profileData.hasDisplayableEmailAddress();
// Both strings are not displayable, use generic string.
if (!canShowFullName && !canShowEmailAddress) {
return defaultString;
}
// Both strings are displayable, use the preferred one.
if (canShowFullName && canShowEmailAddress) {
switch (preference) {
case TitlePreference.FULL_NAME:
return fullName;
case TitlePreference.EMAIL:
return accountEmail;
default:
return defaultString;
}
}
// The preference cannot be fulfilled, use the other displayable string.
return canShowFullName ? fullName : accountEmail;
}
/**
* Returns the type of the sync error/identity error for signed-in non-syncing users.
* TODO(crbug.com/330290259): Merge this into getSyncError().
*/
public static @SyncError int getIdentityError(Profile profile) {
assert profile != null;
SyncService syncService = SyncServiceFactory.getForProfile(profile);
// TODO(crbug.com/40944114): Consider converting this to an assertion instead.
if (syncService == null) {
return SyncError.NO_ERROR;
}
// Do not show identity error if sync is enabled.
if (syncService.isSyncFeatureEnabled()) {
return SyncError.NO_ERROR;
}
// No error for not signed-in users.
if (!IdentityServicesProvider.get()
.getIdentityManager(profile)
.hasPrimaryAccount(ConsentLevel.SIGNIN)) {
return SyncError.NO_ERROR;
}
@SyncError int error = getCommonError(profile);
// Do not show identity error for unrecoverable errors, since they are not actionable.
// TODO(crbug.com/40944114): Remove these unused values after sync-to-signin transition.
if (error == SyncError.OTHER_ERRORS) {
return SyncError.NO_ERROR;
}
return error;
}
/** Returns the errors common to both getSyncError() and getIdentityError(). */
private static @SyncError int getCommonError(Profile profile) {
SyncService syncService = SyncServiceFactory.getForProfile(profile);
assert syncService != null;
if (syncService.getAuthError() == GoogleServiceAuthError.State.INVALID_GAIA_CREDENTIALS) {
return SyncError.AUTH_ERROR;
}
if (syncService.requiresClientUpgrade()) {
return SyncError.CLIENT_OUT_OF_DATE;
}
if (syncService.getAuthError() != GoogleServiceAuthError.State.NONE
|| syncService.hasUnrecoverableError()) {
return SyncError.OTHER_ERRORS;
}
if (syncService.isEngineInitialized()
&& syncService.isPassphraseRequiredForPreferredDataTypes()) {
return SyncError.PASSPHRASE_REQUIRED;
}
if (syncService.isEngineInitialized()
&& syncService.isTrustedVaultKeyRequiredForPreferredDataTypes()) {
return syncService.isEncryptEverythingEnabled()
? SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_EVERYTHING
: SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_PASSWORDS;
}
if (syncService.isEngineInitialized()
&& syncService.isTrustedVaultRecoverabilityDegraded()) {
return syncService.isEncryptEverythingEnabled()
? SyncError.TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_EVERYTHING
: SyncError.TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_PASSWORDS;
}
// This error doesn't lead to a SyncErrorMessage and thus should be thrown at the last.
// Otherwise this would block other errors from showing the SyncErrorMessage.
// TODO(crbug.com/345217772): Look for a better alternative. Maybe return all the sync
// errors at the moment and not just one.
if (syncService.getSelectedTypes().contains(UserSelectableType.PASSWORDS)
&& PasswordManagerUtilBridge.isGmsCoreUpdateRequired(
UserPrefs.get(profile), syncService)) {
return SyncError.UPM_BACKEND_OUTDATED;
}
return SyncError.NO_ERROR;
}
/**
* Gets text for the identity error card.
*
* @param error The identity error.
* @return A ErrorCardDetails instance containing the error message and the button text for the
* identity error.
*/
public static ErrorCardDetails getIdentityErrorErrorCardDetails(@SyncError int error) {
switch (error) {
case SyncError.PASSPHRASE_REQUIRED:
return new ErrorCardDetails(
R.string.identity_error_card_passphrase_required,
R.string.identity_error_card_button_passphrase_required);
case SyncError.CLIENT_OUT_OF_DATE:
return new ErrorCardDetails(
R.string.identity_error_card_client_out_of_date,
R.string.identity_error_card_button_client_out_of_date);
case SyncError.AUTH_ERROR:
return new ErrorCardDetails(
R.string.identity_error_card_auth_error,
R.string.identity_error_card_button_verify);
case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_EVERYTHING:
return new ErrorCardDetails(
R.string.identity_error_card_sync_retrieve_keys_for_everything,
R.string.identity_error_card_button_verify);
case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_PASSWORDS:
return new ErrorCardDetails(
R.string.identity_error_card_sync_retrieve_keys_for_passwords,
R.string.identity_error_card_button_verify);
case SyncError.TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_EVERYTHING:
return new ErrorCardDetails(
R.string.identity_error_card_sync_recoverability_degraded_for_everything,
R.string.identity_error_card_button_verify);
case SyncError.TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_PASSWORDS:
return new ErrorCardDetails(
R.string.identity_error_card_sync_recoverability_degraded_for_passwords,
R.string.identity_error_card_button_verify);
case SyncError.UPM_BACKEND_OUTDATED:
return new ErrorCardDetails(
R.string.sync_error_card_outdated_gms,
R.string.password_manager_outdated_gms_positive_button);
case SyncError.OTHER_ERRORS:
case SyncError.SYNC_SETUP_INCOMPLETE:
case SyncError.NO_ERROR:
assert false; // NOTREACHED()
// fall through
default:
return null;
}
}
/**
* Gets the corresponding histogram name suffix for the error.
*
* @param error Error reason.
* @return Suffix for the histogram.
*/
public static String getHistogramSuffixForError(@SyncError int error) {
assert error != SyncError.NO_ERROR;
switch (error) {
case SyncError.AUTH_ERROR:
return ".AuthError";
case SyncError.PASSPHRASE_REQUIRED:
return ".PassphraseRequired";
case SyncError.SYNC_SETUP_INCOMPLETE:
return ".SyncSetupIncomplete";
case SyncError.CLIENT_OUT_OF_DATE:
return ".ClientOutOfDate";
case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_EVERYTHING:
return ".TrustedVaultKeyRequiredForEverything";
case SyncError.TRUSTED_VAULT_KEY_REQUIRED_FOR_PASSWORDS:
return ".TrustedVaultKeyRequiredForPasswords";
case SyncError.TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_EVERYTHING:
return ".TrustedVaultRecoverabilityDegradedForEverything";
case SyncError.TRUSTED_VAULT_RECOVERABILITY_DEGRADED_FOR_PASSWORDS:
return ".TrustedVaultRecoverabilityDegradedForPasswords";
case SyncError.UPM_BACKEND_OUTDATED:
return ".UpmBackendOutdated";
case SyncError.OTHER_ERRORS:
return ".OtherErrors";
default:
assert false;
return "";
}
}
}