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