• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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