Flutter Engine
FlutterAppDelegate.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/FlutterAppDelegate.h"
6 
7 #import "flutter/fml/logging.h"
8 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h"
9 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
10 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h"
11 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
12 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h"
13 
14 static NSString* const kUIBackgroundMode = @"UIBackgroundModes";
15 static NSString* const kRemoteNotificationCapabitiliy = @"remote-notification";
16 static NSString* const kBackgroundFetchCapatibility = @"fetch";
17 static NSString* const kRestorationStateAppModificationKey = @"mod-date";
18 
19 @interface FlutterAppDelegate ()
20 @property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void);
21 @end
22 
23 @implementation FlutterAppDelegate {
24  FlutterPluginAppLifeCycleDelegate* _lifeCycleDelegate;
25 }
26 
27 - (instancetype)init {
28  if (self = [super init]) {
29  _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
30  }
31  return self;
32 }
33 
34 - (void)dealloc {
35  [_lifeCycleDelegate release];
36  [_rootFlutterViewControllerGetter release];
37  [super dealloc];
38 }
39 
40 - (BOOL)application:(UIApplication*)application
41  willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
42  return [_lifeCycleDelegate application:application willFinishLaunchingWithOptions:launchOptions];
43 }
44 
45 - (BOOL)application:(UIApplication*)application
46  didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
47  return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions];
48 }
49 
50 // Returns the key window's rootViewController, if it's a FlutterViewController.
51 // Otherwise, returns nil.
52 - (FlutterViewController*)rootFlutterViewController {
53  if (_rootFlutterViewControllerGetter != nil) {
54  return _rootFlutterViewControllerGetter();
55  }
56  UIViewController* rootViewController = _window.rootViewController;
57  if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
58  return (FlutterViewController*)rootViewController;
59  }
60  return nil;
61 }
62 
63 // Do not remove, some clients may be calling these via `super`.
64 - (void)applicationDidEnterBackground:(UIApplication*)application {
65 }
66 
67 // Do not remove, some clients may be calling these via `super`.
68 - (void)applicationWillEnterForeground:(UIApplication*)application {
69 }
70 
71 // Do not remove, some clients may be calling these via `super`.
72 - (void)applicationWillResignActive:(UIApplication*)application {
73 }
74 
75 // Do not remove, some clients may be calling these via `super`.
76 - (void)applicationDidBecomeActive:(UIApplication*)application {
77 }
78 
79 // Do not remove, some clients may be calling these via `super`.
80 - (void)applicationWillTerminate:(UIApplication*)application {
81 }
82 
83 #pragma GCC diagnostic push
84 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
85 - (void)application:(UIApplication*)application
86  didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
87  [_lifeCycleDelegate application:application
88  didRegisterUserNotificationSettings:notificationSettings];
89 }
90 #pragma GCC diagnostic pop
91 
92 - (void)application:(UIApplication*)application
93  didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
94  [_lifeCycleDelegate application:application
95  didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
96 }
97 
98 #pragma GCC diagnostic push
99 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
100 - (void)application:(UIApplication*)application
101  didReceiveLocalNotification:(UILocalNotification*)notification {
102  [_lifeCycleDelegate application:application didReceiveLocalNotification:notification];
103 }
104 #pragma GCC diagnostic pop
105 
106 - (void)userNotificationCenter:(UNUserNotificationCenter*)center
107  willPresentNotification:(UNNotification*)notification
108  withCompletionHandler:
109  (void (^)(UNNotificationPresentationOptions options))completionHandler
110  NS_AVAILABLE_IOS(10_0) {
111  if (@available(iOS 10.0, *)) {
112  if ([_lifeCycleDelegate respondsToSelector:_cmd]) {
113  [_lifeCycleDelegate userNotificationCenter:center
114  willPresentNotification:notification
115  withCompletionHandler:completionHandler];
116  }
117  }
118 }
119 
120 /**
121  * Calls all plugins registered for `UNUserNotificationCenterDelegate` callbacks.
122  */
123 - (void)userNotificationCenter:(UNUserNotificationCenter*)center
124  didReceiveNotificationResponse:(UNNotificationResponse*)response
125  withCompletionHandler:(void (^)(void))completionHandler NS_AVAILABLE_IOS(10_0) {
126  if (@available(iOS 10.0, *)) {
127  if ([_lifeCycleDelegate respondsToSelector:_cmd]) {
128  [_lifeCycleDelegate userNotificationCenter:center
129  didReceiveNotificationResponse:response
130  withCompletionHandler:completionHandler];
131  }
132  }
133 }
134 
135 - (BOOL)openURL:(NSURL*)url {
136  NSNumber* isDeepLinkingEnabled =
137  [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"];
138  if (!isDeepLinkingEnabled.boolValue) {
139  // Not set or NO.
140  return NO;
141  } else {
142  FlutterViewController* flutterViewController = [self rootFlutterViewController];
143  if (flutterViewController) {
144  [flutterViewController.engine
145  waitForFirstFrame:3.0
146  callback:^(BOOL didTimeout) {
147  if (didTimeout) {
148  FML_LOG(ERROR)
149  << "Timeout waiting for the first frame when launching an URL.";
150  } else {
151  NSString* fullRoute = url.path;
152  if ([url.query length] != 0) {
153  fullRoute = [NSString stringWithFormat:@"%@?%@", fullRoute, url.query];
154  }
155  if ([url.fragment length] != 0) {
156  fullRoute = [NSString stringWithFormat:@"%@#%@", fullRoute, url.fragment];
157  }
158  [flutterViewController.engine.navigationChannel invokeMethod:@"pushRoute"
159  arguments:fullRoute];
160  }
161  }];
162  return YES;
163  } else {
164  FML_LOG(ERROR) << "Attempting to open an URL without a Flutter RootViewController.";
165  return NO;
166  }
167  }
168 }
169 
170 - (BOOL)application:(UIApplication*)application
171  openURL:(NSURL*)url
172  options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
173  if ([_lifeCycleDelegate application:application openURL:url options:options]) {
174  return YES;
175  }
176  return [self openURL:url];
177 }
178 
179 - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
180  return [_lifeCycleDelegate application:application handleOpenURL:url];
181 }
182 
183 - (BOOL)application:(UIApplication*)application
184  openURL:(NSURL*)url
185  sourceApplication:(NSString*)sourceApplication
186  annotation:(id)annotation {
187  return [_lifeCycleDelegate application:application
188  openURL:url
189  sourceApplication:sourceApplication
190  annotation:annotation];
191 }
192 
193 - (void)application:(UIApplication*)application
194  performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
195  completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) {
196  [_lifeCycleDelegate application:application
197  performActionForShortcutItem:shortcutItem
198  completionHandler:completionHandler];
199 }
200 
201 - (void)application:(UIApplication*)application
202  handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
203  completionHandler:(nonnull void (^)())completionHandler {
204  [_lifeCycleDelegate application:application
205  handleEventsForBackgroundURLSession:identifier
206  completionHandler:completionHandler];
207 }
208 
209 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000
210 - (BOOL)application:(UIApplication*)application
211  continueUserActivity:(NSUserActivity*)userActivity
212  restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>>* __nullable
213  restorableObjects))restorationHandler {
214 #else
215 - (BOOL)application:(UIApplication*)application
216  continueUserActivity:(NSUserActivity*)userActivity
217  restorationHandler:(void (^)(NSArray* __nullable restorableObjects))restorationHandler {
218 #endif
219  if ([_lifeCycleDelegate application:application
220  continueUserActivity:userActivity
221  restorationHandler:restorationHandler]) {
222  return YES;
223  }
224  return [self openURL:userActivity.webpageURL];
225 }
226 
227 #pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController
228 
229 - (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
230  FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
231  if (flutterRootViewController) {
232  return [[flutterRootViewController pluginRegistry] registrarForPlugin:pluginKey];
233  }
234  return nil;
235 }
236 
237 - (BOOL)hasPlugin:(NSString*)pluginKey {
238  FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
239  if (flutterRootViewController) {
240  return [[flutterRootViewController pluginRegistry] hasPlugin:pluginKey];
241  }
242  return false;
243 }
244 
245 - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
246  FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
247  if (flutterRootViewController) {
248  return [[flutterRootViewController pluginRegistry] valuePublishedByPlugin:pluginKey];
249  }
250  return nil;
251 }
252 
253 #pragma mark - Selectors handling
254 
255 - (void)addApplicationLifeCycleDelegate:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate {
256  [_lifeCycleDelegate addDelegate:delegate];
257 }
258 
259 #pragma mark - UIApplicationDelegate method dynamic implementation
260 
261 - (BOOL)respondsToSelector:(SEL)selector {
262  if ([_lifeCycleDelegate isSelectorAddedDynamically:selector]) {
263  return [self delegateRespondsSelectorToPlugins:selector];
264  }
265  return [super respondsToSelector:selector];
266 }
267 
268 - (BOOL)delegateRespondsSelectorToPlugins:(SEL)selector {
269  if ([_lifeCycleDelegate hasPluginThatRespondsToSelector:selector]) {
270  return [_lifeCycleDelegate respondsToSelector:selector];
271  } else {
272  return NO;
273  }
274 }
275 
276 - (id)forwardingTargetForSelector:(SEL)aSelector {
277  if ([_lifeCycleDelegate isSelectorAddedDynamically:aSelector]) {
278  [self logCapabilityConfigurationWarningIfNeeded:aSelector];
279  return _lifeCycleDelegate;
280  }
281  return [super forwardingTargetForSelector:aSelector];
282 }
283 
284 // Mimic the logging from Apple when the capability is not set for the selectors.
285 // However the difference is that Apple logs these message when the app launches, we only
286 // log it when the method is invoked. We can possibly also log it when the app launches, but
287 // it will cause an additional scan over all the plugins.
288 - (void)logCapabilityConfigurationWarningIfNeeded:(SEL)selector {
289  NSArray* backgroundModesArray =
290  [[NSBundle mainBundle] objectForInfoDictionaryKey:kUIBackgroundMode];
291  NSSet* backgroundModesSet = [[[NSSet alloc] initWithArray:backgroundModesArray] autorelease];
292  if (selector == @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)) {
293  if (![backgroundModesSet containsObject:kRemoteNotificationCapabitiliy]) {
294  NSLog(
295  @"You've implemented -[<UIApplicationDelegate> "
296  @"application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need "
297  @"to add \"remote-notification\" to the list of your supported UIBackgroundModes in your "
298  @"Info.plist.");
299  }
300  } else if (selector == @selector(application:performFetchWithCompletionHandler:)) {
301  if (![backgroundModesSet containsObject:kBackgroundFetchCapatibility]) {
302  NSLog(@"You've implemented -[<UIApplicationDelegate> "
303  @"application:performFetchWithCompletionHandler:], but you still need to add \"fetch\" "
304  @"to the list of your supported UIBackgroundModes in your Info.plist.");
305  }
306  }
307 }
308 
309 #pragma mark - State Restoration
310 
311 - (BOOL)application:(UIApplication*)application shouldSaveApplicationState:(NSCoder*)coder {
312  [coder encodeInt64:self.lastAppModificationTime forKey:kRestorationStateAppModificationKey];
313  return YES;
314 }
315 
316 - (BOOL)application:(UIApplication*)application shouldRestoreApplicationState:(NSCoder*)coder {
317  int64_t stateDate = [coder decodeInt64ForKey:kRestorationStateAppModificationKey];
318  return self.lastAppModificationTime == stateDate;
319 }
320 
321 - (BOOL)application:(UIApplication*)application shouldSaveSecureApplicationState:(NSCoder*)coder {
322  [coder encodeInt64:self.lastAppModificationTime forKey:kRestorationStateAppModificationKey];
323  return YES;
324 }
325 
326 - (BOOL)application:(UIApplication*)application
327  shouldRestoreSecureApplicationState:(NSCoder*)coder {
328  int64_t stateDate = [coder decodeInt64ForKey:kRestorationStateAppModificationKey];
329  return self.lastAppModificationTime == stateDate;
330 }
331 
332 - (int64_t)lastAppModificationTime {
333  NSDate* fileDate;
334  NSError* error = nil;
335  [[[NSBundle mainBundle] executableURL] getResourceValue:&fileDate
336  forKey:NSURLContentModificationDateKey
337  error:&error];
338  NSAssert(error == nil, @"Cannot obtain modification date of main bundle: %@", error);
339  return [fileDate timeIntervalSince1970];
340 }
341 
342 @end
const uint8_t uint32_t uint32_t GError ** error
id< FlutterPluginRegistry > pluginRegistry()
#define FML_LOG(severity)
Definition: logging.h:65
static NSString *const kRemoteNotificationCapabitiliy
static NSString *const kRestorationStateAppModificationKey
static NSString *const kBackgroundFetchCapatibility
int BOOL
Definition: windows_types.h:37
FlutterViewController *(^ rootFlutterViewControllerGetter)(void)
int32_t id
static NSString *const kUIBackgroundMode