diff --git a/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.h b/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.h index 569b551cfa951..e9c9f323ccb6a 100644 --- a/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.h +++ b/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.h @@ -9,12 +9,14 @@ NS_ASSUME_NONNULL_BEGIN -// Finds a bundle with the named `bundleID` within `searchURL`. +extern const NSString* kDefaultAssetPath; + +// Finds a bundle with the named `flutterFrameworkBundleID` within `searchURL`. // // Returns `nil` if the bundle cannot be found or if errors are encountered. -NSBundle* FLTFrameworkBundleInternal(NSString* bundleID, NSURL* searchURL); +NSBundle* FLTFrameworkBundleInternal(NSString* flutterFrameworkBundleID, NSURL* searchURL); -// Finds a bundle with the named `bundleID`. +// Finds a bundle with the named `flutterFrameworkBundleID`. // // `+[NSBundle bundleWithIdentifier:]` is slow, and can take in the order of // tens of milliseconds in a minimal flutter app, and closer to 100 milliseconds @@ -28,7 +30,25 @@ NSBundle* FLTFrameworkBundleInternal(NSString* bundleID, NSURL* searchURL); // frameworks used by this file are placed. If the desired bundle cannot be // found here, the implementation falls back to // `+[NSBundle bundleWithIdentifier:]`. -NSBundle* FLTFrameworkBundleWithIdentifier(NSString* bundleID); +NSBundle* FLTFrameworkBundleWithIdentifier(NSString* flutterFrameworkBundleID); + +// Finds the bundle of the application. +// +// Returns [NSBundle mainBundle] if the current running process is the application. +NSBundle* FLTGetApplicationBundle(); + +// Gets the flutter assets path directory from `bundle`. +// +// Returns `kDefaultAssetPath` if unable to find asset path from info.plist in `bundle`. +NSString* FLTAssetPath(NSBundle* bundle); + +// Finds the Flutter asset directory from `bundle`. +// +// The raw path can be set by the application via info.plist's `FLTAssetsPath` key. +// If the key is not set, `flutter_assets` is used as the raw path value. +// +// If no valid asset is found under the raw path, returns nil. +NSURL* FLTAssetsURLFromBundle(NSBundle* bundle); NS_ASSUME_NONNULL_END diff --git a/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.mm b/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.mm index ab403f9b8d52b..bf0002d9ade34 100644 --- a/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.mm +++ b/shell/platform/darwin/common/framework/Source/FlutterNSBundleUtils.mm @@ -8,7 +8,9 @@ FLUTTER_ASSERT_ARC -NSBundle* FLTFrameworkBundleInternal(NSString* bundleID, NSURL* searchURL) { +const NSString* kDefaultAssetPath = @"Frameworks/App.framework/flutter_assets"; + +NSBundle* FLTFrameworkBundleInternal(NSString* flutterFrameworkBundleID, NSURL* searchURL) { NSDirectoryEnumerator* frameworkEnumerator = [NSFileManager.defaultManager enumeratorAtURL:searchURL includingPropertiesForKeys:nil @@ -18,19 +20,49 @@ errorHandler:nil]; for (NSURL* candidate in frameworkEnumerator) { - NSBundle* bundle = [NSBundle bundleWithURL:candidate]; - if ([bundle.bundleIdentifier isEqualToString:bundleID]) { - return bundle; + NSBundle* flutterFrameworkBundle = [NSBundle bundleWithURL:candidate]; + if ([flutterFrameworkBundle.bundleIdentifier isEqualToString:flutterFrameworkBundleID]) { + return flutterFrameworkBundle; } } return nil; } -NSBundle* FLTFrameworkBundleWithIdentifier(NSString* bundleID) { - NSBundle* bundle = FLTFrameworkBundleInternal(bundleID, NSBundle.mainBundle.privateFrameworksURL); - if (bundle != nil) { - return bundle; +NSBundle* FLTGetApplicationBundle() { + NSBundle* mainBundle = [NSBundle mainBundle]; + // App extension bundle is in .app/PlugIns/Extension.appex. + if ([mainBundle.bundleURL.pathExtension isEqualToString:@"appex"]) { + // Up two levels. + return [NSBundle bundleWithURL:mainBundle.bundleURL.URLByDeletingLastPathComponent + .URLByDeletingLastPathComponent]; + } + return mainBundle; +} + +NSBundle* FLTFrameworkBundleWithIdentifier(NSString* flutterFrameworkBundleID) { + NSBundle* appBundle = FLTGetApplicationBundle(); + NSBundle* flutterFrameworkBundle = + FLTFrameworkBundleInternal(flutterFrameworkBundleID, appBundle.privateFrameworksURL); + if (flutterFrameworkBundle == nil) { + // Fallback to slow implementation. + flutterFrameworkBundle = [NSBundle bundleWithIdentifier:flutterFrameworkBundleID]; + } + if (flutterFrameworkBundle == nil) { + flutterFrameworkBundle = [NSBundle mainBundle]; + } + return flutterFrameworkBundle; +} + +NSString* FLTAssetPath(NSBundle* bundle) { + return [bundle objectForInfoDictionaryKey:@"FLTAssetsPath"] ?: kDefaultAssetPath; +} + +NSURL* FLTAssetsURLFromBundle(NSBundle* bundle) { + NSString* flutterAssetsPath = FLTAssetPath(bundle); + NSURL* assets = [bundle URLForResource:flutterAssetsPath withExtension:nil]; + + if (!assets) { + assets = [[NSBundle mainBundle] URLForResource:flutterAssetsPath withExtension:nil]; } - // Fallback to slow implementation. - return [NSBundle bundleWithIdentifier:bundleID]; + return assets; } diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 8aa967ae6ca55..f726f41f23a95 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -348,6 +348,10 @@ shared_library("create_flutter_framework_dylib") { ldflags = [ "-Wl,-install_name,@rpath/Flutter.framework/Flutter" ] + if (darwin_extension_safe) { + ldflags += [ "-fapplication-extension" ] + } + public = _flutter_framework_headers deps = [ @@ -438,7 +442,10 @@ copy("copy_license") { shared_library("copy_and_verify_framework_module") { framework_search_path = rebase_path("$root_out_dir") visibility = [ ":*" ] - cflags_objc = [ "-F$framework_search_path" ] + cflags_objc = [ + "-F$framework_search_path", + "-fapplication-extension", + ] sources = [ "framework/Source/FlutterUmbrellaImport.m" ] deps = [ @@ -446,6 +453,17 @@ shared_library("copy_and_verify_framework_module") { ":copy_framework_info_plist", ":copy_framework_module_map", ] + + if (darwin_extension_safe) { + ldflags = [ + "-F$framework_search_path", + "-fapplication-extension", + "-Xlinker", + "-fatal_warnings", + ] + deps += [ ":copy_dylib" ] + frameworks = [ "Flutter.framework" ] + } } group("universal_flutter_framework") { diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h b/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h index b270414900692..524e68293c95a 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h @@ -353,7 +353,8 @@ typedef enum { * * @param delegate The receiving object, such as the plugin's main class. */ -- (void)addApplicationDelegate:(NSObject*)delegate; +- (void)addApplicationDelegate:(NSObject*)delegate + NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in plugins used in app extensions"); /** * Returns the file name for the given asset. diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm index 4873102e7222f..3d434eb2179f2 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm @@ -41,16 +41,13 @@ // 3. Settings from the NSBundle with the default bundle ID. // 4. Settings from the main NSBundle and default values. - NSBundle* mainBundle = [NSBundle mainBundle]; + NSBundle* mainBundle = FLTGetApplicationBundle(); NSBundle* engineBundle = [NSBundle bundleForClass:[FlutterViewController class]]; bool hasExplicitBundle = bundle != nil; if (bundle == nil) { bundle = FLTFrameworkBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier]); } - if (bundle == nil) { - bundle = mainBundle; - } auto settings = flutter::SettingsFromCommandLine(command_line); @@ -122,29 +119,24 @@ // Checks to see if the flutter assets directory is already present. if (settings.assets_path.empty()) { - NSString* assetsName = [FlutterDartProject flutterAssetsName:bundle]; - NSString* assetsPath = [bundle pathForResource:assetsName ofType:@""]; - - if (assetsPath.length == 0) { - assetsPath = [mainBundle pathForResource:assetsName ofType:@""]; - } + NSURL* assetsURL = FLTAssetsURLFromBundle(bundle); - if (assetsPath.length == 0) { - NSLog(@"Failed to find assets path for \"%@\"", assetsName); + if (!assetsURL) { + NSLog(@"Failed to find assets path for \"%@\"", bundle); } else { - settings.assets_path = assetsPath.UTF8String; + settings.assets_path = assetsURL.path.UTF8String; // Check if there is an application kernel snapshot in the assets directory we could // potentially use. Looking for the snapshot makes sense only if we have a VM that can use // it. if (!flutter::DartVM::IsRunningPrecompiledCode()) { NSURL* applicationKernelSnapshotURL = - [NSURL URLWithString:@(kApplicationKernelSnapshotFileName) - relativeToURL:[NSURL fileURLWithPath:assetsPath]]; - if ([[NSFileManager defaultManager] fileExistsAtPath:applicationKernelSnapshotURL.path]) { + [assetsURL URLByAppendingPathComponent:@(kApplicationKernelSnapshotFileName)]; + NSError* error; + if ([applicationKernelSnapshotURL checkResourceIsReachableAndReturnError:&error]) { settings.application_kernel_asset = applicationKernelSnapshotURL.path.UTF8String; } else { - NSLog(@"Failed to find snapshot: %@", applicationKernelSnapshotURL.path); + NSLog(@"Failed to find snapshot at %@: %@", applicationKernelSnapshotURL.path, error); } } } @@ -339,14 +331,7 @@ + (NSString*)flutterAssetsName:(NSBundle*)bundle { if (bundle == nil) { bundle = FLTFrameworkBundleWithIdentifier([FlutterDartProject defaultBundleIdentifier]); } - if (bundle == nil) { - bundle = [NSBundle mainBundle]; - } - NSString* flutterAssetsName = [bundle objectForInfoDictionaryKey:@"FLTAssetsPath"]; - if (flutterAssetsName == nil) { - flutterAssetsName = @"Frameworks/App.framework/flutter_assets"; - } - return flutterAssetsName; + return FLTAssetPath(bundle); } + (NSString*)domainNetworkPolicy:(NSDictionary*)appTransportSecurity { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm index 7e180603a0a1a..a28aaf8beeab9 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProjectTest.mm @@ -73,6 +73,50 @@ - (void)testFLTFrameworkBundleInternalWhenBundleIsPresent { XCTAssertNotNil(found); } +- (void)testFLTGetApplicationBundleWhenCurrentTargetIsNotExtension { + NSBundle* bundle = FLTGetApplicationBundle(); + XCTAssertEqual(bundle, [NSBundle mainBundle]); +} + +- (void)testFLTGetApplicationBundleWhenCurrentTargetIsExtension { + id mockMainBundle = OCMPartialMock([NSBundle mainBundle]); + NSURL* url = [[NSBundle mainBundle].bundleURL URLByAppendingPathComponent:@"foo/ext.appex"]; + OCMStub([mockMainBundle bundleURL]).andReturn(url); + NSBundle* bundle = FLTGetApplicationBundle(); + [mockMainBundle stopMocking]; + XCTAssertEqualObjects(bundle.bundleURL, [NSBundle mainBundle].bundleURL); +} + +- (void)testFLTAssetsURLFromBundle { + { + // Found asset path in info.plist (even not reachable) + id mockBundle = OCMClassMock([NSBundle class]); + OCMStub([mockBundle objectForInfoDictionaryKey:@"FLTAssetsPath"]).andReturn(@"foo/assets"); + NSURL* mockAssetsURL = OCMClassMock([NSURL class]); + OCMStub([mockBundle URLForResource:@"foo/assets" withExtension:nil]).andReturn(mockAssetsURL); + OCMStub([mockAssetsURL checkResourceIsReachableAndReturnError:NULL]).andReturn(NO); + OCMStub([mockAssetsURL path]).andReturn(@"foo/assets"); + NSURL* url = FLTAssetsURLFromBundle(mockBundle); + XCTAssertEqualObjects(url.path, @"foo/assets"); + } + { + // No asset path in info.plist, defaults to main bundle + id mockBundle = OCMClassMock([NSBundle class]); + id mockMainBundle = OCMPartialMock([NSBundle mainBundle]); + NSURL* mockAssetsURL = OCMClassMock([NSURL class]); + OCMStub([mockBundle URLForResource:@"Frameworks/App.framework/flutter_assets" + withExtension:nil]) + .andReturn(nil); + OCMStub([mockAssetsURL checkResourceIsReachableAndReturnError:NULL]).andReturn(NO); + OCMStub([mockAssetsURL path]).andReturn(@"path/to/foo/assets"); + OCMStub([mockMainBundle URLForResource:@"Frameworks/App.framework/flutter_assets" + withExtension:nil]) + .andReturn(mockAssetsURL); + NSURL* url = FLTAssetsURLFromBundle(mockBundle); + XCTAssertEqualObjects(url.path, @"path/to/foo/assets"); + } +} + - (void)testDisableImpellerSettingIsCorrectlyParsed { id mockMainBundle = OCMPartialMock([NSBundle mainBundle]); OCMStub([mockMainBundle objectForInfoDictionaryKey:@"FLTEnableImpeller"]).andReturn(@"NO"); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index b300714f166ad..df4bd038036a5 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -1515,7 +1515,8 @@ - (void)addMethodCallDelegate:(NSObject*)delegate }]; } -- (void)addApplicationDelegate:(NSObject*)delegate { +- (void)addApplicationDelegate:(NSObject*)delegate + NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in plugins used in app extensions") { id appDelegate = [[UIApplication sharedApplication] delegate]; if ([appDelegate conformsToProtocol:@protocol(FlutterAppLifeCycleProvider)]) { id lifeCycleProvider = diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm index ef454d0d44012..722571bda0a03 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.mm @@ -17,7 +17,10 @@ constexpr char kTextPlainFormat[] = "text/plain"; const UInt32 kKeyPressClickSoundId = 1306; + +#if not APPLICATION_EXTENSION_API_ONLY const NSString* searchURLPrefix = @"x-web-search://?"; +#endif } // namespace @@ -37,6 +40,24 @@ using namespace flutter; +static void SetStatusBarHiddenForSharedApplication(BOOL hidden) { +#if APPLICATION_EXTENSION_API_ONLY + [UIApplication sharedApplication].statusBarHidden = hidden; +#else + FML_LOG(WARNING) << "Application based status bar styling is not available in app extension."; +#endif +} + +static void SetStatusBarStyleForSharedApplication(UIStatusBarStyle style) { +#if APPLICATION_EXTENSION_API_ONLY + // Note: -[UIApplication setStatusBarStyle] is deprecated in iOS9 + // in favor of delegating to the view controller. + [[UIApplication sharedApplication] setStatusBarStyle:style]; +#else + FML_LOG(WARNING) << "Application based status bar styling is not available in app extension."; +#endif +} + @interface FlutterPlatformPlugin () /** @@ -141,6 +162,9 @@ - (void)showShareViewController:(NSString*)content { } - (void)searchWeb:(NSString*)searchTerm { +#if APPLICATION_EXTENSION_API_ONLY + FML_LOG(WARNING) << "SearchWeb.invoke is not availabe in app extension."; +#else NSString* escapedText = [searchTerm stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLHostAllowedCharacterSet]]; @@ -149,6 +173,7 @@ - (void)searchWeb:(NSString*)searchTerm { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:searchURL] options:@{} completionHandler:nil]; +#endif } - (void)playSystemSound:(NSString*)soundType { @@ -231,7 +256,7 @@ - (void)setSystemChromeEnabledSystemUIOverlays:(NSArray*)overlays { // We opt out of view controller based status bar visibility since we want // to be able to modify this on the fly. The key used is // UIViewControllerBasedStatusBarAppearance. - [UIApplication sharedApplication].statusBarHidden = statusBarShouldBeHidden; + SetStatusBarHiddenForSharedApplication(statusBarShouldBeHidden); } } @@ -246,7 +271,7 @@ - (void)setSystemChromeEnabledSystemUIMode:(NSString*)mode { // We opt out of view controller based status bar visibility since we want // to be able to modify this on the fly. The key used is // UIViewControllerBasedStatusBarAppearance. - [UIApplication sharedApplication].statusBarHidden = !edgeToEdge; + SetStatusBarHiddenForSharedApplication(!edgeToEdge); } [[NSNotificationCenter defaultCenter] postNotificationName:edgeToEdge ? FlutterViewControllerShowHomeIndicator @@ -284,9 +309,7 @@ - (void)setSystemChromeSystemUIOverlayStyle:(NSDictionary*)message { object:nil userInfo:@{@(kOverlayStyleUpdateNotificationKey) : @(statusBarStyle)}]; } else { - // Note: -[UIApplication setStatusBarStyle] is deprecated in iOS9 - // in favor of delegating to the view controller. - [[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle]; + SetStatusBarStyleForSharedApplication(statusBarStyle); } } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm index 3e19665973e96..05764102a8e67 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformPluginTest.mm @@ -50,9 +50,11 @@ - (void)testSearchWebInvokedWithEscapedTerm { FlutterResult result = ^(id result) { OCMVerify([mockPlugin searchWeb:@"Testing Word!"]); +#if not APPLICATION_EXTENSION_API_ONLY OCMVerify([mockApplication openURL:[NSURL URLWithString:@"x-web-search://?Testing%20Word!"] options:@{} completionHandler:nil]); +#endif [invokeExpectation fulfill]; }; @@ -82,9 +84,11 @@ - (void)testSearchWebInvokedWithNonEscapedTerm { FlutterResult result = ^(id result) { OCMVerify([mockPlugin searchWeb:@"Test"]); +#if not APPLICATION_EXTENSION_API_ONLY OCMVerify([mockApplication openURL:[NSURL URLWithString:@"x-web-search://?Test"] options:@{} completionHandler:nil]); +#endif [invokeExpectation fulfill]; }; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm b/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm index d493f30d11feb..def41bbca43cc 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm @@ -17,11 +17,16 @@ @selector(application:performFetchWithCompletionHandler:)}; @interface FlutterPluginAppLifeCycleDelegate () -- (void)handleDidEnterBackground:(NSNotification*)notification; -- (void)handleWillEnterForeground:(NSNotification*)notification; -- (void)handleWillResignActive:(NSNotification*)notification; -- (void)handleDidBecomeActive:(NSNotification*)notification; -- (void)handleWillTerminate:(NSNotification*)notification; +- (void)handleDidEnterBackground:(NSNotification*)notification + NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions"); +- (void)handleWillEnterForeground:(NSNotification*)notification + NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions"); +- (void)handleWillResignActive:(NSNotification*)notification + NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions"); +- (void)handleDidBecomeActive:(NSNotification*)notification + NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions"); +- (void)handleWillTerminate:(NSNotification*)notification + NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions"); @end @implementation FlutterPluginAppLifeCycleDelegate { @@ -46,6 +51,7 @@ - (instancetype)init { _notificationUnsubscribers = [[NSMutableArray alloc] init]; std::string cachePath = fml::paths::JoinPaths({getenv("HOME"), kCallbackCacheSubDir}); [FlutterCallbackCache setCachePath:[NSString stringWithUTF8String:cachePath.c_str()]]; +#if not APPLICATION_EXTENSION_API_ONLY [self addObserverFor:UIApplicationDidEnterBackgroundNotification selector:@selector(handleDidEnterBackground:)]; [self addObserverFor:UIApplicationWillEnterForegroundNotification @@ -56,6 +62,7 @@ - (instancetype)init { selector:@selector(handleDidBecomeActive:)]; [self addObserverFor:UIApplicationWillTerminateNotification selector:@selector(handleWillTerminate:)]; +#endif _delegates = [[NSPointerArray weakObjectsPointerArray] retain]; _debugBackgroundTask = UIBackgroundTaskInvalid; } @@ -134,7 +141,8 @@ - (BOOL)application:(UIApplication*)application return YES; } -- (void)handleDidEnterBackground:(NSNotification*)notification { +- (void)handleDidEnterBackground:(NSNotification*)notification + NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") { UIApplication* application = [UIApplication sharedApplication]; #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG // The following keeps the Flutter session alive when the device screen locks @@ -166,7 +174,8 @@ - (void)handleDidEnterBackground:(NSNotification*)notification { } } -- (void)handleWillEnterForeground:(NSNotification*)notification { +- (void)handleWillEnterForeground:(NSNotification*)notification + NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") { UIApplication* application = [UIApplication sharedApplication]; #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG if (_debugBackgroundTask != UIBackgroundTaskInvalid) { @@ -184,7 +193,8 @@ - (void)handleWillEnterForeground:(NSNotification*)notification { } } -- (void)handleWillResignActive:(NSNotification*)notification { +- (void)handleWillResignActive:(NSNotification*)notification + NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") { UIApplication* application = [UIApplication sharedApplication]; for (NSObject* delegate in _delegates) { if (!delegate) { @@ -196,7 +206,8 @@ - (void)handleWillResignActive:(NSNotification*)notification { } } -- (void)handleDidBecomeActive:(NSNotification*)notification { +- (void)handleDidBecomeActive:(NSNotification*)notification + NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") { UIApplication* application = [UIApplication sharedApplication]; for (NSObject* delegate in _delegates) { if (!delegate) { @@ -208,7 +219,8 @@ - (void)handleDidBecomeActive:(NSNotification*)notification { } } -- (void)handleWillTerminate:(NSNotification*)notification { +- (void)handleWillTerminate:(NSNotification*)notification + NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions") { UIApplication* application = [UIApplication sharedApplication]; for (NSObject* delegate in _delegates) { if (!delegate) { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm index 9891f52cf8c0c..9fcc645575f29 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm @@ -20,6 +20,7 @@ - (void)testCreate { XCTAssertNotNil(delegate); } +#if not APPLICATION_EXTENSION_API_ONLY - (void)testDidEnterBackground { XCTNSNotificationExpectation* expectation = [[XCTNSNotificationExpectation alloc] initWithName:UIApplicationDidEnterBackgroundNotification]; @@ -88,5 +89,6 @@ - (void)testWillTerminate { [self waitForExpectations:@[ expectation ] timeout:5.0]; OCMVerify([plugin applicationWillTerminate:[UIApplication sharedApplication]]); } +#endif @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index f5a647195648f..f78ca85f79ee4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1973,7 +1973,7 @@ - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences { #endif [self requestGeometryUpdateForWindowScenes:scenes]; } else { - UIInterfaceOrientationMask currentInterfaceOrientation; + UIInterfaceOrientationMask currentInterfaceOrientation = 0; if (@available(iOS 13.0, *)) { UIWindowScene* windowScene = [self flutterWindowSceneIfViewLoaded]; if (!windowScene) { @@ -1983,7 +1983,13 @@ - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences { } currentInterfaceOrientation = 1 << windowScene.interfaceOrientation; } else { +#if APPLICATION_EXTENSION_API_ONLY + FML_LOG(ERROR) << "Application based status bar orentiation update is not supported in " + "app extension. Orientation: " + << currentInterfaceOrientation; +#else currentInterfaceOrientation = 1 << [[UIApplication sharedApplication] statusBarOrientation]; +#endif } if (!(_orientationPreferences & currentInterfaceOrientation)) { [UIViewController attemptRotationToDeviceOrientation]; @@ -2108,6 +2114,10 @@ - (void)onUserSettingsChanged:(NSNotification*)notification { } - (CGFloat)textScaleFactor { +#if APPLICATION_EXTENSION_API_ONLY + FML_LOG(WARNING) << "Dynamic content size update is not supported in app extension."; + return 1.0; +#else UIContentSizeCategory category = [UIApplication sharedApplication].preferredContentSizeCategory; // The delta is computed by approximating Apple's typography guidelines: // https://developer.apple.com/ios/human-interface-guidelines/visual-design/typography/ @@ -2158,6 +2168,7 @@ - (CGFloat)textScaleFactor { } else { return 1.0; } +#endif } - (BOOL)isAlwaysUse24HourFormat {