1// Copyright 2015 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/settings_table_view_controller.h"
6
7#include <memory>
8
9#include "base/feature_list.h"
10#import "base/mac/foundation_util.h"
11#include "base/metrics/user_metrics.h"
12#include "base/metrics/user_metrics_action.h"
13#include "base/strings/sys_string_conversions.h"
14#include "build/branding_buildflags.h"
15#include "components/autofill/core/common/autofill_prefs.h"
16#include "components/keyed_service/core/service_access_type.h"
17#include "components/password_manager/core/browser/password_store.h"
18#include "components/password_manager/core/common/password_manager_pref_names.h"
19#import "components/prefs/ios/pref_observer_bridge.h"
20#include "components/prefs/pref_member.h"
21#include "components/prefs/pref_service.h"
22#include "components/search_engines/search_engines_pref_names.h"
23#include "components/search_engines/util.h"
24#include "components/signin/public/base/account_consistency_method.h"
25#include "components/signin/public/base/signin_metrics.h"
26#include "components/signin/public/identity_manager/identity_manager.h"
27#import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"
28#include "components/strings/grit/components_strings.h"
29#include "components/sync/driver/sync_service.h"
30#include "ios/chrome/browser/application_context.h"
31#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
32#include "ios/chrome/browser/main/browser.h"
33#include "ios/chrome/browser/passwords/ios_chrome_password_check_manager.h"
34#include "ios/chrome/browser/passwords/ios_chrome_password_check_manager_factory.h"
35#include "ios/chrome/browser/passwords/ios_chrome_password_store_factory.h"
36#include "ios/chrome/browser/passwords/password_check_observer_bridge.h"
37#include "ios/chrome/browser/pref_names.h"
38#include "ios/chrome/browser/search_engines/search_engine_observer_bridge.h"
39#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
40#import "ios/chrome/browser/signin/authentication_service.h"
41#include "ios/chrome/browser/signin/authentication_service_factory.h"
42#import "ios/chrome/browser/signin/chrome_identity_service_observer_bridge.h"
43#include "ios/chrome/browser/signin/identity_manager_factory.h"
44#include "ios/chrome/browser/sync/profile_sync_service_factory.h"
45#import "ios/chrome/browser/sync/sync_observer_bridge.h"
46#include "ios/chrome/browser/sync/sync_setup_service_factory.h"
47#include "ios/chrome/browser/system_flags.h"
48#import "ios/chrome/browser/ui/authentication/cells/signin_promo_view_consumer.h"
49#import "ios/chrome/browser/ui/authentication/cells/table_view_account_item.h"
50#import "ios/chrome/browser/ui/authentication/cells/table_view_signin_promo_item.h"
51#import "ios/chrome/browser/ui/authentication/signin_promo_view_mediator.h"
52#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_feature.h"
53#import "ios/chrome/browser/ui/settings/about_chrome_table_view_controller.h"
54#import "ios/chrome/browser/ui/settings/autofill/autofill_credit_card_table_view_controller.h"
55#import "ios/chrome/browser/ui/settings/autofill/autofill_profile_table_view_controller.h"
56#import "ios/chrome/browser/ui/settings/bandwidth_management_table_view_controller.h"
57#import "ios/chrome/browser/ui/settings/cells/account_sign_in_item.h"
58#import "ios/chrome/browser/ui/settings/cells/settings_check_item.h"
59#import "ios/chrome/browser/ui/settings/cells/settings_image_detail_text_item.h"
60#import "ios/chrome/browser/ui/settings/cells/settings_switch_cell.h"
61#import "ios/chrome/browser/ui/settings/cells/settings_switch_item.h"
62#import "ios/chrome/browser/ui/settings/content_settings_table_view_controller.h"
63#import "ios/chrome/browser/ui/settings/default_browser/default_browser_settings_table_view_controller.h"
64#import "ios/chrome/browser/ui/settings/elements/enterprise_info_popover_view_controller.h"
65#import "ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.h"
66#import "ios/chrome/browser/ui/settings/google_services/google_services_settings_coordinator.h"
67#import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_coordinator.h"
68#import "ios/chrome/browser/ui/settings/language/language_settings_mediator.h"
69#import "ios/chrome/browser/ui/settings/language/language_settings_table_view_controller.h"
70#import "ios/chrome/browser/ui/settings/password/passwords_coordinator.h"
71#import "ios/chrome/browser/ui/settings/privacy/privacy_coordinator.h"
72#import "ios/chrome/browser/ui/settings/safety_check/safety_check_constants.h"
73#import "ios/chrome/browser/ui/settings/safety_check/safety_check_coordinator.h"
74#import "ios/chrome/browser/ui/settings/safety_check/safety_check_utils.h"
75#import "ios/chrome/browser/ui/settings/search_engine_table_view_controller.h"
76#import "ios/chrome/browser/ui/settings/settings_table_view_controller_constants.h"
77#import "ios/chrome/browser/ui/settings/sync/utils/sync_util.h"
78#import "ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.h"
79#import "ios/chrome/browser/ui/settings/utils/pref_backed_boolean.h"
80#import "ios/chrome/browser/ui/settings/voice_search_table_view_controller.h"
81#import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
82#include "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
83#import "ios/chrome/browser/ui/table_view/cells/table_view_detail_icon_item.h"
84#import "ios/chrome/browser/ui/table_view/cells/table_view_info_button_cell.h"
85#import "ios/chrome/browser/ui/table_view/cells/table_view_info_button_item.h"
86#import "ios/chrome/browser/ui/table_view/cells/table_view_text_item.h"
87#import "ios/chrome/browser/ui/table_view/table_view_model.h"
88#include "ios/chrome/browser/ui/ui_feature_flags.h"
89#import "ios/chrome/browser/ui/util/uikit_ui_util.h"
90#include "ios/chrome/browser/upgrade/upgrade_utils.h"
91#include "ios/chrome/browser/voice/speech_input_locale_config.h"
92#import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h"
93#import "ios/chrome/common/ui/colors/semantic_color_names.h"
94#include "ios/chrome/grit/ios_chromium_strings.h"
95#include "ios/chrome/grit/ios_strings.h"
96#import "ios/public/provider/chrome/browser/chrome_browser_provider.h"
97#import "ios/public/provider/chrome/browser/signin/chrome_identity.h"
98#import "ios/public/provider/chrome/browser/signin/signin_presenter.h"
99#import "ios/public/provider/chrome/browser/signin/signin_resources_provider.h"
100#include "ios/public/provider/chrome/browser/voice/voice_search_prefs.h"
101#import "net/base/mac/url_conversions.h"
102#include "ui/base/l10n/l10n_util_mac.h"
103
104#if !defined(__has_feature) || !__has_feature(objc_arc)
105#error "This file requires ARC support."
106#endif
107
108namespace {
109
110const CGFloat kAccountProfilePhotoDimension = 40.0f;
111
112NSString* const kSyncAndGoogleServicesImageName = @"sync_and_google_services";
113NSString* const kSyncAndGoogleServicesSyncErrorImageName =
114    @"sync_and_google_services_sync_error";
115NSString* const kSyncAndGoogleServicesSyncOffImageName =
116    @"sync_and_google_services_sync_off";
117NSString* const kSyncAndGoogleServicesSyncOnImageName =
118    @"sync_and_google_services_sync_on";
119NSString* const kSettingsGoogleServicesImageName = @"settings_google_services";
120NSString* const kSettingsSearchEngineImageName = @"settings_search_engine";
121NSString* const kSettingsPasswordsImageName = @"settings_passwords";
122NSString* const kSettingsAutofillCreditCardImageName =
123    @"settings_payment_methods";
124NSString* const kSettingsAutofillProfileImageName = @"settings_addresses";
125NSString* const kSettingsVoiceSearchImageName = @"settings_voice_search";
126NSString* const kSettingsSafetyCheckImageName = @"settings_safety_check";
127NSString* const kSettingsPrivacyImageName = @"settings_privacy";
128NSString* const kSettingsLanguageSettingsImageName =
129    @"settings_language_settings";
130NSString* const kSettingsContentSettingsImageName =
131    @"settings_content_settings";
132NSString* const kSettingsBandwidthImageName = @"settings_bandwidth";
133NSString* const kSettingsAboutChromeImageName = @"settings_about_chrome";
134NSString* const kSettingsDebugImageName = @"settings_debug";
135NSString* const kSettingsArticleSuggestionsImageName =
136    @"settings_article_suggestions";
137NSString* const kDefaultBrowserWorldImageName = @"default_browser_world";
138
139#if BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
140NSString* kDevViewSourceKey = @"DevViewSource";
141#endif  // BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
142
143}  // namespace
144
145#pragma mark - SettingsTableViewController
146
147@interface SettingsTableViewController () <
148    BooleanObserver,
149    ChromeIdentityServiceObserver,
150    GoogleServicesSettingsCoordinatorDelegate,
151    IdentityManagerObserverBridgeDelegate,
152    ManageSyncSettingsCoordinatorDelegate,
153    PasswordCheckObserver,
154    PasswordsCoordinatorDelegate,
155    PopoverLabelViewControllerDelegate,
156    PrefObserverDelegate,
157    PrivacyCoordinatorDelegate,
158    SafetyCheckCoordinatorDelegate,
159    SettingsControllerProtocol,
160    SearchEngineObserving,
161    SigninPresenter,
162    SigninPromoViewConsumer,
163    SyncObserverModelBridge> {
164  // The browser where the settings are being displayed.
165  Browser* _browser;
166  // The browser state for |_browser|. Never off the record.
167  ChromeBrowserState* _browserState;  // weak
168  // Bridge for TemplateURLServiceObserver.
169  std::unique_ptr<SearchEngineObserverBridge> _searchEngineObserverBridge;
170  std::unique_ptr<signin::IdentityManagerObserverBridge>
171      _identityObserverBridge;
172  std::unique_ptr<SyncObserverBridge> _syncObserverBridge;
173  // A helper object for observing changes in the password check status
174  // and changes to the compromised credentials list.
175  std::unique_ptr<PasswordCheckObserverBridge> _passwordCheckObserver;
176  // The service responsible for password check feature.
177  scoped_refptr<IOSChromePasswordCheckManager> _passwordCheckManager;
178  // Whether the impression of the Signin button has already been recorded.
179  BOOL _hasRecordedSigninImpression;
180  // PrefBackedBoolean for ShowMemoryDebugTools switch.
181  PrefBackedBoolean* _showMemoryDebugToolsEnabled;
182  // PrefBackedBoolean for ArticlesForYou switch.
183  PrefBackedBoolean* _articlesEnabled;
184  // The item related to the switch for the show suggestions setting.
185  SettingsSwitchItem* _showMemoryDebugToolsItem;
186  // The item related to the switch for the show suggestions setting.
187  SettingsSwitchItem* _articlesForYouItem;
188  // The item related to the safety check.
189  SettingsCheckItem* _safetyCheckItem;
190
191  // Mediator to configure the sign-in promo cell. Also used to received
192  // identity update notifications.
193  SigninPromoViewMediator* _signinPromoViewMediator;
194  GoogleServicesSettingsCoordinator* _googleServicesSettingsCoordinator;
195  ManageSyncSettingsCoordinator* _manageSyncSettingsCoordinator;
196
197  // Privacy coordinator.
198  PrivacyCoordinator* _privacyCoordinator;
199
200  // Safety Check coordinator.
201  SafetyCheckCoordinator* _safetyCheckCoordinator;
202
203  // Passwords coordinator.
204  PasswordsCoordinator* _passwordsCoordinator;
205
206  // Cached resized profile image.
207  UIImage* _resizedImage;
208  __weak UIImage* _oldImage;
209
210  // Identity object and observer used for Account Item refresh.
211  ChromeIdentity* _identity;
212  std::unique_ptr<ChromeIdentityServiceObserverBridge> _identityServiceObserver;
213
214  // PrefMember for voice locale code.
215  StringPrefMember _voiceLocaleCode;
216  // Pref observer to track changes to prefs.
217  std::unique_ptr<PrefObserverBridge> _prefObserverBridge;
218  // TODO(crbug.com/662435): Refactor PrefObserverBridge so it owns the
219  // PrefChangeRegistrar.
220  // Registrar for pref changes notifications.
221  PrefChangeRegistrar _prefChangeRegistrar;
222
223  // Updatable Items.
224  TableViewDetailIconItem* _voiceSearchDetailItem;
225  TableViewDetailIconItem* _defaultSearchEngineItem;
226  TableViewDetailIconItem* _passwordsDetailItem;
227  TableViewDetailIconItem* _autoFillProfileDetailItem;
228  TableViewDetailIconItem* _autoFillCreditCardDetailItem;
229
230  // YES if view has been dismissed.
231  BOOL _settingsHasBeenDismissed;
232}
233
234@property(nonatomic, readonly, weak)
235    id<ApplicationCommands, BrowserCommands, BrowsingDataCommands>
236        dispatcher;
237
238// YES if the sign-in is in progress.
239@property(nonatomic, assign) BOOL isSigninInProgress;
240
241// Stops observing browser state services. This is required during the shutdown
242// phase to avoid observing services for a profile that is being killed.
243- (void)stopBrowserStateServiceObservers;
244
245@end
246
247@implementation SettingsTableViewController
248@synthesize dispatcher = _dispatcher;
249
250#pragma mark Initialization
251
252- (instancetype)
253    initWithBrowser:(Browser*)browser
254         dispatcher:
255             (id<ApplicationCommands, BrowserCommands, BrowsingDataCommands>)
256                 dispatcher {
257  DCHECK(browser);
258  DCHECK(!browser->GetBrowserState()->IsOffTheRecord());
259  UITableViewStyle style = base::FeatureList::IsEnabled(kSettingsRefresh)
260                               ? UITableViewStylePlain
261                               : UITableViewStyleGrouped;
262  self = [super initWithStyle:style];
263  if (self) {
264    _browser = browser;
265    _browserState = _browser->GetBrowserState();
266    self.title = l10n_util::GetNSStringWithFixup(IDS_IOS_SETTINGS_TITLE);
267    _searchEngineObserverBridge.reset(new SearchEngineObserverBridge(
268        self,
269        ios::TemplateURLServiceFactory::GetForBrowserState(_browserState)));
270    signin::IdentityManager* identityManager =
271        IdentityManagerFactory::GetForBrowserState(_browserState);
272    // It is expected that |identityManager| should never be nil except in
273    // tests. In that case, the tests should be fixed.
274    DCHECK(identityManager);
275    _identityObserverBridge.reset(
276        new signin::IdentityManagerObserverBridge(identityManager, self));
277    syncer::SyncService* syncService =
278        ProfileSyncServiceFactory::GetForBrowserState(_browserState);
279    _syncObserverBridge.reset(new SyncObserverBridge(self, syncService));
280
281    _showMemoryDebugToolsEnabled = [[PrefBackedBoolean alloc]
282        initWithPrefService:GetApplicationContext()->GetLocalState()
283                   prefName:prefs::kShowMemoryDebuggingTools];
284    [_showMemoryDebugToolsEnabled setObserver:self];
285
286    AuthenticationService* authService =
287        AuthenticationServiceFactory::GetForBrowserState(_browserState);
288    authService->WaitUntilCacheIsPopulated();
289    _identity = authService->GetAuthenticatedIdentity();
290    _identityServiceObserver.reset(
291        new ChromeIdentityServiceObserverBridge(self));
292
293    PrefService* prefService = _browserState->GetPrefs();
294
295    if (base::FeatureList::IsEnabled(kSafetyCheckIOS)) {
296      _passwordCheckManager =
297          IOSChromePasswordCheckManagerFactory::GetForBrowserState(
298              _browserState);
299      _passwordCheckObserver = std::make_unique<PasswordCheckObserverBridge>(
300          self, _passwordCheckManager.get());
301    }
302
303    _articlesEnabled = [[PrefBackedBoolean alloc]
304        initWithPrefService:prefService
305                   prefName:prefs::kArticlesForYouEnabled];
306    [_articlesEnabled setObserver:self];
307
308    _voiceLocaleCode.Init(prefs::kVoiceSearchLocale, prefService);
309
310    _prefChangeRegistrar.Init(prefService);
311    _prefObserverBridge.reset(new PrefObserverBridge(self));
312    // Register to observe any changes on Perf backed values displayed by the
313    // screen.
314    _prefObserverBridge->ObserveChangesForPreference(prefs::kVoiceSearchLocale,
315                                                     &_prefChangeRegistrar);
316    _prefObserverBridge->ObserveChangesForPreference(
317        password_manager::prefs::kCredentialsEnableService,
318        &_prefChangeRegistrar);
319    _prefObserverBridge->ObserveChangesForPreference(
320        autofill::prefs::kAutofillCreditCardEnabled, &_prefChangeRegistrar);
321    _prefObserverBridge->ObserveChangesForPreference(
322        autofill::prefs::kAutofillProfileEnabled, &_prefChangeRegistrar);
323
324    _dispatcher = dispatcher;
325
326    // TODO(crbug.com/764578): -loadModel should not be called from
327    // initializer. A possible fix is to move this call to -viewDidLoad.
328    [self loadModel];
329  }
330  return self;
331}
332
333- (void)dealloc {
334  DCHECK(_settingsHasBeenDismissed)
335      << "-settingsWillBeDismissed must be called before -dealloc";
336}
337
338- (void)stopBrowserStateServiceObservers {
339  _syncObserverBridge.reset();
340  _identityObserverBridge.reset();
341  _identityServiceObserver.reset();
342  [_showMemoryDebugToolsEnabled setObserver:nil];
343  [_articlesEnabled setObserver:nil];
344}
345
346#pragma mark View lifecycle
347
348- (void)viewDidLoad {
349  [super viewDidLoad];
350
351  self.tableView.accessibilityIdentifier = kSettingsTableViewId;
352
353  // Change the separator inset from the settings default because this
354  // TableView shows leading icons.
355  self.tableView.separatorInset =
356      UIEdgeInsetsMake(0, kTableViewSeparatorInsetWithIcon, 0, 0);
357
358  self.navigationItem.largeTitleDisplayMode =
359      UINavigationItemLargeTitleDisplayModeAlways;
360}
361
362#pragma mark SettingsRootTableViewController
363
364- (void)loadModel {
365  [super loadModel];
366
367  TableViewModel<TableViewItem*>* model = self.tableViewModel;
368
369  AuthenticationService* authService =
370      AuthenticationServiceFactory::GetForBrowserState(_browserState);
371  if (!authService->IsAuthenticated()) {
372    // Sign in section
373    [model addSectionWithIdentifier:SettingsSectionIdentifierSignIn];
374    if ([SigninPromoViewMediator
375            shouldDisplaySigninPromoViewWithAccessPoint:
376                signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS
377                                           browserState:_browserState]) {
378      if (!_signinPromoViewMediator) {
379        _signinPromoViewMediator = [[SigninPromoViewMediator alloc]
380            initWithBrowserState:_browserState
381                     accessPoint:signin_metrics::AccessPoint::
382                                     ACCESS_POINT_SETTINGS
383                       presenter:self /* id<SigninPresenter> */];
384        _signinPromoViewMediator.consumer = self;
385      }
386    } else {
387      [_signinPromoViewMediator signinPromoViewIsRemoved];
388      _signinPromoViewMediator = nil;
389    }
390    [model addItem:[self signInTextItem]
391        toSectionWithIdentifier:SettingsSectionIdentifierSignIn];
392  } else {
393    // Account section
394    [model addSectionWithIdentifier:SettingsSectionIdentifierAccount];
395    _hasRecordedSigninImpression = NO;
396    [_signinPromoViewMediator signinPromoViewIsRemoved];
397    _signinPromoViewMediator = nil;
398    [model addItem:[self accountCellItem]
399        toSectionWithIdentifier:SettingsSectionIdentifierAccount];
400  }
401  if (![model
402          hasSectionForSectionIdentifier:SettingsSectionIdentifierAccount]) {
403    // Add the Account section for the Sync & Google services cell, if the user
404    // is signed-out.
405    [model addSectionWithIdentifier:SettingsSectionIdentifierAccount];
406  }
407
408  // Adds experimental Google Services item separate from Sync.
409  if (base::FeatureList::IsEnabled(signin::kMobileIdentityConsistency)) {
410    if (authService->IsAuthenticated()) {
411      [model addItem:[self googleSyncCellItem]
412          toSectionWithIdentifier:SettingsSectionIdentifierAccount];
413    }
414    [model addItem:[self googleServicesCellItem]
415        toSectionWithIdentifier:SettingsSectionIdentifierAccount];
416  } else {
417    [model addItem:[self syncAndGoogleServicesCellItem]
418        toSectionWithIdentifier:SettingsSectionIdentifierAccount];
419  }
420
421  // Defaults section.
422  if (@available(iOS 14, *)) {
423    if (base::FeatureList::IsEnabled(kDefaultBrowserSettings)) {
424      [model addSectionWithIdentifier:SettingsSectionIdentifierDefaults];
425      [model addItem:[self defaultBrowserCellItem]
426          toSectionWithIdentifier:SettingsSectionIdentifierDefaults];
427    }
428  }
429
430  // Basics section
431  [model addSectionWithIdentifier:SettingsSectionIdentifierBasics];
432  // Show managed UI if default search engine is managed by policy.
433  if (base::FeatureList::IsEnabled(kEnableIOSManagedSettingsUI) &&
434      [self isDefaultSearchEngineManagedByPolicy]) {
435    [model addItem:[self managedSearchEngineItem]
436        toSectionWithIdentifier:SettingsSectionIdentifierBasics];
437  } else {
438    if (@available(iOS 14, *)) {
439      if (base::FeatureList::IsEnabled(kDefaultBrowserSettings)) {
440        [model addItem:[self searchEngineDetailItem]
441            toSectionWithIdentifier:SettingsSectionIdentifierDefaults];
442      } else {
443        [model addItem:[self searchEngineDetailItem]
444            toSectionWithIdentifier:SettingsSectionIdentifierBasics];
445      }
446    } else {
447      [model addItem:[self searchEngineDetailItem]
448          toSectionWithIdentifier:SettingsSectionIdentifierBasics];
449    }
450  }
451  [model addItem:[self passwordsDetailItem]
452      toSectionWithIdentifier:SettingsSectionIdentifierBasics];
453  [model addItem:[self autoFillCreditCardDetailItem]
454      toSectionWithIdentifier:SettingsSectionIdentifierBasics];
455  [model addItem:[self autoFillProfileDetailItem]
456      toSectionWithIdentifier:SettingsSectionIdentifierBasics];
457
458  // Advanced Section
459  [model addSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
460  [model addItem:[self voiceSearchDetailItem]
461      toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
462  if (base::FeatureList::IsEnabled(kSafetyCheckIOS)) {
463    [model addItem:[self safetyCheckDetailItem]
464        toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
465  }
466  [model addItem:[self privacyDetailItem]
467      toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
468  _articlesForYouItem = [self articlesForYouSwitchItem];
469  [model addItem:_articlesForYouItem
470      toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
471  [model addItem:[self languageSettingsDetailItem]
472      toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
473  [model addItem:[self contentSettingsDetailItem]
474      toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
475  [model addItem:[self bandwidthManagementDetailItem]
476      toSectionWithIdentifier:SettingsSectionIdentifierAdvanced];
477
478  // Info Section
479  [model addSectionWithIdentifier:SettingsSectionIdentifierInfo];
480  [model addItem:[self aboutChromeDetailItem]
481      toSectionWithIdentifier:SettingsSectionIdentifierInfo];
482
483  // Debug Section
484  if ([self hasDebugSection]) {
485    [model addSectionWithIdentifier:SettingsSectionIdentifierDebug];
486  }
487
488  if (experimental_flags::IsMemoryDebuggingEnabled()) {
489    _showMemoryDebugToolsItem = [self showMemoryDebugSwitchItem];
490    [model addItem:_showMemoryDebugToolsItem
491        toSectionWithIdentifier:SettingsSectionIdentifierDebug];
492  }
493
494#if BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
495  [model addItem:[self viewSourceSwitchItem]
496      toSectionWithIdentifier:SettingsSectionIdentifierDebug];
497  [model addItem:[self tableViewCatalogDetailItem]
498      toSectionWithIdentifier:SettingsSectionIdentifierDebug];
499#endif  // BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
500}
501
502#pragma mark - Model Items
503
504- (TableViewItem*)signInTextItem {
505  if (_signinPromoViewMediator) {
506    TableViewSigninPromoItem* signinPromoItem =
507        [[TableViewSigninPromoItem alloc]
508            initWithType:SettingsItemTypeSigninPromo];
509    signinPromoItem.text =
510        l10n_util::GetNSString(IDS_IOS_SIGNIN_PROMO_SETTINGS_WITH_UNITY);
511    signinPromoItem.configurator =
512        [_signinPromoViewMediator createConfigurator];
513    signinPromoItem.delegate = _signinPromoViewMediator;
514    [_signinPromoViewMediator signinPromoViewIsVisible];
515    return signinPromoItem;
516  }
517  if (!_hasRecordedSigninImpression) {
518    // Once the Settings are open, this button impression will at most be
519    // recorded once until they are closed.
520    signin_metrics::RecordSigninImpressionUserActionForAccessPoint(
521        signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS);
522    _hasRecordedSigninImpression = YES;
523  }
524  AccountSignInItem* signInTextItem =
525      [[AccountSignInItem alloc] initWithType:SettingsItemTypeSignInButton];
526  signInTextItem.accessibilityIdentifier = kSettingsSignInCellId;
527  signInTextItem.detailText =
528      l10n_util::GetNSString(IDS_IOS_SIGN_IN_TO_CHROME_SETTING_SUBTITLE);
529
530  return signInTextItem;
531}
532
533- (TableViewItem*)googleServicesCellItem {
534  SettingsImageDetailTextItem* googleServicesItem =
535      [[SettingsImageDetailTextItem alloc]
536          initWithType:SettingsItemTypeGoogleServices];
537  googleServicesItem.accessoryType =
538      UITableViewCellAccessoryDisclosureIndicator;
539  googleServicesItem.text =
540      l10n_util::GetNSString(IDS_IOS_GOOGLE_SERVICES_SETTINGS_TITLE);
541  googleServicesItem.accessibilityIdentifier = kSettingsGoogleServicesCellId;
542  googleServicesItem.image =
543      [UIImage imageNamed:kSettingsGoogleServicesImageName];
544  return googleServicesItem;
545}
546
547- (TableViewItem*)googleSyncCellItem {
548  // TODO(crbug.com/805214): This branded icon image needs to come from
549  // BrandedImageProvider.
550  TableViewDetailIconItem* googleSyncCellItem =
551      [self detailItemWithType:SettingsItemTypeGoogleSync
552                             text:l10n_util::GetNSString(
553                                      IDS_IOS_GOOGLE_SYNC_SETTINGS_TITLE)
554                       detailText:l10n_util::GetNSString(IDS_IOS_SETTING_ON)
555                    iconImageName:kSyncAndGoogleServicesSyncOnImageName
556          accessibilityIdentifier:kSettingsGoogleSyncAndServicesCellId];
557  [self updateGoogleSyncCellItem:googleSyncCellItem];
558  return googleSyncCellItem;
559}
560
561- (TableViewItem*)syncAndGoogleServicesCellItem {
562  // TODO(crbug.com/805214): This branded icon image needs to come from
563  // BrandedImageProvider.
564  SettingsImageDetailTextItem* googleServicesItem =
565      [[SettingsImageDetailTextItem alloc]
566          initWithType:SettingsItemTypeSyncAndGoogleServices];
567  googleServicesItem.accessoryType =
568      UITableViewCellAccessoryDisclosureIndicator;
569  googleServicesItem.text =
570      l10n_util::GetNSString(IDS_IOS_GOOGLE_SERVICES_SYNC_SETTINGS_TITLE);
571  googleServicesItem.accessibilityIdentifier =
572      kSettingsGoogleSyncAndServicesCellId;
573  [self updateSyncAndGoogleServicesItem:googleServicesItem];
574  return googleServicesItem;
575}
576
577- (TableViewItem*)defaultBrowserCellItem {
578  TableViewDetailIconItem* defaultBrowser = [[TableViewDetailIconItem alloc]
579      initWithType:SettingsItemTypeDefaultBrowser];
580  defaultBrowser.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
581  defaultBrowser.text =
582      l10n_util::GetNSString(IDS_IOS_SETTINGS_SET_DEFAULT_BROWSER);
583  defaultBrowser.iconImageName = kDefaultBrowserWorldImageName;
584
585  return defaultBrowser;
586}
587
588- (TableViewItem*)accountCellItem {
589  TableViewAccountItem* identityAccountItem =
590      [[TableViewAccountItem alloc] initWithType:SettingsItemTypeAccount];
591  identityAccountItem.accessoryType =
592      UITableViewCellAccessoryDisclosureIndicator;
593  identityAccountItem.accessibilityIdentifier = kSettingsAccountCellId;
594  [self updateIdentityAccountItem:identityAccountItem];
595  return identityAccountItem;
596}
597
598- (TableViewItem*)searchEngineDetailItem {
599  NSString* defaultSearchEngineName =
600      base::SysUTF16ToNSString(GetDefaultSearchEngineName(
601          ios::TemplateURLServiceFactory::GetForBrowserState(_browserState)));
602
603  _defaultSearchEngineItem =
604      [self detailItemWithType:SettingsItemTypeSearchEngine
605                             text:l10n_util::GetNSString(
606                                      IDS_IOS_SEARCH_ENGINE_SETTING_TITLE)
607                       detailText:defaultSearchEngineName
608                    iconImageName:kSettingsSearchEngineImageName
609          accessibilityIdentifier:kSettingsSearchEngineCellId];
610  return _defaultSearchEngineItem;
611}
612
613- (TableViewInfoButtonItem*)managedSearchEngineItem {
614  TableViewInfoButtonItem* managedDefaultSearchEngineItem =
615      [[TableViewInfoButtonItem alloc]
616          initWithType:SettingsItemTypeManagedDefaultSearchEngine];
617  managedDefaultSearchEngineItem.text =
618      l10n_util::GetNSString(IDS_IOS_SEARCH_ENGINE_SETTING_TITLE);
619  managedDefaultSearchEngineItem.iconImageName = kSettingsSearchEngineImageName;
620  managedDefaultSearchEngineItem.accessibilityHint =
621      l10n_util::GetNSString(IDS_IOS_TOGGLE_SETTING_MANAGED_ACCESSIBILITY_HINT);
622
623  const base::DictionaryValue* dict = _browserState->GetPrefs()->GetDictionary(
624      DefaultSearchManager::kDefaultSearchProviderDataPrefName);
625  if (dict->FindBoolPath(DefaultSearchManager::kDisabledByPolicy)) {
626    // Default search engine is disabled by policy.
627    managedDefaultSearchEngineItem.statusText =
628        l10n_util::GetNSString(IDS_IOS_SEARCH_ENGINE_SETTING_DISABLED_STATUS);
629  } else {
630    // Default search engine is enabled and set by policy.
631    const std::string* status =
632        dict->FindStringPath(DefaultSearchManager::kShortName);
633    managedDefaultSearchEngineItem.statusText =
634        base::SysUTF8ToNSString(*status);
635  }
636
637  managedDefaultSearchEngineItem.accessibilityIdentifier =
638      kSettingsManagedSearchEngineCellId;
639  return managedDefaultSearchEngineItem;
640}
641
642- (TableViewItem*)passwordsDetailItem {
643  BOOL passwordsEnabled = _browserState->GetPrefs()->GetBoolean(
644      password_manager::prefs::kCredentialsEnableService);
645  NSString* passwordsDetail = passwordsEnabled
646                                  ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
647                                  : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
648  _passwordsDetailItem =
649      [self detailItemWithType:SettingsItemTypePasswords
650                             text:l10n_util::GetNSString(IDS_IOS_PASSWORDS)
651                       detailText:passwordsDetail
652                    iconImageName:kSettingsPasswordsImageName
653          accessibilityIdentifier:kSettingsPasswordsCellId];
654
655  return _passwordsDetailItem;
656}
657
658- (TableViewItem*)autoFillCreditCardDetailItem {
659  BOOL autofillCreditCardEnabled =
660      autofill::prefs::IsAutofillCreditCardEnabled(_browserState->GetPrefs());
661  NSString* detailText = autofillCreditCardEnabled
662                             ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
663                             : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
664  _autoFillCreditCardDetailItem =
665      [self detailItemWithType:SettingsItemTypeAutofillCreditCard
666                             text:l10n_util::GetNSString(
667                                      IDS_AUTOFILL_PAYMENT_METHODS)
668                       detailText:detailText
669                    iconImageName:kSettingsAutofillCreditCardImageName
670          accessibilityIdentifier:kSettingsPaymentMethodsCellId];
671
672  return _autoFillCreditCardDetailItem;
673}
674
675- (TableViewItem*)autoFillProfileDetailItem {
676  BOOL autofillProfileEnabled =
677      autofill::prefs::IsAutofillProfileEnabled(_browserState->GetPrefs());
678  NSString* detailText = autofillProfileEnabled
679                             ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
680                             : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
681  _autoFillProfileDetailItem =
682      [self detailItemWithType:SettingsItemTypeAutofillProfile
683                             text:l10n_util::GetNSString(
684                                      IDS_AUTOFILL_ADDRESSES_SETTINGS_TITLE)
685                       detailText:detailText
686                    iconImageName:kSettingsAutofillProfileImageName
687          accessibilityIdentifier:kSettingsAddressesAndMoreCellId];
688
689  return _autoFillProfileDetailItem;
690}
691
692- (TableViewItem*)voiceSearchDetailItem {
693  voice::SpeechInputLocaleConfig* localeConfig =
694      voice::SpeechInputLocaleConfig::GetInstance();
695  voice::SpeechInputLocale locale =
696      _voiceLocaleCode.GetValue().length()
697          ? localeConfig->GetLocaleForCode(_voiceLocaleCode.GetValue())
698          : localeConfig->GetDefaultLocale();
699  NSString* languageName = base::SysUTF16ToNSString(locale.display_name);
700  _voiceSearchDetailItem =
701      [self detailItemWithType:SettingsItemTypeVoiceSearch
702                             text:l10n_util::GetNSString(
703                                      IDS_IOS_VOICE_SEARCH_SETTING_TITLE)
704                       detailText:languageName
705                    iconImageName:kSettingsVoiceSearchImageName
706          accessibilityIdentifier:kSettingsVoiceSearchCellId];
707  return _voiceSearchDetailItem;
708}
709
710- (SettingsCheckItem*)safetyCheckDetailItem {
711  NSString* safetyCheckTitle =
712      l10n_util::GetNSString(IDS_OPTIONS_ADVANCED_SECTION_TITLE_SAFETY_CHECK);
713  _safetyCheckItem =
714      [[SettingsCheckItem alloc] initWithType:SettingsItemTypeSafetyCheck];
715  _safetyCheckItem.text = safetyCheckTitle;
716  _safetyCheckItem.enabled = YES;
717  _safetyCheckItem.indicatorHidden = YES;
718  _safetyCheckItem.infoButtonHidden = YES;
719  _safetyCheckItem.trailingImage = nil;
720  _safetyCheckItem.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
721  UIImage* safetyCheckIcon = [UIImage imageNamed:kSettingsSafetyCheckImageName];
722  _safetyCheckItem.leadingImage = safetyCheckIcon;
723
724  // Check if an issue state should be shown for updates.
725  if (!IsAppUpToDate() && PreviousSafetyCheckIssueFound()) {
726    UIImage* unSafeIconImage = [[UIImage imageNamed:@"settings_unsafe_state"]
727        imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
728    _safetyCheckItem.trailingImage = unSafeIconImage;
729    _safetyCheckItem.trailingImageTintColor = [UIColor colorNamed:kRedColor];
730  }
731
732  return _safetyCheckItem;
733}
734
735- (TableViewItem*)privacyDetailItem {
736  return [self
737           detailItemWithType:SettingsItemTypePrivacy
738                         text:l10n_util::GetNSString(
739                                  IDS_OPTIONS_ADVANCED_SECTION_TITLE_PRIVACY)
740                   detailText:nil
741                iconImageName:kSettingsPrivacyImageName
742      accessibilityIdentifier:kSettingsPrivacyCellId];
743}
744
745- (TableViewItem*)languageSettingsDetailItem {
746  return [self detailItemWithType:SettingsItemTypeLanguageSettings
747                             text:l10n_util::GetNSString(
748                                      IDS_IOS_LANGUAGE_SETTINGS_TITLE)
749                       detailText:nil
750                    iconImageName:kSettingsLanguageSettingsImageName
751          accessibilityIdentifier:kSettingsLanguagesCellId];
752}
753
754- (TableViewItem*)contentSettingsDetailItem {
755  return [self detailItemWithType:SettingsItemTypeContentSettings
756                             text:l10n_util::GetNSString(
757                                      IDS_IOS_CONTENT_SETTINGS_TITLE)
758                       detailText:nil
759                    iconImageName:kSettingsContentSettingsImageName
760          accessibilityIdentifier:kSettingsContentSettingsCellId];
761}
762
763- (TableViewItem*)bandwidthManagementDetailItem {
764  return [self detailItemWithType:SettingsItemTypeBandwidth
765                             text:l10n_util::GetNSString(
766                                      IDS_IOS_BANDWIDTH_MANAGEMENT_SETTINGS)
767                       detailText:nil
768                    iconImageName:kSettingsBandwidthImageName
769          accessibilityIdentifier:kSettingsBandwidthCellId];
770}
771
772- (TableViewItem*)aboutChromeDetailItem {
773  return [self detailItemWithType:SettingsItemTypeAboutChrome
774                             text:l10n_util::GetNSString(IDS_IOS_PRODUCT_NAME)
775                       detailText:nil
776                    iconImageName:kSettingsAboutChromeImageName
777          accessibilityIdentifier:kSettingsAboutCellId];
778}
779
780- (SettingsSwitchItem*)showMemoryDebugSwitchItem {
781  SettingsSwitchItem* showMemoryDebugSwitchItem =
782      [self switchItemWithType:SettingsItemTypeMemoryDebugging
783                            title:@"Show memory debug tools"
784                    iconImageName:kSettingsDebugImageName
785                  withDefaultsKey:nil
786          accessibilityIdentifier:nil];
787  showMemoryDebugSwitchItem.on = [_showMemoryDebugToolsEnabled value];
788
789  return showMemoryDebugSwitchItem;
790}
791
792- (SettingsSwitchItem*)articlesForYouSwitchItem {
793  NSString* settingTitle =
794      IsDiscoverFeedEnabled()
795          ? l10n_util::GetNSString(IDS_IOS_DISCOVER_FEED_TITLE)
796          : l10n_util::GetNSString(IDS_IOS_CONTENT_SUGGESTIONS_SETTING_TITLE);
797
798  SettingsSwitchItem* articlesForYouSwitchItem =
799      [self switchItemWithType:SettingsItemTypeArticlesForYou
800                            title:settingTitle
801                    iconImageName:kSettingsArticleSuggestionsImageName
802                  withDefaultsKey:nil
803          accessibilityIdentifier:kSettingsArticleSuggestionsCellId];
804  articlesForYouSwitchItem.on = [_articlesEnabled value];
805
806  return articlesForYouSwitchItem;
807}
808#if BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
809
810- (SettingsSwitchItem*)viewSourceSwitchItem {
811  return [self switchItemWithType:SettingsItemTypeViewSource
812                            title:@"View source menu"
813                    iconImageName:kSettingsDebugImageName
814                  withDefaultsKey:kDevViewSourceKey
815          accessibilityIdentifier:nil];
816}
817
818- (TableViewDetailIconItem*)tableViewCatalogDetailItem {
819  return [self detailItemWithType:SettingsItemTypeTableCellCatalog
820                             text:@"TableView Cell Catalog"
821                       detailText:nil
822                    iconImageName:kSettingsDebugImageName
823          accessibilityIdentifier:nil];
824}
825#endif  // BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
826
827#pragma mark Item Constructors
828
829- (TableViewDetailIconItem*)detailItemWithType:(NSInteger)type
830                                          text:(NSString*)text
831                                    detailText:(NSString*)detailText
832                                 iconImageName:(NSString*)iconImageName
833                       accessibilityIdentifier:
834                           (NSString*)accessibilityIdentifier {
835  TableViewDetailIconItem* detailItem =
836      [[TableViewDetailIconItem alloc] initWithType:type];
837  detailItem.text = text;
838  detailItem.detailText = detailText;
839  detailItem.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
840  detailItem.iconImageName = iconImageName;
841  detailItem.accessibilityTraits |= UIAccessibilityTraitButton;
842  detailItem.accessibilityIdentifier = accessibilityIdentifier;
843
844  return detailItem;
845}
846
847- (SettingsSwitchItem*)switchItemWithType:(NSInteger)type
848                                    title:(NSString*)title
849                            iconImageName:(NSString*)iconImageName
850                          withDefaultsKey:(NSString*)key
851                  accessibilityIdentifier:(NSString*)accessibilityIdentifier {
852  SettingsSwitchItem* switchItem =
853      [[SettingsSwitchItem alloc] initWithType:type];
854  switchItem.text = title;
855  switchItem.iconImageName = iconImageName;
856  switchItem.accessibilityIdentifier = accessibilityIdentifier;
857
858  if (key) {
859    NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
860    switchItem.on = [defaults boolForKey:key];
861  }
862
863  return switchItem;
864}
865
866#pragma mark - UITableViewDataSource
867
868- (UITableViewCell*)tableView:(UITableView*)tableView
869        cellForRowAtIndexPath:(NSIndexPath*)indexPath {
870  UITableViewCell* cell = [super tableView:tableView
871                     cellForRowAtIndexPath:indexPath];
872  NSInteger itemType = [self.tableViewModel itemTypeForIndexPath:indexPath];
873
874  if ([cell isKindOfClass:[TableViewDetailIconCell class]]) {
875    TableViewDetailIconCell* detailCell =
876        base::mac::ObjCCastStrict<TableViewDetailIconCell>(cell);
877    if (itemType == SettingsItemTypePasswords) {
878      scoped_refptr<password_manager::PasswordStore> passwordStore =
879          IOSChromePasswordStoreFactory::GetForBrowserState(
880              _browserState, ServiceAccessType::EXPLICIT_ACCESS);
881      if (!passwordStore) {
882        // The password store factory returns a NULL password store if something
883        // goes wrong during the password store initialization. Disable the save
884        // passwords cell in this case.
885        LOG(ERROR) << "Save passwords cell was disabled as the password store"
886                      " cannot be created.";
887        [detailCell setUserInteractionEnabled:NO];
888        detailCell.textLabel.textColor = UIColor.cr_secondaryLabelColor;
889        return cell;
890      }
891    }
892
893    [detailCell setUserInteractionEnabled:YES];
894    detailCell.textLabel.textColor = UIColor.cr_labelColor;
895  }
896
897  switch (itemType) {
898    case SettingsItemTypeMemoryDebugging: {
899      SettingsSwitchCell* switchCell =
900          base::mac::ObjCCastStrict<SettingsSwitchCell>(cell);
901      [switchCell.switchView addTarget:self
902                                action:@selector(memorySwitchToggled:)
903                      forControlEvents:UIControlEventValueChanged];
904      break;
905    }
906    case SettingsItemTypeArticlesForYou: {
907      SettingsSwitchCell* switchCell =
908          base::mac::ObjCCastStrict<SettingsSwitchCell>(cell);
909      [switchCell.switchView addTarget:self
910                                action:@selector(articlesForYouSwitchToggled:)
911                      forControlEvents:UIControlEventValueChanged];
912      break;
913    }
914    case SettingsItemTypeViewSource: {
915#if BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
916      SettingsSwitchCell* switchCell =
917          base::mac::ObjCCastStrict<SettingsSwitchCell>(cell);
918      [switchCell.switchView addTarget:self
919                                action:@selector(viewSourceSwitchToggled:)
920                      forControlEvents:UIControlEventValueChanged];
921#else
922      NOTREACHED();
923#endif  // BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
924      break;
925    }
926    case SettingsItemTypeManagedDefaultSearchEngine: {
927      TableViewInfoButtonCell* managedCell =
928          base::mac::ObjCCastStrict<TableViewInfoButtonCell>(cell);
929      [managedCell.trailingButton
930                 addTarget:self
931                    action:@selector(didTapManagedUIInfoButton:)
932          forControlEvents:UIControlEventTouchUpInside];
933      break;
934    }
935    default:
936      break;
937  }
938
939  return cell;
940}
941
942#pragma mark UITableViewDelegate
943
944- (void)tableView:(UITableView*)tableView
945    didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
946  id object = [self.tableViewModel itemAtIndexPath:indexPath];
947  if ([object respondsToSelector:@selector(isEnabled)] &&
948      ![object performSelector:@selector(isEnabled)]) {
949    // Don't perform any action if the cell isn't enabled.
950    return;
951  }
952
953  NSInteger itemType = [self.tableViewModel itemTypeForIndexPath:indexPath];
954
955  UIViewController<SettingsRootViewControlling>* controller;
956
957  switch (itemType) {
958    case SettingsItemTypeSignInButton:
959      signin_metrics::RecordSigninUserActionForAccessPoint(
960          signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS,
961          signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO);
962      [self showSignInWithIdentity:nil
963                       promoAction:signin_metrics::PromoAction::
964                                       PROMO_ACTION_NO_SIGNIN_PROMO
965                        completion:nil];
966      break;
967    case SettingsItemTypeAccount:
968      base::RecordAction(base::UserMetricsAction("Settings.MyAccount"));
969      controller = [[AccountsTableViewController alloc] initWithBrowser:_browser
970                                              closeSettingsOnAddAccount:NO];
971      break;
972    case SettingsItemTypeSyncAndGoogleServices:
973    case SettingsItemTypeGoogleServices:
974      base::RecordAction(base::UserMetricsAction("Settings.GoogleServices"));
975      [self showGoogleServices];
976      break;
977    case SettingsItemTypeGoogleSync:
978      base::RecordAction(base::UserMetricsAction("Settings.Sync"));
979      [self showGoogleSync];
980      break;
981    case SettingsItemTypeDefaultBrowser:
982      base::RecordAction(
983          base::UserMetricsAction("Settings.ShowDefaultBrowser"));
984      controller = [[DefaultBrowserSettingsTableViewController alloc] init];
985      break;
986    case SettingsItemTypeSearchEngine:
987      base::RecordAction(base::UserMetricsAction("EditSearchEngines"));
988      controller = [[SearchEngineTableViewController alloc]
989          initWithBrowserState:_browserState];
990      break;
991    case SettingsItemTypePasswords:
992      base::RecordAction(
993          base::UserMetricsAction("Options_ShowPasswordManager"));
994      [self showPasswords];
995      break;
996    case SettingsItemTypeAutofillCreditCard:
997      base::RecordAction(base::UserMetricsAction("AutofillCreditCardsViewed"));
998      controller = [[AutofillCreditCardTableViewController alloc]
999          initWithBrowser:_browser];
1000      break;
1001    case SettingsItemTypeAutofillProfile:
1002      base::RecordAction(base::UserMetricsAction("AutofillAddressesViewed"));
1003      controller = [[AutofillProfileTableViewController alloc]
1004          initWithBrowserState:_browserState];
1005      break;
1006    case SettingsItemTypeVoiceSearch:
1007      base::RecordAction(base::UserMetricsAction("Settings.VoiceSearch"));
1008      controller = [[VoiceSearchTableViewController alloc]
1009          initWithPrefs:_browserState->GetPrefs()];
1010      break;
1011    case SettingsItemTypeSafetyCheck:
1012      [self showSafetyCheck];
1013      break;
1014    case SettingsItemTypePrivacy:
1015      base::RecordAction(base::UserMetricsAction("Settings.Privacy"));
1016      [self showPrivacy];
1017      break;
1018    case SettingsItemTypeLanguageSettings: {
1019      base::RecordAction(base::UserMetricsAction("Settings.Language"));
1020      LanguageSettingsMediator* mediator =
1021          [[LanguageSettingsMediator alloc] initWithBrowserState:_browserState];
1022      LanguageSettingsTableViewController* languageSettingsTableViewController =
1023          [[LanguageSettingsTableViewController alloc]
1024              initWithDataSource:mediator
1025                  commandHandler:mediator];
1026      mediator.consumer = languageSettingsTableViewController;
1027      controller = languageSettingsTableViewController;
1028      break;
1029    }
1030    case SettingsItemTypeContentSettings:
1031      base::RecordAction(base::UserMetricsAction("Settings.ContentSettings"));
1032      controller = [[ContentSettingsTableViewController alloc]
1033          initWithBrowserState:_browserState];
1034      break;
1035    case SettingsItemTypeBandwidth:
1036      base::RecordAction(base::UserMetricsAction("Settings.Bandwidth"));
1037      controller = [[BandwidthManagementTableViewController alloc]
1038          initWithBrowserState:_browserState];
1039      break;
1040    case SettingsItemTypeAboutChrome:
1041      base::RecordAction(base::UserMetricsAction("AboutChrome"));
1042      controller = [[AboutChromeTableViewController alloc] init];
1043      break;
1044    case SettingsItemTypeMemoryDebugging:
1045    case SettingsItemTypeViewSource:
1046      // Taps on these don't do anything. They have a switch as accessory view
1047      // and only the switch is tappable.
1048      break;
1049    case SettingsItemTypeTableCellCatalog:
1050      [self.navigationController
1051          pushViewController:[[TableCellCatalogViewController alloc] init]
1052                    animated:YES];
1053      break;
1054    default:
1055      break;
1056  }
1057
1058  if (controller) {
1059    controller.dispatcher = self.dispatcher;
1060    [self.navigationController pushViewController:controller animated:YES];
1061  }
1062}
1063
1064#pragma mark - Actions
1065
1066// Called when the user clicks on the information button of a managed
1067// settings UI cell. Shows a contextual bubble with the information of the
1068// enterprise.
1069- (void)didTapManagedUIInfoButton:(UIButton*)buttonView {
1070  EnterpriseInfoPopoverViewController* bubbleViewController =
1071      [[EnterpriseInfoPopoverViewController alloc] initWithEnterpriseName:nil];
1072  bubbleViewController.delegate = self;
1073
1074  // Disable the button when showing the bubble.
1075  // The button will be enabled when close the bubble in
1076  // (void)popoverPresentationControllerDidDismissPopover: of
1077  // EnterpriseInfoPopoverViewController.
1078  buttonView.enabled = NO;
1079
1080  // Set the anchor and arrow direction of the bubble.
1081  bubbleViewController.popoverPresentationController.sourceView = buttonView;
1082  bubbleViewController.popoverPresentationController.sourceRect =
1083      buttonView.bounds;
1084  bubbleViewController.popoverPresentationController.permittedArrowDirections =
1085      UIPopoverArrowDirectionAny;
1086
1087  [self presentViewController:bubbleViewController animated:YES completion:nil];
1088}
1089
1090#pragma mark Switch Actions
1091
1092- (void)memorySwitchToggled:(UISwitch*)sender {
1093  NSIndexPath* switchPath =
1094      [self.tableViewModel indexPathForItemType:SettingsItemTypeMemoryDebugging
1095                              sectionIdentifier:SettingsSectionIdentifierDebug];
1096
1097  SettingsSwitchItem* switchItem =
1098      base::mac::ObjCCastStrict<SettingsSwitchItem>(
1099          [self.tableViewModel itemAtIndexPath:switchPath]);
1100
1101  BOOL newSwitchValue = sender.isOn;
1102  switchItem.on = newSwitchValue;
1103  [_showMemoryDebugToolsEnabled setValue:newSwitchValue];
1104}
1105
1106- (void)articlesForYouSwitchToggled:(UISwitch*)sender {
1107  NSIndexPath* switchPath = [self.tableViewModel
1108      indexPathForItemType:SettingsItemTypeArticlesForYou
1109         sectionIdentifier:SettingsSectionIdentifierAdvanced];
1110
1111  SettingsSwitchItem* switchItem =
1112      base::mac::ObjCCastStrict<SettingsSwitchItem>(
1113          [self.tableViewModel itemAtIndexPath:switchPath]);
1114
1115  BOOL newSwitchValue = sender.isOn;
1116  switchItem.on = newSwitchValue;
1117  [_articlesEnabled setValue:newSwitchValue];
1118}
1119
1120#if BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
1121- (void)viewSourceSwitchToggled:(UISwitch*)sender {
1122  NSIndexPath* switchPath =
1123      [self.tableViewModel indexPathForItemType:SettingsItemTypeViewSource
1124                              sectionIdentifier:SettingsSectionIdentifierDebug];
1125
1126  SettingsSwitchItem* switchItem =
1127      base::mac::ObjCCastStrict<SettingsSwitchItem>(
1128          [self.tableViewModel itemAtIndexPath:switchPath]);
1129
1130  BOOL newSwitchValue = sender.isOn;
1131  switchItem.on = newSwitchValue;
1132  [self setBooleanNSUserDefaultsValue:newSwitchValue forKey:kDevViewSourceKey];
1133}
1134#endif  // BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
1135
1136#pragma mark - Private methods
1137
1138- (void)showGoogleServices {
1139  DCHECK(!_googleServicesSettingsCoordinator);
1140  _googleServicesSettingsCoordinator =
1141      [[GoogleServicesSettingsCoordinator alloc]
1142          initWithBaseNavigationController:self.navigationController
1143                                   browser:_browser
1144                                      mode:GoogleServicesSettingsModeSettings];
1145  _googleServicesSettingsCoordinator.handler = self.dispatcher;
1146  _googleServicesSettingsCoordinator.delegate = self;
1147  [_googleServicesSettingsCoordinator start];
1148}
1149
1150- (void)showGoogleSync {
1151  DCHECK(!_manageSyncSettingsCoordinator);
1152  _manageSyncSettingsCoordinator = [[ManageSyncSettingsCoordinator alloc]
1153      initWithBaseNavigationController:self.navigationController
1154                               browser:_browser];
1155  _manageSyncSettingsCoordinator.delegate = self;
1156  [_manageSyncSettingsCoordinator start];
1157}
1158
1159- (void)showPasswords {
1160  DCHECK(!_passwordsCoordinator);
1161  _passwordsCoordinator = [[PasswordsCoordinator alloc]
1162      initWithBaseNavigationController:self.navigationController
1163                               browser:_browser];
1164  _passwordsCoordinator.delegate = self;
1165  [_passwordsCoordinator start];
1166}
1167
1168// Shows Safety Check Screen.
1169- (void)showSafetyCheck {
1170  DCHECK(!_safetyCheckCoordinator);
1171  _safetyCheckCoordinator = [[SafetyCheckCoordinator alloc]
1172      initWithBaseNavigationController:self.navigationController
1173                               browser:_browser];
1174  _safetyCheckCoordinator.delegate = self;
1175  [_safetyCheckCoordinator start];
1176}
1177
1178// Checks if there are any remaining password issues from the last time password
1179// check was run.
1180- (BOOL)hasPasswordIssuesRemaining {
1181  return !_passwordCheckManager->GetCompromisedCredentials().empty();
1182}
1183
1184// Displays a red issue state on |_safetyCheckItem| if there is a reamining
1185// issue for any of the checks.
1186- (void)setSafetyCheckIssueStateUnsafe:(BOOL)isUnsafe {
1187  if (isUnsafe && PreviousSafetyCheckIssueFound()) {
1188    UIImage* unSafeIconImage = [[UIImage imageNamed:@"settings_unsafe_state"]
1189        imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
1190    _safetyCheckItem.trailingImage = unSafeIconImage;
1191    _safetyCheckItem.trailingImageTintColor = [UIColor colorNamed:kRedColor];
1192  } else {
1193    _safetyCheckItem.trailingImage = nil;
1194    _safetyCheckItem.trailingImageTintColor = nil;
1195  }
1196  [self reconfigureCellsForItems:@[ _safetyCheckItem ]];
1197}
1198
1199// Shows Privacy screen.
1200- (void)showPrivacy {
1201  DCHECK(!_privacyCoordinator);
1202  _privacyCoordinator = [[PrivacyCoordinator alloc]
1203      initWithBaseNavigationController:self.navigationController
1204                               browser:_browser];
1205  _privacyCoordinator.delegate = self;
1206  [_privacyCoordinator start];
1207}
1208
1209// Sets the NSUserDefaults BOOL |value| for |key|.
1210- (void)setBooleanNSUserDefaultsValue:(BOOL)value forKey:(NSString*)key {
1211  NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
1212  [defaults setBool:value forKey:key];
1213  [defaults synchronize];
1214}
1215
1216// Returns YES if a "Debug" section should be shown. This is always true for
1217// Chromium builds, but for official builds it is gated by an experimental flag
1218// because the "Debug" section should never be showing in stable channel.
1219- (BOOL)hasDebugSection {
1220#if BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
1221  return YES;
1222#else
1223  if (experimental_flags::IsMemoryDebuggingEnabled()) {
1224    return YES;
1225  }
1226  return NO;
1227#endif  // BUILDFLAG(CHROMIUM_BRANDING) && !defined(NDEBUG)
1228}
1229
1230// Updates the identity cell.
1231- (void)updateIdentityAccountItem:(TableViewAccountItem*)identityAccountItem {
1232  AuthenticationService* authService =
1233      AuthenticationServiceFactory::GetForBrowserState(_browserState);
1234  _identity = authService->GetAuthenticatedIdentity();
1235  if (!_identity) {
1236    // This could occur during the sign out process. Just ignore as the account
1237    // cell will be replaced by the "Sign in" button.
1238    return;
1239  }
1240  identityAccountItem.image = [self userAccountImage];
1241  identityAccountItem.text = [_identity userFullName];
1242  identityAccountItem.detailText = _identity.userEmail;
1243}
1244
1245- (void)reloadAccountCell {
1246  if (![self.tableViewModel
1247          hasItemForItemType:SettingsItemTypeAccount
1248           sectionIdentifier:SettingsSectionIdentifierAccount]) {
1249    return;
1250  }
1251  NSIndexPath* accountCellIndexPath = [self.tableViewModel
1252      indexPathForItemType:SettingsItemTypeAccount
1253         sectionIdentifier:SettingsSectionIdentifierAccount];
1254  TableViewAccountItem* identityAccountItem =
1255      base::mac::ObjCCast<TableViewAccountItem>(
1256          [self.tableViewModel itemAtIndexPath:accountCellIndexPath]);
1257  if (identityAccountItem) {
1258    [self updateIdentityAccountItem:identityAccountItem];
1259    [self reconfigureCellsForItems:@[ identityAccountItem ]];
1260  }
1261}
1262
1263// Updates the Sync & Google services item to display the right icon and status
1264// message in the detail text of the cell.
1265- (void)updateSyncAndGoogleServicesItem:
1266    (SettingsImageDetailTextItem*)googleServicesItem {
1267  googleServicesItem.detailTextColor = nil;
1268  syncer::SyncService* syncService =
1269      ProfileSyncServiceFactory::GetForBrowserState(_browserState);
1270  SyncSetupService* syncSetupService =
1271      SyncSetupServiceFactory::GetForBrowserState(_browserState);
1272  AuthenticationService* authService =
1273      AuthenticationServiceFactory::GetForBrowserState(_browserState);
1274  if (!authService->IsAuthenticated()) {
1275    // No sync status when the user is not signed-in.
1276    googleServicesItem.detailText = nil;
1277    googleServicesItem.image =
1278        [UIImage imageNamed:kSyncAndGoogleServicesImageName];
1279  } else if (syncService->GetDisableReasons().Has(
1280                 syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY)) {
1281    googleServicesItem.detailText = l10n_util::GetNSString(
1282        IDS_IOS_GOOGLE_SERVICES_SETTINGS_SYNC_DISABLBED_BY_ADMINISTRATOR_STATUS);
1283    googleServicesItem.image =
1284        [UIImage imageNamed:kSyncAndGoogleServicesSyncOffImageName];
1285  } else if (!syncSetupService->IsFirstSetupComplete()) {
1286    googleServicesItem.detailText =
1287        l10n_util::GetNSString(IDS_IOS_SYNC_SETUP_IN_PROGRESS);
1288    googleServicesItem.image =
1289        [UIImage imageNamed:kSyncAndGoogleServicesSyncOnImageName];
1290  } else if (!IsTransientSyncError(syncSetupService->GetSyncServiceState())) {
1291    googleServicesItem.detailTextColor = [UIColor colorNamed:kRedColor];
1292    googleServicesItem.detailText =
1293        GetSyncErrorDescriptionForSyncSetupService(syncSetupService);
1294    googleServicesItem.image =
1295        [UIImage imageNamed:kSyncAndGoogleServicesSyncErrorImageName];
1296  } else if (syncSetupService->IsSyncEnabled()) {
1297    googleServicesItem.detailText =
1298        l10n_util::GetNSString(IDS_IOS_SIGN_IN_TO_CHROME_SETTING_SYNC_ON);
1299    googleServicesItem.image =
1300        [UIImage imageNamed:kSyncAndGoogleServicesSyncOnImageName];
1301  } else {
1302    googleServicesItem.detailText =
1303        l10n_util::GetNSString(IDS_IOS_SIGN_IN_TO_CHROME_SETTING_SYNC_OFF);
1304    googleServicesItem.image =
1305        [UIImage imageNamed:kSyncAndGoogleServicesSyncOffImageName];
1306  }
1307  DCHECK(googleServicesItem.image);
1308}
1309
1310// Updates the Sync item to display the right icon and status message in the
1311// cell.
1312- (void)updateGoogleSyncCellItem:(TableViewDetailIconItem*)googleSyncItem {
1313  syncer::SyncService* syncService =
1314      ProfileSyncServiceFactory::GetForBrowserState(_browserState);
1315  SyncSetupService* syncSetupService =
1316      SyncSetupServiceFactory::GetForBrowserState(_browserState);
1317  if (syncService->GetDisableReasons().Has(
1318          syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY)) {
1319    googleSyncItem.detailText = l10n_util::GetNSString(
1320        IDS_IOS_GOOGLE_SERVICES_SETTINGS_SYNC_DISABLBED_BY_ADMINISTRATOR_STATUS);
1321    googleSyncItem.iconImageName = kSyncAndGoogleServicesSyncOffImageName;
1322  } else if (!IsTransientSyncError(syncSetupService->GetSyncServiceState())) {
1323    googleSyncItem.detailText =
1324        GetSyncErrorDescriptionForSyncSetupService(syncSetupService);
1325    googleSyncItem.iconImageName = kSyncAndGoogleServicesSyncErrorImageName;
1326  } else if (syncSetupService->IsFirstSetupComplete() &&
1327             syncSetupService->IsSyncEnabled()) {
1328    googleSyncItem.detailText = l10n_util::GetNSString(IDS_IOS_SETTING_ON);
1329    googleSyncItem.iconImageName = kSyncAndGoogleServicesSyncOnImageName;
1330  } else {
1331    googleSyncItem.detailText = l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
1332    googleSyncItem.iconImageName = kSyncAndGoogleServicesSyncOffImageName;
1333  }
1334}
1335
1336// Updates and reloads the Google service cell.
1337- (void)reloadSyncAndGoogleServicesCell {
1338  if (base::FeatureList::IsEnabled(signin::kMobileIdentityConsistency)) {
1339    bool googleSyncCellInitialized = [self.tableViewModel
1340        hasItemForItemType:SettingsItemTypeGoogleSync
1341         sectionIdentifier:SettingsSectionIdentifierAccount];
1342    AuthenticationService* authService =
1343        AuthenticationServiceFactory::GetForBrowserState(_browserState);
1344    if (authService->IsAuthenticated()) {
1345      if (!googleSyncCellInitialized) {
1346        [self.tableViewModel addItem:[self googleSyncCellItem]
1347             toSectionWithIdentifier:SettingsSectionIdentifierAccount];
1348      } else {
1349        NSIndexPath* syncCellIndexPath = [self.tableViewModel
1350            indexPathForItemType:SettingsItemTypeGoogleSync
1351               sectionIdentifier:SettingsSectionIdentifierAccount];
1352        TableViewDetailIconItem* detailIconItem =
1353            base::mac::ObjCCast<TableViewDetailIconItem>(
1354                [self.tableViewModel itemAtIndexPath:syncCellIndexPath]);
1355        [self updateGoogleSyncCellItem:detailIconItem];
1356        [self reconfigureCellsForItems:@[ detailIconItem ]];
1357      }
1358    } else {
1359      if (googleSyncCellInitialized) {
1360        [self.tableViewModel
1361                   removeItemWithType:SettingsItemTypeGoogleSync
1362            fromSectionWithIdentifier:SettingsSectionIdentifierAccount];
1363      }
1364    }
1365  } else {
1366    NSIndexPath* googleServicesCellIndexPath = [self.tableViewModel
1367        indexPathForItemType:SettingsItemTypeSyncAndGoogleServices
1368           sectionIdentifier:SettingsSectionIdentifierAccount];
1369    SettingsImageDetailTextItem* googleServicesItem =
1370        base::mac::ObjCCast<SettingsImageDetailTextItem>(
1371            [self.tableViewModel itemAtIndexPath:googleServicesCellIndexPath]);
1372    DCHECK(googleServicesItem);
1373    [self updateSyncAndGoogleServicesItem:googleServicesItem];
1374    [self reconfigureCellsForItems:@[ googleServicesItem ]];
1375  }
1376}
1377
1378// Check if the default search engine is managed by policy.
1379- (BOOL)isDefaultSearchEngineManagedByPolicy {
1380  const base::DictionaryValue* dict = _browserState->GetPrefs()->GetDictionary(
1381      DefaultSearchManager::kDefaultSearchProviderDataPrefName);
1382
1383  if (dict) {
1384    if (dict->FindBoolPath(DefaultSearchManager::kDisabledByPolicy) ||
1385        dict->FindBoolPath(prefs::kDefaultSearchProviderEnabled))
1386      return YES;
1387  }
1388  return NO;
1389}
1390
1391#pragma mark - SigninPresenter
1392
1393- (void)showSignin:(ShowSigninCommand*)command {
1394  [self.dispatcher showSignin:command baseViewController:self];
1395}
1396
1397#pragma mark Sign in
1398
1399- (void)showSignInWithIdentity:(ChromeIdentity*)identity
1400                   promoAction:(signin_metrics::PromoAction)promoAction
1401                    completion:(ShowSigninCommandCompletionCallback)completion {
1402  DCHECK(!self.isSigninInProgress);
1403  self.isSigninInProgress = YES;
1404  __weak __typeof(self) weakSelf = self;
1405  ShowSigninCommand* command = [[ShowSigninCommand alloc]
1406      initWithOperation:AUTHENTICATION_OPERATION_SIGNIN
1407               identity:identity
1408            accessPoint:signin_metrics::AccessPoint::ACCESS_POINT_SETTINGS
1409            promoAction:promoAction
1410               callback:^(BOOL success) {
1411                 if (completion)
1412                   completion(success);
1413                 [weakSelf didFinishSignin:success];
1414               }];
1415  [self.dispatcher showSignin:command baseViewController:self];
1416}
1417
1418- (void)didFinishSignin:(BOOL)signedIn {
1419  // The sign-in is done. The sign-in promo cell or account cell can be
1420  // reloaded.
1421  DCHECK(self.isSigninInProgress);
1422  self.isSigninInProgress = NO;
1423  DCHECK(!_settingsHasBeenDismissed);
1424  [self reloadData];
1425}
1426
1427#pragma mark SettingsControllerProtocol
1428
1429- (void)reportDismissalUserAction {
1430  base::RecordAction(base::UserMetricsAction("MobileSettingsClose"));
1431}
1432
1433- (void)reportBackUserAction {
1434  // Not called for root settings controller.
1435  NOTREACHED();
1436}
1437
1438- (void)settingsWillBeDismissed {
1439  DCHECK(!_settingsHasBeenDismissed);
1440
1441  [_googleServicesSettingsCoordinator stop];
1442  _googleServicesSettingsCoordinator.delegate = nil;
1443  _googleServicesSettingsCoordinator = nil;
1444
1445  [_safetyCheckCoordinator stop];
1446  _safetyCheckCoordinator = nil;
1447
1448  [_passwordsCoordinator stop];
1449  _passwordsCoordinator.delegate = nil;
1450  _passwordsCoordinator = nil;
1451
1452  [_privacyCoordinator stop];
1453  _privacyCoordinator = nil;
1454
1455  [_manageSyncSettingsCoordinator stop];
1456  _manageSyncSettingsCoordinator = nil;
1457
1458  _settingsHasBeenDismissed = YES;
1459  DCHECK(!self.isSigninInProgress);
1460  [_signinPromoViewMediator signinPromoViewIsRemoved];
1461  _signinPromoViewMediator = nil;
1462  [self stopBrowserStateServiceObservers];
1463}
1464
1465#pragma mark SyncObserverModelBridge
1466
1467- (void)onSyncStateChanged {
1468  [self reloadSyncAndGoogleServicesCell];
1469}
1470
1471#pragma mark - IdentityRefreshLogic
1472
1473// Image used for loggedin user account that supports caching.
1474- (UIImage*)userAccountImage {
1475  UIImage* image = ios::GetChromeBrowserProvider()
1476                       ->GetChromeIdentityService()
1477                       ->GetCachedAvatarForIdentity(_identity);
1478  if (!image) {
1479    image = ios::GetChromeBrowserProvider()
1480                ->GetSigninResourcesProvider()
1481                ->GetDefaultAvatar();
1482    // No cached image, trigger a fetch, which will notify all observers
1483    // (including the corresponding AccountViewBase).
1484    ios::GetChromeBrowserProvider()
1485        ->GetChromeIdentityService()
1486        ->GetAvatarForIdentity(_identity, nil);
1487  }
1488
1489  // If the currently used image has already been resized, use it.
1490  if (_resizedImage && _oldImage == image)
1491    return _resizedImage;
1492
1493  _oldImage = image;
1494
1495  // Resize the profile image.
1496  CGFloat dimension = kAccountProfilePhotoDimension;
1497  if (image.size.width != dimension || image.size.height != dimension) {
1498    image = ResizeImage(image, CGSizeMake(dimension, dimension),
1499                        ProjectionMode::kAspectFit);
1500  }
1501  _resizedImage = image;
1502  return _resizedImage;
1503}
1504
1505#pragma mark - SearchEngineObserverBridge
1506
1507- (void)searchEngineChanged {
1508  _defaultSearchEngineItem.detailText =
1509      base::SysUTF16ToNSString(GetDefaultSearchEngineName(
1510          ios::TemplateURLServiceFactory::GetForBrowserState(_browserState)));
1511  [self reconfigureCellsForItems:@[ _defaultSearchEngineItem ]];
1512}
1513
1514#pragma mark ChromeIdentityServiceObserver
1515
1516- (void)profileUpdate:(ChromeIdentity*)identity {
1517  if ([_identity isEqual:identity]) {
1518    [self reloadAccountCell];
1519  }
1520}
1521
1522- (void)chromeIdentityServiceWillBeDestroyed {
1523  _identityServiceObserver.reset();
1524}
1525
1526#pragma mark - BooleanObserver
1527
1528- (void)booleanDidChange:(id<ObservableBoolean>)observableBoolean {
1529  if (observableBoolean == _showMemoryDebugToolsEnabled) {
1530    // Update the Item.
1531    _showMemoryDebugToolsItem.on = [_showMemoryDebugToolsEnabled value];
1532    // Update the Cell.
1533    [self reconfigureCellsForItems:@[ _showMemoryDebugToolsItem ]];
1534  } else if (observableBoolean == _articlesEnabled) {
1535    _articlesForYouItem.on = [_articlesEnabled value];
1536    [self reconfigureCellsForItems:@[ _articlesForYouItem ]];
1537  } else {
1538    NOTREACHED();
1539  }
1540}
1541
1542#pragma mark - PasswordCheckObserver
1543
1544- (void)passwordCheckStateDidChange:(PasswordCheckState)state {
1545  [self setSafetyCheckIssueStateUnsafe:[self hasPasswordIssuesRemaining]];
1546}
1547
1548- (void)compromisedCredentialsDidChange:
1549    (password_manager::InsecureCredentialsManager::CredentialsView)credentials {
1550  [self setSafetyCheckIssueStateUnsafe:[self hasPasswordIssuesRemaining]];
1551}
1552
1553#pragma mark - PrefObserverDelegate
1554
1555- (void)onPreferenceChanged:(const std::string&)preferenceName {
1556  if (preferenceName == prefs::kVoiceSearchLocale) {
1557    voice::SpeechInputLocaleConfig* localeConfig =
1558        voice::SpeechInputLocaleConfig::GetInstance();
1559    voice::SpeechInputLocale locale =
1560        _voiceLocaleCode.GetValue().length()
1561            ? localeConfig->GetLocaleForCode(_voiceLocaleCode.GetValue())
1562            : localeConfig->GetDefaultLocale();
1563    NSString* languageName = base::SysUTF16ToNSString(locale.display_name);
1564    _voiceSearchDetailItem.detailText = languageName;
1565    [self reconfigureCellsForItems:@[ _voiceSearchDetailItem ]];
1566  }
1567
1568  if (preferenceName == password_manager::prefs::kCredentialsEnableService) {
1569    BOOL passwordsEnabled =
1570        _browserState->GetPrefs()->GetBoolean(preferenceName);
1571    NSString* passwordsDetail =
1572        passwordsEnabled ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
1573                         : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
1574    _passwordsDetailItem.detailText = passwordsDetail;
1575    [self reconfigureCellsForItems:@[ _passwordsDetailItem ]];
1576  }
1577
1578  if (preferenceName == autofill::prefs::kAutofillProfileEnabled) {
1579    BOOL autofillProfileEnabled =
1580        autofill::prefs::IsAutofillProfileEnabled(_browserState->GetPrefs());
1581    NSString* detailText = autofillProfileEnabled
1582                               ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
1583                               : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
1584    _autoFillProfileDetailItem.detailText = detailText;
1585    [self reconfigureCellsForItems:@[ _autoFillProfileDetailItem ]];
1586  }
1587
1588  if (preferenceName == autofill::prefs::kAutofillCreditCardEnabled) {
1589    BOOL autofillCreditCardEnabled =
1590        autofill::prefs::IsAutofillCreditCardEnabled(_browserState->GetPrefs());
1591    NSString* detailText = autofillCreditCardEnabled
1592                               ? l10n_util::GetNSString(IDS_IOS_SETTING_ON)
1593                               : l10n_util::GetNSString(IDS_IOS_SETTING_OFF);
1594    _autoFillCreditCardDetailItem.detailText = detailText;
1595    [self reconfigureCellsForItems:@[ _autoFillCreditCardDetailItem ]];
1596  }
1597}
1598
1599#pragma mark - SigninPromoViewConsumer
1600
1601- (void)configureSigninPromoWithConfigurator:
1602            (SigninPromoViewConfigurator*)configurator
1603                             identityChanged:(BOOL)identityChanged {
1604  DCHECK(!self.isSigninInProgress);
1605  if (![self.tableViewModel
1606          hasItemForItemType:SettingsItemTypeSigninPromo
1607           sectionIdentifier:SettingsSectionIdentifierSignIn]) {
1608    return;
1609  }
1610  NSIndexPath* signinPromoCellIndexPath = [self.tableViewModel
1611      indexPathForItemType:SettingsItemTypeSigninPromo
1612         sectionIdentifier:SettingsSectionIdentifierSignIn];
1613  DCHECK(signinPromoCellIndexPath.item != NSNotFound);
1614  TableViewSigninPromoItem* signinPromoItem =
1615      base::mac::ObjCCast<TableViewSigninPromoItem>(
1616          [self.tableViewModel itemAtIndexPath:signinPromoCellIndexPath]);
1617  if (signinPromoItem) {
1618    signinPromoItem.configurator = configurator;
1619    signinPromoItem.delegate = _signinPromoViewMediator;
1620    [self reconfigureCellsForItems:@[ signinPromoItem ]];
1621  }
1622}
1623
1624- (void)signinPromoViewMediator:(SigninPromoViewMediator*)mediator
1625    shouldOpenSigninWithIdentity:(ChromeIdentity*)identity
1626                     promoAction:(signin_metrics::PromoAction)promoAction
1627                      completion:
1628                          (ShowSigninCommandCompletionCallback)completion {
1629  [self showSignInWithIdentity:identity
1630                   promoAction:promoAction
1631                    completion:completion];
1632}
1633
1634- (void)signinPromoViewMediatorCloseButtonWasTapped:
1635    (SigninPromoViewMediator*)mediator {
1636  [self reloadData];
1637}
1638
1639#pragma mark - GoogleServicesSettingsCoordinatorDelegate
1640
1641- (void)googleServicesSettingsCoordinatorDidRemove:
1642    (GoogleServicesSettingsCoordinator*)coordinator {
1643  DCHECK_EQ(_googleServicesSettingsCoordinator, coordinator);
1644  [_googleServicesSettingsCoordinator stop];
1645  _googleServicesSettingsCoordinator.delegate = nil;
1646  _googleServicesSettingsCoordinator = nil;
1647}
1648
1649#pragma mark - SafetyCheckCoordinatorDelegate
1650
1651- (void)safetyCheckCoordinatorDidRemove:(SafetyCheckCoordinator*)coordinator {
1652  DCHECK_EQ(_safetyCheckCoordinator, coordinator);
1653  [_safetyCheckCoordinator stop];
1654  _safetyCheckCoordinator.delegate = nil;
1655  _safetyCheckCoordinator = nil;
1656}
1657
1658#pragma mark - SafetyCheckCoordinatorDelegate
1659
1660- (void)passwordsCoordinatorDidRemove:(PasswordsCoordinator*)coordinator {
1661  DCHECK_EQ(_passwordsCoordinator, coordinator);
1662  [_passwordsCoordinator stop];
1663  _passwordsCoordinator.delegate = nil;
1664  _passwordsCoordinator = nil;
1665}
1666
1667#pragma mark - PrivacyCoordinatorDelegate
1668
1669- (void)privacyCoordinatorViewControllerWasRemoved:
1670    (PrivacyCoordinator*)coordinator {
1671  DCHECK_EQ(_privacyCoordinator, coordinator);
1672  [_privacyCoordinator stop];
1673  _privacyCoordinator = nil;
1674}
1675
1676#pragma mark - IdentityManagerObserverBridgeDelegate
1677
1678// Notifies this controller that the sign in state has changed.
1679- (void)signinStateDidChange {
1680  // While the sign-in view is in progress, the TableView should not be
1681  // updated. Otherwise, it would lead to an UI glitch either while the sign
1682  // in UI is appearing or disappearing. The TableView will be reloaded once
1683  // the animation is finished.
1684  // See: -[SettingsTableViewController didFinishSignin:].
1685  if (self.isSigninInProgress)
1686    return;
1687  // Sign in state changes are rare. Just reload the entire table when
1688  // this happens.
1689  [self reloadData];
1690}
1691- (void)onPrimaryAccountSet:(const CoreAccountInfo&)primaryAccountInfo {
1692  [self signinStateDidChange];
1693}
1694
1695- (void)onPrimaryAccountCleared:
1696    (const CoreAccountInfo&)previousPrimaryAccountInfo {
1697  [self signinStateDidChange];
1698}
1699
1700#pragma mark - UIAdaptivePresentationControllerDelegate
1701
1702- (void)presentationControllerDidDismiss:
1703    (UIPresentationController*)presentationController {
1704  base::RecordAction(base::UserMetricsAction("IOSSettingsCloseWithSwipe"));
1705}
1706
1707#pragma mark - PopoverLabelViewControllerDelegate
1708
1709- (void)didTapLinkURL:(NSURL*)URL {
1710  GURL convertedURL = net::GURLWithNSURL(URL);
1711  [self view:nil didTapLinkURL:convertedURL];
1712}
1713
1714#pragma mark - ManageSyncSettingsCoordinatorDelegate
1715
1716- (void)manageSyncSettingsCoordinatorWasRemoved:
1717    (ManageSyncSettingsCoordinator*)coordinator {
1718  DCHECK_EQ(_manageSyncSettingsCoordinator, coordinator);
1719  [_manageSyncSettingsCoordinator stop];
1720  _manageSyncSettingsCoordinator = nil;
1721}
1722
1723@end
1724