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/FlutterPluginAppLifeCycleDelegate.h" 6#include "flutter/fml/logging.h" 7#include "flutter/fml/paths.h" 8#include "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" 9 10static const char* kCallbackCacheSubDir = "Library/Caches/"; 11 12static const SEL selectorsHandledByPlugins[] = { 13 @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:), 14 @selector(application:performFetchWithCompletionHandler:)}; 15 16@interface FlutterPluginAppLifeCycleDelegate () 17- (void)handleDidEnterBackground:(NSNotification*)notification; 18- (void)handleWillEnterForeground:(NSNotification*)notification; 19- (void)handleWillResignActive:(NSNotification*)notification; 20- (void)handleDidBecomeActive:(NSNotification*)notification; 21- (void)handleWillTerminate:(NSNotification*)notification; 22@end 23 24@implementation FlutterPluginAppLifeCycleDelegate { 25 NSMutableArray* _notificationUnsubscribers; 26 UIBackgroundTaskIdentifier _debugBackgroundTask; 27 28 // Weak references to registered plugins. 29 NSPointerArray* _delegates; 30} 31 32- (void)addObserverFor:(NSString*)name selector:(SEL)selector { 33 [[NSNotificationCenter defaultCenter] addObserver:self selector:selector name:name object:nil]; 34 __block NSObject* blockSelf = self; 35 dispatch_block_t unsubscribe = ^{ 36 [[NSNotificationCenter defaultCenter] removeObserver:blockSelf name:name object:nil]; 37 }; 38 [_notificationUnsubscribers addObject:[[unsubscribe copy] autorelease]]; 39} 40 41- (instancetype)init { 42 if (self = [super init]) { 43 _notificationUnsubscribers = [[NSMutableArray alloc] init]; 44 std::string cachePath = fml::paths::JoinPaths({getenv("HOME"), kCallbackCacheSubDir}); 45 [self addObserverFor:UIApplicationDidEnterBackgroundNotification 46 selector:@selector(handleDidEnterBackground:)]; 47 [self addObserverFor:UIApplicationWillEnterForegroundNotification 48 selector:@selector(handleWillEnterForeground:)]; 49 [self addObserverFor:UIApplicationWillResignActiveNotification 50 selector:@selector(handleWillResignActive:)]; 51 [self addObserverFor:UIApplicationDidBecomeActiveNotification 52 selector:@selector(handleDidBecomeActive:)]; 53 [self addObserverFor:UIApplicationWillTerminateNotification 54 selector:@selector(handleWillTerminate:)]; 55 _delegates = [[NSPointerArray weakObjectsPointerArray] retain]; 56 } 57 return self; 58} 59 60- (void)dealloc { 61 for (dispatch_block_t unsubscribe in _notificationUnsubscribers) { 62 unsubscribe(); 63 } 64 [_notificationUnsubscribers release]; 65 [_delegates release]; 66 [super dealloc]; 67} 68 69static BOOL isPowerOfTwo(NSUInteger x) { 70 return x != 0 && (x & (x - 1)) == 0; 71} 72 73- (BOOL)isSelectorAddedDynamically:(SEL)selector { 74 for (const SEL& aSelector : selectorsHandledByPlugins) { 75 if (selector == aSelector) { 76 return YES; 77 } 78 } 79 return NO; 80} 81 82- (BOOL)hasPluginThatRespondsToSelector:(SEL)selector { 83 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in [_delegates allObjects]) { 84 if (!delegate) { 85 continue; 86 } 87 if ([delegate respondsToSelector:selector]) { 88 return YES; 89 } 90 } 91 return NO; 92} 93 94- (void)addDelegate:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate { 95 [_delegates addPointer:(__bridge void*)delegate]; 96 if (isPowerOfTwo([_delegates count])) { 97 [_delegates compact]; 98 } 99} 100 101- (BOOL)application:(UIApplication*)application 102 didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { 103 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in [_delegates allObjects]) { 104 if (!delegate) { 105 continue; 106 } 107 if ([delegate respondsToSelector:_cmd]) { 108 if (![delegate application:application didFinishLaunchingWithOptions:launchOptions]) { 109 return NO; 110 } 111 } 112 } 113 return YES; 114} 115 116- (BOOL)application:(UIApplication*)application 117 willFinishLaunchingWithOptions:(NSDictionary*)launchOptions { 118 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in [_delegates allObjects]) { 119 if (!delegate) { 120 continue; 121 } 122 if ([delegate respondsToSelector:_cmd]) { 123 if (![delegate application:application willFinishLaunchingWithOptions:launchOptions]) { 124 return NO; 125 } 126 } 127 } 128 return YES; 129} 130 131- (void)handleDidEnterBackground:(NSNotification*)notification { 132 UIApplication* application = [UIApplication sharedApplication]; 133#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG 134 // The following keeps the Flutter session alive when the device screen locks 135 // in debug mode. It allows continued use of features like hot reload and 136 // taking screenshots once the device unlocks again. 137 // 138 // Note the name is not an identifier and multiple instances can exist. 139 _debugBackgroundTask = [application 140 beginBackgroundTaskWithName:@"Flutter debug task" 141 expirationHandler:^{ 142 [application endBackgroundTask:_debugBackgroundTask]; 143 FML_LOG(WARNING) 144 << "\nThe OS has terminated the Flutter debug connection for being " 145 "inactive in the background for too long.\n\n" 146 "There are no errors with your Flutter application.\n\n" 147 "To reconnect, launch your application again via 'flutter run'"; 148 }]; 149#endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG 150 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 151 if (!delegate) { 152 continue; 153 } 154 if ([delegate respondsToSelector:@selector(applicationDidEnterBackground:)]) { 155 [delegate applicationDidEnterBackground:application]; 156 } 157 } 158} 159 160- (void)handleWillEnterForeground:(NSNotification*)notification { 161 UIApplication* application = [UIApplication sharedApplication]; 162#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG 163 [application endBackgroundTask:_debugBackgroundTask]; 164#endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG 165 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 166 if (!delegate) { 167 continue; 168 } 169 if ([delegate respondsToSelector:@selector(applicationWillEnterForeground:)]) { 170 [delegate applicationWillEnterForeground:application]; 171 } 172 } 173} 174 175- (void)handleWillResignActive:(NSNotification*)notification { 176 UIApplication* application = [UIApplication sharedApplication]; 177 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 178 if (!delegate) { 179 continue; 180 } 181 if ([delegate respondsToSelector:@selector(applicationWillResignActive:)]) { 182 [delegate applicationWillResignActive:application]; 183 } 184 } 185} 186 187- (void)handleDidBecomeActive:(NSNotification*)notification { 188 UIApplication* application = [UIApplication sharedApplication]; 189 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 190 if (!delegate) { 191 continue; 192 } 193 if ([delegate respondsToSelector:@selector(applicationDidBecomeActive:)]) { 194 [delegate applicationDidBecomeActive:application]; 195 } 196 } 197} 198 199- (void)handleWillTerminate:(NSNotification*)notification { 200 UIApplication* application = [UIApplication sharedApplication]; 201 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 202 if (!delegate) { 203 continue; 204 } 205 if ([delegate respondsToSelector:@selector(applicationWillTerminate:)]) { 206 [delegate applicationWillTerminate:application]; 207 } 208 } 209} 210 211#pragma GCC diagnostic push 212#pragma GCC diagnostic ignored "-Wdeprecated-declarations" 213- (void)application:(UIApplication*)application 214 didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings { 215 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 216 if (!delegate) { 217 continue; 218 } 219 if ([delegate respondsToSelector:_cmd]) { 220 [delegate application:application didRegisterUserNotificationSettings:notificationSettings]; 221 } 222 } 223} 224#pragma GCC diagnostic pop 225 226- (void)application:(UIApplication*)application 227 didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { 228 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 229 if (!delegate) { 230 continue; 231 } 232 if ([delegate respondsToSelector:_cmd]) { 233 [delegate application:application 234 didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; 235 } 236 } 237} 238 239- (void)application:(UIApplication*)application 240 didReceiveRemoteNotification:(NSDictionary*)userInfo 241 fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { 242 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 243 if (!delegate) { 244 continue; 245 } 246 if ([delegate respondsToSelector:_cmd]) { 247 if ([delegate application:application 248 didReceiveRemoteNotification:userInfo 249 fetchCompletionHandler:completionHandler]) { 250 return; 251 } 252 } 253 } 254} 255 256#pragma GCC diagnostic push 257#pragma GCC diagnostic ignored "-Wdeprecated-declarations" 258- (void)application:(UIApplication*)application 259 didReceiveLocalNotification:(UILocalNotification*)notification { 260 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 261 if (!delegate) { 262 continue; 263 } 264 if ([delegate respondsToSelector:_cmd]) { 265 [delegate application:application didReceiveLocalNotification:notification]; 266 } 267 } 268} 269#pragma GCC diagnostic pop 270 271- (void)userNotificationCenter:(UNUserNotificationCenter*)center 272 willPresentNotification:(UNNotification*)notification 273 withCompletionHandler: 274 (void (^)(UNNotificationPresentationOptions options))completionHandler { 275 if (@available(iOS 10.0, *)) { 276 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 277 if (!delegate) { 278 continue; 279 } 280 if ([delegate respondsToSelector:_cmd]) { 281 [delegate userNotificationCenter:center 282 willPresentNotification:notification 283 withCompletionHandler:completionHandler]; 284 } 285 } 286 } 287} 288 289- (BOOL)application:(UIApplication*)application 290 openURL:(NSURL*)url 291 options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options { 292 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 293 if (!delegate) { 294 continue; 295 } 296 if ([delegate respondsToSelector:_cmd]) { 297 if ([delegate application:application openURL:url options:options]) { 298 return YES; 299 } 300 } 301 } 302 return NO; 303} 304 305- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url { 306 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 307 if (!delegate) { 308 continue; 309 } 310 if ([delegate respondsToSelector:_cmd]) { 311 if ([delegate application:application handleOpenURL:url]) { 312 return YES; 313 } 314 } 315 } 316 return NO; 317} 318 319- (BOOL)application:(UIApplication*)application 320 openURL:(NSURL*)url 321 sourceApplication:(NSString*)sourceApplication 322 annotation:(id)annotation { 323 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 324 if (!delegate) { 325 continue; 326 } 327 if ([delegate respondsToSelector:_cmd]) { 328 if ([delegate application:application 329 openURL:url 330 sourceApplication:sourceApplication 331 annotation:annotation]) { 332 return YES; 333 } 334 } 335 } 336 return NO; 337} 338 339- (void)application:(UIApplication*)application 340 performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem 341 completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) { 342 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 343 if (!delegate) { 344 continue; 345 } 346 if ([delegate respondsToSelector:_cmd]) { 347 if ([delegate application:application 348 performActionForShortcutItem:shortcutItem 349 completionHandler:completionHandler]) { 350 return; 351 } 352 } 353 } 354} 355 356- (BOOL)application:(UIApplication*)application 357 handleEventsForBackgroundURLSession:(nonnull NSString*)identifier 358 completionHandler:(nonnull void (^)())completionHandler { 359 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 360 if (!delegate) { 361 continue; 362 } 363 if ([delegate respondsToSelector:_cmd]) { 364 if ([delegate application:application 365 handleEventsForBackgroundURLSession:identifier 366 completionHandler:completionHandler]) { 367 return YES; 368 } 369 } 370 } 371 return NO; 372} 373 374- (BOOL)application:(UIApplication*)application 375 performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { 376 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 377 if (!delegate) { 378 continue; 379 } 380 if ([delegate respondsToSelector:_cmd]) { 381 if ([delegate application:application performFetchWithCompletionHandler:completionHandler]) { 382 return YES; 383 } 384 } 385 } 386 return NO; 387} 388 389- (BOOL)application:(UIApplication*)application 390 continueUserActivity:(NSUserActivity*)userActivity 391 restorationHandler:(void (^)(NSArray*))restorationHandler { 392 for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) { 393 if (!delegate) { 394 continue; 395 } 396 if ([delegate respondsToSelector:_cmd]) { 397 if ([delegate application:application 398 continueUserActivity:userActivity 399 restorationHandler:restorationHandler]) { 400 return YES; 401 } 402 } 403 } 404 return NO; 405} 406@end 407