From f8d59eeabcf96b19cc8ffd5b430448df093f0e65 Mon Sep 17 00:00:00 2001 From: Sigurd Meldgaard Date: Thu, 17 May 2018 13:13:22 +0200 Subject: [PATCH] Delegate AppDelegate life-cycle callbacks to plugins via separate object (flutter/engine#5173) * Move the handling of delegating AppDelegate callback out of FlutterAppDelegate. Also moves the plugin registry to FlutterViewController. So each view-controller will handle its own plugins. This is intended to simplify including one or more Flutter views in an existing iOS app and giving more precise control of plugin registration. Fixes: https://github.com/flutter/flutter/issues/16539 * formatting * Update license golden file --- .../shell/platform/darwin/ios/BUILD.gn | 2 + .../darwin/ios/framework/Headers/Flutter.h | 1 + .../framework/Headers/FlutterAppDelegate.h | 14 +- .../ios/framework/Headers/FlutterPlugin.h | 8 + .../FlutterPluginAppLifeCycleDelegate.h | 135 +++++++++ .../framework/Headers/FlutterViewController.h | 5 +- .../framework/Source/FlutterAppDelegate.mm | 259 ++++------------ .../FlutterPluginAppLifeCycleDelegate.mm | 278 ++++++++++++++++++ .../framework/Source/FlutterViewController.mm | 87 ++++++ 9 files changed, 567 insertions(+), 222 deletions(-) create mode 100644 engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h create mode 100644 engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm diff --git a/engine/src/flutter/shell/platform/darwin/ios/BUILD.gn b/engine/src/flutter/shell/platform/darwin/ios/BUILD.gn index 19d2486a69f..41793e0c67d 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/BUILD.gn +++ b/engine/src/flutter/shell/platform/darwin/ios/BUILD.gn @@ -22,6 +22,7 @@ _flutter_framework_headers = [ "framework/Headers/FlutterMacros.h", "framework/Headers/FlutterNavigationController.h", "framework/Headers/FlutterPlugin.h", + "framework/Headers/FlutterPluginAppLifeCycleDelegate.h", "framework/Headers/FlutterTexture.h", "framework/Headers/FlutterViewController.h", ] @@ -43,6 +44,7 @@ shared_library("create_flutter_framework_dylib") { "framework/Source/FlutterNavigationController.mm", "framework/Source/FlutterPlatformPlugin.h", "framework/Source/FlutterPlatformPlugin.mm", + "framework/Source/FlutterPluginAppLifeCycleDelegate.mm", "framework/Source/FlutterStandardCodec.mm", "framework/Source/FlutterStandardCodec_Internal.h", "framework/Source/FlutterTextInputDelegate.h", diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/Flutter.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/Flutter.h index 939040c8e5d..d850854f2f5 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/Flutter.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/Flutter.h @@ -43,6 +43,7 @@ #include "FlutterMacros.h" #include "FlutterNavigationController.h" #include "FlutterPlugin.h" +#include "FlutterPluginAppLifeCycleDelegate.h" #include "FlutterTexture.h" #include "FlutterViewController.h" diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h index 4f28e70b65f..f175ea29258 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h @@ -24,22 +24,10 @@ * code as necessary from FlutterAppDelegate.mm. */ FLUTTER_EXPORT -@interface FlutterAppDelegate : UIResponder +@interface FlutterAppDelegate : UIResponder @property(strong, nonatomic) UIWindow* window; -// Can be overriden by subclasses to provide a custom FlutterBinaryMessenger, -// typically a FlutterViewController, for plugin interop. -// -// Defaults to window's rootViewController. -- (NSObject*)binaryMessenger; - -// Can be overriden by subclasses to provide a custom FlutterTextureRegistry, -// typically a FlutterViewController, for plugin interop. -// -// Defaults to window's rootViewController. -- (NSObject*)textures; - @end #endif // FLUTTER_FLUTTERDARTPROJECT_H_ diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h index 646ffa30d74..46bee66f99d 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h @@ -253,6 +253,14 @@ NS_ASSUME_NONNULL_BEGIN - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey; @end +/** + Implement this in the `UIAppDelegate` of your app to enable Flutter plugins to register themselves to the application + life cycle events. +*/ +@protocol FlutterAppLifeCycleProvider +- (void)addApplicationLifeCycleDelegate:(NSObject*)delegate; +@end + NS_ASSUME_NONNULL_END; #endif // FLUTTER_FLUTTERPLUGIN_H_ diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h new file mode 100644 index 00000000000..676ef6f476d --- /dev/null +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h @@ -0,0 +1,135 @@ +// Copyright 2018 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. + +#ifndef FLUTTER_FLUTTERPLUGINAPPLIFECYCLEDELEGATE_H_ +#define FLUTTER_FLUTTERPLUGINAPPLIFECYCLEDELEGATE_H_ + +#include "FlutterPlugin.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Propagates `UIAppDelegate` callbacks to registered plugins. +*/ +FLUTTER_EXPORT +@interface FlutterPluginAppLifeCycleDelegate : NSObject +/** + Registers `delegate` to receive life cycle callbacks via this FlutterPluginAppLifecycleDelegate as long as it is alive. + + `delegate` will only referenced weakly. +*/ +- (void)addDelegate:(NSObject*)delegate; + +/** + Calls all plugins registered for `UIApplicationDelegate` callbacks. + + - Returns: `NO` if any plugin vetoes application launch. + */ +- (BOOL)application:(UIApplication*)application + didFinishLaunchingWithOptions:(NSDictionary*)launchOptions; + +/** + Calls all plugins registered for `UIApplicationDelegate` callbacks. + */ +- (void)applicationDidBecomeActive:(UIApplication*)application; + +/** + Calls all plugins registered for `UIApplicationDelegate` callbacks. + */ +- (void)applicationWillResignActive:(UIApplication*)application; + +/** + Calls all plugins registered for `UIApplicationDelegate` callbacks. + */ +- (void)applicationDidEnterBackground:(UIApplication*)application; + +/** + Calls all plugins registered for `UIApplicationDelegate` callbacks. + */ +- (void)applicationWillEnterForeground:(UIApplication*)application; + +/** + Calls all plugins registered for `UIApplicationDelegate` callbacks. + */ +- (void)applicationWillTerminate:(UIApplication*)application; + +/** + Called if this plugin has been registered for `UIApplicationDelegate` callbacks. + */ +- (void)application:(UIApplication*)application + didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings; + +/** + Calls all plugins registered for `UIApplicationDelegate` callbacks. + */ +- (void)application:(UIApplication*)application + didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken; + +/** + Calls all plugins registered for `UIApplicationDelegate` callbacks. + */ +- (void)application:(UIApplication*)application + didReceiveRemoteNotification:(NSDictionary*)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler; + +/** + Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles + the request. + + - Returns: `YES` if any plugin handles the request. +*/ +- (BOOL)application:(UIApplication*)application + openURL:(NSURL*)url + options:(NSDictionary*)options; + +/** + Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles + the request. + + - Returns: `YES` if any plugin handles the request. + */ +- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url; + +/** + Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles + the request. + + - Returns: `YES` if any plugin handles the request. +*/ +- (BOOL)application:(UIApplication*)application + openURL:(NSURL*)url + sourceApplication:(NSString*)sourceApplication + annotation:(id)annotation; + +/** + Calls all plugins registered for `UIApplicationDelegate` callbacks. +*/ +- (void)application:(UIApplication*)application + performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem + completionHandler:(void (^)(BOOL succeeded))completionHandler + API_AVAILABLE(ios(9.0)); + +/** + Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles + the request. + + - Returns: `YES` if any plugin handles the request. +*/ +- (BOOL)application:(UIApplication*)application + handleEventsForBackgroundURLSession:(nonnull NSString*)identifier + completionHandler:(nonnull void (^)())completionHandler; + +/** + Calls all plugins registered for `UIApplicationDelegate` callbacks in order of registration until some plugin handles + the request. + + - Returns: `YES` if any plugin handles the request. +*/ +- (BOOL)application:(UIApplication*)application + performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler; +@end + +NS_ASSUME_NONNULL_END + +#endif // FLUTTER_FLUTTERPLUGINAPPLIFECYCLEDELEGATE_H_ \ No newline at end of file diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h index 7ef6868a276..524890ca2c0 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h @@ -11,10 +11,11 @@ #include "FlutterBinaryMessenger.h" #include "FlutterDartProject.h" #include "FlutterMacros.h" +#include "FlutterPlugin.h" #include "FlutterTexture.h" FLUTTER_EXPORT -@interface FlutterViewController : UIViewController +@interface FlutterViewController : UIViewController - (instancetype)initWithProject:(FlutterDartProject*)project nibName:(NSString*)nibNameOrNil @@ -49,6 +50,8 @@ FLUTTER_EXPORT */ - (void)setInitialRoute:(NSString*)route; +- (id)pluginRegistry; + @end #endif // FLUTTER_FLUTTERVIEWCONTROLLER_H_ diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm index a5132bf4ee4..b175966f3db 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm @@ -3,46 +3,28 @@ // found in the LICENSE file. #include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h" +#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h" #include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" -#include "lib/fxl/logging.h" - -@interface FlutterAppDelegate () -@property(readonly, nonatomic) NSMutableArray* pluginDelegates; -@property(readonly, nonatomic) NSMutableDictionary* pluginPublications; -@end - -@interface FlutterAppDelegateRegistrar : NSObject -- (instancetype)initWithPlugin:(NSString*)pluginKey appDelegate:(FlutterAppDelegate*)delegate; -@end @implementation FlutterAppDelegate { - UIBackgroundTaskIdentifier _debugBackgroundTask; + FlutterPluginAppLifeCycleDelegate* _lifeCycleDelegate; } - (instancetype)init { if (self = [super init]) { - _pluginDelegates = [NSMutableArray new]; - _pluginPublications = [NSMutableDictionary new]; + _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; } return self; } - (void)dealloc { - [_pluginDelegates release]; - [_pluginPublications release]; + [_lifeCycleDelegate release]; [super dealloc]; } - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { - for (id plugin in _pluginDelegates) { - if ([plugin respondsToSelector:_cmd]) { - if (![plugin application:application didFinishLaunchingWithOptions:launchOptions]) { - return NO; - } - } - } - return YES; + return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions]; } // Returns the key window's rootViewController, if it's a FlutterViewController. @@ -65,257 +47,118 @@ } - (void)applicationDidEnterBackground:(UIApplication*)application { -#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG - // The following keeps the Flutter session alive when the device screen locks - // in debug mode. It allows continued use of features like hot reload and - // taking screenshots once the device unlocks again. - // - // Note the name is not an identifier and multiple instances can exist. - _debugBackgroundTask = [application - beginBackgroundTaskWithName:@"Flutter debug task" - expirationHandler:^{ - FXL_LOG(WARNING) - << "\nThe OS has terminated the Flutter debug connection for being " - "inactive in the background for too long.\n\n" - "There are no errors with your Flutter application.\n\n" - "To reconnect, launch your application again via 'flutter run'"; - }]; -#endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG - for (id plugin in _pluginDelegates) { - if ([plugin respondsToSelector:_cmd]) { - [plugin applicationDidEnterBackground:application]; - } - } + [_lifeCycleDelegate applicationDidEnterBackground:application]; } - (void)applicationWillEnterForeground:(UIApplication*)application { -#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG - [application endBackgroundTask:_debugBackgroundTask]; -#endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG - for (id plugin in _pluginDelegates) { - if ([plugin respondsToSelector:_cmd]) { - [plugin applicationWillEnterForeground:application]; - } - } + [_lifeCycleDelegate applicationWillEnterForeground:application]; } - (void)applicationWillResignActive:(UIApplication*)application { - for (id plugin in _pluginDelegates) { - if ([plugin respondsToSelector:_cmd]) { - [plugin applicationWillResignActive:application]; - } - } + [_lifeCycleDelegate applicationWillResignActive:application]; } - (void)applicationDidBecomeActive:(UIApplication*)application { - for (id plugin in _pluginDelegates) { - if ([plugin respondsToSelector:_cmd]) { - [plugin applicationDidBecomeActive:application]; - } - } + [_lifeCycleDelegate applicationDidBecomeActive:application]; } - (void)applicationWillTerminate:(UIApplication*)application { - for (id plugin in _pluginDelegates) { - if ([plugin respondsToSelector:_cmd]) { - [plugin applicationWillTerminate:application]; - } - } + [_lifeCycleDelegate applicationWillTerminate:application]; } - (void)application:(UIApplication*)application didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings { - for (id plugin in _pluginDelegates) { - if ([plugin respondsToSelector:_cmd]) { - [plugin application:application didRegisterUserNotificationSettings:notificationSettings]; - } - } + [_lifeCycleDelegate application:application + didRegisterUserNotificationSettings:notificationSettings]; } - (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { - for (id plugin in _pluginDelegates) { - if ([plugin respondsToSelector:_cmd]) { - [plugin application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; - } - } + [_lifeCycleDelegate application:application + didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } - (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { - for (id plugin in _pluginDelegates) { - if ([plugin respondsToSelector:_cmd]) { - if ([plugin application:application - didReceiveRemoteNotification:userInfo - fetchCompletionHandler:completionHandler]) { - return; - } - } - } + [_lifeCycleDelegate application:application + didReceiveRemoteNotification:userInfo + fetchCompletionHandler:completionHandler]; } - (BOOL)application:(UIApplication*)application openURL:(NSURL*)url options:(NSDictionary*)options { - for (id plugin in _pluginDelegates) { - if ([plugin respondsToSelector:_cmd]) { - if ([plugin application:application openURL:url options:options]) { - return YES; - } - } - } - return NO; + return [_lifeCycleDelegate application:application openURL:url options:options]; } - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url { - for (id plugin in _pluginDelegates) { - if ([plugin respondsToSelector:_cmd]) { - if ([plugin application:application handleOpenURL:url]) { - return YES; - } - } - } - return NO; + return [_lifeCycleDelegate application:application handleOpenURL:url]; } - (BOOL)application:(UIApplication*)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation { - for (id plugin in _pluginDelegates) { - if ([plugin respondsToSelector:_cmd]) { - if ([plugin application:application - openURL:url - sourceApplication:sourceApplication - annotation:annotation]) { - return YES; - } - } - } - return NO; + return [_lifeCycleDelegate application:application + openURL:url + sourceApplication:sourceApplication + annotation:annotation]; } - (void)application:(UIApplication*)application performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) { - for (id plugin in _pluginDelegates) { - if ([plugin respondsToSelector:_cmd]) { - if ([plugin application:application - performActionForShortcutItem:shortcutItem - completionHandler:completionHandler]) { - return; - } - } - } + [_lifeCycleDelegate application:application + performActionForShortcutItem:shortcutItem + completionHandler:completionHandler]; } - (void)application:(UIApplication*)application handleEventsForBackgroundURLSession:(nonnull NSString*)identifier completionHandler:(nonnull void (^)())completionHandler { - for (id plugin in _pluginDelegates) { - if ([plugin respondsToSelector:_cmd]) { - if ([plugin application:application - handleEventsForBackgroundURLSession:identifier - completionHandler:completionHandler]) { - return; - } - } - } + [_lifeCycleDelegate application:application + handleEventsForBackgroundURLSession:identifier + completionHandler:completionHandler]; } - (void)application:(UIApplication*)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { - for (id plugin in _pluginDelegates) { - if ([plugin respondsToSelector:_cmd]) { - if ([plugin application:application performFetchWithCompletionHandler:completionHandler]) { - return; - } - } - } + [_lifeCycleDelegate application:application performFetchWithCompletionHandler:completionHandler]; } -// TODO(xster): move when doing https://github.com/flutter/flutter/issues/3671. -- (NSObject*)binaryMessenger { - UIViewController* rootViewController = _window.rootViewController; - if ([rootViewController conformsToProtocol:@protocol(FlutterBinaryMessenger)]) { - return (NSObject*)rootViewController; - } - return nil; -} - -- (NSObject*)textures { - UIViewController* rootViewController = _window.rootViewController; - if ([rootViewController conformsToProtocol:@protocol(FlutterTextureRegistry)]) { - return (NSObject*)rootViewController; - } - return nil; -} +#pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController - (NSObject*)registrarForPlugin:(NSString*)pluginKey { - NSAssert(self.pluginPublications[pluginKey] == nil, @"Duplicate plugin key: %@", pluginKey); - self.pluginPublications[pluginKey] = [NSNull null]; - return - [[[FlutterAppDelegateRegistrar alloc] initWithPlugin:pluginKey appDelegate:self] autorelease]; + UIViewController* rootViewController = _window.rootViewController; + if ([rootViewController isKindOfClass:[FlutterViewController class]]) { + return + [[(FlutterViewController*)rootViewController pluginRegistry] registrarForPlugin:pluginKey]; + } + return nil; } - (BOOL)hasPlugin:(NSString*)pluginKey { - return _pluginPublications[pluginKey] != nil; + UIViewController* rootViewController = _window.rootViewController; + if ([rootViewController isKindOfClass:[FlutterViewController class]]) { + return [[(FlutterViewController*)rootViewController pluginRegistry] hasPlugin:pluginKey]; + } + return nil; } - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey { - return _pluginPublications[pluginKey]; -} -@end - -@implementation FlutterAppDelegateRegistrar { - NSString* _pluginKey; - FlutterAppDelegate* _appDelegate; + UIViewController* rootViewController = _window.rootViewController; + if ([rootViewController isKindOfClass:[FlutterViewController class]]) { + return [[(FlutterViewController*)rootViewController pluginRegistry] + valuePublishedByPlugin:pluginKey]; + } + return nil; } -- (instancetype)initWithPlugin:(NSString*)pluginKey appDelegate:(FlutterAppDelegate*)appDelegate { - self = [super init]; - NSAssert(self, @"Super init cannot be nil"); - _pluginKey = [pluginKey retain]; - _appDelegate = [appDelegate retain]; - return self; -} +#pragma mark - FlutterAppLifeCycleProvider methods -- (void)dealloc { - [_pluginKey release]; - [_appDelegate release]; - [super dealloc]; -} - -- (NSObject*)messenger { - return [_appDelegate binaryMessenger]; -} - -- (NSObject*)textures { - return [_appDelegate textures]; -} - -- (void)publish:(NSObject*)value { - _appDelegate.pluginPublications[_pluginKey] = value; -} - -- (void)addMethodCallDelegate:(NSObject*)delegate - channel:(FlutterMethodChannel*)channel { - [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { - [delegate handleMethodCall:call result:result]; - }]; -} - -- (void)addApplicationDelegate:(NSObject*)delegate { - [_appDelegate.pluginDelegates addObject:delegate]; -} - -- (NSString*)lookupKeyForAsset:(NSString*)asset { - return [FlutterDartProject lookupKeyForAsset:asset]; -} - -- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package { - return [FlutterDartProject lookupKeyForAsset:asset fromPackage:package]; +- (void)addApplicationLifeCycleDelegate:(NSObject*)delegate { + [_lifeCycleDelegate addDelegate:delegate]; } @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm new file mode 100644 index 00000000000..900d048c57a --- /dev/null +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm @@ -0,0 +1,278 @@ +// Copyright 2018 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. + +#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h" +#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" +#include "lib/fxl/logging.h" + +@implementation FlutterPluginAppLifeCycleDelegate { + UIBackgroundTaskIdentifier _debugBackgroundTask; + + // Weak references to registered plugins. + NSPointerArray* _pluginDelegates; +} + +- (instancetype)init { + if (self = [super init]) { + _pluginDelegates = [[NSPointerArray weakObjectsPointerArray] retain]; + } + return self; +} + +- (void)dealloc { + [_pluginDelegates release]; + [super dealloc]; +} + +static BOOL isPowerOfTwo(NSUInteger x) { + return x != 0 && (x & (x - 1)) == 0; +} + +- (void)addDelegate:(NSObject*)delegate { + [_pluginDelegates addPointer:(__bridge void*)delegate]; + if (isPowerOfTwo([_pluginDelegates count])) { + [_pluginDelegates compact]; + } +} + +- (BOOL)application:(UIApplication*)application + didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + for (id plugin in [_pluginDelegates allObjects]) { + if (!plugin) { + continue; + } + if ([plugin respondsToSelector:_cmd]) { + if (![plugin application:application didFinishLaunchingWithOptions:launchOptions]) { + return NO; + } + } + } + return YES; +} + +// Returns the key window's rootViewController, if it's a FlutterViewController. +// Otherwise, returns nil. +- (FlutterViewController*)rootFlutterViewController { + UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController; + if ([viewController isKindOfClass:[FlutterViewController class]]) { + return (FlutterViewController*)viewController; + } + return nil; +} + +- (void)applicationDidEnterBackground:(UIApplication*)application { +#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG + // The following keeps the Flutter session alive when the device screen locks + // in debug mode. It allows continued use of features like hot reload and + // taking screenshots once the device unlocks again. + // + // Note the name is not an identifier and multiple instances can exist. + _debugBackgroundTask = [application + beginBackgroundTaskWithName:@"Flutter debug task" + expirationHandler:^{ + FXL_LOG(WARNING) + << "\nThe OS has terminated the Flutter debug connection for being " + "inactive in the background for too long.\n\n" + "There are no errors with your Flutter application.\n\n" + "To reconnect, launch your application again via 'flutter run'"; + }]; +#endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG + for (id plugin in _pluginDelegates) { + if (!plugin) { + continue; + } + if ([plugin respondsToSelector:_cmd]) { + [plugin applicationDidEnterBackground:application]; + } + } +} + +- (void)applicationWillEnterForeground:(UIApplication*)application { +#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG + [application endBackgroundTask:_debugBackgroundTask]; +#endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG + for (id plugin in _pluginDelegates) { + if (!plugin) { + continue; + } + if ([plugin respondsToSelector:_cmd]) { + [plugin applicationWillEnterForeground:application]; + } + } +} + +- (void)applicationWillResignActive:(UIApplication*)application { + for (id plugin in _pluginDelegates) { + if (!plugin) { + continue; + } + if ([plugin respondsToSelector:_cmd]) { + [plugin applicationWillResignActive:application]; + } + } +} + +- (void)applicationDidBecomeActive:(UIApplication*)application { + for (id plugin in _pluginDelegates) { + if (!plugin) { + continue; + } + if ([plugin respondsToSelector:_cmd]) { + [plugin applicationDidBecomeActive:application]; + } + } +} + +- (void)applicationWillTerminate:(UIApplication*)application { + for (id plugin in _pluginDelegates) { + if (!plugin) { + continue; + } + if ([plugin respondsToSelector:_cmd]) { + [plugin applicationWillTerminate:application]; + } + } +} + +- (void)application:(UIApplication*)application + didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings { + for (id plugin in _pluginDelegates) { + if (!plugin) { + continue; + } + if ([plugin respondsToSelector:_cmd]) { + [plugin application:application didRegisterUserNotificationSettings:notificationSettings]; + } + } +} + +- (void)application:(UIApplication*)application + didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { + for (id plugin in _pluginDelegates) { + if (!plugin) { + continue; + } + if ([plugin respondsToSelector:_cmd]) { + [plugin application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; + } + } +} + +- (void)application:(UIApplication*)application + didReceiveRemoteNotification:(NSDictionary*)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { + for (id plugin in _pluginDelegates) { + if (!plugin) { + continue; + } + if ([plugin respondsToSelector:_cmd]) { + if ([plugin application:application + didReceiveRemoteNotification:userInfo + fetchCompletionHandler:completionHandler]) { + return; + } + } + } +} + +- (BOOL)application:(UIApplication*)application + openURL:(NSURL*)url + options:(NSDictionary*)options { + for (id plugin in _pluginDelegates) { + if (!plugin) { + continue; + } + if ([plugin respondsToSelector:_cmd]) { + if ([plugin application:application openURL:url options:options]) { + return YES; + } + } + } + return NO; +} + +- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url { + for (id plugin in _pluginDelegates) { + if (!plugin) { + continue; + } + if ([plugin respondsToSelector:_cmd]) { + if ([plugin application:application handleOpenURL:url]) { + return YES; + } + } + } + return NO; +} + +- (BOOL)application:(UIApplication*)application + openURL:(NSURL*)url + sourceApplication:(NSString*)sourceApplication + annotation:(id)annotation { + for (id plugin in _pluginDelegates) { + if (!plugin) { + continue; + } + if ([plugin respondsToSelector:_cmd]) { + if ([plugin application:application + openURL:url + sourceApplication:sourceApplication + annotation:annotation]) { + return YES; + } + } + } + return NO; +} + +- (void)application:(UIApplication*)application + performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem + completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) { + for (id plugin in _pluginDelegates) { + if (!plugin) { + continue; + } + if ([plugin respondsToSelector:_cmd]) { + if ([plugin application:application + performActionForShortcutItem:shortcutItem + completionHandler:completionHandler]) { + return; + } + } + } +} + +- (BOOL)application:(UIApplication*)application + handleEventsForBackgroundURLSession:(nonnull NSString*)identifier + completionHandler:(nonnull void (^)())completionHandler { + for (id plugin in _pluginDelegates) { + if (!plugin) { + continue; + } + if ([plugin respondsToSelector:_cmd]) { + if ([plugin application:application + handleEventsForBackgroundURLSession:identifier + completionHandler:completionHandler]) { + return YES; + } + } + } + return NO; +} + +- (BOOL)application:(UIApplication*)application + performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { + for (id plugin in _pluginDelegates) { + if (!plugin) { + continue; + } + if ([plugin respondsToSelector:_cmd]) { + if ([plugin application:application performFetchWithCompletionHandler:completionHandler]) { + return YES; + } + } + } + return NO; +} +@end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 2e13ffb738c..e3076c6c05c 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -22,6 +22,12 @@ #include "flutter/shell/platform/darwin/ios/platform_view_ios.h" @interface FlutterViewController () +@property(nonatomic, readonly) NSMutableDictionary* pluginPublications; +@end + +@interface FlutterViewControllerRegistrar : NSObject +- (instancetype)initWithPlugin:(NSString*)pluginKey + flutterViewController:(FlutterViewController*)flutterViewController; @end @implementation FlutterViewController { @@ -93,6 +99,8 @@ if ([self setupShell]) { [self setupChannels]; [self setupNotificationCenterObservers]; + + _pluginPublications = [NSMutableDictionary new]; } } @@ -426,6 +434,7 @@ - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; + [_pluginPublications release]; [super dealloc]; } @@ -971,4 +980,82 @@ constexpr CGFloat kStandardStatusBarHeight = 20.0; return [FlutterDartProject lookupKeyForAsset:asset fromPackage:package]; } +- (id)pluginRegistry { + return self; +} + +#pragma mark - FlutterPluginRegistry + +- (NSObject*)registrarForPlugin:(NSString*)pluginKey { + NSAssert(self.pluginPublications[pluginKey] == nil, @"Duplicate plugin key: %@", pluginKey); + self.pluginPublications[pluginKey] = [NSNull null]; + return + [[FlutterViewControllerRegistrar alloc] initWithPlugin:pluginKey flutterViewController:self]; +} + +- (BOOL)hasPlugin:(NSString*)pluginKey { + return _pluginPublications[pluginKey] != nil; +} + +- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey { + return _pluginPublications[pluginKey]; +} +@end + +@implementation FlutterViewControllerRegistrar { + NSString* _pluginKey; + FlutterViewController* _flutterViewController; +} + +- (instancetype)initWithPlugin:(NSString*)pluginKey + flutterViewController:(FlutterViewController*)flutterViewController { + self = [super init]; + NSAssert(self, @"Super init cannot be nil"); + _pluginKey = [pluginKey retain]; + _flutterViewController = [flutterViewController retain]; + return self; +} + +- (void)dealloc { + [_pluginKey release]; + [_flutterViewController release]; + [super dealloc]; +} + +- (NSObject*)messenger { + return _flutterViewController; +} + +- (NSObject*)textures { + return _flutterViewController; +} + +- (void)publish:(NSObject*)value { + _flutterViewController.pluginPublications[_pluginKey] = value; +} + +- (void)addMethodCallDelegate:(NSObject*)delegate + channel:(FlutterMethodChannel*)channel { + [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { + [delegate handleMethodCall:call result:result]; + }]; +} + +- (void)addApplicationDelegate:(NSObject*)delegate { + id appDelegate = [[UIApplication sharedApplication] delegate]; + if ([appDelegate conformsToProtocol:@protocol(FlutterAppLifeCycleProvider)]) { + id lifeCycleProvider = + (id)appDelegate; + [lifeCycleProvider addApplicationLifeCycleDelegate:delegate]; + } +} + +- (NSString*)lookupKeyForAsset:(NSString*)asset { + return [_flutterViewController lookupKeyForAsset:asset]; +} + +- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package { + return [_flutterViewController lookupKeyForAsset:asset fromPackage:package]; +} + @end