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