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/google_services/accounts_table_view_controller.h"
6
7#import "base/mac/foundation_util.h"
8#include "base/metrics/histogram_macros.h"
9#include "base/metrics/user_metrics.h"
10#include "base/strings/sys_string_conversions.h"
11#include "base/strings/utf_string_conversions.h"
12#import "components/signin/public/identity_manager/identity_manager.h"
13#import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"
14#include "components/strings/grit/components_strings.h"
15#include "components/sync/driver/sync_service.h"
16#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
17#include "ios/chrome/browser/main/browser.h"
18#import "ios/chrome/browser/signin/authentication_service.h"
19#include "ios/chrome/browser/signin/authentication_service_factory.h"
20#import "ios/chrome/browser/signin/chrome_identity_service_observer_bridge.h"
21#include "ios/chrome/browser/signin/identity_manager_factory.h"
22#include "ios/chrome/browser/sync/profile_sync_service_factory.h"
23#include "ios/chrome/browser/sync/sync_setup_service.h"
24#include "ios/chrome/browser/sync/sync_setup_service_factory.h"
25#import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.h"
26#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
27#import "ios/chrome/browser/ui/authentication/cells/table_view_account_item.h"
28#import "ios/chrome/browser/ui/authentication/resized_avatar_cache.h"
29#import "ios/chrome/browser/ui/commands/application_commands.h"
30#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
31#import "ios/chrome/browser/ui/commands/show_signin_command.h"
32#import "ios/chrome/browser/ui/icons/chrome_icon.h"
33#import "ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller_constants.h"
34#import "ios/chrome/browser/ui/settings/sync/utils/sync_util.h"
35#import "ios/chrome/browser/ui/table_view/cells/table_view_detail_text_item.h"
36#import "ios/chrome/browser/ui/table_view/cells/table_view_link_header_footer_item.h"
37#import "ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.h"
38#import "ios/chrome/browser/ui/table_view/cells/table_view_text_item.h"
39#import "ios/chrome/browser/ui/table_view/table_view_model.h"
40#include "ios/chrome/browser/ui/ui_feature_flags.h"
41#import "ios/chrome/common/ui/colors/semantic_color_names.h"
42#include "ios/chrome/grit/ios_chromium_strings.h"
43#include "ios/chrome/grit/ios_strings.h"
44#import "ios/public/provider/chrome/browser/chrome_browser_provider.h"
45#import "ios/public/provider/chrome/browser/images/branded_image_provider.h"
46#import "ios/public/provider/chrome/browser/signin/chrome_identity.h"
47#import "ios/public/provider/chrome/browser/signin/chrome_identity_browser_opener.h"
48#import "ios/public/provider/chrome/browser/signin/chrome_identity_service.h"
49#import "net/base/mac/url_conversions.h"
50#include "ui/base/l10n/l10n_util_mac.h"
51
52#if !defined(__has_feature) || !__has_feature(objc_arc)
53#error "This file requires ARC support."
54#endif
55
56using signin_metrics::AccessPoint;
57using signin_metrics::PromoAction;
58
59namespace {
60
61typedef NS_ENUM(NSInteger, SectionIdentifier) {
62  SectionIdentifierAccounts = kSectionIdentifierEnumZero,
63  SectionIdentifierSync,
64  SectionIdentifierSignOut,
65};
66
67typedef NS_ENUM(NSInteger, ItemType) {
68  ItemTypeAccount = kItemTypeEnumZero,
69  ItemTypeAddAccount,
70  // Provides sign out items used only for non-managed accounts.
71  ItemTypeSignOut,
72  // Sign out item that clears Chrome data. Used for both managed
73  // and non-managed accounts.
74  ItemTypeSignOutAndClearData,
75  ItemTypeHeader,
76  // Detailed description of the actions taken by sign out e.g. turning off sync
77  // and clearing Chrome data.
78  ItemTypeSignOutManagedAccountFooter,
79  // Detailed description of the actions taken by sign out, e.g. turning off
80  // sync.
81  ItemTypeSignOutNonManagedAccountFooter,
82};
83
84}  // namespace
85
86@interface AccountsTableViewController () <
87    ChromeIdentityServiceObserver,
88    ChromeIdentityBrowserOpener,
89    IdentityManagerObserverBridgeDelegate> {
90  Browser* _browser;
91  BOOL _closeSettingsOnAddAccount;
92  std::unique_ptr<signin::IdentityManagerObserverBridge>
93      _identityManagerObserver;
94  // Modal alert for sign out.
95  AlertCoordinator* _alertCoordinator;
96  // Whether an authentication operation is in progress (e.g switch accounts,
97  // sign out).
98  BOOL _authenticationOperationInProgress;
99  // Whether the view controller is currently being dismissed and new dismiss
100  // requests should be ignored.
101  BOOL _isBeingDismissed;
102  ios::DismissASMViewControllerBlock _dimissAccountDetailsViewControllerBlock;
103  ResizedAvatarCache* _avatarCache;
104  std::unique_ptr<ChromeIdentityServiceObserverBridge> _identityServiceObserver;
105
106  // Enable lookup of item corresponding to a given identity GAIA ID string.
107  NSDictionary<NSString*, TableViewItem*>* _identityMap;
108}
109
110// Modal alert for confirming account removal.
111@property(nonatomic, strong) AlertCoordinator* removeAccountCoordinator;
112
113// If YES, the UI elements are disabled.
114@property(nonatomic, assign) BOOL uiDisabled;
115
116// Stops observing browser state services. This is required during the shutdown
117// phase to avoid observing services for a browser state that is being killed.
118- (void)stopBrowserStateServiceObservers;
119
120@end
121
122@implementation AccountsTableViewController
123
124@synthesize dispatcher = _dispatcher;
125
126- (instancetype)initWithBrowser:(Browser*)browser
127      closeSettingsOnAddAccount:(BOOL)closeSettingsOnAddAccount {
128  DCHECK(browser);
129  DCHECK(!browser->GetBrowserState()->IsOffTheRecord());
130  UITableViewStyle style = base::FeatureList::IsEnabled(kSettingsRefresh)
131                               ? UITableViewStylePlain
132                               : UITableViewStyleGrouped;
133  self = [super initWithStyle:style];
134  if (self) {
135    _browser = browser;
136    _closeSettingsOnAddAccount = closeSettingsOnAddAccount;
137    [self authService]->WaitUntilCacheIsPopulated();
138    _identityManagerObserver =
139        std::make_unique<signin::IdentityManagerObserverBridge>(
140            IdentityManagerFactory::GetForBrowserState(
141                _browser->GetBrowserState()),
142            self);
143    _avatarCache = [[ResizedAvatarCache alloc] init];
144    _identityServiceObserver.reset(
145        new ChromeIdentityServiceObserverBridge(self));
146  }
147
148  return self;
149}
150
151- (void)viewDidLoad {
152  [super viewDidLoad];
153  self.tableView.accessibilityIdentifier = kSettingsAccountsTableViewId;
154
155  [self loadModel];
156}
157
158- (void)stopBrowserStateServiceObservers {
159  _identityManagerObserver.reset();
160}
161
162#pragma mark - SettingsControllerProtocol
163
164- (void)reportDismissalUserAction {
165  base::RecordAction(base::UserMetricsAction("MobileAccountsSettingsClose"));
166}
167
168- (void)reportBackUserAction {
169  base::RecordAction(base::UserMetricsAction("MobileAccountsSettingsBack"));
170}
171
172- (void)settingsWillBeDismissed {
173  [_alertCoordinator stop];
174  [self.removeAccountCoordinator stop];
175  [self stopBrowserStateServiceObservers];
176}
177
178#pragma mark - SettingsRootTableViewController
179
180- (void)reloadData {
181  if (![self authService] -> IsAuthenticated()) {
182    // This accounts table view will be popped or dismissed when the user
183    // is signed out. Avoid reloading it in that case as that would lead to an
184    // empty table view.
185    return;
186  }
187  [super reloadData];
188}
189
190- (void)loadModel {
191  // Update the title with the name with the currently signed-in account.
192  ChromeIdentity* authenticatedIdentity =
193      [self authService] -> GetAuthenticatedIdentity();
194  NSString* title = nil;
195  if (authenticatedIdentity) {
196    title = [authenticatedIdentity userFullName];
197    if (!title) {
198      title = [authenticatedIdentity userEmail];
199    }
200  }
201  self.title = title;
202
203  [super loadModel];
204
205  if (![self authService] -> IsAuthenticated())
206    return;
207
208  TableViewModel* model = self.tableViewModel;
209
210  NSMutableDictionary<NSString*, TableViewItem*>* mutableIdentityMap =
211      [[NSMutableDictionary alloc] init];
212
213  // Account cells.
214  [model addSectionWithIdentifier:SectionIdentifierAccounts];
215  [model setHeader:[self header]
216      forSectionWithIdentifier:SectionIdentifierAccounts];
217  signin::IdentityManager* identityManager =
218      IdentityManagerFactory::GetForBrowserState(_browser->GetBrowserState());
219
220  NSString* authenticatedEmail = [authenticatedIdentity userEmail];
221  for (const auto& account : identityManager->GetAccountsWithRefreshTokens()) {
222    ChromeIdentity* identity = ios::GetChromeBrowserProvider()
223                                   ->GetChromeIdentityService()
224                                   ->GetIdentityWithGaiaID(account.gaia);
225    // TODO(crbug.com/1081274): This re-ordering will be redundant once we
226    // apply ordering changes to the account reconciler.
227    TableViewItem* item = [self accountItem:identity];
228    if ([identity.userEmail isEqual:authenticatedEmail]) {
229      [model insertItem:item
230          inSectionWithIdentifier:SectionIdentifierAccounts
231                          atIndex:0];
232    } else {
233      [model addItem:item toSectionWithIdentifier:SectionIdentifierAccounts];
234    }
235
236    [mutableIdentityMap setObject:item forKey:identity.gaiaID];
237  }
238  _identityMap = mutableIdentityMap;
239
240  [model addItem:[self addAccountItem]
241      toSectionWithIdentifier:SectionIdentifierAccounts];
242
243  // Sign out section.
244  [model addSectionWithIdentifier:SectionIdentifierSignOut];
245  // Adds a signout option if the account is not managed.
246  if (![self authService]->IsAuthenticatedIdentityManaged()) {
247    [model addItem:[self signOutItem]
248        toSectionWithIdentifier:SectionIdentifierSignOut];
249  }
250  // Adds a signout and clear data option.
251  [model addItem:[self signOutAndClearDataItem]
252      toSectionWithIdentifier:SectionIdentifierSignOut];
253
254  // Adds a footer with signout explanation depending on the type of
255  // account whether managed or non-managed.
256  if ([self authService]->IsAuthenticatedIdentityManaged()) {
257    [model setFooter:[self signOutManagedAccountFooterItem]
258        forSectionWithIdentifier:SectionIdentifierSignOut];
259  } else {
260    [model setFooter:[self signOutNonManagedAccountFooterItem]
261        forSectionWithIdentifier:SectionIdentifierSignOut];
262  }
263}
264
265#pragma mark - Model objects
266
267- (TableViewTextHeaderFooterItem*)header {
268  TableViewTextHeaderFooterItem* header =
269      [[TableViewTextHeaderFooterItem alloc] initWithType:ItemTypeHeader];
270  header.text = l10n_util::GetNSString(IDS_IOS_OPTIONS_ACCOUNTS_DESCRIPTION);
271  return header;
272}
273
274- (TableViewLinkHeaderFooterItem*)signOutNonManagedAccountFooterItem {
275  TableViewLinkHeaderFooterItem* footer = [[TableViewLinkHeaderFooterItem alloc]
276      initWithType:ItemTypeSignOutNonManagedAccountFooter];
277  footer.text = l10n_util::GetNSString(
278      IDS_IOS_DISCONNECT_NON_MANAGED_ACCOUNT_FOOTER_INFO_MOBILE);
279  return footer;
280}
281
282- (TableViewLinkHeaderFooterItem*)signOutManagedAccountFooterItem {
283  TableViewLinkHeaderFooterItem* footer = [[TableViewLinkHeaderFooterItem alloc]
284      initWithType:ItemTypeSignOutManagedAccountFooter];
285  footer.text = l10n_util::GetNSStringF(
286      IDS_IOS_DISCONNECT_MANAGED_ACCOUNT_FOOTER_INFO_MOBILE, self.hostedDomain);
287  return footer;
288}
289
290- (TableViewItem*)accountItem:(ChromeIdentity*)identity {
291  TableViewAccountItem* item =
292      [[TableViewAccountItem alloc] initWithType:ItemTypeAccount];
293  [self updateAccountItem:item withIdentity:identity];
294  return item;
295}
296
297- (void)updateAccountItem:(TableViewAccountItem*)item
298             withIdentity:(ChromeIdentity*)identity {
299  item.image = [_avatarCache resizedAvatarForIdentity:identity];
300  item.text = identity.userEmail;
301  item.chromeIdentity = identity;
302  item.accessibilityIdentifier = identity.userEmail;
303  item.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
304}
305
306- (TableViewItem*)addAccountItem {
307  TableViewAccountItem* item =
308      [[TableViewAccountItem alloc] initWithType:ItemTypeAddAccount];
309  item.text =
310      l10n_util::GetNSString(IDS_IOS_OPTIONS_ACCOUNTS_ADD_ACCOUNT_BUTTON);
311  item.accessibilityIdentifier = kSettingsAccountsTableViewAddAccountCellId;
312  item.image = [[UIImage imageNamed:@"settings_accounts_add_account"]
313      imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
314  return item;
315}
316
317- (TableViewItem*)signOutItem {
318  TableViewTextItem* item =
319      [[TableViewTextItem alloc] initWithType:ItemTypeSignOut];
320  item.text =
321      l10n_util::GetNSString(IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE);
322  item.textColor = [UIColor colorNamed:kBlueColor];
323  item.accessibilityTraits |= UIAccessibilityTraitButton;
324  item.accessibilityIdentifier = kSettingsAccountsTableViewSignoutCellId;
325  return item;
326}
327
328- (TableViewItem*)signOutAndClearDataItem {
329  TableViewTextItem* item =
330      [[TableViewTextItem alloc] initWithType:ItemTypeSignOutAndClearData];
331  item.text = l10n_util::GetNSString(
332      IDS_IOS_DISCONNECT_DIALOG_CONTINUE_AND_CLEAR_MOBILE);
333  item.textColor = [UIColor colorNamed:kRedColor];
334  item.accessibilityTraits |= UIAccessibilityTraitButton;
335  item.accessibilityIdentifier =
336      kSettingsAccountsTableViewSignoutAndClearDataCellId;
337  return item;
338}
339
340#pragma mark - UITableViewDelegate
341
342- (void)tableView:(UITableView*)tableView
343    didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
344  // If there is an operation in process that does not allow selecting a cell
345  // exit without performing the selection.
346  if (self.uiDisabled) {
347    return;
348  }
349
350  [super tableView:tableView didSelectRowAtIndexPath:indexPath];
351
352  NSInteger itemType = [self.tableViewModel itemTypeForIndexPath:indexPath];
353
354  switch (itemType) {
355    case ItemTypeAccount: {
356      TableViewAccountItem* item =
357          base::mac::ObjCCastStrict<TableViewAccountItem>(
358              [self.tableViewModel itemAtIndexPath:indexPath]);
359      DCHECK(item.chromeIdentity);
360
361      UIView* itemView =
362          [[tableView cellForRowAtIndexPath:indexPath] contentView];
363      [self showAccountDetails:item.chromeIdentity itemView:itemView];
364      break;
365    }
366    case ItemTypeAddAccount: {
367      [self showAddAccount];
368      break;
369    }
370    case ItemTypeSignOut: {
371      UIView* itemView =
372          [[tableView cellForRowAtIndexPath:indexPath] contentView];
373      [self showSignOutWithClearData:NO itemView:itemView];
374      break;
375    }
376    case ItemTypeSignOutAndClearData: {
377      UIView* itemView =
378          [[tableView cellForRowAtIndexPath:indexPath] contentView];
379      [self showSignOutWithClearData:YES itemView:itemView];
380      break;
381    }
382    default:
383      break;
384  }
385
386  [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
387}
388
389#pragma mark - IdentityManagerObserverBridgeDelegate
390
391- (void)onEndBatchOfRefreshTokenStateChanges {
392  [self reloadData];
393  [self popViewIfSignedOut];
394  if (![self authService] -> IsAuthenticated() &&
395                                 _dimissAccountDetailsViewControllerBlock) {
396    _dimissAccountDetailsViewControllerBlock(/*animated=*/YES);
397    _dimissAccountDetailsViewControllerBlock = nil;
398  }
399}
400
401#pragma mark - Authentication operations
402
403- (void)showAddAccount {
404  if ([_alertCoordinator isVisible])
405    return;
406  _authenticationOperationInProgress = YES;
407
408  __weak __typeof(self) weakSelf = self;
409  ShowSigninCommand* command = [[ShowSigninCommand alloc]
410      initWithOperation:AUTHENTICATION_OPERATION_ADD_ACCOUNT
411               identity:nil
412            accessPoint:AccessPoint::ACCESS_POINT_SETTINGS
413            promoAction:PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO
414               callback:^(BOOL success) {
415                 [weakSelf handleDidAddAccount:success];
416               }];
417  DCHECK(self.dispatcher);
418  [self.dispatcher showSignin:command baseViewController:self];
419}
420
421- (void)handleDidAddAccount:(BOOL)success {
422  [self handleAuthenticationOperationDidFinish];
423  if (success && _closeSettingsOnAddAccount) {
424    [self.dispatcher closeSettingsUI];
425  }
426}
427
428- (void)showAccountDetails:(ChromeIdentity*)identity
429                  itemView:(UIView*)itemView {
430  if ([_alertCoordinator isVisible])
431    return;
432  if (base::FeatureList::IsEnabled(kEnableMyGoogle)) {
433    _alertCoordinator = [[ActionSheetCoordinator alloc]
434        initWithBaseViewController:self
435                           browser:_browser
436                             title:nil
437                           message:identity.userEmail
438                              rect:itemView.frame
439                              view:itemView];
440
441    [_alertCoordinator
442        addItemWithTitle:l10n_util::GetNSString(
443                             IDS_IOS_MANAGE_YOUR_GOOGLE_ACCOUNT_TITLE)
444                  action:^{
445                    _dimissAccountDetailsViewControllerBlock =
446                        ios::GetChromeBrowserProvider()
447                            ->GetChromeIdentityService()
448                            ->PresentAccountDetailsController(identity, self,
449                                                              /*animated=*/YES);
450                  }
451                   style:UIAlertActionStyleDefault];
452
453    self.removeAccountCoordinator = [[AlertCoordinator alloc]
454        initWithBaseViewController:self
455                           browser:_browser
456                             title:l10n_util::GetNSStringF(
457                                       IDS_IOS_REMOVE_ACCOUNT_ALERT_TITLE,
458                                       base::SysNSStringToUTF16(
459                                           identity.userEmail))
460                           message:
461                               l10n_util::GetNSString(
462                                   IDS_IOS_REMOVE_ACCOUNT_CONFIRMATION_MESSAGE)];
463
464    __weak AccountsTableViewController* weakSelf = self;
465    [_alertCoordinator
466        addItemWithTitle:l10n_util::GetNSString(
467                             IDS_IOS_REMOVE_GOOGLE_ACCOUNT_TITLE)
468                  action:^{
469                    [weakSelf.removeAccountCoordinator
470                        addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
471                                  action:nil
472                                   style:UIAlertActionStyleCancel];
473                    [weakSelf.removeAccountCoordinator
474                        addItemWithTitle:l10n_util::GetNSString(
475                                             IDS_IOS_REMOVE_ACCOUNT_LABEL)
476                                  action:^{
477                                    weakSelf.uiDisabled = YES;
478                                    ios::GetChromeBrowserProvider()
479                                        ->GetChromeIdentityService()
480                                        ->ForgetIdentity(
481                                            identity, ^(NSError* error) {
482                                              weakSelf.uiDisabled = NO;
483                                            });
484                                  }
485                                   style:UIAlertActionStyleDestructive];
486
487                    [weakSelf.removeAccountCoordinator start];
488                  }
489                   style:UIAlertActionStyleDestructive];
490
491    [_alertCoordinator start];
492  } else {
493    _dimissAccountDetailsViewControllerBlock =
494        ios::GetChromeBrowserProvider()
495            ->GetChromeIdentityService()
496            ->PresentAccountDetailsController(identity, self, /*animated=*/YES);
497  }
498}
499
500- (void)showSignOutWithClearData:(BOOL)forceClearData
501                        itemView:(UIView*)itemView {
502  if (_authenticationOperationInProgress || [_alertCoordinator isVisible] ||
503      self != [self.navigationController topViewController]) {
504    // An action is already in progress, ignore user's request.
505    return;
506  }
507
508  NSString* alertMessage = nil;
509  NSString* signOutTitle = nil;
510  UIAlertActionStyle actionStyle = UIAlertActionStyleDefault;
511
512  if (forceClearData) {
513    alertMessage = l10n_util::GetNSString(
514        IDS_IOS_DISCONNECT_DESTRUCTIVE_DIALOG_INFO_MOBILE);
515    signOutTitle = l10n_util::GetNSString(
516        IDS_IOS_DISCONNECT_DIALOG_CONTINUE_AND_CLEAR_MOBILE);
517    actionStyle = UIAlertActionStyleDestructive;
518  } else {
519    alertMessage =
520        l10n_util::GetNSString(IDS_IOS_DISCONNECT_KEEP_DATA_DIALOG_INFO_MOBILE);
521    signOutTitle = l10n_util::GetNSString(
522        IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE);
523    actionStyle = UIAlertActionStyleDefault;
524  }
525
526  _alertCoordinator =
527      [[ActionSheetCoordinator alloc] initWithBaseViewController:self
528                                                         browser:_browser
529                                                           title:nil
530                                                         message:alertMessage
531                                                            rect:itemView.frame
532                                                            view:itemView];
533
534  __weak AccountsTableViewController* weakSelf = self;
535  [_alertCoordinator
536      addItemWithTitle:signOutTitle
537                action:^{
538                  [weakSelf handleSignOutWithForceClearData:forceClearData];
539                }
540                 style:actionStyle];
541  [_alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
542                               action:nil
543                                style:UIAlertActionStyleCancel];
544  [_alertCoordinator start];
545}
546
547- (void)handleSignOutWithForceClearData:(BOOL)forceClearData {
548  AuthenticationService* authService = [self authService];
549  if (authService->IsAuthenticated()) {
550    _authenticationOperationInProgress = YES;
551    [self preventUserInteraction];
552    authService->SignOut(
553        signin_metrics::USER_CLICKED_SIGNOUT_SETTINGS, forceClearData, ^{
554          [self allowUserInteraction];
555          [self handleAuthenticationOperationDidFinish];
556        });
557    // Get UMA metrics on the usage of different options for signout available
558    // for users with non-managed accounts.
559    if (![self authService]->IsAuthenticatedIdentityManaged()) {
560      UMA_HISTOGRAM_BOOLEAN("Signin.UserRequestedWipeDataOnSignout",
561                            forceClearData);
562    }
563    if (forceClearData) {
564      base::RecordAction(base::UserMetricsAction(
565          "Signin_SignoutClearData_FromAccountListSettings"));
566    } else {
567      base::RecordAction(
568          base::UserMetricsAction("Signin_Signout_FromAccountListSettings"));
569    }
570  }
571}
572
573// Sets |_authenticationOperationInProgress| to NO and pops this accounts
574// table view controller if the user is signed out.
575- (void)handleAuthenticationOperationDidFinish {
576  DCHECK(_authenticationOperationInProgress);
577  _authenticationOperationInProgress = NO;
578  [self popViewIfSignedOut];
579}
580
581- (void)popViewIfSignedOut {
582  if ([self authService] -> IsAuthenticated()) {
583    return;
584  }
585  if (_authenticationOperationInProgress) {
586    // The signed out state might be temporary (e.g. account switch, ...).
587    // Don't pop this view based on intermediary values.
588    return;
589  }
590  [self dismissSelfAnimated:NO];
591}
592
593- (void)dismissSelfAnimated:(BOOL)animated {
594  if (_isBeingDismissed) {
595    return;
596  }
597  _isBeingDismissed = YES;
598  [_alertCoordinator stop];
599  [_removeAccountCoordinator stop];
600  [self.navigationController popToViewController:self animated:NO];
601  [base::mac::ObjCCastStrict<SettingsNavigationController>(
602      self.navigationController)
603      popViewControllerOrCloseSettingsAnimated:animated];
604}
605
606#pragma mark - Access to authentication service
607
608- (AuthenticationService*)authService {
609  return AuthenticationServiceFactory::GetForBrowserState(
610      _browser->GetBrowserState());
611}
612
613#pragma mark - IdentityManager
614
615- (base::string16)hostedDomain {
616  signin::IdentityManager* identityManager =
617      IdentityManagerFactory::GetForBrowserState(_browser->GetBrowserState());
618  base::Optional<AccountInfo> accountInfo =
619      identityManager->FindExtendedAccountInfoForAccountWithRefreshToken(
620          identityManager->GetPrimaryAccountInfo());
621  std::string hosted_domain = accountInfo.has_value()
622                                  ? accountInfo.value().hosted_domain
623                                  : std::string();
624  return base::UTF8ToUTF16(hosted_domain);
625}
626
627#pragma mark - ChromeIdentityBrowserOpener
628
629- (void)openURL:(NSURL*)url
630              view:(UIView*)view
631    viewController:(UIViewController*)viewController {
632  OpenNewTabCommand* command =
633      [OpenNewTabCommand commandWithURLFromChrome:net::GURLWithNSURL(url)];
634  [self.dispatcher closeSettingsUIAndOpenURL:command];
635}
636
637#pragma mark - ChromeIdentityServiceObserver
638
639- (void)profileUpdate:(ChromeIdentity*)identity {
640  TableViewAccountItem* item = base::mac::ObjCCastStrict<TableViewAccountItem>(
641      [_identityMap objectForKey:identity.gaiaID]);
642  if (!item) {
643    return;
644  }
645  [self updateAccountItem:item withIdentity:identity];
646  NSIndexPath* indexPath = [self.tableViewModel indexPathForItem:item];
647  [self.tableView reloadRowsAtIndexPaths:@[ indexPath ]
648                        withRowAnimation:UITableViewRowAnimationAutomatic];
649}
650
651- (void)chromeIdentityServiceWillBeDestroyed {
652  _identityServiceObserver.reset();
653}
654
655#pragma mark - UIAdaptivePresentationControllerDelegate
656
657- (void)presentationControllerDidDismiss:
658    (UIPresentationController*)presentationController {
659  base::RecordAction(
660      base::UserMetricsAction("IOSAccountsSettingsCloseWithSwipe"));
661}
662
663@end
664