blob: a206b520d1353c2dd5eef03e1d850ae0791e97f3 [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.
#import "ios/chrome/browser/ui/settings/google_services/google_services_settings_mediator.h"
#include "base/auto_reset.h"
#include "base/mac/foundation_util.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/safe_browsing/core/features.h"
#import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"
#include "components/sync/driver/sync_service.h"
#include "components/ukm/ios/features.h"
#include "components/unified_consent/pref_names.h"
#include "ios/chrome/browser/pref_names.h"
#import "ios/chrome/browser/signin/authentication_service.h"
#import "ios/chrome/browser/signin/authentication_service_factory.h"
#include "ios/chrome/browser/signin/chrome_identity_service_observer_bridge.h"
#include "ios/chrome/browser/sync/sync_observer_bridge.h"
#import "ios/chrome/browser/ui/authentication/cells/table_view_account_item.h"
#import "ios/chrome/browser/ui/authentication/resized_avatar_cache.h"
#import "ios/chrome/browser/ui/settings/cells/account_sign_in_item.h"
#import "ios/chrome/browser/ui/settings/cells/settings_image_detail_text_item.h"
#import "ios/chrome/browser/ui/settings/cells/settings_switch_item.h"
#import "ios/chrome/browser/ui/settings/cells/sync_switch_item.h"
#import "ios/chrome/browser/ui/settings/google_services/google_services_settings_command_handler.h"
#import "ios/chrome/browser/ui/settings/google_services/google_services_settings_constants.h"
#import "ios/chrome/browser/ui/settings/sync/utils/sync_util.h"
#import "ios/chrome/browser/ui/settings/utils/observable_boolean.h"
#import "ios/chrome/browser/ui/settings/utils/pref_backed_boolean.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_image_item.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_info_button_item.h"
#import "ios/chrome/browser/ui/ui_feature_flags.h"
#import "ios/chrome/browser/ui/util/uikit_ui_util.h"
#import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
#include "ios/chrome/grit/ios_chromium_strings.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/public/provider/chrome/browser/signin/chrome_identity.h"
#include "ui/base/l10n/l10n_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using l10n_util::GetNSString;
using safe_browsing::kSafeBrowsingAvailableOnIOS;
typedef NSArray<TableViewItem*>* ItemArray;
namespace {
NSString* const kBetterSearchAndBrowsingItemAccessibilityID =
@"betterSearchAndBrowsingItem_switch";
// List of sections.
typedef NS_ENUM(NSInteger, SectionIdentifier) {
IdentitySectionIdentifier = kSectionIdentifierEnumZero,
SyncSectionIdentifier,
NonPersonalizedSectionIdentifier,
};
// List of items. For implementation details in
// GoogleServicesSettingsViewController, two SyncSwitchItem items should not
// share the same type. The cell UISwitch tag is used to save the item type, and
// when the user taps on the switch, this tag is used to retreive the item based
// on the type.
typedef NS_ENUM(NSInteger, ItemType) {
// IdentitySectionIdentifier section.
IdentityItemType = kItemTypeEnumZero,
ManageGoogleAccountItemType,
// SyncSectionIdentifier section.
SignInItemType,
RestartAuthenticationFlowErrorItemType,
ReauthDialogAsSyncIsInAuthErrorItemType,
ShowPassphraseDialogErrorItemType,
SyncNeedsTrustedVaultKeyErrorItemType,
SyncDisabledByAdministratorErrorItemType,
SyncSettingsNotCofirmedErrorItemType,
SyncChromeDataItemType,
ManageSyncItemType,
// NonPersonalizedSectionIdentifier section.
AutocompleteSearchesAndURLsItemType,
AutocompleteSearchesAndURLsManagedItemType,
SafeBrowsingItemType,
SafeBrowsingManagedItemType,
ImproveChromeItemType,
BetterSearchAndBrowsingItemType,
ItemTypePasswordLeakCheckSwitch,
};
// Enterprise icon.
NSString* kGoogleServicesEnterpriseImage = @"google_services_enterprise";
// Sync error icon.
NSString* kGoogleServicesSyncErrorImage = @"google_services_sync_error";
} // namespace
@interface GoogleServicesSettingsMediator () <
BooleanObserver,
ChromeIdentityServiceObserver,
IdentityManagerObserverBridgeDelegate,
SyncObserverModelBridge> {
// Sync observer.
std::unique_ptr<SyncObserverBridge> _syncObserver;
// Identity manager observer.
std::unique_ptr<signin::IdentityManagerObserverBridge>
_identityManagerObserverBridge;
// Chrome identity observer.
std::unique_ptr<ChromeIdentityServiceObserverBridge> _identityServiceObserver;
}
// Returns YES if the user is authenticated.
@property(nonatomic, assign, readonly) BOOL isAuthenticated;
// Returns YES if Sync settings has been confirmed.
@property(nonatomic, assign, readonly) BOOL isSyncSettingsConfirmed;
// Returns YES if the user cannot turn on sync for enterprise policy reasons.
@property(nonatomic, assign, readonly) BOOL isSyncDisabledByAdministrator;
// Returns YES if the user is allowed to turn on sync (even if there is a sync
// error).
@property(nonatomic, assign, readonly) BOOL isSyncCanBeAvailable;
// Sync setup service.
@property(nonatomic, assign, readonly) SyncSetupService* syncSetupService;
// ** Identity section.
// Avatar cache.
@property(nonatomic, strong) ResizedAvatarCache* resizedAvatarCache;
// Account item.
@property(nonatomic, strong) TableViewAccountItem* accountItem;
// ** Sync section.
// YES if the impression of the Signin cell has already been recorded.
@property(nonatomic, assign) BOOL hasRecordedSigninImpression;
// Sync error item (in the sync section).
@property(nonatomic, strong) TableViewItem* syncErrorItem;
// Sync your Chrome data switch item.
@property(nonatomic, strong) SyncSwitchItem* syncChromeDataSwitchItem;
// Items to open "Manage sync" settings.
@property(nonatomic, strong) TableViewImageItem* manageSyncItem;
// ** Non personalized section.
// Preference value for the "Autocomplete searches and URLs" feature.
@property(nonatomic, strong, readonly)
PrefBackedBoolean* autocompleteSearchPreference;
// Preference value for the "Safe Browsing" feature.
@property(nonatomic, strong, readonly)
PrefBackedBoolean* safeBrowsingPreference;
// Preference value for the "Help improve Chromium's features" feature.
@property(nonatomic, strong, readonly)
PrefBackedBoolean* sendDataUsagePreference;
// Preference value for the "Help improve Chromium's features" for Wifi-Only.
// TODO(crbug.com/872101): Needs to create the UI to change from Wifi-Only to
// always
@property(nonatomic, strong, readonly)
PrefBackedBoolean* sendDataUsageWifiOnlyPreference;
// Preference value for the "Make searches and browsing better" feature.
@property(nonatomic, strong, readonly)
PrefBackedBoolean* anonymizedDataCollectionPreference;
// The observable boolean that binds to the password leak check settings
// state.
@property(nonatomic, strong, readonly)
PrefBackedBoolean* passwordLeakCheckEnabled;
// The item related to the switch for the automatic password leak detection
// setting.
@property(nonatomic, strong, null_resettable)
SettingsSwitchItem* passwordLeakCheckItem;
// All the items for the non-personalized section.
@property(nonatomic, strong, readonly) ItemArray nonPersonalizedItems;
// Pref service used to check if a specific pref is managed by enterprise
// policies.
@property(nonatomic, assign, readonly) PrefService* userPrefService;
@end
@implementation GoogleServicesSettingsMediator
@synthesize nonPersonalizedItems = _nonPersonalizedItems;
- (instancetype)initWithUserPrefService:(PrefService*)userPrefService
localPrefService:(PrefService*)localPrefService
syncSetupService:(SyncSetupService*)syncSetupService
mode:(GoogleServicesSettingsMode)mode {
self = [super init];
if (self) {
DCHECK(userPrefService);
DCHECK(localPrefService);
DCHECK(syncSetupService);
_mode = mode;
_syncSetupService = syncSetupService;
_userPrefService = userPrefService;
_autocompleteSearchPreference = [[PrefBackedBoolean alloc]
initWithPrefService:userPrefService
prefName:prefs::kSearchSuggestEnabled];
_autocompleteSearchPreference.observer = self;
_safeBrowsingPreference = [[PrefBackedBoolean alloc]
initWithPrefService:userPrefService
prefName:prefs::kSafeBrowsingEnabled];
_safeBrowsingPreference.observer = self;
_sendDataUsagePreference = [[PrefBackedBoolean alloc]
initWithPrefService:localPrefService
prefName:metrics::prefs::kMetricsReportingEnabled];
_sendDataUsagePreference.observer = self;
if (!base::FeatureList::IsEnabled(kUmaCellular)) {
// When flag is not, kMetricsReportingWifiOnly pref has not been
// initialized, so don't create a PrefBackedBoolean for it.
_sendDataUsageWifiOnlyPreference = [[PrefBackedBoolean alloc]
initWithPrefService:localPrefService
prefName:prefs::kMetricsReportingWifiOnly];
}
_passwordLeakCheckEnabled = [[PrefBackedBoolean alloc]
initWithPrefService:userPrefService
prefName:password_manager::prefs::
kPasswordLeakDetectionEnabled];
_passwordLeakCheckEnabled.observer = self;
_anonymizedDataCollectionPreference = [[PrefBackedBoolean alloc]
initWithPrefService:userPrefService
prefName:unified_consent::prefs::
kUrlKeyedAnonymizedDataCollectionEnabled];
_anonymizedDataCollectionPreference.observer = self;
_resizedAvatarCache = [[ResizedAvatarCache alloc] init];
}
return self;
}
#pragma mark - Loads identity section
// Loads the identity section.
- (void)loadIdentitySection {
self.accountItem = nil;
if (!self.isAuthenticated)
return;
[self createIdentitySection];
[self configureIdentityAccountItem];
}
// Creates the identity section.
- (void)createIdentitySection {
TableViewModel* model = self.consumer.tableViewModel;
[model insertSectionWithIdentifier:IdentitySectionIdentifier atIndex:0];
DCHECK(!self.accountItem);
self.accountItem =
[[TableViewAccountItem alloc] initWithType:IdentityItemType];
self.accountItem.accessibilityIdentifier =
kAccountListItemAccessibilityIdentifier;
if (self.mode == GoogleServicesSettingsModeAdvancedSigninSettings) {
self.accountItem.mode = TableViewAccountModeNonTappable;
} else {
self.accountItem.accessoryType =
UITableViewCellAccessoryDisclosureIndicator;
}
[model addItem:self.accountItem
toSectionWithIdentifier:IdentitySectionIdentifier];
TableViewImageItem* manageGoogleAccount =
[[TableViewImageItem alloc] initWithType:ManageGoogleAccountItemType];
manageGoogleAccount.title =
GetNSString(IDS_IOS_MANAGE_YOUR_GOOGLE_ACCOUNT_TITLE);
[model addItem:manageGoogleAccount
toSectionWithIdentifier:IdentitySectionIdentifier];
}
// Creates, removes or updates the identity section as needed. And notifies the
// consumer.
- (void)updateIdentitySectionAndNotifyConsumer {
TableViewModel* model = self.consumer.tableViewModel;
BOOL hasIdentitySection =
[model hasSectionForSectionIdentifier:IdentitySectionIdentifier];
if (!self.isAuthenticated) {
if (!hasIdentitySection) {
DCHECK(!self.accountItem);
return;
}
self.accountItem = nil;
NSInteger sectionIndex =
[model sectionForSectionIdentifier:IdentitySectionIdentifier];
[model removeSectionWithIdentifier:IdentitySectionIdentifier];
NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:sectionIndex];
[self.consumer deleteSections:indexSet];
return;
}
if (!hasIdentitySection) {
[self createIdentitySection];
NSInteger sectionIndex =
[model sectionForSectionIdentifier:IdentitySectionIdentifier];
NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:sectionIndex];
[self.consumer insertSections:indexSet];
}
[self configureIdentityAccountItem];
[self.consumer reloadItem:self.accountItem];
}
// Configures the identity account item.
- (void)configureIdentityAccountItem {
DCHECK(self.accountItem);
ChromeIdentity* identity = self.authService->GetAuthenticatedIdentity();
DCHECK(identity);
self.accountItem.image =
[self.resizedAvatarCache resizedAvatarForIdentity:identity];
self.accountItem.text = identity.userFullName;
if (self.mode == GoogleServicesSettingsModeAdvancedSigninSettings ||
self.isSyncSettingsConfirmed) {
self.accountItem.detailText = GetNSString(IDS_IOS_SYNC_SETUP_IN_PROGRESS);
} else {
self.accountItem.detailText = identity.userEmail;
}
}
#pragma mark - Loads sync section
// Loads the sync section.
- (void)loadSyncSection {
self.syncErrorItem = nil;
self.syncChromeDataSwitchItem = nil;
self.manageSyncItem = nil;
TableViewModel* model = self.consumer.tableViewModel;
[model addSectionWithIdentifier:SyncSectionIdentifier];
[self updateSyncSection:NO];
}
// Updates the sync section. If |notifyConsumer| is YES, the consumer is
// notified about model changes.
- (void)updateSyncSection:(BOOL)notifyConsumer {
BOOL needsAccountSigninItemUpdate = [self updateAccountSignInItem];
BOOL needsSyncErrorItemsUpdate = [self updateSyncErrorItems];
BOOL needsSyncChromeDataItemUpdate = [self updateSyncChromeDataItem];
BOOL needsManageSyncItemUpdate = [self updateManageSyncItem];
if (notifyConsumer &&
(needsAccountSigninItemUpdate || needsSyncErrorItemsUpdate ||
needsSyncChromeDataItemUpdate || needsManageSyncItemUpdate)) {
TableViewModel* model = self.consumer.tableViewModel;
NSUInteger sectionIndex =
[model sectionForSectionIdentifier:SyncSectionIdentifier];
NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:sectionIndex];
[self.consumer reloadSections:indexSet];
}
}
// Adds, removes and updates the account sign-in item in the model as needed.
// Returns YES if the consumer should be notified.
- (BOOL)updateAccountSignInItem {
TableViewModel* model = self.consumer.tableViewModel;
BOOL hasAccountSignInItem = [model hasItemForItemType:SignInItemType
sectionIdentifier:SyncSectionIdentifier];
if (self.isAuthenticated) {
self.hasRecordedSigninImpression = NO;
if (!hasAccountSignInItem)
return NO;
[model removeItemWithType:SignInItemType
fromSectionWithIdentifier:SyncSectionIdentifier];
return YES;
}
if (hasAccountSignInItem)
return NO;
AccountSignInItem* accountSignInItem =
[[AccountSignInItem alloc] initWithType:SignInItemType];
accountSignInItem.detailText =
GetNSString(IDS_IOS_GOOGLE_SERVICES_SETTINGS_SIGN_IN_DETAIL_TEXT);
[model addItem:accountSignInItem
toSectionWithIdentifier:SyncSectionIdentifier];
if (!self.hasRecordedSigninImpression) {
// Once the Settings are open, this button impression will at most be
// recorded once per dialog displayed and per sign-in.
signin_metrics::RecordSigninImpressionUserActionForAccessPoint(
signin_metrics::AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS);
self.hasRecordedSigninImpression = YES;
}
return YES;
}
// Adds, removes and updates the sync error item in the model as needed. Returns
// YES if the consumer should be notified.
- (BOOL)updateSyncErrorItems {
TableViewModel* model = self.consumer.tableViewModel;
BOOL hasError = NO;
ItemType type;
if (self.isSyncDisabledByAdministrator) {
type = SyncDisabledByAdministratorErrorItemType;
hasError = YES;
} else if (self.isAuthenticated && self.syncSetupService->IsSyncEnabled()) {
switch (self.syncSetupService->GetSyncServiceState()) {
case SyncSetupService::kSyncServiceUnrecoverableError:
type = RestartAuthenticationFlowErrorItemType;
hasError = YES;
break;
case SyncSetupService::kSyncServiceSignInNeedsUpdate:
type = ReauthDialogAsSyncIsInAuthErrorItemType;
hasError = YES;
break;
case SyncSetupService::kSyncServiceNeedsPassphrase:
type = ShowPassphraseDialogErrorItemType;
hasError = YES;
break;
case SyncSetupService::kSyncServiceNeedsTrustedVaultKey:
type = SyncNeedsTrustedVaultKeyErrorItemType;
hasError = YES;
break;
case SyncSetupService::kSyncSettingsNotConfirmed:
if (self.mode == GoogleServicesSettingsModeSettings) {
type = SyncSettingsNotCofirmedErrorItemType;
hasError = YES;
}
break;
case SyncSetupService::kNoSyncServiceError:
case SyncSetupService::kSyncServiceCouldNotConnect:
case SyncSetupService::kSyncServiceServiceUnavailable:
break;
}
}
if ((!hasError && !self.syncErrorItem) ||
(hasError && self.syncErrorItem && type == self.syncErrorItem.type)) {
// Nothing to update.
return NO;
}
if (self.syncErrorItem) {
// Remove the previous sync error item, since it is either the wrong error
// (if hasError is YES), or there is no error anymore.
[model removeItemWithType:self.syncErrorItem.type
fromSectionWithIdentifier:SyncSectionIdentifier];
self.syncErrorItem = nil;
if (!hasError)
return YES;
}
// Add the sync error item and its section.
if (type == SyncDisabledByAdministratorErrorItemType) {
self.syncErrorItem = [self createSyncDisabledByAdministratorErrorItem];
} else {
self.syncErrorItem = [self createSyncErrorItemWithItemType:type];
}
[model insertItem:self.syncErrorItem
inSectionWithIdentifier:SyncSectionIdentifier
atIndex:0];
return YES;
}
// Reloads the manage sync item, and returns YES if the section should be
// reloaded.
- (BOOL)updateManageSyncItem {
TableViewModel* model = self.consumer.tableViewModel;
if (self.isSyncCanBeAvailable) {
BOOL needsUpdate = NO;
if (!self.manageSyncItem) {
self.manageSyncItem =
[[TableViewImageItem alloc] initWithType:ManageSyncItemType];
self.manageSyncItem.accessoryType =
UITableViewCellAccessoryDisclosureIndicator;
self.manageSyncItem.title =
GetNSString(IDS_IOS_MANAGE_SYNC_SETTINGS_TITLE);
self.manageSyncItem.accessibilityIdentifier =
kManageSyncCellAccessibilityIdentifier;
[model addItem:self.manageSyncItem
toSectionWithIdentifier:SyncSectionIdentifier];
needsUpdate = YES;
}
needsUpdate =
needsUpdate || self.manageSyncItem.enabled != self.isSyncEnabled;
self.manageSyncItem.enabled = self.isSyncEnabled;
self.manageSyncItem.textColor =
self.manageSyncItem.enabled ? nil : UIColor.cr_secondaryLabelColor;
return needsUpdate;
}
if (!self.manageSyncItem)
return NO;
[model removeItemWithType:ManageSyncItemType
fromSectionWithIdentifier:SyncSectionIdentifier];
self.manageSyncItem = nil;
return YES;
}
// Updates the sync Chrome data item, and returns YES if the item has been
// updated.
- (BOOL)updateSyncChromeDataItem {
TableViewModel* model = self.consumer.tableViewModel;
if (self.isSyncCanBeAvailable) {
BOOL needsUpdate = NO;
if (!self.syncChromeDataSwitchItem) {
self.syncChromeDataSwitchItem =
[self switchItemWithItemType:SyncChromeDataItemType
textStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_SYNC_CHROME_DATA
detailStringID:0
dataType:0];
[model addItem:self.syncChromeDataSwitchItem
toSectionWithIdentifier:SyncSectionIdentifier];
needsUpdate = YES;
}
needsUpdate =
needsUpdate || self.isSyncEnabled != self.syncChromeDataSwitchItem.on;
self.syncChromeDataSwitchItem.on = self.isSyncEnabled;
return needsUpdate;
}
if (!self.syncChromeDataSwitchItem)
return NO;
self.syncChromeDataSwitchItem = nil;
[model removeItemWithType:SyncChromeDataItemType
fromSectionWithIdentifier:SyncSectionIdentifier];
return YES;
}
#pragma mark - Load non personalized section
// Loads NonPersonalizedSectionIdentifier section.
- (void)loadNonPersonalizedSection {
TableViewModel* model = self.consumer.tableViewModel;
[model addSectionWithIdentifier:NonPersonalizedSectionIdentifier];
for (TableViewItem* item in self.nonPersonalizedItems) {
[model addItem:item
toSectionWithIdentifier:NonPersonalizedSectionIdentifier];
}
[self updateNonPersonalizedSection];
}
// Updates the non-personalized section according to the user consent.
- (void)updateNonPersonalizedSection {
for (TableViewItem* item in self.nonPersonalizedItems) {
ItemType type = static_cast<ItemType>(item.type);
switch (type) {
case AutocompleteSearchesAndURLsItemType:
base::mac::ObjCCast<SyncSwitchItem>(item).on =
self.autocompleteSearchPreference.value;
break;
case AutocompleteSearchesAndURLsManagedItemType:
base::mac::ObjCCast<TableViewInfoButtonItem>(item).statusText =
self.autocompleteSearchPreference.value
? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
: l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
break;
case SafeBrowsingItemType:
base::mac::ObjCCast<SyncSwitchItem>(item).on =
self.safeBrowsingPreference.value;
break;
case SafeBrowsingManagedItemType:
base::mac::ObjCCast<TableViewInfoButtonItem>(item).statusText =
self.safeBrowsingPreference.value
? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
: l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
break;
case ImproveChromeItemType:
base::mac::ObjCCast<SyncSwitchItem>(item).on =
self.sendDataUsagePreference.value;
break;
case BetterSearchAndBrowsingItemType:
base::mac::ObjCCast<SyncSwitchItem>(item).on =
self.anonymizedDataCollectionPreference.value;
break;
case ItemTypePasswordLeakCheckSwitch:
[self updateLeakCheckItem];
break;
case IdentityItemType:
case ManageGoogleAccountItemType:
case SignInItemType:
case RestartAuthenticationFlowErrorItemType:
case ReauthDialogAsSyncIsInAuthErrorItemType:
case ShowPassphraseDialogErrorItemType:
case SyncNeedsTrustedVaultKeyErrorItemType:
case SyncDisabledByAdministratorErrorItemType:
case SyncSettingsNotCofirmedErrorItemType:
case SyncChromeDataItemType:
case ManageSyncItemType:
NOTREACHED();
break;
}
}
}
#pragma mark - Properties
- (BOOL)isAuthenticated {
return self.authService->IsAuthenticated();
}
- (BOOL)isSyncSettingsConfirmed {
return self.syncSetupService->GetSyncServiceState() ==
SyncSetupService::kSyncSettingsNotConfirmed;
}
- (BOOL)isSyncDisabledByAdministrator {
return self.syncService->GetDisableReasons().Has(
syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY);
}
- (BOOL)isSyncEnabled {
// Sync is not active when |syncSetupService->IsFirstSetupComplete()| is
// false. Show sync being turned off in the UI in this cases.
return self.syncSetupService->IsSyncEnabled() &&
(self.syncSetupService->IsFirstSetupComplete() ||
self.mode == GoogleServicesSettingsModeAdvancedSigninSettings);
}
- (BOOL)isSyncCanBeAvailable {
return self.isAuthenticated && !self.isSyncDisabledByAdministrator;
}
- (ItemArray)nonPersonalizedItems {
if (!_nonPersonalizedItems) {
NSMutableArray* items = [NSMutableArray array];
if (base::FeatureList::IsEnabled(kEnableIOSManagedSettingsUI) &&
self.userPrefService->IsManagedPreference(
prefs::kSearchSuggestEnabled)) {
TableViewInfoButtonItem* autocompleteItem = [self
TableViewInfoButtonItemType:AutocompleteSearchesAndURLsManagedItemType
textStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_AUTOCOMPLETE_SEARCHES_AND_URLS_TEXT
detailStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_AUTOCOMPLETE_SEARCHES_AND_URLS_DETAIL
status:self.autocompleteSearchPreference];
[items addObject:autocompleteItem];
} else {
SyncSwitchItem* autocompleteItem = [self
switchItemWithItemType:AutocompleteSearchesAndURLsItemType
textStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_AUTOCOMPLETE_SEARCHES_AND_URLS_TEXT
detailStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_AUTOCOMPLETE_SEARCHES_AND_URLS_DETAIL
dataType:0];
[items addObject:autocompleteItem];
}
if (base::FeatureList::IsEnabled(kSafeBrowsingAvailableOnIOS)) {
if (base::FeatureList::IsEnabled(kEnableIOSManagedSettingsUI) &&
self.userPrefService->IsManagedPreference(
prefs::kSafeBrowsingEnabled)) {
TableViewInfoButtonItem* safeBrowsingManagedItem = [self
TableViewInfoButtonItemType:
AutocompleteSearchesAndURLsManagedItemType
textStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_SAFE_BROWSING_TEXT
detailStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_SAFE_BROWSING_DETAIL
status:self.safeBrowsingPreference];
[items addObject:safeBrowsingManagedItem];
} else {
SyncSwitchItem* safeBrowsingItem = [self
switchItemWithItemType:SafeBrowsingItemType
textStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_SAFE_BROWSING_TEXT
detailStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_SAFE_BROWSING_DETAIL
dataType:0];
safeBrowsingItem.accessibilityIdentifier =
kSafeBrowsingItemAccessibilityIdentifier;
[items addObject:safeBrowsingItem];
}
}
[items addObject:self.passwordLeakCheckItem];
SyncSwitchItem* improveChromeItem =
[self switchItemWithItemType:ImproveChromeItemType
textStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_IMPROVE_CHROME_TEXT
detailStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_IMPROVE_CHROME_DETAIL
dataType:0];
[items addObject:improveChromeItem];
SyncSwitchItem* betterSearchAndBrowsingItemType = [self
switchItemWithItemType:BetterSearchAndBrowsingItemType
textStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_BETTER_SEARCH_AND_BROWSING_TEXT
detailStringID:
IDS_IOS_GOOGLE_SERVICES_SETTINGS_BETTER_SEARCH_AND_BROWSING_DETAIL
dataType:0];
betterSearchAndBrowsingItemType.accessibilityIdentifier =
kBetterSearchAndBrowsingItemAccessibilityID;
[items addObject:betterSearchAndBrowsingItemType];
_nonPersonalizedItems = items;
}
return _nonPersonalizedItems;
}
- (SettingsSwitchItem*)passwordLeakCheckItem {
if (!_passwordLeakCheckItem) {
SettingsSwitchItem* passwordLeakCheckItem = [[SettingsSwitchItem alloc]
initWithType:ItemTypePasswordLeakCheckSwitch];
passwordLeakCheckItem.text =
l10n_util::GetNSString(IDS_IOS_LEAK_CHECK_SWITCH);
passwordLeakCheckItem.on = [self passwordLeakCheckItemOnState];
passwordLeakCheckItem.accessibilityIdentifier =
kPasswordLeakCheckItemAccessibilityIdentifier;
passwordLeakCheckItem.enabled = self.isAuthenticated;
_passwordLeakCheckItem = passwordLeakCheckItem;
}
return _passwordLeakCheckItem;
}
#pragma mark - Private
// Creates a SyncSwitchItem instance.
- (SyncSwitchItem*)switchItemWithItemType:(NSInteger)itemType
textStringID:(int)textStringID
detailStringID:(int)detailStringID
dataType:(NSInteger)dataType {
SyncSwitchItem* switchItem = [[SyncSwitchItem alloc] initWithType:itemType];
switchItem.text = GetNSString(textStringID);
if (detailStringID)
switchItem.detailText = GetNSString(detailStringID);
switchItem.dataType = dataType;
return switchItem;
}
// Create a TableViewInfoButtonItem instance.
- (TableViewInfoButtonItem*)TableViewInfoButtonItemType:(NSInteger)itemType
textStringID:(int)textStringID
detailStringID:(int)detailStringID
status:(BOOL)status {
TableViewInfoButtonItem* managedItem =
[[TableViewInfoButtonItem alloc] initWithType:itemType];
managedItem.text = GetNSString(textStringID);
managedItem.detailText = GetNSString(detailStringID);
managedItem.statusText = status ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
: l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
return managedItem;
}
// Creates an item to display the sync error. |itemType| should only be one of
// those types:
// + RestartAuthenticationFlowErrorItemType
// + ReauthDialogAsSyncIsInAuthErrorItemType
// + ShowPassphraseDialogErrorItemType
// + SyncNeedsTrustedVaultKeyErrorItemType
// + SyncSettingsNotCofirmedErrorItemType
- (TableViewItem*)createSyncErrorItemWithItemType:(NSInteger)itemType {
DCHECK(itemType == RestartAuthenticationFlowErrorItemType ||
itemType == ReauthDialogAsSyncIsInAuthErrorItemType ||
itemType == ShowPassphraseDialogErrorItemType ||
itemType == SyncNeedsTrustedVaultKeyErrorItemType ||
itemType == SyncSettingsNotCofirmedErrorItemType);
SettingsImageDetailTextItem* syncErrorItem =
[[SettingsImageDetailTextItem alloc] initWithType:itemType];
syncErrorItem.text = GetNSString(IDS_IOS_SYNC_ERROR_TITLE);
syncErrorItem.detailText =
GetSyncErrorDescriptionForSyncSetupService(self.syncSetupService);
if (itemType == SyncSettingsNotCofirmedErrorItemType) {
// Special case for the sync error title.
syncErrorItem.text = GetNSString(IDS_IOS_SYNC_SETUP_NOT_CONFIRMED_TITLE);
} else if (itemType == ShowPassphraseDialogErrorItemType) {
// Special case only for the sync passphrase error message. The regular
// error message should be still be displayed in the first settings screen.
syncErrorItem.detailText = GetNSString(
IDS_IOS_GOOGLE_SERVICES_SETTINGS_ENTER_PASSPHRASE_TO_START_SYNC);
} else if (itemType == SyncNeedsTrustedVaultKeyErrorItemType) {
// Special case only for the sync encryption key error message. The regular
// error message should be still be displayed in the first settings screen.
syncErrorItem.detailText =
GetNSString(IDS_IOS_GOOGLE_SERVICES_SETTINGS_SYNC_ENCRYPTION_FIX_NOW);
// Also override the title to be more accurate, if only passwords are being
// encrypted.
if (!self.syncSetupService->IsEncryptEverythingEnabled()) {
syncErrorItem.text = GetNSString(IDS_IOS_SYNC_PASSWORDS_ERROR_TITLE);
}
}
syncErrorItem.image = [UIImage imageNamed:kGoogleServicesSyncErrorImage];
return syncErrorItem;
}
// Returns an item to show to the user the sync cannot be turned on for an
// enterprise policy reason.
- (TableViewItem*)createSyncDisabledByAdministratorErrorItem {
TableViewImageItem* item = [[TableViewImageItem alloc]
initWithType:SyncDisabledByAdministratorErrorItemType];
item.image = [UIImage imageNamed:kGoogleServicesEnterpriseImage];
item.title = GetNSString(
IDS_IOS_GOOGLE_SERVICES_SETTINGS_SYNC_DISABLBED_BY_ADMINISTRATOR_TITLE);
item.enabled = NO;
item.textColor = UIColor.cr_secondaryLabelColor;
return item;
}
// Returns a boolean indicating if the switch should appear as "On" or "Off"
// based on the sync preference and the sign in status.
- (BOOL)passwordLeakCheckItemOnState {
return self.safeBrowsingPreference.value &&
self.passwordLeakCheckEnabled.value && self.isAuthenticated;
}
// Updates the detail text and on state of the leak check item based on the
// state.
- (void)updateLeakCheckItem {
self.passwordLeakCheckItem.enabled =
self.isAuthenticated && self.safeBrowsingPreference.value;
self.passwordLeakCheckItem.on = [self passwordLeakCheckItemOnState];
if (!self.isAuthenticated && self.passwordLeakCheckEnabled.value) {
// If the user is signed out and the sync preference is enabled, this
// informs that it will be turned on on sign in.
self.passwordLeakCheckItem.detailText =
l10n_util::GetNSString(IDS_IOS_LEAK_CHECK_SIGNED_OUT_ENABLED_DESC);
return;
}
self.passwordLeakCheckItem.detailText = nil;
}
// Updates leak item and asks the consumer to reload it.
- (void)updateLeakCheckItemAndReload {
[self updateLeakCheckItem];
[self.consumer reloadItem:self.passwordLeakCheckItem];
}
#pragma mark - GoogleServicesSettingsViewControllerModelDelegate
- (void)googleServicesSettingsViewControllerLoadModel:
(GoogleServicesSettingsViewController*)controller {
DCHECK_EQ(self.consumer, controller);
[self loadIdentitySection];
[self loadSyncSection];
[self loadNonPersonalizedSection];
_identityManagerObserverBridge.reset(
new signin::IdentityManagerObserverBridge(self.identityManager, self));
DCHECK(self.syncService);
_syncObserver.reset(new SyncObserverBridge(self, self.syncService));
_identityServiceObserver.reset(new ChromeIdentityServiceObserverBridge(self));
}
#pragma mark - GoogleServicesSettingsServiceDelegate
- (void)toggleSwitchItem:(TableViewItem*)item withValue:(BOOL)value {
ItemType type = static_cast<ItemType>(item.type);
SyncSwitchItem* syncSwitchItem = base::mac::ObjCCast<SyncSwitchItem>(item);
syncSwitchItem.on = value;
switch (type) {
case AutocompleteSearchesAndURLsItemType:
self.autocompleteSearchPreference.value = value;
break;
case SafeBrowsingItemType:
self.safeBrowsingPreference.value = value;
[self updateLeakCheckItemAndReload];
break;
case ImproveChromeItemType:
self.sendDataUsagePreference.value = value;
// Don't set value if sendDataUsageWifiOnlyPreference has not been
// allocated.
if (value && self.sendDataUsageWifiOnlyPreference) {
// Should be wifi only, until https://crbug.com/872101 is fixed.
self.sendDataUsageWifiOnlyPreference.value = YES;
}
break;
case BetterSearchAndBrowsingItemType:
self.anonymizedDataCollectionPreference.value = value;
break;
case SyncChromeDataItemType:
self.syncSetupService->SetSyncEnabled(value);
if (self.mode == GoogleServicesSettingsModeSettings &&
!self.syncSetupService->IsFirstSetupComplete()) {
// FirstSetupComplete flag needs to be turned on when the user enables
// sync for the first time. This flag should not be turned on in
// GoogleServicesSettingsModeAdvancedSigninSettings. In that mode,
// this flag should be turned on only when the user clicks the confirm
// button.
CHECK(value);
self.syncSetupService->PrepareForFirstSyncSetup();
self.syncSetupService->SetFirstSetupComplete(
syncer::SyncFirstSetupCompleteSource::BASIC_FLOW);
}
break;
case ItemTypePasswordLeakCheckSwitch:
// Update the pref.
self.passwordLeakCheckEnabled.value = value;
// Update the item.
[self updateLeakCheckItem];
break;
case AutocompleteSearchesAndURLsManagedItemType:
case IdentityItemType:
case ManageGoogleAccountItemType:
case SignInItemType:
case RestartAuthenticationFlowErrorItemType:
case ReauthDialogAsSyncIsInAuthErrorItemType:
case SafeBrowsingManagedItemType:
case ShowPassphraseDialogErrorItemType:
case SyncNeedsTrustedVaultKeyErrorItemType:
case SyncDisabledByAdministratorErrorItemType:
case SyncSettingsNotCofirmedErrorItemType:
case ManageSyncItemType:
NOTREACHED();
break;
}
}
- (void)didSelectItem:(TableViewItem*)item {
ItemType type = static_cast<ItemType>(item.type);
switch (type) {
case IdentityItemType:
[self.commandHandler openAccountSettings];
break;
case ManageGoogleAccountItemType:
if (base::FeatureList::IsEnabled(kEnableMyGoogle)) {
[self.commandHandler openManageGoogleAccount];
} else {
[self.commandHandler openManageGoogleAccountWebPage];
}
break;
case SignInItemType:
[self.commandHandler showSignIn];
break;
case RestartAuthenticationFlowErrorItemType:
[self.commandHandler restartAuthenticationFlow];
break;
case ReauthDialogAsSyncIsInAuthErrorItemType:
[self.commandHandler openReauthDialogAsSyncIsInAuthError];
break;
case ShowPassphraseDialogErrorItemType:
[self.commandHandler openPassphraseDialog];
break;
case SyncNeedsTrustedVaultKeyErrorItemType:
[self.commandHandler openTrustedVaultReauth];
break;
case ManageSyncItemType:
[self.commandHandler openManageSyncSettings];
break;
case SyncDisabledByAdministratorErrorItemType:
case SyncSettingsNotCofirmedErrorItemType:
case AutocompleteSearchesAndURLsItemType:
case AutocompleteSearchesAndURLsManagedItemType:
case SafeBrowsingItemType:
case SafeBrowsingManagedItemType:
case ItemTypePasswordLeakCheckSwitch:
case ImproveChromeItemType:
case BetterSearchAndBrowsingItemType:
case SyncChromeDataItemType:
break;
}
}
#pragma mark - SyncObserverModelBridge
- (void)onSyncStateChanged {
[self updateSyncSection:YES];
// It is possible for |onSyncStateChanged| to be called before
// |onPrimaryAccountCleared|, when the primary account is removed.
if (self.isAuthenticated && self.accountItem) {
[self configureIdentityAccountItem];
[self.consumer reloadItem:self.accountItem];
}
}
#pragma mark - IdentityManagerObserverBridgeDelegate
- (void)onPrimaryAccountSet:(const CoreAccountInfo&)primaryAccountInfo {
[self updateSyncSection:YES];
[self updateIdentitySectionAndNotifyConsumer];
[self updateLeakCheckItemAndReload];
}
- (void)onPrimaryAccountCleared:
(const CoreAccountInfo&)previousPrimaryAccountInfo {
[self updateSyncSection:YES];
[self updateIdentitySectionAndNotifyConsumer];
[self updateLeakCheckItemAndReload];
}
#pragma mark - BooleanObserver
- (void)booleanDidChange:(id<ObservableBoolean>)observableBoolean {
[self updateNonPersonalizedSection];
TableViewModel* model = self.consumer.tableViewModel;
NSUInteger index =
[model sectionForSectionIdentifier:NonPersonalizedSectionIdentifier];
NSIndexSet* sectionIndexToReload = [NSIndexSet indexSetWithIndex:index];
[self.consumer reloadSections:sectionIndexToReload];
}
#pragma mark - ChromeIdentityServiceObserver
- (void)profileUpdate:(ChromeIdentity*)identity {
[self updateIdentitySectionAndNotifyConsumer];
[self updateLeakCheckItemAndReload];
}
- (void)chromeIdentityServiceWillBeDestroyed {
_identityServiceObserver.reset();
}
@end