Flutter iOS Embedder
FlutterPluginAppLifeCycleDelegate.mm
Go to the documentation of this file.
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 
6 
7 #include "flutter/fml/paths.h"
8 #include "flutter/lib/ui/plugins/callback_cache.h"
9 #import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
13 
15 
16 static const char* kCallbackCacheSubDir = "Library/Caches/";
17 
18 static const SEL kSelectorsHandledByPlugins[] = {
19  @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:),
20  @selector(application:performFetchWithCompletionHandler:)};
21 
23 - (void)handleDidEnterBackground:(NSNotification*)notification
24  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
25 - (void)handleWillEnterForeground:(NSNotification*)notification
26  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
27 - (void)handleWillResignActive:(NSNotification*)notification
28  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
29 - (void)handleDidBecomeActive:(NSNotification*)notification
30  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
31 - (void)handleWillTerminate:(NSNotification*)notification
32  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions");
33 
34 @property(nonatomic, assign) BOOL didForwardApplicationWillLaunch;
35 @property(nonatomic, assign) BOOL didForwardApplicationDidLaunch;
36 @end
37 
38 @implementation FlutterPluginAppLifeCycleDelegate {
39  UIBackgroundTaskIdentifier _debugBackgroundTask;
40 
41  // Weak references to registered plugins.
42  NSPointerArray* _delegates;
43 }
44 
45 - (void)addObserverFor:(NSString*)name selector:(SEL)selector {
46  [[NSNotificationCenter defaultCenter] addObserver:self selector:selector name:name object:nil];
47 }
48 
49 - (instancetype)init {
50  if (self = [super init]) {
51  std::string cachePath = fml::paths::JoinPaths({getenv("HOME"), kCallbackCacheSubDir});
52  [FlutterCallbackCache setCachePath:[NSString stringWithUTF8String:cachePath.c_str()]];
54  [self addObserverFor:UIApplicationDidEnterBackgroundNotification
55  selector:@selector(handleDidEnterBackground:)];
56  [self addObserverFor:UIApplicationWillEnterForegroundNotification
57  selector:@selector(handleWillEnterForeground:)];
58  [self addObserverFor:UIApplicationWillResignActiveNotification
59  selector:@selector(handleWillResignActive:)];
60  [self addObserverFor:UIApplicationDidBecomeActiveNotification
61  selector:@selector(handleDidBecomeActive:)];
62  [self addObserverFor:UIApplicationWillTerminateNotification
63  selector:@selector(handleWillTerminate:)];
64  }
65  _delegates = [NSPointerArray weakObjectsPointerArray];
66  _debugBackgroundTask = UIBackgroundTaskInvalid;
67  }
68  return self;
69 }
70 
71 static BOOL IsPowerOfTwo(NSUInteger x) {
72  return x != 0 && (x & (x - 1)) == 0;
73 }
74 
75 - (BOOL)isSelectorAddedDynamically:(SEL)selector {
76  for (const SEL& aSelector : kSelectorsHandledByPlugins) {
77  if (selector == aSelector) {
78  return YES;
79  }
80  }
81  return NO;
82 }
83 
84 - (BOOL)hasPluginThatRespondsToSelector:(SEL)selector {
85  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
86  if (!delegate) {
87  continue;
88  }
89  if ([delegate respondsToSelector:selector]) {
90  return YES;
91  }
92  }
93  return NO;
94 }
95 
96 - (BOOL)appSupportsSceneLifecycle {
97  // When UIScene lifecycle is being used, some application lifecycle events are not call by UIKit.
98  // However, the notifications are still sent. When a Flutter app has been migrated to UIScene,
99  // Flutter should not use the notifications to forward application events to plugins since they
100  // are not expected to be called.
101  // See https://flutter.cn/go/ios-ui-scene-lifecycle-migration?tab=t.0#heading=h.eq8gyd4ds50u
103 }
104 
105 - (BOOL)pluginSupportsSceneLifecycle:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate {
106  // The fallback is unnecessary if the plugin conforms to FlutterSceneLifeCycleDelegate.
107  // This means that the plugin has migrated to scene lifecycle events and shouldn't require
108  // application events. However, the plugin may still have the application event implemented to
109  // maintain compatibility with un-migrated apps, which is why the fallback should be checked
110  // before checking that the delegate responds to the selector.
111  return [delegate conformsToProtocol:@protocol(FlutterSceneLifeCycleDelegate)];
112 }
113 
114 - (void)addDelegate:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate {
115  [_delegates addPointer:(__bridge void*)delegate];
116  if (IsPowerOfTwo([_delegates count])) {
117  [_delegates compact];
118  }
119 }
120 
121 - (void)sceneFallbackDidFinishLaunchingApplication:(UIApplication*)application {
122  // If the application:didFinishingLaunchingWithOptions: event has already been sent to plugins, do
123  // not send again.
124  if (self.didForwardApplicationDidLaunch) {
125  return;
126  }
127  // Send nil launchOptions since UIKit sends nil when UIScene is enabled.
128  [self application:application didFinishLaunchingWithOptions:@{}];
129 }
130 
131 - (void)sceneFallbackWillFinishLaunchingApplication:(UIApplication*)application {
132  // If the application:willFinishLaunchingWithOptions: event has already been sent to plugins, do
133  // not send again.
134  if (self.didForwardApplicationWillLaunch) {
135  return;
136  }
137  // If the application:didFinishingLaunchingWithOptions: event has already been sent to plugins, do
138  // not send willFinishLaunchingWithOptions since it should happen before, not after.
139  if (self.didForwardApplicationDidLaunch) {
140  return;
141  }
142  // Send nil launchOptions since UIKit sends nil when UIScene is enabled.
143  [self application:application willFinishLaunchingWithOptions:@{}];
144 }
145 
146 - (BOOL)application:(UIApplication*)application
147  didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
148  if (_delegates.count > 0) {
149  self.didForwardApplicationDidLaunch = YES;
150  }
151  return [self application:application
152  didFinishLaunchingWithOptions:launchOptions
153  isFallbackForScene:NO];
154 }
155 
156 - (BOOL)sceneWillConnectFallback:(UISceneConnectionOptions*)connectionOptions {
157  UIApplication* application = FlutterSharedApplication.application;
158  if (!application) {
159  return NO;
160  }
161  NSDictionary<UIApplicationLaunchOptionsKey, id>* convertedLaunchOptions =
162  ConvertConnectionOptions(connectionOptions);
163  if (convertedLaunchOptions.count == 0) {
164  // Only use fallback if there are meaningful launch options.
165  return NO;
166  }
167  if (![self application:application
168  didFinishLaunchingWithOptions:convertedLaunchOptions
169  isFallbackForScene:YES]) {
170  return YES;
171  }
172  return NO;
173 }
174 
175 - (BOOL)application:(UIApplication*)application
176  didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
177  isFallbackForScene:(BOOL)isFallback {
178  // Use a snapshot of the delegates to allow plugins to add or remove themselves
179  // during the notification loop without causing a mutation crash.
180  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
181  if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
182  continue;
183  }
184  if ([delegate respondsToSelector:@selector(application:didFinishLaunchingWithOptions:)]) {
185  if (![delegate application:application didFinishLaunchingWithOptions:launchOptions]) {
186  return NO;
187  }
188  }
189  }
190  return YES;
191 }
192 
193 /* Makes a best attempt to convert UISceneConnectionOptions from the scene event
194  * (`scene:willConnectToSession:options:`) to a NSDictionary of options used to the application
195  * lifecycle event.
196  *
197  * For more information on UISceneConnectionOptions, see
198  * https://developer.apple.com/documentation/uikit/uiscene/connectionoptions.
199  *
200  * For information about the possible keys in the NSDictionary and how to handle them, see
201  * https://developer.apple.com/documentation/uikit/uiapplication/launchoptionskey
202  */
203 static NSDictionary<UIApplicationLaunchOptionsKey, id>* ConvertConnectionOptions(
204  UISceneConnectionOptions* connectionOptions) {
205  NSMutableDictionary<UIApplicationLaunchOptionsKey, id>* convertedOptions =
206  [NSMutableDictionary dictionary];
207 
208  if (connectionOptions.shortcutItem) {
209  convertedOptions[UIApplicationLaunchOptionsShortcutItemKey] = connectionOptions.shortcutItem;
210  }
211  if (connectionOptions.sourceApplication) {
212  convertedOptions[UIApplicationLaunchOptionsSourceApplicationKey] =
213  connectionOptions.sourceApplication;
214  }
215  if (connectionOptions.URLContexts.anyObject.URL) {
216  convertedOptions[UIApplicationLaunchOptionsURLKey] =
217  connectionOptions.URLContexts.anyObject.URL;
218  }
219  return convertedOptions;
220 }
221 
222 - (BOOL)application:(UIApplication*)application
223  willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
224  if (_delegates.count > 0) {
225  self.didForwardApplicationWillLaunch = YES;
226  }
227  flutter::DartCallbackCache::LoadCacheFromDisk();
228  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
229  if (!delegate) {
230  continue;
231  }
232  if ([delegate respondsToSelector:_cmd]) {
233  if (![delegate application:application willFinishLaunchingWithOptions:launchOptions]) {
234  return NO;
235  }
236  }
237  }
238  return YES;
239 }
240 
241 - (void)handleDidEnterBackground:(NSNotification*)notification
242  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
243  if ([self appSupportsSceneLifecycle]) {
244  return;
245  }
246  UIApplication* application = [UIApplication sharedApplication];
247 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
248  // The following keeps the Flutter session alive when the device screen locks
249  // in debug mode. It allows continued use of features like hot reload and
250  // taking screenshots once the device unlocks again.
251  //
252  // Note the name is not an identifier and multiple instances can exist.
253  _debugBackgroundTask = [application
254  beginBackgroundTaskWithName:@"Flutter debug task"
255  expirationHandler:^{
256  if (_debugBackgroundTask != UIBackgroundTaskInvalid) {
257  [application endBackgroundTask:_debugBackgroundTask];
258  _debugBackgroundTask = UIBackgroundTaskInvalid;
259  }
260  [FlutterLogger
261  logWarning:@"\nThe OS has terminated the Flutter debug connection for being "
262  "inactive in the background for too long.\n\n"
263  "There are no errors with your Flutter application.\n\n"
264  "To reconnect, launch your application again via 'flutter run'"];
265  }];
266 #endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
267  [self applicationDidEnterBackground:application isFallbackForScene:NO];
268 }
269 
270 - (void)sceneDidEnterBackgroundFallback {
271  UIApplication* application = FlutterSharedApplication.application;
272  if (!application) {
273  return;
274  }
275  [self applicationDidEnterBackground:application isFallbackForScene:YES];
276 }
277 
278 - (void)applicationDidEnterBackground:(UIApplication*)application
279  isFallbackForScene:(BOOL)isFallback {
280  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
281  if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
282  continue;
283  }
284  if ([delegate respondsToSelector:@selector(applicationDidEnterBackground:)]) {
285  [delegate applicationDidEnterBackground:application];
286  }
287  }
288 }
289 
290 - (void)handleWillEnterForeground:(NSNotification*)notification
291  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
292  if ([self appSupportsSceneLifecycle]) {
293  return;
294  }
295  UIApplication* application = [UIApplication sharedApplication];
296 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
297  if (_debugBackgroundTask != UIBackgroundTaskInvalid) {
298  [application endBackgroundTask:_debugBackgroundTask];
299  _debugBackgroundTask = UIBackgroundTaskInvalid;
300  }
301 #endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
302  [self applicationWillEnterForeground:application isFallbackForScene:NO];
303 }
304 
305 - (void)sceneWillEnterForegroundFallback {
306  UIApplication* application = FlutterSharedApplication.application;
307  if (!application) {
308  return;
309  }
310  [self applicationWillEnterForeground:application isFallbackForScene:YES];
311 }
312 
313 - (void)applicationWillEnterForeground:(UIApplication*)application
314  isFallbackForScene:(BOOL)isFallback {
315  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
316  if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
317  continue;
318  }
319  if ([delegate respondsToSelector:@selector(applicationWillEnterForeground:)]) {
320  [delegate applicationWillEnterForeground:application];
321  }
322  }
323 }
324 
325 - (void)handleWillResignActive:(NSNotification*)notification
326  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
327  if ([self appSupportsSceneLifecycle]) {
328  return;
329  }
330  UIApplication* application = [UIApplication sharedApplication];
331  [self applicationWillResignActive:application isFallbackForScene:NO];
332 }
333 
334 - (void)sceneWillResignActiveFallback {
335  UIApplication* application = FlutterSharedApplication.application;
336  if (!application) {
337  return;
338  }
339  [self applicationWillResignActive:application isFallbackForScene:YES];
340 }
341 
342 - (void)applicationWillResignActive:(UIApplication*)application
343  isFallbackForScene:(BOOL)isFallback {
344  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
345  if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
346  continue;
347  }
348  if ([delegate respondsToSelector:@selector(applicationWillResignActive:)]) {
349  [delegate applicationWillResignActive:application];
350  }
351  }
352 }
353 
354 - (void)handleDidBecomeActive:(NSNotification*)notification
355  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
356  if ([self appSupportsSceneLifecycle]) {
357  return;
358  }
359  UIApplication* application = [UIApplication sharedApplication];
360  [self applicationDidBecomeActive:application isFallbackForScene:NO];
361 }
362 
363 - (void)sceneDidBecomeActiveFallback {
364  UIApplication* application = FlutterSharedApplication.application;
365  if (!application) {
366  return;
367  }
368  [self applicationDidBecomeActive:application isFallbackForScene:YES];
369 }
370 
371 - (void)applicationDidBecomeActive:(UIApplication*)application isFallbackForScene:(BOOL)isFallback {
372  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
373  if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
374  continue;
375  }
376  if ([delegate respondsToSelector:@selector(applicationDidBecomeActive:)]) {
377  [delegate applicationDidBecomeActive:application];
378  }
379  }
380 }
381 
382 - (void)handleWillTerminate:(NSNotification*)notification
383  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
384  UIApplication* application = [UIApplication sharedApplication];
385  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
386  if (!delegate) {
387  continue;
388  }
389  if ([delegate respondsToSelector:@selector(applicationWillTerminate:)]) {
390  [delegate applicationWillTerminate:application];
391  }
392  }
393 }
394 
395 #pragma GCC diagnostic push
396 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
397 - (void)application:(UIApplication*)application
398  didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
399  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
400  if (!delegate) {
401  continue;
402  }
403  if ([delegate respondsToSelector:_cmd]) {
404  [delegate application:application didRegisterUserNotificationSettings:notificationSettings];
405  }
406  }
407 }
408 #pragma GCC diagnostic pop
409 
410 - (void)application:(UIApplication*)application
411  didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
412  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
413  if (!delegate) {
414  continue;
415  }
416  if ([delegate respondsToSelector:_cmd]) {
417  [delegate application:application
418  didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
419  }
420  }
421 }
422 
423 - (void)application:(UIApplication*)application
424  didFailToRegisterForRemoteNotificationsWithError:(NSError*)error {
425  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
426  if (!delegate) {
427  continue;
428  }
429  if ([delegate respondsToSelector:_cmd]) {
430  [delegate application:application didFailToRegisterForRemoteNotificationsWithError:error];
431  }
432  }
433 }
434 
435 - (void)application:(UIApplication*)application
436  didReceiveRemoteNotification:(NSDictionary*)userInfo
437  fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
438  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
439  if (!delegate) {
440  continue;
441  }
442  if ([delegate respondsToSelector:_cmd]) {
443  if ([delegate application:application
444  didReceiveRemoteNotification:userInfo
445  fetchCompletionHandler:completionHandler]) {
446  return;
447  }
448  }
449  }
450 }
451 
452 #pragma GCC diagnostic push
453 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
454 - (void)application:(UIApplication*)application
455  didReceiveLocalNotification:(UILocalNotification*)notification {
456  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
457  if (!delegate) {
458  continue;
459  }
460  if ([delegate respondsToSelector:_cmd]) {
461  [delegate application:application didReceiveLocalNotification:notification];
462  }
463  }
464 }
465 #pragma GCC diagnostic pop
466 
467 - (void)userNotificationCenter:(UNUserNotificationCenter*)center
468  willPresentNotification:(UNNotification*)notification
469  withCompletionHandler:
470  (void (^)(UNNotificationPresentationOptions options))completionHandler {
471  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
472  if ([delegate respondsToSelector:_cmd]) {
473  [delegate userNotificationCenter:center
474  willPresentNotification:notification
475  withCompletionHandler:completionHandler];
476  }
477  }
478 }
479 
480 - (void)userNotificationCenter:(UNUserNotificationCenter*)center
481  didReceiveNotificationResponse:(UNNotificationResponse*)response
482  withCompletionHandler:(void (^)(void))completionHandler {
483  for (id<FlutterApplicationLifeCycleDelegate> delegate in _delegates.allObjects) {
484  if ([delegate respondsToSelector:_cmd]) {
485  [delegate userNotificationCenter:center
486  didReceiveNotificationResponse:response
487  withCompletionHandler:completionHandler];
488  }
489  }
490 }
491 
492 - (BOOL)application:(UIApplication*)application
493  openURL:(NSURL*)url
494  options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
495  return [self application:application openURL:url options:options isFallbackForScene:NO];
496 }
497 
498 - (BOOL)sceneFallbackOpenURLContexts:(NSSet<UIOpenURLContext*>*)URLContexts {
499  for (UIOpenURLContext* context in URLContexts) {
500  if ([self application:FlutterSharedApplication.application
501  openURL:context.URL
502  options:ConvertOptions(context.options)
503  isFallbackForScene:YES]) {
504  return YES;
505  };
506  };
507  return NO;
508 }
509 
510 - (BOOL)application:(UIApplication*)application
511  openURL:(NSURL*)url
512  options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options
513  isFallbackForScene:(BOOL)isFallback {
514  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
515  if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
516  continue;
517  }
518  if ([delegate respondsToSelector:@selector(application:openURL:options:)]) {
519  if ([delegate application:application openURL:url options:options]) {
520  return YES;
521  }
522  }
523  }
524  return NO;
525 }
526 
527 /* Converts UISceneOpenURLOptions from the scene event (`sceneFallbackOpenURLContexts`) to a
528  * NSDictionary of options used to the application lifecycle event.
529  *
530  * For more information on UISceneOpenURLOptions, see
531  * https://developer.apple.com/documentation/uikit/uiopenurlcontext/options.
532  *
533  * For information about the possible keys in the NSDictionary and how to handle them, see
534  * https://developer.apple.com/documentation/uikit/uiapplication/openurloptionskey
535  */
536 static NSDictionary<UIApplicationOpenURLOptionsKey, id>* ConvertOptions(
537  UISceneOpenURLOptions* options) {
538  NSMutableDictionary<UIApplicationOpenURLOptionsKey, id>* convertedOptions =
539  [NSMutableDictionary dictionary];
540  if (options.sourceApplication) {
541  convertedOptions[UIApplicationOpenURLOptionsSourceApplicationKey] = options.sourceApplication;
542  }
543  if (options.annotation) {
544  convertedOptions[UIApplicationOpenURLOptionsAnnotationKey] = options.annotation;
545  }
546  convertedOptions[UIApplicationOpenURLOptionsOpenInPlaceKey] = @(options.openInPlace);
547  if (@available(iOS 14.5, *)) {
548  if (options.eventAttribution) {
549  convertedOptions[UIApplicationOpenURLOptionsEventAttributionKey] = options.eventAttribution;
550  }
551  }
552  return convertedOptions;
553 }
554 
555 - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
556  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
557  if (!delegate) {
558  continue;
559  }
560  if ([delegate respondsToSelector:_cmd]) {
561  if ([delegate application:application handleOpenURL:url]) {
562  return YES;
563  }
564  }
565  }
566  return NO;
567 }
568 
569 - (BOOL)application:(UIApplication*)application
570  openURL:(NSURL*)url
571  sourceApplication:(NSString*)sourceApplication
572  annotation:(id)annotation {
573  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
574  if (!delegate) {
575  continue;
576  }
577  if ([delegate respondsToSelector:_cmd]) {
578  if ([delegate application:application
579  openURL:url
580  sourceApplication:sourceApplication
581  annotation:annotation]) {
582  return YES;
583  }
584  }
585  }
586  return NO;
587 }
588 
589 - (void)application:(UIApplication*)application
590  performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
591  completionHandler:(void (^)(BOOL succeeded))completionHandler {
592  [self application:application
593  performActionForShortcutItem:shortcutItem
594  completionHandler:completionHandler
595  isFallbackForScene:NO];
596 }
597 
598 - (BOOL)sceneFallbackPerformActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
599  completionHandler:(void (^)(BOOL succeeded))completionHandler {
600  UIApplication* application = FlutterSharedApplication.application;
601  if (!application) {
602  return NO;
603  }
604  return [self application:application
605  performActionForShortcutItem:shortcutItem
606  completionHandler:completionHandler
607  isFallbackForScene:YES];
608 }
609 
610 - (BOOL)application:(UIApplication*)application
611  performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
612  completionHandler:(void (^)(BOOL succeeded))completionHandler
613  isFallbackForScene:(BOOL)isFallback {
614  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
615  if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
616  continue;
617  }
618  if ([delegate respondsToSelector:@selector(application:
619  performActionForShortcutItem:completionHandler:)]) {
620  if ([delegate application:application
621  performActionForShortcutItem:shortcutItem
622  completionHandler:completionHandler]) {
623  return YES;
624  }
625  }
626  }
627  return NO;
628 }
629 
630 - (BOOL)application:(UIApplication*)application
631  handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
632  completionHandler:(nonnull void (^)())completionHandler {
633  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
634  if (!delegate) {
635  continue;
636  }
637  if ([delegate respondsToSelector:_cmd]) {
638  if ([delegate application:application
639  handleEventsForBackgroundURLSession:identifier
640  completionHandler:completionHandler]) {
641  return YES;
642  }
643  }
644  }
645  return NO;
646 }
647 
648 - (BOOL)application:(UIApplication*)application
649  performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
650  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
651  if (!delegate) {
652  continue;
653  }
654  if ([delegate respondsToSelector:_cmd]) {
655  if ([delegate application:application performFetchWithCompletionHandler:completionHandler]) {
656  return YES;
657  }
658  }
659  }
660  return NO;
661 }
662 
663 - (BOOL)application:(UIApplication*)application
664  continueUserActivity:(NSUserActivity*)userActivity
665  restorationHandler:(void (^)(NSArray*))restorationHandler {
666  return [self application:application
667  continueUserActivity:userActivity
668  restorationHandler:restorationHandler
669  isFallbackForScene:NO];
670 }
671 
672 - (BOOL)sceneFallbackContinueUserActivity:(NSUserActivity*)userActivity {
673  UIApplication* application = FlutterSharedApplication.application;
674  if (!application) {
675  return NO;
676  }
677  return [self application:application
678  continueUserActivity:userActivity
679  restorationHandler:nil
680  isFallbackForScene:YES];
681 }
682 
683 - (BOOL)application:(UIApplication*)application
684  continueUserActivity:(NSUserActivity*)userActivity
685  restorationHandler:(void (^)(NSArray*))restorationHandler
686  isFallbackForScene:(BOOL)isFallback {
687  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates.allObjects) {
688  if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
689  continue;
690  }
691  if ([delegate respondsToSelector:@selector(application:
692  continueUserActivity:restorationHandler:)]) {
693  if ([delegate application:application
694  continueUserActivity:userActivity
695  restorationHandler:restorationHandler]) {
696  return YES;
697  }
698  }
699  }
700  return NO;
701 }
702 @end
static const SEL kSelectorsHandledByPlugins[]
NSPointerArray * _delegates
static FLUTTER_ASSERT_ARC const char * kCallbackCacheSubDir
void setCachePath:(NSString *path)