1// Copyright 2013 The Flutter Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h" 6#include "flutter/fml/logging.h" 7#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h" 8#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" 9#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h" 10 11static NSString* kUIBackgroundMode = @"UIBackgroundModes"; 12static NSString* kRemoteNotificationCapabitiliy = @"remote-notification"; 13static NSString* kBackgroundFetchCapatibility = @"fetch"; 14 15@implementation FlutterAppDelegate { 16 FlutterPluginAppLifeCycleDelegate* _lifeCycleDelegate; 17} 18 19- (instancetype)init { 20 if (self = [super init]) { 21 _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; 22 } 23 return self; 24} 25 26- (void)dealloc { 27 [_lifeCycleDelegate release]; 28 [super dealloc]; 29} 30 31- (BOOL)application:(UIApplication*)application 32 willFinishLaunchingWithOptions:(NSDictionary*)launchOptions { 33 return [_lifeCycleDelegate application:application willFinishLaunchingWithOptions:launchOptions]; 34} 35 36- (BOOL)application:(UIApplication*)application 37 didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { 38 return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions]; 39} 40 41// Returns the key window's rootViewController, if it's a FlutterViewController. 42// Otherwise, returns nil. 43- (FlutterViewController*)rootFlutterViewController { 44 UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController; 45 if ([viewController isKindOfClass:[FlutterViewController class]]) { 46 return (FlutterViewController*)viewController; 47 } 48 return nil; 49} 50 51- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { 52 [super touchesBegan:touches withEvent:event]; 53 54 // Pass status bar taps to key window Flutter rootViewController. 55 if (self.rootFlutterViewController != nil) { 56 [self.rootFlutterViewController handleStatusBarTouches:event]; 57 } 58} 59 60#pragma GCC diagnostic push 61#pragma GCC diagnostic ignored "-Wdeprecated-declarations" 62- (void)application:(UIApplication*)application 63 didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings { 64 [_lifeCycleDelegate application:application 65 didRegisterUserNotificationSettings:notificationSettings]; 66} 67#pragma GCC diagnostic pop 68 69- (void)application:(UIApplication*)application 70 didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { 71 [_lifeCycleDelegate application:application 72 didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; 73} 74 75#pragma GCC diagnostic push 76#pragma GCC diagnostic ignored "-Wdeprecated-declarations" 77- (void)application:(UIApplication*)application 78 didReceiveLocalNotification:(UILocalNotification*)notification { 79 [_lifeCycleDelegate application:application didReceiveLocalNotification:notification]; 80} 81#pragma GCC diagnostic pop 82 83- (void)userNotificationCenter:(UNUserNotificationCenter*)center 84 willPresentNotification:(UNNotification*)notification 85 withCompletionHandler: 86 (void (^)(UNNotificationPresentationOptions options))completionHandler 87 API_AVAILABLE(ios(10)) { 88 if (@available(iOS 10.0, *)) { 89 [_lifeCycleDelegate userNotificationCenter:center 90 willPresentNotification:notification 91 withCompletionHandler:completionHandler]; 92 } 93} 94 95- (BOOL)application:(UIApplication*)application 96 openURL:(NSURL*)url 97 options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options { 98 return [_lifeCycleDelegate application:application openURL:url options:options]; 99} 100 101- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url { 102 return [_lifeCycleDelegate application:application handleOpenURL:url]; 103} 104 105- (BOOL)application:(UIApplication*)application 106 openURL:(NSURL*)url 107 sourceApplication:(NSString*)sourceApplication 108 annotation:(id)annotation { 109 return [_lifeCycleDelegate application:application 110 openURL:url 111 sourceApplication:sourceApplication 112 annotation:annotation]; 113} 114 115- (void)application:(UIApplication*)application 116 performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem 117 completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) { 118 [_lifeCycleDelegate application:application 119 performActionForShortcutItem:shortcutItem 120 completionHandler:completionHandler]; 121} 122 123- (void)application:(UIApplication*)application 124 handleEventsForBackgroundURLSession:(nonnull NSString*)identifier 125 completionHandler:(nonnull void (^)())completionHandler { 126 [_lifeCycleDelegate application:application 127 handleEventsForBackgroundURLSession:identifier 128 completionHandler:completionHandler]; 129} 130 131#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000 132- (BOOL)application:(UIApplication*)application 133 continueUserActivity:(NSUserActivity*)userActivity 134 restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>>* __nullable 135 restorableObjects))restorationHandler { 136#else 137- (BOOL)application:(UIApplication*)application 138 continueUserActivity:(NSUserActivity*)userActivity 139 restorationHandler:(void (^)(NSArray* __nullable restorableObjects))restorationHandler { 140#endif 141 return [_lifeCycleDelegate application:application 142 continueUserActivity:userActivity 143 restorationHandler:restorationHandler]; 144} 145 146#pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController 147 148- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey { 149 UIViewController* rootViewController = _window.rootViewController; 150 if ([rootViewController isKindOfClass:[FlutterViewController class]]) { 151 return 152 [[(FlutterViewController*)rootViewController pluginRegistry] registrarForPlugin:pluginKey]; 153 } 154 return nil; 155} 156 157- (BOOL)hasPlugin:(NSString*)pluginKey { 158 UIViewController* rootViewController = _window.rootViewController; 159 if ([rootViewController isKindOfClass:[FlutterViewController class]]) { 160 return [[(FlutterViewController*)rootViewController pluginRegistry] hasPlugin:pluginKey]; 161 } 162 return false; 163} 164 165- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey { 166 UIViewController* rootViewController = _window.rootViewController; 167 if ([rootViewController isKindOfClass:[FlutterViewController class]]) { 168 return [[(FlutterViewController*)rootViewController pluginRegistry] 169 valuePublishedByPlugin:pluginKey]; 170 } 171 return nil; 172} 173 174#pragma mark - Selectors handling 175 176- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate { 177 [_lifeCycleDelegate addDelegate:delegate]; 178} 179 180#pragma mark - UIApplicationDelegate method dynamic implementation 181 182- (BOOL)respondsToSelector:(SEL)selector { 183 if ([_lifeCycleDelegate isSelectorAddedDynamically:selector]) { 184 return [self delegateRespondsSelectorToPlugins:selector]; 185 } 186 return [super respondsToSelector:selector]; 187} 188 189- (BOOL)delegateRespondsSelectorToPlugins:(SEL)selector { 190 if ([_lifeCycleDelegate hasPluginThatRespondsToSelector:selector]) { 191 return [_lifeCycleDelegate respondsToSelector:selector]; 192 } else { 193 return NO; 194 } 195} 196 197- (id)forwardingTargetForSelector:(SEL)aSelector { 198 if ([_lifeCycleDelegate isSelectorAddedDynamically:aSelector]) { 199 [self logCapabilityConfigurationWarningIfNeeded:aSelector]; 200 return _lifeCycleDelegate; 201 } 202 return [super forwardingTargetForSelector:aSelector]; 203} 204 205// Mimic the logging from Apple when the capability is not set for the selectors. 206// However the difference is that Apple logs these message when the app launches, we only 207// log it when the method is invoked. We can possibly also log it when the app launches, but 208// it will cause an additional scan over all the plugins. 209- (void)logCapabilityConfigurationWarningIfNeeded:(SEL)selector { 210 NSArray* backgroundModesArray = 211 [[NSBundle mainBundle] objectForInfoDictionaryKey:kUIBackgroundMode]; 212 NSSet* backgroundModesSet = [[[NSSet alloc] initWithArray:backgroundModesArray] autorelease]; 213 if (selector == @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)) { 214 if (![backgroundModesSet containsObject:kRemoteNotificationCapabitiliy]) { 215 NSLog( 216 @"You've implemented -[<UIApplicationDelegate> " 217 @"application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need " 218 @"to add \"remote-notification\" to the list of your supported UIBackgroundModes in your " 219 @"Info.plist."); 220 } 221 } else if (selector == @selector(application:performFetchWithCompletionHandler:)) { 222 if (![backgroundModesSet containsObject:kBackgroundFetchCapatibility]) { 223 NSLog(@"You've implemented -[<UIApplicationDelegate> " 224 @"application:performFetchWithCompletionHandler:], but you still need to add \"fetch\" " 225 @"to the list of your supported UIBackgroundModes in your Info.plist."); 226 } 227 } 228} 229 230@end 231