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