1// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_mediator.h"
6
7#include "base/auto_reset.h"
8#include "base/check_op.h"
9#include "base/mac/foundation_util.h"
10#include "base/notreached.h"
11#include "components/autofill/core/common/autofill_prefs.h"
12#import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"
13#include "components/strings/grit/components_strings.h"
14#include "components/sync/driver/sync_service.h"
15#import "ios/chrome/browser/signin/authentication_service.h"
16#include "ios/chrome/browser/sync/profile_sync_service_factory.h"
17#include "ios/chrome/browser/sync/sync_observer_bridge.h"
18#include "ios/chrome/browser/sync/sync_setup_service.h"
19#import "ios/chrome/browser/ui/list_model/list_model.h"
20#import "ios/chrome/browser/ui/settings/cells/settings_image_detail_text_item.h"
21#import "ios/chrome/browser/ui/settings/cells/sync_switch_item.h"
22#import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_command_handler.h"
23#import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_constants.h"
24#import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_consumer.h"
25#import "ios/chrome/browser/ui/settings/google_services/sync_error_settings_command_handler.h"
26#import "ios/chrome/browser/ui/settings/sync/utils/sync_util.h"
27#import "ios/chrome/browser/ui/settings/utils/pref_backed_boolean.h"
28#import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
29#import "ios/chrome/browser/ui/table_view/cells/table_view_image_item.h"
30#import "ios/chrome/browser/ui/table_view/cells/table_view_item.h"
31#import "ios/chrome/browser/ui/util/uikit_ui_util.h"
32#import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
33#include "ios/chrome/grit/ios_chromium_strings.h"
34#include "ios/chrome/grit/ios_strings.h"
35#include "ui/base/l10n/l10n_util.h"
36
37#if !defined(__has_feature) || !__has_feature(objc_arc)
38#error "This file requires ARC support."
39#endif
40
41using l10n_util::GetNSString;
42
43namespace {
44
45// Enterprise icon.
46NSString* kGoogleServicesEnterpriseImage = @"google_services_enterprise";
47// Sync error icon.
48NSString* kGoogleServicesSyncErrorImage = @"google_services_sync_error";
49}  // namespace
50
51@interface ManageSyncSettingsMediator () <BooleanObserver,
52                                          IdentityManagerObserverBridgeDelegate,
53                                          SyncObserverModelBridge> {
54  // Sync observer.
55  std::unique_ptr<SyncObserverBridge> _syncObserver;
56  // Whether Sync State changes should be currently ignored.
57  BOOL _ignoreSyncStateChanges;
58}
59
60// Preference value for kAutofillWalletImportEnabled.
61@property(nonatomic, strong, readonly)
62    PrefBackedBoolean* autocompleteWalletPreference;
63// Sync service.
64@property(nonatomic, assign) syncer::SyncService* syncService;
65// Model item for sync everything.
66@property(nonatomic, strong) SyncSwitchItem* syncEverythingItem;
67// Model item for each data types.
68@property(nonatomic, strong) NSArray<SyncSwitchItem*>* syncSwitchItems;
69// Autocomplete wallet item.
70@property(nonatomic, strong) SyncSwitchItem* autocompleteWalletItem;
71// Encryption item.
72@property(nonatomic, strong) TableViewImageItem* encryptionItem;
73// Sync error item.
74@property(nonatomic, strong) TableViewItem* syncErrorItem;
75// Returns YES if the sync data items should be enabled.
76@property(nonatomic, assign, readonly) BOOL shouldSyncDataItemEnabled;
77// Returns whether the Sync settings should be disabled because of a Sync error.
78@property(nonatomic, assign, readonly) BOOL disabledBecauseOfSyncError;
79// Returns YES if the user cannot turn on sync for enterprise policy reasons.
80@property(nonatomic, assign, readonly) BOOL isSyncDisabledByAdministrator;
81// Returns YES if the user is authenticated.
82@property(nonatomic, assign, readonly) BOOL isAuthenticated;
83
84@end
85
86@implementation ManageSyncSettingsMediator
87
88- (instancetype)initWithSyncService:(syncer::SyncService*)syncService
89                    userPrefService:(PrefService*)userPrefService {
90  self = [super init];
91  if (self) {
92    DCHECK(syncService);
93    self.syncService = syncService;
94    _syncObserver.reset(new SyncObserverBridge(self, syncService));
95    _autocompleteWalletPreference = [[PrefBackedBoolean alloc]
96        initWithPrefService:userPrefService
97                   prefName:autofill::prefs::kAutofillWalletImportEnabled];
98    _autocompleteWalletPreference.observer = self;
99  }
100  return self;
101}
102
103#pragma mark - Loads sync data type section
104
105// Loads the sync data type section.
106- (void)loadSyncDataTypeSection {
107  TableViewModel* model = self.consumer.tableViewModel;
108  [model addSectionWithIdentifier:SyncDataTypeSectionIdentifier];
109  self.syncEverythingItem =
110      [[SyncSwitchItem alloc] initWithType:SyncEverythingItemType];
111  self.syncEverythingItem.text = GetNSString(IDS_IOS_SYNC_EVERYTHING_TITLE);
112  [self updateSyncEverythingItemNotifyConsumer:NO];
113  [model addItem:self.syncEverythingItem
114      toSectionWithIdentifier:SyncDataTypeSectionIdentifier];
115  self.syncSwitchItems = @[
116    [self switchItemWithDataType:SyncSetupService::kSyncAutofill],
117    [self switchItemWithDataType:SyncSetupService::kSyncBookmarks],
118    [self switchItemWithDataType:SyncSetupService::kSyncOmniboxHistory],
119    [self switchItemWithDataType:SyncSetupService::kSyncOpenTabs],
120    [self switchItemWithDataType:SyncSetupService::kSyncPasswords],
121    [self switchItemWithDataType:SyncSetupService::kSyncReadingList],
122    [self switchItemWithDataType:SyncSetupService::kSyncPreferences]
123  ];
124  for (SyncSwitchItem* switchItem in self.syncSwitchItems) {
125    [model addItem:switchItem
126        toSectionWithIdentifier:SyncDataTypeSectionIdentifier];
127  }
128  self.autocompleteWalletItem =
129      [[SyncSwitchItem alloc] initWithType:AutocompleteWalletItemType];
130  self.autocompleteWalletItem.text =
131      GetNSString(IDS_AUTOFILL_ENABLE_PAYMENTS_INTEGRATION_CHECKBOX_LABEL);
132  [model addItem:self.autocompleteWalletItem
133      toSectionWithIdentifier:SyncDataTypeSectionIdentifier];
134  [self updateSyncItemsNotifyConsumer:NO];
135}
136
137// Updates the sync everything item, and notify the consumer if |notifyConsumer|
138// is set to YES.
139- (void)updateSyncEverythingItemNotifyConsumer:(BOOL)notifyConsumer {
140  BOOL shouldSyncEverythingBeEditable =
141      self.syncSetupService->IsSyncEnabled() &&
142      (!self.disabledBecauseOfSyncError || self.syncSettingsNotConfirmed);
143  BOOL shouldSyncEverythingItemBeOn =
144      self.syncSetupService->IsSyncEnabled() &&
145      self.syncSetupService->IsSyncingAllDataTypes();
146  BOOL needsUpdate =
147      (self.syncEverythingItem.on != shouldSyncEverythingItemBeOn) ||
148      (self.syncEverythingItem.enabled != shouldSyncEverythingBeEditable);
149  self.syncEverythingItem.on = shouldSyncEverythingItemBeOn;
150  self.syncEverythingItem.enabled = shouldSyncEverythingBeEditable;
151  if (needsUpdate && notifyConsumer) {
152    [self.consumer reloadItem:self.syncEverythingItem];
153  }
154}
155
156// Updates all the items related to sync (sync data items and autocomplete
157// wallet item). The consumer is notified if |notifyConsumer| is set to YES.
158- (void)updateSyncItemsNotifyConsumer:(BOOL)notifyConsumer {
159  [self updateSyncDataItemsNotifyConsumer:notifyConsumer];
160  [self updateAutocompleteWalletItemNotifyConsumer:notifyConsumer];
161}
162
163// Updates all the sync data type items, and notify the consumer if
164// |notifyConsumer| is set to YES.
165- (void)updateSyncDataItemsNotifyConsumer:(BOOL)notifyConsumer {
166  for (SyncSwitchItem* syncSwitchItem in self.syncSwitchItems) {
167    SyncSetupService::SyncableDatatype dataType =
168        static_cast<SyncSetupService::SyncableDatatype>(
169            syncSwitchItem.dataType);
170    syncer::ModelType modelType = self.syncSetupService->GetModelType(dataType);
171    BOOL isDataTypeSynced =
172        self.syncSetupService->IsDataTypePreferred(modelType);
173    BOOL needsUpdate =
174        (syncSwitchItem.on != isDataTypeSynced) ||
175        (syncSwitchItem.isEnabled != self.shouldSyncDataItemEnabled);
176    syncSwitchItem.on = isDataTypeSynced;
177    syncSwitchItem.enabled = self.shouldSyncDataItemEnabled;
178    if (needsUpdate && notifyConsumer) {
179      [self.consumer reloadItem:syncSwitchItem];
180    }
181  }
182}
183
184// Updates the autocomplete wallet item. The consumer is notified if
185// |notifyConsumer| is set to YES.
186- (void)updateAutocompleteWalletItemNotifyConsumer:(BOOL)notifyConsumer {
187  syncer::ModelType autofillModelType =
188      self.syncSetupService->GetModelType(SyncSetupService::kSyncAutofill);
189  BOOL isAutofillOn =
190      self.syncSetupService->IsDataTypePreferred(autofillModelType);
191  BOOL autocompleteWalletEnabled =
192      isAutofillOn && self.shouldSyncDataItemEnabled;
193  BOOL autocompleteWalletOn = self.autocompleteWalletPreference.value;
194  BOOL needsUpdate =
195      (self.autocompleteWalletItem.enabled != autocompleteWalletEnabled) ||
196      (self.autocompleteWalletItem.on != autocompleteWalletOn);
197  self.autocompleteWalletItem.enabled = autocompleteWalletEnabled;
198  self.autocompleteWalletItem.on = autocompleteWalletOn;
199  if (needsUpdate && notifyConsumer) {
200    [self.consumer reloadItem:self.autocompleteWalletItem];
201  }
202}
203
204#pragma mark - Loads the advanced settings section
205
206// Loads the advanced settings section.
207- (void)loadAdvancedSettingsSection {
208  TableViewModel* model = self.consumer.tableViewModel;
209  [model addSectionWithIdentifier:AdvancedSettingsSectionIdentifier];
210  // EncryptionItemType.
211  self.encryptionItem =
212      [[TableViewImageItem alloc] initWithType:EncryptionItemType];
213  self.encryptionItem.title = GetNSString(IDS_IOS_MANAGE_SYNC_ENCRYPTION);
214  // For kSyncServiceNeedsTrustedVaultKey, the disclosure indicator should not
215  // be shown since the reauth dialog for the trusted vault is presented from
216  // the bottom, and is not part of navigation controller.
217  BOOL hasDisclosureIndicator =
218      self.syncSetupService->GetSyncServiceState() !=
219      SyncSetupService::kSyncServiceNeedsTrustedVaultKey;
220  self.encryptionItem.accessoryType =
221      hasDisclosureIndicator ? UITableViewCellAccessoryDisclosureIndicator
222                             : UITableViewCellAccessoryNone;
223  [model addItem:self.encryptionItem
224      toSectionWithIdentifier:AdvancedSettingsSectionIdentifier];
225  [self updateEncryptionItem:NO];
226
227  // GoogleActivityControlsItemType.
228  TableViewImageItem* googleActivityControlsItem =
229      [[TableViewImageItem alloc] initWithType:GoogleActivityControlsItemType];
230  googleActivityControlsItem.title =
231      GetNSString(IDS_IOS_MANAGE_SYNC_GOOGLE_ACTIVITY_CONTROLS_TITLE);
232  googleActivityControlsItem.detailText =
233      GetNSString(IDS_IOS_MANAGE_SYNC_GOOGLE_ACTIVITY_CONTROLS_DESCRIPTION);
234  googleActivityControlsItem.accessibilityTraits |= UIAccessibilityTraitButton;
235  [model addItem:googleActivityControlsItem
236      toSectionWithIdentifier:AdvancedSettingsSectionIdentifier];
237
238  // AdvancedSettingsSectionIdentifier.
239  TableViewImageItem* dataFromChromeSyncItem =
240      [[TableViewImageItem alloc] initWithType:DataFromChromeSync];
241  dataFromChromeSyncItem.title =
242      GetNSString(IDS_IOS_MANAGE_SYNC_DATA_FROM_CHROME_SYNC_TITLE);
243  dataFromChromeSyncItem.detailText =
244      GetNSString(IDS_IOS_MANAGE_SYNC_DATA_FROM_CHROME_SYNC_DESCRIPTION);
245  dataFromChromeSyncItem.accessibilityIdentifier =
246      kDataFromChromeSyncAccessibilityIdentifier;
247  dataFromChromeSyncItem.accessibilityTraits |= UIAccessibilityTraitButton;
248  [model addItem:dataFromChromeSyncItem
249      toSectionWithIdentifier:AdvancedSettingsSectionIdentifier];
250}
251
252// Updates encryption item, and notifies the consumer if |notifyConsumer| is set
253// to YES.
254- (void)updateEncryptionItem:(BOOL)notifyConsumer {
255  BOOL needsUpdate =
256      self.shouldEncryptionItemBeEnabled &&
257      (self.encryptionItem.enabled != self.shouldEncryptionItemBeEnabled);
258  if (self.shouldEncryptionItemBeEnabled &&
259      self.syncSetupService->GetSyncServiceState() ==
260          SyncSetupService::kSyncServiceNeedsPassphrase) {
261    needsUpdate = needsUpdate || self.encryptionItem.image == nil;
262    self.encryptionItem.image =
263        [UIImage imageNamed:kGoogleServicesSyncErrorImage];
264    self.encryptionItem.detailText = GetNSString(
265        IDS_IOS_GOOGLE_SERVICES_SETTINGS_ENTER_PASSPHRASE_TO_START_SYNC);
266  } else if (self.shouldEncryptionItemBeEnabled &&
267             self.syncSetupService->GetSyncServiceState() ==
268                 SyncSetupService::kSyncServiceNeedsTrustedVaultKey) {
269    needsUpdate = needsUpdate || self.encryptionItem.image == nil;
270    self.encryptionItem.image =
271        [UIImage imageNamed:kGoogleServicesSyncErrorImage];
272    self.encryptionItem.detailText =
273        GetNSString(self.syncSetupService->IsEncryptEverythingEnabled()
274                        ? IDS_IOS_SYNC_ERROR_DESCRIPTION
275                        : IDS_IOS_SYNC_PASSWORDS_ERROR_DESCRIPTION);
276  } else {
277    needsUpdate = needsUpdate || self.encryptionItem.image != nil;
278    self.encryptionItem.image = nil;
279    self.encryptionItem.detailText = nil;
280  }
281  self.encryptionItem.enabled = self.shouldEncryptionItemBeEnabled;
282  if (self.shouldEncryptionItemBeEnabled) {
283    self.encryptionItem.textColor = nil;
284  } else {
285    self.encryptionItem.textColor = UIColor.cr_secondaryLabelColor;
286  }
287  if (needsUpdate && notifyConsumer) {
288    [self.consumer reloadItem:self.self.encryptionItem];
289  }
290}
291
292#pragma mark - Private
293
294// Creates a SyncSwitchItem instance.
295- (SyncSwitchItem*)switchItemWithDataType:
296    (SyncSetupService::SyncableDatatype)dataType {
297  NSInteger itemType = 0;
298  int textStringID = 0;
299  switch (dataType) {
300    case SyncSetupService::kSyncBookmarks:
301      itemType = BookmarksDataTypeItemType;
302      textStringID = IDS_SYNC_DATATYPE_BOOKMARKS;
303      break;
304    case SyncSetupService::kSyncOmniboxHistory:
305      itemType = HistoryDataTypeItemType;
306      textStringID = IDS_SYNC_DATATYPE_TYPED_URLS;
307      break;
308    case SyncSetupService::kSyncPasswords:
309      itemType = PasswordsDataTypeItemType;
310      textStringID = IDS_SYNC_DATATYPE_PASSWORDS;
311      break;
312    case SyncSetupService::kSyncOpenTabs:
313      itemType = OpenTabsDataTypeItemType;
314      textStringID = IDS_SYNC_DATATYPE_TABS;
315      break;
316    case SyncSetupService::kSyncAutofill:
317      itemType = AutofillDataTypeItemType;
318      textStringID = IDS_SYNC_DATATYPE_AUTOFILL;
319      break;
320    case SyncSetupService::kSyncPreferences:
321      itemType = SettingsDataTypeItemType;
322      textStringID = IDS_SYNC_DATATYPE_PREFERENCES;
323      break;
324    case SyncSetupService::kSyncReadingList:
325      itemType = ReadingListDataTypeItemType;
326      textStringID = IDS_SYNC_DATATYPE_READING_LIST;
327      break;
328    case SyncSetupService::kNumberOfSyncableDatatypes:
329      NOTREACHED();
330      break;
331  }
332  DCHECK_NE(itemType, 0);
333  DCHECK_NE(textStringID, 0);
334  SyncSwitchItem* switchItem = [[SyncSwitchItem alloc] initWithType:itemType];
335  switchItem.text = GetNSString(textStringID);
336  switchItem.dataType = dataType;
337  return switchItem;
338}
339
340#pragma mark - Properties
341
342- (BOOL)syncSettingsNotConfirmed {
343  SyncSetupService::SyncServiceState state =
344      self.syncSetupService->GetSyncServiceState();
345  return state == SyncSetupService::kSyncSettingsNotConfirmed;
346}
347
348- (BOOL)disabledBecauseOfSyncError {
349  SyncSetupService::SyncServiceState state =
350      self.syncSetupService->GetSyncServiceState();
351  return state != SyncSetupService::kNoSyncServiceError &&
352         state != SyncSetupService::kSyncServiceNeedsPassphrase &&
353         state != SyncSetupService::kSyncServiceNeedsTrustedVaultKey;
354}
355
356- (BOOL)shouldSyncDataItemEnabled {
357  return (!self.syncSetupService->IsSyncingAllDataTypes() &&
358          self.syncSetupService->IsSyncEnabled() &&
359          (!self.disabledBecauseOfSyncError || self.syncSettingsNotConfirmed));
360}
361
362- (BOOL)shouldEncryptionItemBeEnabled {
363  return self.syncService->IsEngineInitialized() &&
364         self.syncSetupService->IsSyncEnabled() &&
365         !self.disabledBecauseOfSyncError;
366}
367
368#pragma mark - ManageSyncSettingsTableViewControllerModelDelegate
369
370- (void)manageSyncSettingsTableViewControllerLoadModel:
371    (id<ManageSyncSettingsConsumer>)controller {
372  DCHECK_EQ(self.consumer, controller);
373  [self loadSyncErrorsSection];
374  [self loadSyncDataTypeSection];
375  [self loadAdvancedSettingsSection];
376}
377
378#pragma mark - BooleanObserver
379
380- (void)booleanDidChange:(id<ObservableBoolean>)observableBoolean {
381  [self updateAutocompleteWalletItemNotifyConsumer:YES];
382}
383
384#pragma mark - SyncObserverModelBridge
385
386- (void)onSyncStateChanged {
387  if (_ignoreSyncStateChanges) {
388    // The UI should not updated so the switch animations can run smoothly.
389    return;
390  }
391  [self updateSyncErrorsSection:YES];
392  [self updateSyncEverythingItemNotifyConsumer:YES];
393  [self updateSyncItemsNotifyConsumer:YES];
394  [self updateEncryptionItem:YES];
395}
396
397#pragma mark - IdentityManagerObserverBridgeDelegate
398
399- (void)onPrimaryAccountSet:(const CoreAccountInfo&)primaryAccountInfo {
400  [self updateSyncErrorsSection:YES];
401}
402
403- (void)onPrimaryAccountCleared:
404    (const CoreAccountInfo&)previousPrimaryAccountInfo {
405  [self updateSyncErrorsSection:YES];
406}
407
408#pragma mark - ManageSyncSettingsServiceDelegate
409
410- (void)toggleSwitchItem:(TableViewItem*)item withValue:(BOOL)value {
411  {
412    // The notifications should be ignored to get smooth switch animations.
413    // Notifications are sent by SyncObserverModelBridge while changing
414    // settings.
415    base::AutoReset<BOOL> autoReset(&_ignoreSyncStateChanges, YES);
416    SyncSwitchItem* syncSwitchItem = base::mac::ObjCCast<SyncSwitchItem>(item);
417    syncSwitchItem.on = value;
418    SyncSettingsItemType itemType =
419        static_cast<SyncSettingsItemType>(item.type);
420    switch (itemType) {
421      case SyncEverythingItemType:
422        self.syncSetupService->SetSyncingAllDataTypes(value);
423        if (value) {
424          // When sync everything is turned on, the autocomplete wallet
425          // should be turned on. This code can be removed once
426          // crbug.com/937234 is fixed.
427          self.autocompleteWalletPreference.value = true;
428        }
429        break;
430      case AutofillDataTypeItemType:
431      case BookmarksDataTypeItemType:
432      case HistoryDataTypeItemType:
433      case OpenTabsDataTypeItemType:
434      case PasswordsDataTypeItemType:
435      case ReadingListDataTypeItemType:
436      case SettingsDataTypeItemType: {
437        DCHECK(syncSwitchItem);
438        SyncSetupService::SyncableDatatype dataType =
439            static_cast<SyncSetupService::SyncableDatatype>(
440                syncSwitchItem.dataType);
441        syncer::ModelType modelType =
442            self.syncSetupService->GetModelType(dataType);
443        self.syncSetupService->SetDataTypeEnabled(modelType, value);
444        if (dataType == SyncSetupService::kSyncAutofill) {
445          // When the auto fill data type is updated, the autocomplete wallet
446          // should be updated too. Autocomplete wallet should not be enabled
447          // when auto fill data type disabled. This behaviour not be
448          // implemented in the UI code. This code can be removed once
449          // crbug.com/937234 is fixed.
450          self.autocompleteWalletPreference.value = value;
451        }
452        break;
453      }
454      case AutocompleteWalletItemType:
455        self.autocompleteWalletPreference.value = value;
456        break;
457      case EncryptionItemType:
458      case GoogleActivityControlsItemType:
459      case DataFromChromeSync:
460      case RestartAuthenticationFlowErrorItemType:
461      case ReauthDialogAsSyncIsInAuthErrorItemType:
462      case ShowPassphraseDialogErrorItemType:
463      case SyncNeedsTrustedVaultKeyErrorItemType:
464      case SyncDisabledByAdministratorErrorItemType:
465        NOTREACHED();
466        break;
467    }
468  }
469  [self updateSyncEverythingItemNotifyConsumer:YES];
470  [self updateSyncItemsNotifyConsumer:YES];
471}
472
473- (void)didSelectItem:(TableViewItem*)item {
474  SyncSettingsItemType itemType = static_cast<SyncSettingsItemType>(item.type);
475  switch (itemType) {
476    case EncryptionItemType:
477      if (self.syncSetupService->GetSyncServiceState() ==
478          SyncSetupService::kSyncServiceNeedsTrustedVaultKey) {
479        [self.syncErrorHandler openTrustedVaultReauth];
480        break;
481      }
482      [self.syncErrorHandler openPassphraseDialog];
483      break;
484    case GoogleActivityControlsItemType:
485      [self.commandHandler openWebAppActivityDialog];
486      break;
487    case DataFromChromeSync:
488      [self.commandHandler openDataFromChromeSyncWebPage];
489      break;
490    case RestartAuthenticationFlowErrorItemType:
491      [self.syncErrorHandler restartAuthenticationFlow];
492      break;
493    case ReauthDialogAsSyncIsInAuthErrorItemType:
494      [self.syncErrorHandler openReauthDialogAsSyncIsInAuthError];
495      break;
496    case ShowPassphraseDialogErrorItemType:
497      [self.syncErrorHandler openPassphraseDialog];
498      break;
499    case SyncNeedsTrustedVaultKeyErrorItemType:
500      [self.syncErrorHandler openTrustedVaultReauth];
501      break;
502    case SyncEverythingItemType:
503    case AutofillDataTypeItemType:
504    case BookmarksDataTypeItemType:
505    case HistoryDataTypeItemType:
506    case OpenTabsDataTypeItemType:
507    case PasswordsDataTypeItemType:
508    case ReadingListDataTypeItemType:
509    case SettingsDataTypeItemType:
510    case AutocompleteWalletItemType:
511    case SyncDisabledByAdministratorErrorItemType:
512      // Nothing to do.
513      break;
514  }
515}
516
517// Creates an item to display the sync error. |itemType| should only be one of
518// those types:
519//   + RestartAuthenticationFlowErrorItemType
520//   + ReauthDialogAsSyncIsInAuthErrorItemType
521//   + ShowPassphraseDialogErrorItemType
522//   + SyncNeedsTrustedVaultKeyErrorItemType
523- (TableViewItem*)createSyncErrorItemWithItemType:(NSInteger)itemType {
524  DCHECK(itemType == RestartAuthenticationFlowErrorItemType ||
525         itemType == ReauthDialogAsSyncIsInAuthErrorItemType ||
526         itemType == ShowPassphraseDialogErrorItemType ||
527         itemType == SyncNeedsTrustedVaultKeyErrorItemType);
528  SettingsImageDetailTextItem* syncErrorItem =
529      [[SettingsImageDetailTextItem alloc] initWithType:itemType];
530  syncErrorItem.text = GetNSString(IDS_IOS_SYNC_ERROR_TITLE);
531  syncErrorItem.detailText =
532      GetSyncErrorDescriptionForSyncSetupService(self.syncSetupService);
533  if (itemType == ShowPassphraseDialogErrorItemType) {
534    // Special case only for the sync passphrase error message. The regular
535    // error message should be still be displayed in the first settings screen.
536    syncErrorItem.detailText = GetNSString(
537        IDS_IOS_GOOGLE_SERVICES_SETTINGS_ENTER_PASSPHRASE_TO_START_SYNC);
538  } else if (itemType == SyncNeedsTrustedVaultKeyErrorItemType) {
539    // Special case only for the sync encryption key error message. The regular
540    // error message should be still be displayed in the first settings screen.
541    syncErrorItem.detailText =
542        GetNSString(IDS_IOS_GOOGLE_SERVICES_SETTINGS_SYNC_ENCRYPTION_FIX_NOW);
543
544    // Also override the title to be more accurate, if only passwords are being
545    // encrypted.
546    if (!self.syncSetupService->IsEncryptEverythingEnabled()) {
547      syncErrorItem.text = GetNSString(IDS_IOS_SYNC_PASSWORDS_ERROR_TITLE);
548    }
549  }
550  syncErrorItem.image = [UIImage imageNamed:kGoogleServicesSyncErrorImage];
551  return syncErrorItem;
552}
553
554// Loads the sync errors section.
555- (void)loadSyncErrorsSection {
556  [self.consumer.tableViewModel
557      addSectionWithIdentifier:SyncErrorsSectionIdentifier];
558  [self updateSyncErrorsSection:NO];
559}
560
561// Updates the sync errors section. If |notifyConsumer| is YES, the consumer is
562// notified about model changes.
563- (void)updateSyncErrorsSection:(BOOL)notifyConsumer {
564  if (!base::FeatureList::IsEnabled(signin::kMobileIdentityConsistency)) {
565    return;
566  }
567  BOOL needsSyncErrorItemsUpdate = [self updateSyncErrorItems];
568  if (notifyConsumer && needsSyncErrorItemsUpdate) {
569    NSUInteger sectionIndex = [self.consumer.tableViewModel
570        sectionForSectionIdentifier:SyncErrorsSectionIdentifier];
571    NSIndexSet* indexSet = [NSIndexSet indexSetWithIndex:sectionIndex];
572    [self.consumer reloadSections:indexSet];
573  }
574}
575
576// Adds, removes and updates the sync error item in the model as needed. Returns
577// YES if the consumer should be notified.
578- (BOOL)updateSyncErrorItems {
579  TableViewModel* model = self.consumer.tableViewModel;
580  BOOL hasError = NO;
581  SyncSettingsItemType type;
582
583  if (self.isSyncDisabledByAdministrator) {
584    type = SyncDisabledByAdministratorErrorItemType;
585    hasError = YES;
586  } else if (self.isAuthenticated && self.syncSetupService->IsSyncEnabled()) {
587    switch (self.syncSetupService->GetSyncServiceState()) {
588      case SyncSetupService::kSyncServiceUnrecoverableError:
589        type = RestartAuthenticationFlowErrorItemType;
590        hasError = YES;
591        break;
592      case SyncSetupService::kSyncServiceSignInNeedsUpdate:
593        type = ReauthDialogAsSyncIsInAuthErrorItemType;
594        hasError = YES;
595        break;
596      case SyncSetupService::kSyncServiceNeedsPassphrase:
597        type = ShowPassphraseDialogErrorItemType;
598        hasError = YES;
599        break;
600      case SyncSetupService::kSyncServiceNeedsTrustedVaultKey:
601        type = SyncNeedsTrustedVaultKeyErrorItemType;
602        hasError = YES;
603        break;
604      case SyncSetupService::kSyncSettingsNotConfirmed:
605      case SyncSetupService::kNoSyncServiceError:
606      case SyncSetupService::kSyncServiceCouldNotConnect:
607      case SyncSetupService::kSyncServiceServiceUnavailable:
608        break;
609    }
610  }
611
612  if ((!hasError && !self.syncErrorItem) ||
613      (hasError && self.syncErrorItem && type == self.syncErrorItem.type)) {
614    // Nothing to update.
615    return NO;
616  }
617
618  if (self.syncErrorItem) {
619    // Remove the previous sync error item, since it is either the wrong error
620    // (if hasError is YES), or there is no error anymore.
621    [model removeItemWithType:self.syncErrorItem.type
622        fromSectionWithIdentifier:SyncErrorsSectionIdentifier];
623    self.syncErrorItem = nil;
624    if (!hasError)
625      return YES;
626  }
627  // Add the sync error item and its section.
628  if (type == SyncDisabledByAdministratorErrorItemType) {
629    self.syncErrorItem = [self createSyncDisabledByAdministratorErrorItem];
630  } else {
631    self.syncErrorItem = [self createSyncErrorItemWithItemType:type];
632  }
633  [model insertItem:self.syncErrorItem
634      inSectionWithIdentifier:SyncErrorsSectionIdentifier
635                      atIndex:0];
636  return YES;
637}
638
639// Returns an item to show to the user the sync cannot be turned on for an
640// enterprise policy reason.
641- (TableViewItem*)createSyncDisabledByAdministratorErrorItem {
642  TableViewImageItem* item = [[TableViewImageItem alloc]
643      initWithType:SyncDisabledByAdministratorErrorItemType];
644  item.image = [UIImage imageNamed:kGoogleServicesEnterpriseImage];
645  item.title = GetNSString(
646      IDS_IOS_GOOGLE_SERVICES_SETTINGS_SYNC_DISABLBED_BY_ADMINISTRATOR_TITLE);
647  item.enabled = NO;
648  item.textColor = UIColor.cr_secondaryLabelColor;
649  return item;
650}
651
652#pragma mark - Properties
653
654- (BOOL)isSyncDisabledByAdministrator {
655  return self.syncService->GetDisableReasons().Has(
656      syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY);
657}
658
659- (BOOL)isAuthenticated {
660  return self.authService->IsAuthenticated();
661}
662
663@end
664