| 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
5 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h"
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"
10 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterCallbackCache_Internal.h"
11 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h"
12 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h"
13
14 FLUTTER_ASSERT_ARC
15
16 static const char* kCallbackCacheSubDir = "Library/Caches/";
17
18 static const SEL kSelectorsHandledByPlugins[] = {
19 @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:),
20 @selector(application:performFetchWithCompletionHandler:)};
21
22 @interface FlutterPluginAppLifeCycleDelegate ()
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()]];
53 if (FlutterSharedApplication.isAvailable) {
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
102 return FlutterSharedApplication.hasSceneDelegate;
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
FlutterCallbackCache_Internal.h
FlutterPluginAppLifeCycleDelegate.h
kSelectorsHandledByPlugins
static const SEL kSelectorsHandledByPlugins[]
Definition: FlutterPluginAppLifeCycleDelegate.mm:18
_delegates
NSPointerArray * _delegates
Definition: FlutterPluginAppLifeCycleDelegate.mm:38
kCallbackCacheSubDir
static FLUTTER_ASSERT_ARC const char * kCallbackCacheSubDir
Definition: FlutterPluginAppLifeCycleDelegate.mm:16
FlutterSceneLifeCycle_Internal.h
FlutterSharedApplication.h
FlutterCallbackCache
Definition: FlutterCallbackCache.h:38
+[FlutterCallbackCache setCachePath:]
void setCachePath:(NSString *path)
FlutterPluginAppLifeCycleDelegate
Definition: FlutterPluginAppLifeCycleDelegate.h:16
FlutterSharedApplication
Definition: FlutterSharedApplication.h:13
FlutterSharedApplication::application
UIApplication * application
Definition: FlutterSharedApplication.h:27
FlutterSharedApplication::hasSceneDelegate
BOOL hasSceneDelegate
Definition: FlutterSharedApplication.h:32
FlutterSharedApplication::isAvailable
BOOL isAvailable
Definition: FlutterSharedApplication.h:22
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
FlutterApplicationLifeCycleDelegate-p
Definition: FlutterPlugin.h:26