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.dev/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  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
179  if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
180  continue;
181  }
182  if ([delegate respondsToSelector:@selector(application:didFinishLaunchingWithOptions:)]) {
183  if (![delegate application:application didFinishLaunchingWithOptions:launchOptions]) {
184  return NO;
185  }
186  }
187  }
188  return YES;
189 }
190 
191 /* Makes a best attempt to convert UISceneConnectionOptions from the scene event
192  * (`scene:willConnectToSession:options:`) to a NSDictionary of options used to the application
193  * lifecycle event.
194  *
195  * For more information on UISceneConnectionOptions, see
196  * https://developer.apple.com/documentation/uikit/uiscene/connectionoptions.
197  *
198  * For information about the possible keys in the NSDictionary and how to handle them, see
199  * https://developer.apple.com/documentation/uikit/uiapplication/launchoptionskey
200  */
201 static NSDictionary<UIApplicationLaunchOptionsKey, id>* ConvertConnectionOptions(
202  UISceneConnectionOptions* connectionOptions) {
203  NSMutableDictionary<UIApplicationLaunchOptionsKey, id>* convertedOptions =
204  [NSMutableDictionary dictionary];
205 
206  if (connectionOptions.shortcutItem) {
207  convertedOptions[UIApplicationLaunchOptionsShortcutItemKey] = connectionOptions.shortcutItem;
208  }
209  if (connectionOptions.sourceApplication) {
210  convertedOptions[UIApplicationLaunchOptionsSourceApplicationKey] =
211  connectionOptions.sourceApplication;
212  }
213  if (connectionOptions.URLContexts.anyObject.URL) {
214  convertedOptions[UIApplicationLaunchOptionsURLKey] =
215  connectionOptions.URLContexts.anyObject.URL;
216  }
217  return convertedOptions;
218 }
219 
220 - (BOOL)application:(UIApplication*)application
221  willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
222  if (_delegates.count > 0) {
223  self.didForwardApplicationWillLaunch = YES;
224  }
225  flutter::DartCallbackCache::LoadCacheFromDisk();
226  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in [_delegates allObjects]) {
227  if (!delegate) {
228  continue;
229  }
230  if ([delegate respondsToSelector:_cmd]) {
231  if (![delegate application:application willFinishLaunchingWithOptions:launchOptions]) {
232  return NO;
233  }
234  }
235  }
236  return YES;
237 }
238 
239 - (void)handleDidEnterBackground:(NSNotification*)notification
240  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
241  if ([self appSupportsSceneLifecycle]) {
242  return;
243  }
244  UIApplication* application = [UIApplication sharedApplication];
245 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
246  // The following keeps the Flutter session alive when the device screen locks
247  // in debug mode. It allows continued use of features like hot reload and
248  // taking screenshots once the device unlocks again.
249  //
250  // Note the name is not an identifier and multiple instances can exist.
251  _debugBackgroundTask = [application
252  beginBackgroundTaskWithName:@"Flutter debug task"
253  expirationHandler:^{
254  if (_debugBackgroundTask != UIBackgroundTaskInvalid) {
255  [application endBackgroundTask:_debugBackgroundTask];
256  _debugBackgroundTask = UIBackgroundTaskInvalid;
257  }
258  [FlutterLogger
259  logWarning:@"\nThe OS has terminated the Flutter debug connection for being "
260  "inactive in the background for too long.\n\n"
261  "There are no errors with your Flutter application.\n\n"
262  "To reconnect, launch your application again via 'flutter run'"];
263  }];
264 #endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
265  [self applicationDidEnterBackground:application isFallbackForScene:NO];
266 }
267 
268 - (void)sceneDidEnterBackgroundFallback {
269  UIApplication* application = FlutterSharedApplication.application;
270  if (!application) {
271  return;
272  }
273  [self applicationDidEnterBackground:application isFallbackForScene:YES];
274 }
275 
276 - (void)applicationDidEnterBackground:(UIApplication*)application
277  isFallbackForScene:(BOOL)isFallback {
278  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
279  if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
280  continue;
281  }
282  if ([delegate respondsToSelector:@selector(applicationDidEnterBackground:)]) {
283  [delegate applicationDidEnterBackground:application];
284  }
285  }
286 }
287 
288 - (void)handleWillEnterForeground:(NSNotification*)notification
289  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
290  if ([self appSupportsSceneLifecycle]) {
291  return;
292  }
293  UIApplication* application = [UIApplication sharedApplication];
294 #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
295  if (_debugBackgroundTask != UIBackgroundTaskInvalid) {
296  [application endBackgroundTask:_debugBackgroundTask];
297  _debugBackgroundTask = UIBackgroundTaskInvalid;
298  }
299 #endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
300  [self applicationWillEnterForeground:application isFallbackForScene:NO];
301 }
302 
303 - (void)sceneWillEnterForegroundFallback {
304  UIApplication* application = FlutterSharedApplication.application;
305  if (!application) {
306  return;
307  }
308  [self applicationWillEnterForeground:application isFallbackForScene:YES];
309 }
310 
311 - (void)applicationWillEnterForeground:(UIApplication*)application
312  isFallbackForScene:(BOOL)isFallback {
313  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
314  if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
315  continue;
316  }
317  if ([delegate respondsToSelector:@selector(applicationWillEnterForeground:)]) {
318  [delegate applicationWillEnterForeground:application];
319  }
320  }
321 }
322 
323 - (void)handleWillResignActive:(NSNotification*)notification
324  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
325  if ([self appSupportsSceneLifecycle]) {
326  return;
327  }
328  UIApplication* application = [UIApplication sharedApplication];
329  [self applicationWillResignActive:application isFallbackForScene:NO];
330 }
331 
332 - (void)sceneWillResignActiveFallback {
333  UIApplication* application = FlutterSharedApplication.application;
334  if (!application) {
335  return;
336  }
337  [self applicationWillResignActive:application isFallbackForScene:YES];
338 }
339 
340 - (void)applicationWillResignActive:(UIApplication*)application
341  isFallbackForScene:(BOOL)isFallback {
342  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
343  if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
344  continue;
345  }
346  if ([delegate respondsToSelector:@selector(applicationWillResignActive:)]) {
347  [delegate applicationWillResignActive:application];
348  }
349  }
350 }
351 
352 - (void)handleDidBecomeActive:(NSNotification*)notification
353  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
354  if ([self appSupportsSceneLifecycle]) {
355  return;
356  }
357  UIApplication* application = [UIApplication sharedApplication];
358  [self applicationDidBecomeActive:application isFallbackForScene:NO];
359 }
360 
361 - (void)sceneDidBecomeActiveFallback {
362  UIApplication* application = FlutterSharedApplication.application;
363  if (!application) {
364  return;
365  }
366  [self applicationDidBecomeActive:application isFallbackForScene:YES];
367 }
368 
369 - (void)applicationDidBecomeActive:(UIApplication*)application isFallbackForScene:(BOOL)isFallback {
370  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
371  if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
372  continue;
373  }
374  if ([delegate respondsToSelector:@selector(applicationDidBecomeActive:)]) {
375  [delegate applicationDidBecomeActive:application];
376  }
377  }
378 }
379 
380 - (void)handleWillTerminate:(NSNotification*)notification
381  NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") {
382  UIApplication* application = [UIApplication sharedApplication];
383  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
384  if (!delegate) {
385  continue;
386  }
387  if ([delegate respondsToSelector:@selector(applicationWillTerminate:)]) {
388  [delegate applicationWillTerminate:application];
389  }
390  }
391 }
392 
393 #pragma GCC diagnostic push
394 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
395 - (void)application:(UIApplication*)application
396  didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
397  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
398  if (!delegate) {
399  continue;
400  }
401  if ([delegate respondsToSelector:_cmd]) {
402  [delegate application:application didRegisterUserNotificationSettings:notificationSettings];
403  }
404  }
405 }
406 #pragma GCC diagnostic pop
407 
408 - (void)application:(UIApplication*)application
409  didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
410  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
411  if (!delegate) {
412  continue;
413  }
414  if ([delegate respondsToSelector:_cmd]) {
415  [delegate application:application
416  didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
417  }
418  }
419 }
420 
421 - (void)application:(UIApplication*)application
422  didFailToRegisterForRemoteNotificationsWithError:(NSError*)error {
423  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
424  if (!delegate) {
425  continue;
426  }
427  if ([delegate respondsToSelector:_cmd]) {
428  [delegate application:application didFailToRegisterForRemoteNotificationsWithError:error];
429  }
430  }
431 }
432 
433 - (void)application:(UIApplication*)application
434  didReceiveRemoteNotification:(NSDictionary*)userInfo
435  fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
436  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
437  if (!delegate) {
438  continue;
439  }
440  if ([delegate respondsToSelector:_cmd]) {
441  if ([delegate application:application
442  didReceiveRemoteNotification:userInfo
443  fetchCompletionHandler:completionHandler]) {
444  return;
445  }
446  }
447  }
448 }
449 
450 #pragma GCC diagnostic push
451 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
452 - (void)application:(UIApplication*)application
453  didReceiveLocalNotification:(UILocalNotification*)notification {
454  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
455  if (!delegate) {
456  continue;
457  }
458  if ([delegate respondsToSelector:_cmd]) {
459  [delegate application:application didReceiveLocalNotification:notification];
460  }
461  }
462 }
463 #pragma GCC diagnostic pop
464 
465 - (void)userNotificationCenter:(UNUserNotificationCenter*)center
466  willPresentNotification:(UNNotification*)notification
467  withCompletionHandler:
468  (void (^)(UNNotificationPresentationOptions options))completionHandler {
469  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
470  if ([delegate respondsToSelector:_cmd]) {
471  [delegate userNotificationCenter:center
472  willPresentNotification:notification
473  withCompletionHandler:completionHandler];
474  }
475  }
476 }
477 
478 - (void)userNotificationCenter:(UNUserNotificationCenter*)center
479  didReceiveNotificationResponse:(UNNotificationResponse*)response
480  withCompletionHandler:(void (^)(void))completionHandler {
481  for (id<FlutterApplicationLifeCycleDelegate> delegate in _delegates) {
482  if ([delegate respondsToSelector:_cmd]) {
483  [delegate userNotificationCenter:center
484  didReceiveNotificationResponse:response
485  withCompletionHandler:completionHandler];
486  }
487  }
488 }
489 
490 - (BOOL)application:(UIApplication*)application
491  openURL:(NSURL*)url
492  options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
493  return [self application:application openURL:url options:options isFallbackForScene:NO];
494 }
495 
496 - (BOOL)sceneFallbackOpenURLContexts:(NSSet<UIOpenURLContext*>*)URLContexts {
497  for (UIOpenURLContext* context in URLContexts) {
498  if ([self application:FlutterSharedApplication.application
499  openURL:context.URL
500  options:ConvertOptions(context.options)
501  isFallbackForScene:YES]) {
502  return YES;
503  };
504  };
505  return NO;
506 }
507 
508 - (BOOL)application:(UIApplication*)application
509  openURL:(NSURL*)url
510  options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options
511  isFallbackForScene:(BOOL)isFallback {
512  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
513  if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
514  continue;
515  }
516  if ([delegate respondsToSelector:@selector(application:openURL:options:)]) {
517  if ([delegate application:application openURL:url options:options]) {
518  return YES;
519  }
520  }
521  }
522  return NO;
523 }
524 
525 /* Converts UISceneOpenURLOptions from the scene event (`sceneFallbackOpenURLContexts`) to a
526  * NSDictionary of options used to the application lifecycle event.
527  *
528  * For more information on UISceneOpenURLOptions, see
529  * https://developer.apple.com/documentation/uikit/uiopenurlcontext/options.
530  *
531  * For information about the possible keys in the NSDictionary and how to handle them, see
532  * https://developer.apple.com/documentation/uikit/uiapplication/openurloptionskey
533  */
534 static NSDictionary<UIApplicationOpenURLOptionsKey, id>* ConvertOptions(
535  UISceneOpenURLOptions* options) {
536  NSMutableDictionary<UIApplicationOpenURLOptionsKey, id>* convertedOptions =
537  [NSMutableDictionary dictionary];
538  if (options.sourceApplication) {
539  convertedOptions[UIApplicationOpenURLOptionsSourceApplicationKey] = options.sourceApplication;
540  }
541  if (options.annotation) {
542  convertedOptions[UIApplicationOpenURLOptionsAnnotationKey] = options.annotation;
543  }
544  convertedOptions[UIApplicationOpenURLOptionsOpenInPlaceKey] = @(options.openInPlace);
545  if (@available(iOS 14.5, *)) {
546  if (options.eventAttribution) {
547  convertedOptions[UIApplicationOpenURLOptionsEventAttributionKey] = options.eventAttribution;
548  }
549  }
550  return convertedOptions;
551 }
552 
553 - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
554  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
555  if (!delegate) {
556  continue;
557  }
558  if ([delegate respondsToSelector:_cmd]) {
559  if ([delegate application:application handleOpenURL:url]) {
560  return YES;
561  }
562  }
563  }
564  return NO;
565 }
566 
567 - (BOOL)application:(UIApplication*)application
568  openURL:(NSURL*)url
569  sourceApplication:(NSString*)sourceApplication
570  annotation:(id)annotation {
571  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
572  if (!delegate) {
573  continue;
574  }
575  if ([delegate respondsToSelector:_cmd]) {
576  if ([delegate application:application
577  openURL:url
578  sourceApplication:sourceApplication
579  annotation:annotation]) {
580  return YES;
581  }
582  }
583  }
584  return NO;
585 }
586 
587 - (void)application:(UIApplication*)application
588  performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
589  completionHandler:(void (^)(BOOL succeeded))completionHandler {
590  [self application:application
591  performActionForShortcutItem:shortcutItem
592  completionHandler:completionHandler
593  isFallbackForScene:NO];
594 }
595 
596 - (BOOL)sceneFallbackPerformActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
597  completionHandler:(void (^)(BOOL succeeded))completionHandler {
598  UIApplication* application = FlutterSharedApplication.application;
599  if (!application) {
600  return NO;
601  }
602  return [self application:application
603  performActionForShortcutItem:shortcutItem
604  completionHandler:completionHandler
605  isFallbackForScene:YES];
606 }
607 
608 - (BOOL)application:(UIApplication*)application
609  performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
610  completionHandler:(void (^)(BOOL succeeded))completionHandler
611  isFallbackForScene:(BOOL)isFallback {
612  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
613  if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
614  continue;
615  }
616  if ([delegate respondsToSelector:@selector(application:
617  performActionForShortcutItem:completionHandler:)]) {
618  if ([delegate application:application
619  performActionForShortcutItem:shortcutItem
620  completionHandler:completionHandler]) {
621  return YES;
622  }
623  }
624  }
625  return NO;
626 }
627 
628 - (BOOL)application:(UIApplication*)application
629  handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
630  completionHandler:(nonnull void (^)())completionHandler {
631  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
632  if (!delegate) {
633  continue;
634  }
635  if ([delegate respondsToSelector:_cmd]) {
636  if ([delegate application:application
637  handleEventsForBackgroundURLSession:identifier
638  completionHandler:completionHandler]) {
639  return YES;
640  }
641  }
642  }
643  return NO;
644 }
645 
646 - (BOOL)application:(UIApplication*)application
647  performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
648  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
649  if (!delegate) {
650  continue;
651  }
652  if ([delegate respondsToSelector:_cmd]) {
653  if ([delegate application:application performFetchWithCompletionHandler:completionHandler]) {
654  return YES;
655  }
656  }
657  }
658  return NO;
659 }
660 
661 - (BOOL)application:(UIApplication*)application
662  continueUserActivity:(NSUserActivity*)userActivity
663  restorationHandler:(void (^)(NSArray*))restorationHandler {
664  return [self application:application
665  continueUserActivity:userActivity
666  restorationHandler:restorationHandler
667  isFallbackForScene:NO];
668 }
669 
670 - (BOOL)sceneFallbackContinueUserActivity:(NSUserActivity*)userActivity {
671  UIApplication* application = FlutterSharedApplication.application;
672  if (!application) {
673  return NO;
674  }
675  return [self application:application
676  continueUserActivity:userActivity
677  restorationHandler:nil
678  isFallbackForScene:YES];
679 }
680 
681 - (BOOL)application:(UIApplication*)application
682  continueUserActivity:(NSUserActivity*)userActivity
683  restorationHandler:(void (^)(NSArray*))restorationHandler
684  isFallbackForScene:(BOOL)isFallback {
685  for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
686  if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) {
687  continue;
688  }
689  if ([delegate respondsToSelector:@selector(application:
690  continueUserActivity:restorationHandler:)]) {
691  if ([delegate application:application
692  continueUserActivity:userActivity
693  restorationHandler:restorationHandler]) {
694  return YES;
695  }
696  }
697  }
698  return NO;
699 }
700 @end
static const SEL kSelectorsHandledByPlugins[]
NSPointerArray * _delegates
static FLUTTER_ASSERT_ARC const char * kCallbackCacheSubDir
void setCachePath:(NSString *path)