blob: e4c57f50d73f23d1c674c5183cb8a81b92cbb3eb [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller.h"
#import "base/mac/foundation_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#import "components/signin/ios/browser/features.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"
#include "components/strings/grit/components_strings.h"
#include "components/sync/driver/sync_service.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/main/browser.h"
#import "ios/chrome/browser/signin/authentication_service.h"
#include "ios/chrome/browser/signin/authentication_service_factory.h"
#import "ios/chrome/browser/signin/chrome_identity_service_observer_bridge.h"
#include "ios/chrome/browser/signin/identity_manager_factory.h"
#include "ios/chrome/browser/sync/sync_setup_service.h"
#include "ios/chrome/browser/sync/sync_setup_service_factory.h"
#import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.h"
#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
#import "ios/chrome/browser/ui/authentication/authentication_ui_util.h"
#import "ios/chrome/browser/ui/authentication/cells/table_view_account_item.h"
#import "ios/chrome/browser/ui/authentication/resized_avatar_cache.h"
#import "ios/chrome/browser/ui/authentication/signout_action_sheet_coordinator.h"
#import "ios/chrome/browser/ui/commands/application_commands.h"
#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
#import "ios/chrome/browser/ui/commands/show_signin_command.h"
#import "ios/chrome/browser/ui/icons/chrome_icon.h"
#import "ios/chrome/browser/ui/settings/google_services/accounts_table_view_controller_constants.h"
#import "ios/chrome/browser/ui/settings/sync/utils/sync_util.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_detail_text_item.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_link_header_footer_item.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_text_header_footer_item.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_text_item.h"
#import "ios/chrome/browser/ui/table_view/table_view_model.h"
#import "ios/chrome/browser/ui/table_view/table_view_utils.h"
#include "ios/chrome/browser/ui/ui_feature_flags.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#include "ios/chrome/grit/ios_chromium_strings.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/public/provider/chrome/browser/chrome_browser_provider.h"
#import "ios/public/provider/chrome/browser/images/branded_image_provider.h"
#import "ios/public/provider/chrome/browser/signin/chrome_identity.h"
#import "ios/public/provider/chrome/browser/signin/chrome_identity_browser_opener.h"
#import "ios/public/provider/chrome/browser/signin/chrome_identity_service.h"
#import "net/base/mac/url_conversions.h"
#include "ui/base/l10n/l10n_util_mac.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using signin_metrics::AccessPoint;
using signin_metrics::PromoAction;
namespace {
typedef NS_ENUM(NSInteger, SectionIdentifier) {
SectionIdentifierAccounts = kSectionIdentifierEnumZero,
SectionIdentifierSync,
SectionIdentifierSignOut,
};
typedef NS_ENUM(NSInteger, ItemType) {
ItemTypeAccount = kItemTypeEnumZero,
ItemTypeAddAccount,
// Provides sign out items used only for non-managed accounts.
ItemTypeSignOut,
// Sign out item that clears Chrome data. Used for both managed
// and non-managed accounts.
ItemTypeSignOutAndClearData,
ItemTypeHeader,
// Detailed description of the actions taken by sign out e.g. turning off sync
// and clearing Chrome data.
ItemTypeSignOutManagedAccountFooter,
// Detailed description of the actions taken by sign out, e.g. turning off
// sync.
ItemTypeSignOutNonManagedAccountFooter,
// Detailed description of the actions taken by sign out, e.g. turning off
// sync. Related to kSimplifySignOutIOS feature only.
ItemTypeSignOutSyncingFooter,
};
} // namespace
@interface AccountsTableViewController () <
ChromeIdentityServiceObserver,
ChromeIdentityBrowserOpener,
IdentityManagerObserverBridgeDelegate,
SignoutActionSheetCoordinatorDelegate> {
Browser* _browser;
BOOL _closeSettingsOnAddAccount;
std::unique_ptr<signin::IdentityManagerObserverBridge>
_identityManagerObserver;
// Whether an authentication operation is in progress (e.g switch accounts,
// sign out).
BOOL _authenticationOperationInProgress;
// Whether the view controller is currently being dismissed and new dismiss
// requests should be ignored.
BOOL _isBeingDismissed;
ResizedAvatarCache* _avatarCache;
std::unique_ptr<ChromeIdentityServiceObserverBridge> _identityServiceObserver;
// Enable lookup of item corresponding to a given identity GAIA ID string.
NSDictionary<NSString*, TableViewItem*>* _identityMap;
}
// Modal alert for sign out.
@property(nonatomic, strong) AlertCoordinator* alertCoordinator;
// Callback to dismiss MyGoogle (Account Detail).
@property(nonatomic, copy)
ios::DismissASMViewControllerBlock dismissAccountDetailsViewControllerBlock;
// Modal alert for confirming account removal.
@property(nonatomic, strong) AlertCoordinator* removeAccountCoordinator;
// Modal alert for sign out in experiment kSimplifySignOutIOS.
@property(nonatomic, strong) SignoutActionSheetCoordinator* signoutCoordinator;
// If YES, the UI elements are disabled.
@property(nonatomic, assign) BOOL uiDisabled;
// Stops observing browser state services. This is required during the shutdown
// phase to avoid observing services for a browser state that is being killed.
- (void)stopBrowserStateServiceObservers;
@end
@implementation AccountsTableViewController
@synthesize dispatcher = _dispatcher;
- (instancetype)initWithBrowser:(Browser*)browser
closeSettingsOnAddAccount:(BOOL)closeSettingsOnAddAccount {
DCHECK(browser);
DCHECK(!browser->GetBrowserState()->IsOffTheRecord());
self = [super initWithStyle:ChromeTableViewStyle()];
if (self) {
_browser = browser;
_closeSettingsOnAddAccount = closeSettingsOnAddAccount;
_identityManagerObserver =
std::make_unique<signin::IdentityManagerObserverBridge>(
IdentityManagerFactory::GetForBrowserState(
_browser->GetBrowserState()),
self);
_avatarCache = [[ResizedAvatarCache alloc] init];
_identityServiceObserver.reset(
new ChromeIdentityServiceObserverBridge(self));
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.accessibilityIdentifier = kSettingsAccountsTableViewId;
[self loadModel];
}
- (void)stopBrowserStateServiceObservers {
_identityManagerObserver.reset();
}
#pragma mark - SettingsControllerProtocol
- (void)reportDismissalUserAction {
base::RecordAction(base::UserMetricsAction("MobileAccountsSettingsClose"));
}
- (void)reportBackUserAction {
base::RecordAction(base::UserMetricsAction("MobileAccountsSettingsBack"));
}
- (void)settingsWillBeDismissed {
[self.alertCoordinator stop];
self.alertCoordinator = nil;
[self.signoutCoordinator stop];
self.signoutCoordinator = nil;
[self.removeAccountCoordinator stop];
self.removeAccountCoordinator = nil;
[self stopBrowserStateServiceObservers];
_browser = nullptr;
}
#pragma mark - SettingsRootTableViewController
- (void)reloadData {
if (!_browser)
return;
if (![self authService]->HasPrimaryIdentity(signin::ConsentLevel::kSignin)) {
// This accounts table view will be popped or dismissed when the user
// is signed out. Avoid reloading it in that case as that would lead to an
// empty table view.
return;
}
[super reloadData];
}
- (void)loadModel {
if (!_browser)
return;
// Update the title with the name with the currently signed-in account.
ChromeIdentity* authenticatedIdentity =
[self authService]->GetPrimaryIdentity(signin::ConsentLevel::kSignin);
NSString* title = nil;
if (authenticatedIdentity) {
title = [authenticatedIdentity userFullName];
if (!title) {
title = [authenticatedIdentity userEmail];
}
}
self.title = title;
[super loadModel];
if (![self authService]->HasPrimaryIdentity(signin::ConsentLevel::kSignin))
return;
TableViewModel* model = self.tableViewModel;
NSMutableDictionary<NSString*, TableViewItem*>* mutableIdentityMap =
[[NSMutableDictionary alloc] init];
// Account cells.
[model addSectionWithIdentifier:SectionIdentifierAccounts];
[model setHeader:[self header]
forSectionWithIdentifier:SectionIdentifierAccounts];
signin::IdentityManager* identityManager =
IdentityManagerFactory::GetForBrowserState(_browser->GetBrowserState());
NSString* authenticatedEmail = [authenticatedIdentity userEmail];
for (const auto& account : identityManager->GetAccountsWithRefreshTokens()) {
ios::ChromeIdentityService* identityService =
ios::GetChromeBrowserProvider().GetChromeIdentityService();
ChromeIdentity* identity =
identityService->GetIdentityWithGaiaID(account.gaia);
if (!identity) {
// Ignore the case in which the identity is invalid at lookup time. This
// may be due to inconsistencies between the identity service and
// ProfileOAuth2TokenService.
continue;
}
// TODO(crbug.com/1081274): This re-ordering will be redundant once we
// apply ordering changes to the account reconciler.
TableViewItem* item = [self accountItem:identity];
if ([identity.userEmail isEqual:authenticatedEmail]) {
[model insertItem:item
inSectionWithIdentifier:SectionIdentifierAccounts
atIndex:0];
} else {
[model addItem:item toSectionWithIdentifier:SectionIdentifierAccounts];
}
[mutableIdentityMap setObject:item forKey:identity.gaiaID];
}
_identityMap = mutableIdentityMap;
[model addItem:[self addAccountItem]
toSectionWithIdentifier:SectionIdentifierAccounts];
// Sign out section.
[model addSectionWithIdentifier:SectionIdentifierSignOut];
if (base::FeatureList::IsEnabled(signin::kSimplifySignOutIOS)) {
[model addItem:[self signOutItem]
toSectionWithIdentifier:SectionIdentifierSignOut];
} else {
// Adds a signout option if the account is not managed.
if (![self authService]->HasPrimaryIdentityManaged(
signin::ConsentLevel::kSignin)) {
[model addItem:[self signOutItem]
toSectionWithIdentifier:SectionIdentifierSignOut];
}
// Adds a signout and clear data option.
[model addItem:[self signOutAndClearDataItem]
toSectionWithIdentifier:SectionIdentifierSignOut];
}
// Adds a footer with signout explanation depending on the type of
// account whether managed or non-managed.
if (base::FeatureList::IsEnabled(signin::kSimplifySignOutIOS)) {
SyncSetupService* syncSetupService =
SyncSetupServiceFactory::GetForBrowserState(
_browser->GetBrowserState());
if (syncSetupService->IsFirstSetupComplete()) {
[model setFooter:[self signOutSyncingFooterItem]
forSectionWithIdentifier:SectionIdentifierSignOut];
}
} else if ([self authService]->HasPrimaryIdentityManaged(
signin::ConsentLevel::kSignin)) {
[model setFooter:[self signOutManagedAccountFooterItem]
forSectionWithIdentifier:SectionIdentifierSignOut];
} else {
[model setFooter:[self signOutNonManagedAccountFooterItem]
forSectionWithIdentifier:SectionIdentifierSignOut];
}
}
#pragma mark - Model objects
- (TableViewTextHeaderFooterItem*)header {
TableViewTextHeaderFooterItem* header =
[[TableViewTextHeaderFooterItem alloc] initWithType:ItemTypeHeader];
header.text = l10n_util::GetNSString(IDS_IOS_OPTIONS_ACCOUNTS_DESCRIPTION);
return header;
}
- (TableViewLinkHeaderFooterItem*)signOutNonManagedAccountFooterItem {
DCHECK(!base::FeatureList::IsEnabled(signin::kSimplifySignOutIOS));
TableViewLinkHeaderFooterItem* footer = [[TableViewLinkHeaderFooterItem alloc]
initWithType:ItemTypeSignOutNonManagedAccountFooter];
footer.text = l10n_util::GetNSString(
IDS_IOS_DISCONNECT_NON_MANAGED_ACCOUNT_FOOTER_INFO_MOBILE);
return footer;
}
- (TableViewLinkHeaderFooterItem*)signOutManagedAccountFooterItem {
DCHECK(!base::FeatureList::IsEnabled(signin::kSimplifySignOutIOS));
TableViewLinkHeaderFooterItem* footer = [[TableViewLinkHeaderFooterItem alloc]
initWithType:ItemTypeSignOutManagedAccountFooter];
std::u16string hostedDomain = HostedDomainForPrimaryAccount(_browser);
footer.text = l10n_util::GetNSStringF(
IDS_IOS_DISCONNECT_MANAGED_ACCOUNT_FOOTER_INFO_MOBILE, hostedDomain);
return footer;
}
- (TableViewLinkHeaderFooterItem*)signOutSyncingFooterItem {
DCHECK(base::FeatureList::IsEnabled(signin::kSimplifySignOutIOS));
TableViewLinkHeaderFooterItem* footer = [[TableViewLinkHeaderFooterItem alloc]
initWithType:ItemTypeSignOutSyncingFooter];
footer.text = l10n_util::GetNSString(
IDS_IOS_DISCONNECT_DIALOG_SYNCING_FOOTER_INFO_MOBILE);
return footer;
}
- (TableViewItem*)accountItem:(ChromeIdentity*)identity {
TableViewAccountItem* item =
[[TableViewAccountItem alloc] initWithType:ItemTypeAccount];
[self updateAccountItem:item withIdentity:identity];
return item;
}
- (void)updateAccountItem:(TableViewAccountItem*)item
withIdentity:(ChromeIdentity*)identity {
item.image = [_avatarCache resizedAvatarForIdentity:identity];
item.text = identity.userEmail;
item.chromeIdentity = identity;
item.accessibilityIdentifier = identity.userEmail;
item.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
- (TableViewItem*)addAccountItem {
TableViewAccountItem* item =
[[TableViewAccountItem alloc] initWithType:ItemTypeAddAccount];
item.text =
l10n_util::GetNSString(IDS_IOS_OPTIONS_ACCOUNTS_ADD_ACCOUNT_BUTTON);
item.accessibilityIdentifier = kSettingsAccountsTableViewAddAccountCellId;
item.image = [[UIImage imageNamed:@"settings_accounts_add_account"]
imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
return item;
}
- (TableViewItem*)signOutItem {
TableViewTextItem* item =
[[TableViewTextItem alloc] initWithType:ItemTypeSignOut];
item.text =
l10n_util::GetNSString(IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE);
if (base::FeatureList::IsEnabled(signin::kSimplifySignOutIOS)) {
item.textColor = [UIColor colorNamed:kRedColor];
} else {
item.textColor = [UIColor colorNamed:kBlueColor];
}
item.accessibilityTraits |= UIAccessibilityTraitButton;
item.accessibilityIdentifier = kSettingsAccountsTableViewSignoutCellId;
return item;
}
- (TableViewItem*)signOutAndClearDataItem {
TableViewTextItem* item =
[[TableViewTextItem alloc] initWithType:ItemTypeSignOutAndClearData];
item.text = l10n_util::GetNSString(
IDS_IOS_DISCONNECT_DIALOG_CONTINUE_AND_CLEAR_MOBILE);
item.textColor = [UIColor colorNamed:kRedColor];
item.accessibilityTraits |= UIAccessibilityTraitButton;
item.accessibilityIdentifier =
kSettingsAccountsTableViewSignoutAndClearDataCellId;
return item;
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView*)tableView
didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
// If there is an operation in process that does not allow selecting a cell
// exit without performing the selection.
if (self.uiDisabled) {
return;
}
[super tableView:tableView didSelectRowAtIndexPath:indexPath];
NSInteger itemType = [self.tableViewModel itemTypeForIndexPath:indexPath];
switch (itemType) {
case ItemTypeAccount: {
TableViewAccountItem* item =
base::mac::ObjCCastStrict<TableViewAccountItem>(
[self.tableViewModel itemAtIndexPath:indexPath]);
DCHECK(item.chromeIdentity);
UIView* itemView =
[[tableView cellForRowAtIndexPath:indexPath] contentView];
[self showAccountDetails:item.chromeIdentity itemView:itemView];
break;
}
case ItemTypeAddAccount: {
[self showAddAccount];
break;
}
case ItemTypeSignOut: {
UIView* itemView =
[[tableView cellForRowAtIndexPath:indexPath] contentView];
if (base::FeatureList::IsEnabled(signin::kSimplifySignOutIOS)) {
[self showMICESignOutWithItemView:itemView];
} else {
[self showSignOutWithClearData:NO itemView:itemView];
}
break;
}
case ItemTypeSignOutAndClearData: {
DCHECK(!base::FeatureList::IsEnabled(signin::kSimplifySignOutIOS));
UIView* itemView =
[[tableView cellForRowAtIndexPath:indexPath] contentView];
[self showSignOutWithClearData:YES itemView:itemView];
break;
}
default:
break;
}
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
#pragma mark - IdentityManagerObserverBridgeDelegate
- (void)onEndBatchOfRefreshTokenStateChanges {
DCHECK(_browser) << "-onEndBatchOfRefreshTokenStateChanges called after "
"-stopBrowserStateServiceObservers";
[self reloadData];
if (![self authService]->HasPrimaryIdentity(signin::ConsentLevel::kSignin) &&
self.dismissAccountDetailsViewControllerBlock) {
self.dismissAccountDetailsViewControllerBlock(/*animated=*/YES);
self.dismissAccountDetailsViewControllerBlock = nil;
}
// Only attempt to pop the top-most view controller once the account list
// has been dismissed.
[self popViewIfSignedOut];
}
#pragma mark - Authentication operations
- (void)showAddAccount {
DCHECK(!self.alertCoordinator);
_authenticationOperationInProgress = YES;
__weak __typeof(self) weakSelf = self;
ShowSigninCommand* command = [[ShowSigninCommand alloc]
initWithOperation:AUTHENTICATION_OPERATION_ADD_ACCOUNT
identity:nil
accessPoint:AccessPoint::ACCESS_POINT_SETTINGS
promoAction:PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO
callback:^(BOOL success) {
[weakSelf handleDidAddAccount:success];
}];
DCHECK(self.dispatcher);
[self.dispatcher showSignin:command baseViewController:self];
}
- (void)handleDidAddAccount:(BOOL)success {
[self handleAuthenticationOperationDidFinish];
if (success && _closeSettingsOnAddAccount) {
[self.dispatcher closeSettingsUI];
}
}
- (void)showAccountDetails:(ChromeIdentity*)identity
itemView:(UIView*)itemView {
DCHECK(!self.alertCoordinator);
self.alertCoordinator = [[ActionSheetCoordinator alloc]
initWithBaseViewController:self
browser:_browser
title:nil
message:identity.userEmail
rect:itemView.frame
view:itemView];
__weak __typeof(self) weakSelf = self;
if (signin::IsSSOEditingEnabled()) {
[self.alertCoordinator
addItemWithTitle:l10n_util::GetNSString(
IDS_IOS_MANAGE_YOUR_GOOGLE_ACCOUNT_TITLE)
action:^{
[weakSelf handleManageGoogleAccountWithIdentity:identity];
}
style:UIAlertActionStyleDefault];
}
[self.alertCoordinator
addItemWithTitle:l10n_util::GetNSString(
IDS_IOS_REMOVE_GOOGLE_ACCOUNT_TITLE)
action:^{
[weakSelf handleRemoveSecondaryAccountWithIdentity:identity];
}
style:UIAlertActionStyleDestructive];
[self.alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
action:^() {
[weakSelf handleAlertCoordinatorCancel];
}
style:UIAlertActionStyleCancel];
[self.alertCoordinator start];
}
// Handles the manage Google account action from |self.alertCoordinator|.
// Action sheet created in |showAccountDetails:itemView:|
- (void)handleManageGoogleAccountWithIdentity:(ChromeIdentity*)identity {
DCHECK(self.alertCoordinator);
// |self.alertCoordinator| should not be stopped, since the coordinator has
// been confirmed.
self.alertCoordinator = nil;
self.dismissAccountDetailsViewControllerBlock =
ios::GetChromeBrowserProvider()
.GetChromeIdentityService()
->PresentAccountDetailsController(identity, self,
/*animated=*/YES);
}
// Handles the secondary account remove action from |self.alertCoordinator|.
// Action sheet created in |showAccountDetails:itemView:|
- (void)handleRemoveSecondaryAccountWithIdentity:(ChromeIdentity*)identity {
DCHECK(self.alertCoordinator);
// |self.alertCoordinator| should not be stopped, since the coordinator has
// been confirmed.
self.alertCoordinator = nil;
DCHECK(!self.removeAccountCoordinator);
NSString* title =
l10n_util::GetNSStringF(IDS_IOS_REMOVE_ACCOUNT_ALERT_TITLE,
base::SysNSStringToUTF16(identity.userEmail));
NSString* message =
l10n_util::GetNSString(IDS_IOS_REMOVE_ACCOUNT_CONFIRMATION_MESSAGE);
self.removeAccountCoordinator =
[[AlertCoordinator alloc] initWithBaseViewController:self
browser:_browser
title:title
message:message];
__weak __typeof(self) weakSelf = self;
[self.removeAccountCoordinator
addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
action:^{
weakSelf.removeAccountCoordinator = nil;
}
style:UIAlertActionStyleCancel];
[self.removeAccountCoordinator
addItemWithTitle:l10n_util::GetNSString(IDS_IOS_REMOVE_ACCOUNT_LABEL)
action:^{
[weakSelf removeSecondaryIdentity:identity];
}
style:UIAlertActionStyleDestructive];
[self.removeAccountCoordinator start];
}
- (void)removeSecondaryIdentity:(ChromeIdentity*)identity {
DCHECK(self.removeAccountCoordinator);
self.removeAccountCoordinator = nil;
self.uiDisabled = YES;
ios::GetChromeBrowserProvider().GetChromeIdentityService()->ForgetIdentity(
identity, ^(NSError* error) {
self.uiDisabled = NO;
});
}
- (void)showMICESignOutWithItemView:(UIView*)itemView {
DCHECK(!self.signoutCoordinator);
DCHECK(base::FeatureList::IsEnabled(signin::kSimplifySignOutIOS));
if (_authenticationOperationInProgress ||
self != [self.navigationController topViewController]) {
// An action is already in progress, ignore user's request.
return;
}
self.signoutCoordinator = [[SignoutActionSheetCoordinator alloc]
initWithBaseViewController:self
browser:_browser
rect:itemView.frame
view:itemView];
__weak AccountsTableViewController* weakSelf = self;
self.signoutCoordinator.completion = ^(BOOL success) {
if (success) {
[weakSelf handleAuthenticationOperationDidFinish];
}
[weakSelf.signoutCoordinator stop];
weakSelf.signoutCoordinator = nil;
};
self.signoutCoordinator.delegate = self;
[self.signoutCoordinator start];
}
- (void)showSignOutWithClearData:(BOOL)forceClearData
itemView:(UIView*)itemView {
DCHECK(!self.alertCoordinator);
DCHECK(!base::FeatureList::IsEnabled(signin::kSimplifySignOutIOS));
if (_authenticationOperationInProgress ||
self != [self.navigationController topViewController]) {
// An action is already in progress, ignore user's request.
return;
}
NSString* alertMessage = nil;
NSString* signOutTitle = nil;
UIAlertActionStyle actionStyle = UIAlertActionStyleDefault;
if (forceClearData) {
alertMessage = l10n_util::GetNSString(
IDS_IOS_DISCONNECT_DESTRUCTIVE_DIALOG_INFO_MOBILE);
signOutTitle = l10n_util::GetNSString(
IDS_IOS_DISCONNECT_DIALOG_CONTINUE_AND_CLEAR_MOBILE);
actionStyle = UIAlertActionStyleDestructive;
} else {
alertMessage =
l10n_util::GetNSString(IDS_IOS_DISCONNECT_KEEP_DATA_DIALOG_INFO_MOBILE);
signOutTitle = l10n_util::GetNSString(
IDS_IOS_DISCONNECT_DIALOG_CONTINUE_BUTTON_MOBILE);
actionStyle = UIAlertActionStyleDefault;
}
self.alertCoordinator =
[[ActionSheetCoordinator alloc] initWithBaseViewController:self
browser:_browser
title:nil
message:alertMessage
rect:itemView.frame
view:itemView];
__weak AccountsTableViewController* weakSelf = self;
[self.alertCoordinator
addItemWithTitle:signOutTitle
action:^{
[weakSelf handleSignOutWithForceClearData:forceClearData];
}
style:actionStyle];
[self.alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
action:^() {
[weakSelf handleAlertCoordinatorCancel];
}
style:UIAlertActionStyleCancel];
[self.alertCoordinator start];
}
- (void)handleSignOutWithForceClearData:(BOOL)forceClearData {
if (!_browser)
return;
// |self.alertCoordinator| should not be stopped, since the coordinator has
// been confirmed.
DCHECK(self.alertCoordinator);
self.alertCoordinator = nil;
AuthenticationService* authService = [self authService];
if (authService->HasPrimaryIdentity(signin::ConsentLevel::kSignin)) {
_authenticationOperationInProgress = YES;
[self preventUserInteraction];
__weak AccountsTableViewController* weakSelf = self;
authService->SignOut(
signin_metrics::USER_CLICKED_SIGNOUT_SETTINGS, forceClearData, ^{
// Metrics logging must occur before dismissing the currently
// presented view controller from |handleSignoutDidFinish|.
[weakSelf logSignoutMetricsWithForceClearData:forceClearData];
[weakSelf allowUserInteraction];
[weakSelf handleAuthenticationOperationDidFinish];
});
}
}
// Logs the UMA metrics to record the data retention option selected by the user
// on signout. If the account is managed the data will always be cleared.
- (void)logSignoutMetricsWithForceClearData:(BOOL)forceClearData {
if (![self authService]->HasPrimaryIdentityManaged(
signin::ConsentLevel::kSignin)) {
UMA_HISTOGRAM_BOOLEAN("Signin.UserRequestedWipeDataOnSignout",
forceClearData);
}
if (forceClearData) {
base::RecordAction(base::UserMetricsAction(
"Signin_SignoutClearData_FromAccountListSettings"));
} else {
base::RecordAction(
base::UserMetricsAction("Signin_Signout_FromAccountListSettings"));
}
}
// Handles the cancel action for |self.alertCoordinator|.
- (void)handleAlertCoordinatorCancel {
DCHECK(self.alertCoordinator);
// |self.alertCoordinator| should not be stopped, since the coordinator has
// been cancelled.
self.alertCoordinator = nil;
}
// Sets |_authenticationOperationInProgress| to NO and pops this accounts
// table view controller if the user is signed out.
- (void)handleAuthenticationOperationDidFinish {
DCHECK(_authenticationOperationInProgress);
_authenticationOperationInProgress = NO;
[self popViewIfSignedOut];
}
- (void)popViewIfSignedOut {
if (!_browser)
return;
if ([self authService]->HasPrimaryIdentity(signin::ConsentLevel::kSignin)) {
return;
}
if (_authenticationOperationInProgress) {
// The signed out state might be temporary (e.g. account switch, ...).
// Don't pop this view based on intermediary values.
return;
}
if (_isBeingDismissed) {
return;
}
_isBeingDismissed = YES;
void (^popAccountsTableViewController)() = ^() {
[base::mac::ObjCCastStrict<SettingsNavigationController>(
self.navigationController)
popViewControllerOrCloseSettingsAnimated:YES];
};
if (self.presentedViewController) {
// If |self| is presenting a view controller (like |self.alertCoordinator|,
// |_removeAccountCoordinator| or the account detail view controller, it
// has to be dismissed before |self| can be poped from the navigation
// controller.
// This issue can be easily reproduced with EG tests, but not with Chrome
// app itself.
[self dismissViewControllerAnimated:NO
completion:^{
popAccountsTableViewController();
}];
} else {
// Pops |self|.
popAccountsTableViewController();
}
}
#pragma mark - Access to authentication service
- (AuthenticationService*)authService {
DCHECK(_browser) << "-authService called after -settingsWillBeDismissed";
return AuthenticationServiceFactory::GetForBrowserState(
_browser->GetBrowserState());
}
#pragma mark - ChromeIdentityBrowserOpener
- (void)openURL:(NSURL*)url
view:(UIView*)view
viewController:(UIViewController*)viewController {
OpenNewTabCommand* command =
[OpenNewTabCommand commandWithURLFromChrome:net::GURLWithNSURL(url)];
[self.dispatcher closeSettingsUIAndOpenURL:command];
}
#pragma mark - ChromeIdentityServiceObserver
- (void)profileUpdate:(ChromeIdentity*)identity {
TableViewAccountItem* item = base::mac::ObjCCastStrict<TableViewAccountItem>(
[_identityMap objectForKey:identity.gaiaID]);
if (!item) {
return;
}
[self updateAccountItem:item withIdentity:identity];
NSIndexPath* indexPath = [self.tableViewModel indexPathForItem:item];
[self.tableView reloadRowsAtIndexPaths:@[ indexPath ]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
- (void)chromeIdentityServiceWillBeDestroyed {
_identityServiceObserver.reset();
}
#pragma mark - UIAdaptivePresentationControllerDelegate
- (void)presentationControllerDidDismiss:
(UIPresentationController*)presentationController {
base::RecordAction(
base::UserMetricsAction("IOSAccountsSettingsCloseWithSwipe"));
}
#pragma mark - SignoutActionSheetCoordinatorDelegate
- (void)didSelectSignoutDataRetentionStrategy {
_authenticationOperationInProgress = YES;
}
@end