diff --git a/GoogleSignIn/Sources/GIDEMMErrorHandler.h b/GoogleSignIn/Sources/GIDEMMErrorHandler.h index 5051f56e..9f7ef71c 100644 --- a/GoogleSignIn/Sources/GIDEMMErrorHandler.h +++ b/GoogleSignIn/Sources/GIDEMMErrorHandler.h @@ -18,20 +18,38 @@ #if TARGET_OS_IOS && !TARGET_OS_MACCATALYST #import +#import + +#import "GoogleSignIn/Sources/GIDApplication/API/GIDApplication.h" +#import "GoogleSignIn/Sources/GIDEMMAlertPresenter/API/GIDEMMAlertPresenter.h" NS_ASSUME_NONNULL_BEGIN -// The handler for displaying EMM-specific errors to users. +/// The handler for displaying EMM-specific errors to users. @interface GIDEMMErrorHandler : NSObject -// Retrieve the shared instance of this class. +/// Returns the shared instance of this class. + (instancetype)sharedInstance; -// Handles EMM-specific errors in the server |response|. |completion| is always called -// exactly once, asynchronously on the main thread, with |YES| if |response| carries an EMM -// error and |NO| otherwise. For an EMM error, a remediation dialog is presented and -// |completion| is called after the user dismisses it — unless another EMM dialog is already -// pending or no dialog can be presented, in which case it is called as soon as possible. +/// Creates a handler that finds the key window and opens URLs through `application`, and shows its +/// alerts through `alertPresenter`. +- (instancetype)initWithApplication:(id)application + alertPresenter:(id)alertPresenter + NS_DESIGNATED_INITIALIZER; + +/// Creates a handler backed by `UIApplication.sharedApplication` and a +/// `GIDEMMAlertWindowPresenter`. +- (instancetype)init; + +/// Returns the application's key window, or nil if there is none. +/// Exposed for the unit tests. +- (nullable UIWindow *)keyWindow; + +/// Handles EMM-specific errors in the server |response|. |completion| is always called +/// exactly once, asynchronously on the main thread, with |YES| if |response| carries an EMM +/// error and |NO| otherwise. For an EMM error, a remediation dialog is presented and +/// |completion| is called after the user dismisses it — unless another EMM dialog is already +/// pending or no dialog can be presented, in which case it is called as soon as possible. - (void)handleErrorFromResponse:(NSDictionary *)response completion:(void (^)(BOOL handled))completion; diff --git a/GoogleSignIn/Sources/GIDEMMErrorHandler.m b/GoogleSignIn/Sources/GIDEMMErrorHandler.m index 019d6136..f6ef6888 100644 --- a/GoogleSignIn/Sources/GIDEMMErrorHandler.m +++ b/GoogleSignIn/Sources/GIDEMMErrorHandler.m @@ -19,6 +19,8 @@ #import +#import "GoogleSignIn/Sources/GIDApplication/Implementation/GIDSharedApplication.h" +#import "GoogleSignIn/Sources/GIDEMMAlertPresenter/Implementation/GIDEMMAlertWindowPresenter.h" #import "GoogleSignIn/Sources/GIDSignInStrings.h" NS_ASSUME_NONNULL_BEGIN @@ -43,6 +45,10 @@ } ErrorCode; @implementation GIDEMMErrorHandler { + // Finds the key window and opens URLs. + id _application; + // Shows and hides the remediation alert. + id _alertPresenter; // Whether or not a dialog is pending user interaction. BOOL _pendingDialog; } @@ -56,6 +62,21 @@ + (instancetype)sharedInstance { return sharedInstance; } +- (instancetype)initWithApplication:(id)application + alertPresenter:(id)alertPresenter { + self = [super init]; + if (self) { + _application = application; + _alertPresenter = alertPresenter; + } + return self; +} + +- (instancetype)init { + return [self initWithApplication:[[GIDSharedApplication alloc] init] + alertPresenter:[[GIDEMMAlertWindowPresenter alloc] init]]; +} + - (void)handleErrorFromResponse:(NSDictionary *)response completion:(void (^)(BOOL handled))completion { ErrorCode errorCode = ErrorCodeNone; @@ -119,26 +140,8 @@ - (void)handleErrorFromResponse:(NSDictionary *)response completion(YES); return; } - UIWindow *alertWindow; - if (@available(iOS 13, *)) { - if (keyWindow.windowScene) { - alertWindow = [[UIWindow alloc] initWithWindowScene:keyWindow.windowScene]; - } - } - if (!alertWindow) { - CGRect keyWindowBounds = CGRectIsEmpty(keyWindow.bounds) ? - keyWindow.bounds : [UIScreen mainScreen].bounds; - alertWindow = [[UIWindow alloc] initWithFrame:keyWindowBounds]; - } - alertWindow.backgroundColor = [UIColor clearColor]; - alertWindow.rootViewController = [[UIViewController alloc] init]; - alertWindow.rootViewController.view.backgroundColor = [UIColor clearColor]; - alertWindow.windowLevel = UIWindowLevelAlert; - [alertWindow makeKeyAndVisible]; void (^finish)(void) = ^{ - alertWindow.hidden = YES; - alertWindow.rootViewController = nil; - [keyWindow makeKeyAndVisible]; + [self->_alertPresenter dismissAlert]; clearPendingDialog(); completion(YES); }; @@ -157,7 +160,7 @@ - (void)handleErrorFromResponse:(NSDictionary *)response break; } if (alert) { - [alertWindow.rootViewController presentViewController:alert animated:YES completion:nil]; + [self->_alertPresenter presentAlert:alert overKeyWindow:keyWindow]; } else { // Should not happen but just in case. finish(); @@ -167,32 +170,7 @@ - (void)handleErrorFromResponse:(NSDictionary *)response // This method is exposed to the unit test. - (nullable UIWindow *)keyWindow { -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000 - if (@available(iOS 15, *)) { - for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) { - if ([scene isKindOfClass:[UIWindowScene class]] && - scene.activationState == UISceneActivationStateForegroundActive) { - return ((UIWindowScene *)scene).keyWindow; - } - } - } else -#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000 - { -#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_15_0 - if (@available(iOS 13, *)) { - for (UIWindow *window in UIApplication.sharedApplication.windows) { - if (window.isKeyWindow) { - return window; - } - } - } else { -#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_13_0 - return UIApplication.sharedApplication.keyWindow; -#endif // __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_13_0 - } -#endif // __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_15_0 - } - return nil; + return [_application keyWindow]; } #pragma mark - Alerts @@ -286,7 +264,7 @@ - (UIAlertController *)appVerificationRequiredAlertWithURL:(nullable NSURL *)url } - (void)openURL:(NSURL *)url { - [UIApplication.sharedApplication openURL:url options:@{} completionHandler:nil]; + [_application openURL:url]; } #pragma mark - Localization