Index: ios/chrome/browser/ui/authentication/signin_interaction_controller.mm |
diff --git a/ios/chrome/browser/ui/authentication/signin_interaction_controller.mm b/ios/chrome/browser/ui/authentication/signin_interaction_controller.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a1fc182f2132c3befdd9cb238e696ca8ff9dafdc |
--- /dev/null |
+++ b/ios/chrome/browser/ui/authentication/signin_interaction_controller.mm |
@@ -0,0 +1,374 @@ |
+// Copyright 2014 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/authentication/signin_interaction_controller.h" |
+ |
+#include "base/ios/weak_nsobject.h" |
+#include "base/logging.h" |
+#include "base/mac/scoped_block.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "components/prefs/pref_service.h" |
+#include "components/signin/core/browser/signin_manager.h" |
+#include "components/signin/core/common/signin_pref_names.h" |
+#import "ios/chrome/browser/browser_state/chrome_browser_state.h" |
+#include "ios/chrome/browser/signin/authentication_service.h" |
+#include "ios/chrome/browser/signin/authentication_service_factory.h" |
+#include "ios/chrome/browser/signin/signin_manager_factory.h" |
+#import "ios/chrome/browser/signin/signin_util.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/chrome_signin_view_controller.h" |
+#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h" |
+#import "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
+#import "ios/public/provider/chrome/browser/signin/chrome_identity.h" |
+#import "ios/public/provider/chrome/browser/signin/chrome_identity_interaction_manager.h" |
+#import "ios/public/provider/chrome/browser/signin/chrome_identity_service.h" |
+ |
+using signin_ui::CompletionCallback; |
+ |
+@interface SigninInteractionController ()< |
+ ChromeIdentityInteractionManagerDelegate, |
+ ChromeSigninViewControllerDelegate> { |
+ ios::ChromeBrowserState* browserState_; |
+ signin_metrics::AccessPoint signInAccessPoint_; |
+ base::scoped_nsobject<UIViewController> presentingViewController_; |
+ BOOL isPresentedOnSettings_; |
+ BOOL isCancelling_; |
+ BOOL isDismissing_; |
+ BOOL interactionManagerDismissalIgnored_; |
+ base::scoped_nsobject<AlertCoordinator> alertCoordinator_; |
+ base::mac::ScopedBlock<CompletionCallback> completionCallback_; |
+ base::scoped_nsobject<ChromeSigninViewController> signinViewController_; |
+ base::scoped_nsobject<ChromeIdentityInteractionManager> |
+ identityInteractionManager_; |
+ base::scoped_nsobject<ChromeIdentity> signInIdentity_; |
+} |
+@end |
+ |
+@implementation SigninInteractionController |
+ |
+- (id)init { |
+ NOTREACHED(); |
+ return nil; |
+} |
+ |
+- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState |
+ presentingViewController:(UIViewController*)presentingViewController |
+ isPresentedOnSettings:(BOOL)isPresentedOnSettings |
+ signInAccessPoint:(signin_metrics::AccessPoint)accessPoint { |
+ self = [super init]; |
+ if (self) { |
+ DCHECK(browserState); |
+ DCHECK(presentingViewController); |
+ browserState_ = browserState; |
+ presentingViewController_.reset([presentingViewController retain]); |
+ isPresentedOnSettings_ = isPresentedOnSettings; |
+ signInAccessPoint_ = accessPoint; |
+ } |
+ return self; |
+} |
+ |
+- (void)cancel { |
+ // Cancelling and dismissing the |identityInteractionManager_| may call the |
+ // |completionCallback_| which could lead to |self| being released before the |
+ // end of this method. |self| is retained here to prevent this from happening. |
+ base::scoped_nsobject<SigninInteractionController> strongSelf([self retain]); |
+ isCancelling_ = YES; |
+ [alertCoordinator_ executeCancelHandler]; |
+ [alertCoordinator_ stop]; |
+ [identityInteractionManager_ cancelAndDismissAnimated:NO]; |
+ [signinViewController_ cancel]; |
+ isCancelling_ = NO; |
+} |
+ |
+- (void)cancelAndDismiss { |
+ isDismissing_ = YES; |
+ [self cancel]; |
+ isDismissing_ = NO; |
+} |
+ |
+- (void)signInWithCompletion:(CompletionCallback)completion |
+ viewController:(UIViewController*)viewController { |
+ signin_metrics::LogSigninAccessPointStarted(signInAccessPoint_); |
+ completionCallback_.reset(completion, base::scoped_policy::RETAIN); |
+ if (ios::GetChromeBrowserProvider() |
+ ->GetChromeIdentityService() |
+ ->HasIdentities()) { |
+ DCHECK(!signinViewController_); |
+ [self showSigninViewControllerWithIdentity:nil]; |
+ } else { |
+ identityInteractionManager_ = |
+ ios::GetChromeBrowserProvider() |
+ ->GetChromeIdentityService() |
+ ->NewChromeIdentityInteractionManager(browserState_, self); |
+ if (!identityInteractionManager_) { |
+ // Abort sign-in if the ChromeIdentityInteractionManager returned is |
+ // nil (this can happen when the iOS internal provider is not used). |
+ [self runCompletionCallbackWithSuccess:NO executeCommand:nil]; |
+ return; |
+ } |
+ |
+ base::WeakNSObject<SigninInteractionController> weakSelf(self); |
+ [identityInteractionManager_ |
+ addAccountWithCompletion:^(ChromeIdentity* identity, NSError* error) { |
+ [weakSelf handleIdentityAdded:identity |
+ error:error |
+ shouldSignIn:YES |
+ viewController:viewController]; |
+ }]; |
+ } |
+} |
+ |
+- (void)reAuthenticateWithCompletion:(CompletionCallback)completion |
+ viewController:(UIViewController*)viewController { |
+ signin_metrics::LogSigninAccessPointStarted(signInAccessPoint_); |
+ completionCallback_.reset(completion, base::scoped_policy::RETAIN); |
+ AccountInfo accountInfo = |
+ ios::SigninManagerFactory::GetForBrowserState(browserState_) |
+ ->GetAuthenticatedAccountInfo(); |
+ std::string emailToReauthenticate = accountInfo.email; |
+ std::string idToReauthenticate = accountInfo.gaia; |
+ if (emailToReauthenticate.empty() || idToReauthenticate.empty()) { |
+ // This corresponds to a re-authenticate request after the user was signed |
+ // out. This corresponds to the case where the identity was removed as a |
+ // result of the permissions being removed on the server or the identity |
+ // being removed from another app. |
+ // |
+ // Simply use the the last signed-in user email in this case and go though |
+ // the entire sign-in flow as sync needs to be configured. |
+ emailToReauthenticate = browserState_->GetPrefs()->GetString( |
+ prefs::kGoogleServicesLastUsername); |
+ idToReauthenticate = browserState_->GetPrefs()->GetString( |
+ prefs::kGoogleServicesLastAccountId); |
+ } |
+ DCHECK(!emailToReauthenticate.empty()); |
+ DCHECK(!idToReauthenticate.empty()); |
+ identityInteractionManager_ = |
+ ios::GetChromeBrowserProvider() |
+ ->GetChromeIdentityService() |
+ ->NewChromeIdentityInteractionManager(browserState_, self); |
+ base::WeakNSObject<SigninInteractionController> weakSelf(self); |
+ [identityInteractionManager_ |
+ reauthenticateUserWithID:base::SysUTF8ToNSString(idToReauthenticate) |
+ email:base::SysUTF8ToNSString(emailToReauthenticate) |
+ completion:^(ChromeIdentity* identity, NSError* error) { |
+ [weakSelf handleIdentityAdded:identity |
+ error:error |
+ shouldSignIn:YES |
+ viewController:viewController]; |
+ }]; |
+} |
+ |
+- (void)addAccountWithCompletion:(CompletionCallback)completion |
+ viewController:(UIViewController*)viewController { |
+ completionCallback_.reset(completion, base::scoped_policy::RETAIN); |
+ identityInteractionManager_ = |
+ ios::GetChromeBrowserProvider() |
+ ->GetChromeIdentityService() |
+ ->NewChromeIdentityInteractionManager(browserState_, self); |
+ base::WeakNSObject<SigninInteractionController> weakSelf(self); |
+ [identityInteractionManager_ |
+ addAccountWithCompletion:^(ChromeIdentity* identity, NSError* error) { |
+ [weakSelf handleIdentityAdded:identity |
+ error:error |
+ shouldSignIn:NO |
+ viewController:viewController]; |
+ }]; |
+} |
+ |
+#pragma mark - ChromeIdentityInteractionManager operations |
+ |
+- (void)handleIdentityAdded:(ChromeIdentity*)identity |
+ error:(NSError*)error |
+ shouldSignIn:(BOOL)shouldSignIn |
+ viewController:(UIViewController*)viewController { |
+ if (!identityInteractionManager_) |
+ return; |
+ |
+ if (error) { |
+ // Filter out cancel and errors handled internally by ChromeIdentity. |
+ if (!ShouldHandleSigninError(error)) { |
+ [self runCompletionCallbackWithSuccess:NO executeCommand:nil]; |
+ return; |
+ } |
+ |
+ base::WeakNSObject<SigninInteractionController> weakSelf(self); |
+ ProceduralBlock dismissAction = ^{ |
+ [weakSelf runCompletionCallbackWithSuccess:NO executeCommand:nil]; |
+ }; |
+ |
+ alertCoordinator_.reset([ios_internal::ErrorCoordinator( |
+ error, dismissAction, viewController) retain]); |
+ [alertCoordinator_ start]; |
+ return; |
+ } |
+ if (shouldSignIn) { |
+ [self showSigninViewControllerWithIdentity:identity]; |
+ } else { |
+ [self runCompletionCallbackWithSuccess:YES executeCommand:nil]; |
+ } |
+} |
+ |
+- (void)dismissPresentedViewControllersAnimated:(BOOL)animated |
+ completion:(ProceduralBlock)completion { |
+ if ([presentingViewController_ presentedViewController]) { |
+ [presentingViewController_ dismissViewControllerAnimated:animated |
+ completion:completion]; |
+ } else if (completion) { |
+ completion(); |
+ } |
+ interactionManagerDismissalIgnored_ = NO; |
+} |
+ |
+#pragma mark - ChromeIdentityInteractionManagerDelegate |
+ |
+- (void)interactionManager:(ChromeIdentityInteractionManager*)interactionManager |
+ presentViewController:(UIViewController*)viewController |
+ animated:(BOOL)animated |
+ completion:(ProceduralBlock)completion { |
+ [presentingViewController_ presentViewController:viewController |
+ animated:animated |
+ completion:completion]; |
+} |
+ |
+- (void)interactionManager:(ChromeIdentityInteractionManager*)interactionManager |
+ dismissViewControllerAnimated:(BOOL)animated |
+ completion:(ProceduralBlock)completion { |
+ // Avoid awkward double transitions by not dismissing |
+ // identityInteractionManager_| if the signin view controller will be |
+ // displayed on top of it. |identityInteractionManager_| will be dismissed |
+ // when the signin view controller will be dismissed. |
+ if ([interactionManager isCanceling]) { |
+ [self dismissPresentedViewControllersAnimated:animated |
+ completion:completion]; |
+ } else { |
+ interactionManagerDismissalIgnored_ = YES; |
+ if (completion) { |
+ completion(); |
+ } |
+ } |
+} |
+ |
+#pragma mark - ChromeSigninViewController operations |
+ |
+- (void)showSigninViewControllerWithIdentity:(ChromeIdentity*)signInIdentity { |
+ signinViewController_.reset([[ChromeSigninViewController alloc] |
+ initWithBrowserState:browserState_ |
+ isPresentedOnSettings:isPresentedOnSettings_ |
+ signInAccessPoint:signInAccessPoint_ |
+ signInIdentity:signInIdentity]); |
+ [signinViewController_ setDelegate:self]; |
+ [signinViewController_ |
+ setModalPresentationStyle:UIModalPresentationFormSheet]; |
+ [signinViewController_ |
+ setModalTransitionStyle:UIModalTransitionStyleCoverVertical]; |
+ signInIdentity_.reset([signInIdentity retain]); |
+ |
+ UIViewController* presentingViewController = presentingViewController_; |
+ if (identityInteractionManager_) { |
+ // If |identityInteractionManager_| is currently displayed, |
+ // |signinViewController_| is presented on top of it (instead of on top of |
+ // |presentingViewController_|), to avoid an awkward transition (dismissing |
+ // |identityInteractionManager_|, followed by presenting |
+ // |signinViewController_|). |
+ while (presentingViewController.presentedViewController) { |
+ presentingViewController = |
+ presentingViewController.presentedViewController; |
+ } |
+ } |
+ [presentingViewController presentViewController:signinViewController_ |
+ animated:YES |
+ completion:nil]; |
+} |
+ |
+- (void)dismissSigninViewControllerWithSignInSuccess:(BOOL)success |
+ executeCommand: |
+ (GenericChromeCommand*)command { |
+ DCHECK(signinViewController_); |
+ if ((isCancelling_ && !isDismissing_) || |
+ ![presentingViewController_ presentedViewController]) { |
+ [self runCompletionCallbackWithSuccess:success executeCommand:command]; |
+ return; |
+ } |
+ ProceduralBlock completion = ^{ |
+ [self runCompletionCallbackWithSuccess:success executeCommand:command]; |
+ }; |
+ [self dismissPresentedViewControllersAnimated:YES completion:completion]; |
+} |
+ |
+#pragma mark - ChromeSigninViewControllerDelegate |
+ |
+- (void)willStartSignIn:(ChromeSigninViewController*)controller { |
+ DCHECK_EQ(controller, signinViewController_.get()); |
+} |
+ |
+- (void)willStartAddAccount:(ChromeSigninViewController*)controller { |
+ DCHECK_EQ(controller, signinViewController_.get()); |
+} |
+ |
+- (void)didSkipSignIn:(ChromeSigninViewController*)controller { |
+ DCHECK_EQ(controller, signinViewController_.get()); |
+ [self dismissSigninViewControllerWithSignInSuccess:NO executeCommand:nil]; |
+} |
+ |
+- (void)didSignIn:(ChromeSigninViewController*)controller { |
+ DCHECK_EQ(controller, signinViewController_.get()); |
+} |
+ |
+- (void)didUndoSignIn:(ChromeSigninViewController*)controller |
+ identity:(ChromeIdentity*)identity { |
+ DCHECK_EQ(controller, signinViewController_.get()); |
+ if ([signInIdentity_.get() isEqual:identity]) { |
+ signInIdentity_.reset(); |
+ // This is best effort. If the operation fails, the account will be left on |
+ // the device. The user will not be warned either as this call is |
+ // asynchronous (but undo is not), the application might be in an unknown |
+ // state when the forget identity operation finishes. |
+ ios::GetChromeBrowserProvider()->GetChromeIdentityService()->ForgetIdentity( |
+ identity, nil); |
+ [self dismissSigninViewControllerWithSignInSuccess:NO executeCommand:nil]; |
+ } |
+} |
+ |
+- (void)didFailSignIn:(ChromeSigninViewController*)controller { |
+ DCHECK_EQ(controller, signinViewController_.get()); |
+ [self dismissSigninViewControllerWithSignInSuccess:NO executeCommand:nil]; |
+} |
+ |
+- (void)didAcceptSignIn:(ChromeSigninViewController*)controller |
+ executeCommand:(GenericChromeCommand*)command { |
+ DCHECK_EQ(controller, signinViewController_.get()); |
+ [self dismissSigninViewControllerWithSignInSuccess:YES |
+ executeCommand:command]; |
+} |
+ |
+#pragma mark - Utility methods |
+ |
+- (void)runCompletionCallbackWithSuccess:(BOOL)success |
+ executeCommand:(GenericChromeCommand*)command { |
+ // In order to avoid awkward double transitions, |identityInteractionManager_| |
+ // is not dismissed when requested (except when canceling). However, in case |
+ // of errors, |identityInteractionManager_| needs to be directly dismissed, |
+ // which is done here. |
+ if (interactionManagerDismissalIgnored_) { |
+ [self dismissPresentedViewControllersAnimated:YES completion:nil]; |
+ } |
+ |
+ identityInteractionManager_.reset(); |
+ signinViewController_.reset(); |
+ UIViewController* presentingViewController = presentingViewController_; |
+ // Ensure self is not destroyed in the callbacks. |
+ base::scoped_nsobject<SigninInteractionController> strongSelf([self retain]); |
+ if (completionCallback_) { |
+ completionCallback_.get()(success); |
+ completionCallback_.reset(); |
+ } |
+ strongSelf.reset(); |
+ if (command) { |
+ [presentingViewController chromeExecuteCommand:command]; |
+ } |
+} |
+ |
+@end |