blob: 6c43a2279102bf0d59a6568622449eca6dd9131a [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/settings/model/sync/utils/sync_util.h"
#import "base/metrics/histogram_functions.h"
#import "base/notreached.h"
#import "base/strings/strcat.h"
#import "base/strings/utf_string_conversions.h"
#import "components/infobars/core/infobar_manager.h"
#import "components/signin/public/base/signin_switches.h"
#import "components/signin/public/identity_manager/account_info.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "components/strings/grit/components_strings.h"
#import "components/sync/base/features.h"
#import "components/sync/service/sync_service.h"
#import "ios/chrome/browser/infobars/model/infobar_manager_impl.h"
#import "ios/chrome/browser/settings/model/sync/utils/account_error_ui_info.h"
#import "ios/chrome/browser/settings/model/sync/utils/identity_error_util.h"
#import "ios/chrome/browser/settings/model/sync/utils/sync_error_infobar_delegate.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/show_signin_command.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util.h"
namespace {
constexpr char kSyncErrorInfobarDisplayedHistogramName[] =
"Sync.SyncErrorInfobarDisplayed2";
// Enumerated constants for logging when a sign-in error infobar was shown
// to the user. This was added for crbug/265352 to quantify how often this
// bug shows up in the wild. The logged histogram count should be interpreted
// as a ratio of the number of active sync users.
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// LINT.IfChange(SyncErrorInfobarTypes)
enum InfobarSyncError : uint8_t {
SYNC_SIGN_IN_NEEDS_UPDATE = 1,
// DEPRECATED. No longer recorded.
// SYNC_SERVICE_UNAVAILABLE = 2
SYNC_NEEDS_PASSPHRASE = 3,
// SYNC_UNRECOVERABLE_ERROR = 4, (deprecated)
SYNC_SYNC_SETTINGS_NOT_CONFIRMED = 5,
SYNC_NEEDS_TRUSTED_VAULT_KEY = 6,
SYNC_TRUSTED_VAULT_RECOVERABILITY_DEGRADED = 7,
kMaxValue = SYNC_TRUSTED_VAULT_RECOVERABILITY_DEGRADED,
};
// LINT.ThenChange(/tools/metrics/histograms/metadata/sync/enums.xml:SyncErrorInfobarTypes)
// Converts `syncer::SyncService::UserActionableError` to `InfobarSyncError`.
// Returns `std::nullopt` when the error is `kNone`.
std::optional<InfobarSyncError> InfobarSyncErrorFromUserActionableError(
syncer::SyncService::UserActionableError error) {
switch (error) {
case syncer::SyncService::UserActionableError::kNone:
return std::nullopt;
case syncer::SyncService::UserActionableError::kSignInNeedsUpdate:
return SYNC_SIGN_IN_NEEDS_UPDATE;
case syncer::SyncService::UserActionableError::kNeedsPassphrase:
return SYNC_NEEDS_PASSPHRASE;
case syncer::SyncService::UserActionableError::
kNeedsTrustedVaultKeyForPasswords:
case syncer::SyncService::UserActionableError::
kNeedsTrustedVaultKeyForEverything:
return SYNC_NEEDS_TRUSTED_VAULT_KEY;
case syncer::SyncService::UserActionableError::
kTrustedVaultRecoverabilityDegradedForPasswords:
case syncer::SyncService::UserActionableError::
kTrustedVaultRecoverabilityDegradedForEverything:
return SYNC_TRUSTED_VAULT_RECOVERABILITY_DEGRADED;
// TODO(crbug.com/370026230): Update this case once GetAccountErrorUIInfo()
// returns a non-nil value for it.
case syncer::SyncService::UserActionableError::kNeedsClientUpgrade:
NOTREACHED();
}
}
// Gets the the title of the identity error info bar for the given `error`.
std::u16string GetIdentityErrorInfoBarTitle(
syncer::SyncService::UserActionableError error) {
switch (error) {
case syncer::SyncService::UserActionableError::kSignInNeedsUpdate:
return l10n_util::GetStringUTF16(
IDS_IOS_IDENTITY_ERROR_INFOBAR_VERIFY_ITS_YOU_TITLE);
case syncer::SyncService::UserActionableError::kNeedsPassphrase:
return l10n_util::GetStringUTF16(
IDS_IOS_IDENTITY_ERROR_INFOBAR_ENTER_PASSPHRASE_TITLE);
case syncer::SyncService::UserActionableError::
kNeedsTrustedVaultKeyForPasswords:
return base::FeatureList::IsEnabled(
syncer::kSyncTrustedVaultInfobarMessageImprovements)
? l10n_util::GetStringUTF16(
IDS_IOS_IDENTITY_ERROR_INFOBAR_GET_ALL_YOUR_PASSWORDS_TITLE)
: l10n_util::GetStringUTF16(
IDS_IOS_IDENTITY_ERROR_INFOBAR_VERIFY_ITS_YOU_TITLE);
case syncer::SyncService::UserActionableError::
kNeedsTrustedVaultKeyForEverything:
case syncer::SyncService::UserActionableError::
kTrustedVaultRecoverabilityDegradedForPasswords:
case syncer::SyncService::UserActionableError::
kTrustedVaultRecoverabilityDegradedForEverything:
return l10n_util::GetStringUTF16(
IDS_IOS_IDENTITY_ERROR_INFOBAR_VERIFY_ITS_YOU_TITLE);
case syncer::SyncService::UserActionableError::kNone:
case syncer::SyncService::UserActionableError::kNeedsClientUpgrade:
NOTREACHED();
}
}
// Gets the message of the identity error info bar.
NSString* GetIdentityErrorInfoBarMessage(
syncer::SyncService::UserActionableError error,
const std::u16string& email) {
switch (error) {
case syncer::SyncService::UserActionableError::kSignInNeedsUpdate:
case syncer::SyncService::UserActionableError::kNeedsPassphrase:
case syncer::SyncService::UserActionableError::
kNeedsTrustedVaultKeyForEverything:
return l10n_util::GetNSString(
IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_YOUR_CHROME_DATA_MESSAGE);
case syncer::SyncService::UserActionableError::
kNeedsTrustedVaultKeyForPasswords: {
return base::FeatureList::IsEnabled(
syncer::kSyncTrustedVaultInfobarMessageImprovements)
? l10n_util::GetNSString(
IDS_IOS_IDENTITY_ERROR_INFOBAR_GET_ALL_YOUR_PASSWORDS_ON_THIS_DEVICE)
: l10n_util::GetNSStringF(
IDS_IOS_IDENTITY_ERROR_INFOBAR_KEEP_USING_PASSWORDS_MESSAGE_WITH_EMAIL,
email);
}
case syncer::SyncService::UserActionableError::
kTrustedVaultRecoverabilityDegradedForPasswords:
return l10n_util::GetNSString(
IDS_IOS_IDENTITY_ERROR_INFOBAR_MAKE_SURE_YOU_CAN_ALWAYS_USE_PASSWORDS_MESSAGE);
case syncer::SyncService::UserActionableError::
kTrustedVaultRecoverabilityDegradedForEverything:
return l10n_util::GetNSString(
IDS_IOS_IDENTITY_ERROR_INFOBAR_MAKE_SURE_YOU_CAN_ALWAYS_USE_CHROME_DATA_MESSAGE);
case syncer::SyncService::UserActionableError::kNone:
case syncer::SyncService::UserActionableError::kNeedsClientUpgrade:
NOTREACHED();
}
}
NSString* GetIdentityErrorInfoBarButtonLabel(
syncer::SyncService::UserActionableError error) {
switch (error) {
case syncer::SyncService::UserActionableError::kSignInNeedsUpdate:
return l10n_util::GetNSString(
IDS_IOS_IDENTITY_ERROR_INFOBAR_VERIFY_BUTTON_LABEL);
case syncer::SyncService::UserActionableError::kNeedsPassphrase:
return l10n_util::GetNSString(
IDS_IOS_IDENTITY_ERROR_INFOBAR_ENTER_BUTTON_LABEL);
case syncer::SyncService::UserActionableError::
kNeedsTrustedVaultKeyForPasswords:
return base::FeatureList::IsEnabled(
syncer::kSyncTrustedVaultInfobarMessageImprovements)
? l10n_util::GetNSString(
IDS_IOS_IDENTITY_ERROR_INFOBAR_OKAY_BUTTON_LABEL)
: l10n_util::GetNSString(
IDS_IOS_IDENTITY_ERROR_INFOBAR_VERIFY_BUTTON_LABEL);
case syncer::SyncService::UserActionableError::
kNeedsTrustedVaultKeyForEverything:
case syncer::SyncService::UserActionableError::
kTrustedVaultRecoverabilityDegradedForPasswords:
case syncer::SyncService::UserActionableError::
kTrustedVaultRecoverabilityDegradedForEverything:
return l10n_util::GetNSString(
IDS_IOS_IDENTITY_ERROR_INFOBAR_VERIFY_BUTTON_LABEL);
case syncer::SyncService::UserActionableError::kNone:
case syncer::SyncService::UserActionableError::kNeedsClientUpgrade:
NOTREACHED();
}
}
std::string GetSyncErrorInfobarHistogramSuffix(
SyncErrorInfoBarTrigger trigger) {
switch (trigger) {
case SyncErrorInfoBarTrigger::kNewTabOpened:
return ".NewTab";
case SyncErrorInfoBarTrigger::kPasswordFormParsed:
return ".PasswordForm";
}
NOTREACHED();
}
} // namespace
NSString* GetSyncErrorDescriptionForSyncService(
syncer::SyncService* syncService) {
DCHECK(syncService);
switch (syncService->GetUserActionableError()) {
case syncer::SyncService::UserActionableError::kNone:
return nil;
case syncer::SyncService::UserActionableError::kSignInNeedsUpdate:
return l10n_util::GetNSString(IDS_IOS_SYNC_LOGIN_INFO_OUT_OF_DATE);
case syncer::SyncService::UserActionableError::kNeedsPassphrase:
return l10n_util::GetNSString(IDS_IOS_SYNC_ENCRYPTION_DESCRIPTION);
case syncer::SyncService::UserActionableError::
kNeedsTrustedVaultKeyForEverything:
return l10n_util::GetNSString(IDS_IOS_SYNC_ERROR_DESCRIPTION);
case syncer::SyncService::UserActionableError::
kNeedsTrustedVaultKeyForPasswords:
// The encryption error affects passwords only as per
// syncer::AlwaysEncryptedUserTypes().
return l10n_util::GetNSString(IDS_IOS_SYNC_PASSWORDS_ERROR_DESCRIPTION);
case syncer::SyncService::UserActionableError::
kTrustedVaultRecoverabilityDegradedForEverything:
return l10n_util::GetNSString(
IDS_IOS_GOOGLE_SERVICES_SETTINGS_SYNC_FIX_RECOVERABILITY_DEGRADED_FOR_EVERYTHING);
case syncer::SyncService::UserActionableError::
kTrustedVaultRecoverabilityDegradedForPasswords:
// The encryption error affects passwords only as per
// syncer::AlwaysEncryptedUserTypes().
return l10n_util::GetNSString(
IDS_IOS_GOOGLE_SERVICES_SETTINGS_SYNC_FIX_RECOVERABILITY_DEGRADED_FOR_PASSWORDS);
case syncer::SyncService::UserActionableError::kNeedsClientUpgrade:
// UI not implemented for this case.
return nil;
}
}
std::u16string GetSyncErrorInfoBarTitleForProfile(ProfileIOS* profile) {
DCHECK(profile);
syncer::SyncService* sync_service =
SyncServiceFactory::GetForProfile(profile);
DCHECK(sync_service);
if (GetAccountErrorUIInfo(sync_service) != nil) {
return GetIdentityErrorInfoBarTitle(sync_service->GetUserActionableError());
} else {
// There is no title in Sync error info bar.
return std::u16string();
}
}
NSString* GetSyncErrorMessageForProfile(ProfileIOS* profile) {
syncer::SyncService* syncService = SyncServiceFactory::GetForProfile(profile);
DCHECK(syncService);
const syncer::SyncService::UserActionableError error =
syncService->GetUserActionableError();
if (GetAccountErrorUIInfo(syncService) != nil) {
return GetIdentityErrorInfoBarMessage(
error, base::UTF8ToUTF16(syncService->GetAccountInfo().email));
}
switch (error) {
case syncer::SyncService::UserActionableError::kNone:
return nil;
case syncer::SyncService::UserActionableError::kSignInNeedsUpdate:
return l10n_util::GetNSString(IDS_IOS_SYNC_ERROR_INFO_OUT_OF_DATE);
case syncer::SyncService::UserActionableError::kNeedsPassphrase:
return l10n_util::GetNSString(IDS_IOS_SYNC_CONFIGURE_ENCRYPTION);
case syncer::SyncService::UserActionableError::
kNeedsTrustedVaultKeyForPasswords:
case syncer::SyncService::UserActionableError::
kNeedsTrustedVaultKeyForEverything:
case syncer::SyncService::UserActionableError::
kTrustedVaultRecoverabilityDegradedForPasswords:
case syncer::SyncService::UserActionableError::
kTrustedVaultRecoverabilityDegradedForEverything:
return GetSyncErrorDescriptionForSyncService(syncService);
case syncer::SyncService::UserActionableError::kNeedsClientUpgrade:
// UI not implemented for this case.
return nil;
}
}
NSString* GetSyncErrorButtonTitleForProfile(ProfileIOS* profile) {
DCHECK(profile);
syncer::SyncService* syncService = SyncServiceFactory::GetForProfile(profile);
DCHECK(syncService);
const syncer::SyncService::UserActionableError error =
syncService->GetUserActionableError();
if (GetAccountErrorUIInfo(syncService) != nil) {
return GetIdentityErrorInfoBarButtonLabel(error);
}
switch (error) {
case syncer::SyncService::UserActionableError::kSignInNeedsUpdate:
return l10n_util::GetNSString(IDS_IOS_SYNC_UPDATE_CREDENTIALS_BUTTON);
case syncer::SyncService::UserActionableError::kNeedsPassphrase:
return l10n_util::GetNSString(IDS_IOS_SYNC_ENTER_PASSPHRASE_BUTTON);
case syncer::SyncService::UserActionableError::
kNeedsTrustedVaultKeyForPasswords:
case syncer::SyncService::UserActionableError::
kNeedsTrustedVaultKeyForEverything:
case syncer::SyncService::UserActionableError::
kTrustedVaultRecoverabilityDegradedForPasswords:
case syncer::SyncService::UserActionableError::
kTrustedVaultRecoverabilityDegradedForEverything:
return l10n_util::GetNSString(IDS_IOS_SYNC_VERIFY_ITS_YOU_BUTTON);
case syncer::SyncService::UserActionableError::kNone:
// UI not implemented for this case.
case syncer::SyncService::UserActionableError::kNeedsClientUpgrade:
return nil;
}
}
bool ShouldShowSyncSettings(syncer::SyncService::UserActionableError error) {
switch (error) {
case syncer::SyncService::UserActionableError::kNone:
return true;
case syncer::SyncService::UserActionableError::kSignInNeedsUpdate:
case syncer::SyncService::UserActionableError::kNeedsPassphrase:
case syncer::SyncService::UserActionableError::
kNeedsTrustedVaultKeyForPasswords:
case syncer::SyncService::UserActionableError::
kNeedsTrustedVaultKeyForEverything:
case syncer::SyncService::UserActionableError::
kTrustedVaultRecoverabilityDegradedForPasswords:
case syncer::SyncService::UserActionableError::
kTrustedVaultRecoverabilityDegradedForEverything:
// UI not implemented for this case.
case syncer::SyncService::UserActionableError::kNeedsClientUpgrade:
return false;
}
}
bool DisplaySyncErrors(ProfileIOS* profile,
web::WebState* web_state,
id<SyncPresenter> presenter,
SyncErrorInfoBarTrigger trigger) {
// Avoid displaying sync errors on incognito tabs.
if (profile->IsOffTheRecord()) {
return false;
}
syncer::SyncService* syncService = SyncServiceFactory::GetForProfile(profile);
if (!syncService) {
return false;
}
if (GetAccountErrorUIInfo(syncService) == nil) {
// If the identity error info bar isn't used, fallback to the Sync error
// info bar.
// Avoid showing the sync error info bar when sync changes are still
// pending. This is particularely requires during first run when the
// advanced sign-in settings are being presented on the NTP before sync
// changes being committed.
if (syncService->IsSetupInProgress()) {
return false;
}
signin::IdentityManager* identityManager =
IdentityManagerFactory::GetForProfile(profile);
if (!identityManager->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
return false;
}
}
std::optional<InfobarSyncError> infobarSyncError =
InfobarSyncErrorFromUserActionableError(
syncService->GetUserActionableError());
if (!infobarSyncError.has_value()) {
return false;
}
DCHECK(web_state);
infobars::InfoBarManager* infoBarManager =
InfoBarManagerImpl::FromWebState(web_state);
DCHECK(infoBarManager);
bool infobar_displayed = SyncErrorInfoBarDelegate::Create(
infoBarManager, profile, presenter, trigger);
if (infobar_displayed) {
// Logs when an infobar is shown to user. See crbug.com/265352.
base::UmaHistogramEnumeration(kSyncErrorInfobarDisplayedHistogramName,
*infobarSyncError);
base::UmaHistogramEnumeration(
base::StrCat({kSyncErrorInfobarDisplayedHistogramName,
GetSyncErrorInfobarHistogramSuffix(trigger)}),
*infobarSyncError);
}
return infobar_displayed;
}
void LogSyncErrorInfobarDismissed(
syncer::SyncService::UserActionableError error) {
std::optional<InfobarSyncError> infobarSyncError =
InfobarSyncErrorFromUserActionableError(error);
if (infobarSyncError.has_value()) {
base::UmaHistogramEnumeration("Sync.SyncErrorInfobarDismissed",
*infobarSyncError);
}
}