blob: a50924e1661ce63588d5f77f2bea77195b14b93e [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h"
#import "flutter/fml/logging.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h"
static NSString* const kUIBackgroundMode = @"UIBackgroundModes";
static NSString* const kRemoteNotificationCapabitiliy = @"remote-notification";
static NSString* const kBackgroundFetchCapatibility = @"fetch";
static NSString* const kRestorationStateAppModificationKey = @"mod-date";
@interface FlutterAppDelegate ()
@property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void);
@end
@implementation FlutterAppDelegate {
FlutterPluginAppLifeCycleDelegate* _lifeCycleDelegate;
}
- (instancetype)init {
if (self = [super init]) {
_lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init];
}
return self;
}
- (void)dealloc {
[_lifeCycleDelegate release];
[_rootFlutterViewControllerGetter release];
[_window release];
[super dealloc];
}
- (BOOL)application:(UIApplication*)application
willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
return [_lifeCycleDelegate application:application willFinishLaunchingWithOptions:launchOptions];
}
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions];
}
// Returns the key window's rootViewController, if it's a FlutterViewController.
// Otherwise, returns nil.
- (FlutterViewController*)rootFlutterViewController {
if (_rootFlutterViewControllerGetter != nil) {
return _rootFlutterViewControllerGetter();
}
UIViewController* rootViewController = _window.rootViewController;
if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
return (FlutterViewController*)rootViewController;
}
return nil;
}
// Do not remove, some clients may be calling these via `super`.
- (void)applicationDidEnterBackground:(UIApplication*)application {
}
// Do not remove, some clients may be calling these via `super`.
- (void)applicationWillEnterForeground:(UIApplication*)application {
}
// Do not remove, some clients may be calling these via `super`.
- (void)applicationWillResignActive:(UIApplication*)application {
}
// Do not remove, some clients may be calling these via `super`.
- (void)applicationDidBecomeActive:(UIApplication*)application {
}
// Do not remove, some clients may be calling these via `super`.
- (void)applicationWillTerminate:(UIApplication*)application {
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
- (void)application:(UIApplication*)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
[_lifeCycleDelegate application:application
didRegisterUserNotificationSettings:notificationSettings];
}
#pragma GCC diagnostic pop
- (void)application:(UIApplication*)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
[_lifeCycleDelegate application:application
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication*)application
didFailToRegisterForRemoteNotificationsWithError:(NSError*)error {
[_lifeCycleDelegate application:application
didFailToRegisterForRemoteNotificationsWithError:error];
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
- (void)application:(UIApplication*)application
didReceiveLocalNotification:(UILocalNotification*)notification {
[_lifeCycleDelegate application:application didReceiveLocalNotification:notification];
}
#pragma GCC diagnostic pop
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
willPresentNotification:(UNNotification*)notification
withCompletionHandler:
(void (^)(UNNotificationPresentationOptions options))completionHandler
NS_AVAILABLE_IOS(10_0) {
if (@available(iOS 10.0, *)) {
if ([_lifeCycleDelegate respondsToSelector:_cmd]) {
[_lifeCycleDelegate userNotificationCenter:center
willPresentNotification:notification
withCompletionHandler:completionHandler];
}
}
}
/**
* Calls all plugins registered for `UNUserNotificationCenterDelegate` callbacks.
*/
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
didReceiveNotificationResponse:(UNNotificationResponse*)response
withCompletionHandler:(void (^)(void))completionHandler NS_AVAILABLE_IOS(10_0) {
if (@available(iOS 10.0, *)) {
if ([_lifeCycleDelegate respondsToSelector:_cmd]) {
[_lifeCycleDelegate userNotificationCenter:center
didReceiveNotificationResponse:response
withCompletionHandler:completionHandler];
}
}
}
- (BOOL)openURL:(NSURL*)url {
NSNumber* isDeepLinkingEnabled =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"];
if (!isDeepLinkingEnabled.boolValue) {
// Not set or NO.
return NO;
} else {
FlutterViewController* flutterViewController = [self rootFlutterViewController];
if (flutterViewController) {
[flutterViewController.engine
waitForFirstFrame:3.0
callback:^(BOOL didTimeout) {
if (didTimeout) {
FML_LOG(ERROR)
<< "Timeout waiting for the first frame when launching an URL.";
} else {
NSString* fullRoute = url.path;
if ([url.query length] != 0) {
fullRoute = [NSString stringWithFormat:@"%@?%@", fullRoute, url.query];
}
if ([url.fragment length] != 0) {
fullRoute = [NSString stringWithFormat:@"%@#%@", fullRoute, url.fragment];
}
[flutterViewController.engine.navigationChannel invokeMethod:@"pushRoute"
arguments:fullRoute];
}
}];
return YES;
} else {
FML_LOG(ERROR) << "Attempting to open an URL without a Flutter RootViewController.";
return NO;
}
}
}
- (BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
if ([_lifeCycleDelegate application:application openURL:url options:options]) {
return YES;
}
return [self openURL:url];
}
- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
return [_lifeCycleDelegate application:application handleOpenURL:url];
}
- (BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
sourceApplication:(NSString*)sourceApplication
annotation:(id)annotation {
return [_lifeCycleDelegate application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation];
}
- (void)application:(UIApplication*)application
performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) {
[_lifeCycleDelegate application:application
performActionForShortcutItem:shortcutItem
completionHandler:completionHandler];
}
- (void)application:(UIApplication*)application
handleEventsForBackgroundURLSession:(nonnull NSString*)identifier
completionHandler:(nonnull void (^)())completionHandler {
[_lifeCycleDelegate application:application
handleEventsForBackgroundURLSession:identifier
completionHandler:completionHandler];
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000
- (BOOL)application:(UIApplication*)application
continueUserActivity:(NSUserActivity*)userActivity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>>* __nullable
restorableObjects))restorationHandler {
#else
- (BOOL)application:(UIApplication*)application
continueUserActivity:(NSUserActivity*)userActivity
restorationHandler:(void (^)(NSArray* __nullable restorableObjects))restorationHandler {
#endif
if ([_lifeCycleDelegate application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler]) {
return YES;
}
return [self openURL:userActivity.webpageURL];
}
#pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController
- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
if (flutterRootViewController) {
return [[flutterRootViewController pluginRegistry] registrarForPlugin:pluginKey];
}
return nil;
}
- (BOOL)hasPlugin:(NSString*)pluginKey {
FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
if (flutterRootViewController) {
return [[flutterRootViewController pluginRegistry] hasPlugin:pluginKey];
}
return false;
}
- (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey {
FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
if (flutterRootViewController) {
return [[flutterRootViewController pluginRegistry] valuePublishedByPlugin:pluginKey];
}
return nil;
}
#pragma mark - Selectors handling
- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate {
[_lifeCycleDelegate addDelegate:delegate];
}
#pragma mark - UIApplicationDelegate method dynamic implementation
- (BOOL)respondsToSelector:(SEL)selector {
if ([_lifeCycleDelegate isSelectorAddedDynamically:selector]) {
return [self delegateRespondsSelectorToPlugins:selector];
}
return [super respondsToSelector:selector];
}
- (BOOL)delegateRespondsSelectorToPlugins:(SEL)selector {
if ([_lifeCycleDelegate hasPluginThatRespondsToSelector:selector]) {
return [_lifeCycleDelegate respondsToSelector:selector];
} else {
return NO;
}
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([_lifeCycleDelegate isSelectorAddedDynamically:aSelector]) {
[self logCapabilityConfigurationWarningIfNeeded:aSelector];
return _lifeCycleDelegate;
}
return [super forwardingTargetForSelector:aSelector];
}
// Mimic the logging from Apple when the capability is not set for the selectors.
// However the difference is that Apple logs these message when the app launches, we only
// log it when the method is invoked. We can possibly also log it when the app launches, but
// it will cause an additional scan over all the plugins.
- (void)logCapabilityConfigurationWarningIfNeeded:(SEL)selector {
NSArray* backgroundModesArray =
[[NSBundle mainBundle] objectForInfoDictionaryKey:kUIBackgroundMode];
NSSet* backgroundModesSet = [[[NSSet alloc] initWithArray:backgroundModesArray] autorelease];
if (selector == @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)) {
if (![backgroundModesSet containsObject:kRemoteNotificationCapabitiliy]) {
NSLog(
@"You've implemented -[<UIApplicationDelegate> "
@"application:didReceiveRemoteNotification:fetchCompletionHandler:], but you still need "
@"to add \"remote-notification\" to the list of your supported UIBackgroundModes in your "
@"Info.plist.");
}
} else if (selector == @selector(application:performFetchWithCompletionHandler:)) {
if (![backgroundModesSet containsObject:kBackgroundFetchCapatibility]) {
NSLog(@"You've implemented -[<UIApplicationDelegate> "
@"application:performFetchWithCompletionHandler:], but you still need to add \"fetch\" "
@"to the list of your supported UIBackgroundModes in your Info.plist.");
}
}
}
#pragma mark - State Restoration
- (BOOL)application:(UIApplication*)application shouldSaveApplicationState:(NSCoder*)coder {
[coder encodeInt64:self.lastAppModificationTime forKey:kRestorationStateAppModificationKey];
return YES;
}
- (BOOL)application:(UIApplication*)application shouldRestoreApplicationState:(NSCoder*)coder {
int64_t stateDate = [coder decodeInt64ForKey:kRestorationStateAppModificationKey];
return self.lastAppModificationTime == stateDate;
}
- (BOOL)application:(UIApplication*)application shouldSaveSecureApplicationState:(NSCoder*)coder {
[coder encodeInt64:self.lastAppModificationTime forKey:kRestorationStateAppModificationKey];
return YES;
}
- (BOOL)application:(UIApplication*)application
shouldRestoreSecureApplicationState:(NSCoder*)coder {
int64_t stateDate = [coder decodeInt64ForKey:kRestorationStateAppModificationKey];
return self.lastAppModificationTime == stateDate;
}
- (int64_t)lastAppModificationTime {
NSDate* fileDate;
NSError* error = nil;
[[[NSBundle mainBundle] executableURL] getResourceValue:&fileDate
forKey:NSURLContentModificationDateKey
error:&error];
NSAssert(error == nil, @"Cannot obtain modification date of main bundle: %@", error);
return [fileDate timeIntervalSince1970];
}
@end