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