From 7d4dc9f200c17cb79cbe9d452649158f7bd8f816 Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Tue, 31 May 2016 16:24:02 -0700 Subject: [PATCH] System chrome platform service updates 1) Add ability to specify a system UI overlay style, to provide a hook into the style of the status bar icons on iOS. 2) Migrate the Activity service's task description API to the system chrome platform service. The old API will be removed once the Flutter repo is updated to use the new API after an engine roll. --- .../platform/ios/system_chrome_impl.h | 10 +++ .../platform/ios/system_chrome_impl.mm | 57 +++++++++++++++-- .../domokit/platform/SystemChromeImpl.java | 35 +++++++++++ sky/services/platform/system_chrome.mojom | 62 +++++++++++++++++-- .../framework/Source/FlutterViewController.mm | 44 +++++++++++-- 5 files changed, 193 insertions(+), 15 deletions(-) diff --git a/sky/services/platform/ios/system_chrome_impl.h b/sky/services/platform/ios/system_chrome_impl.h index 50755ea56d30d..8a0ef6d3d0642 100644 --- a/sky/services/platform/ios/system_chrome_impl.h +++ b/sky/services/platform/ios/system_chrome_impl.h @@ -22,10 +22,18 @@ class SystemChromeImpl : public SystemChrome { uint32_t device_orientation_mask, const SetPreferredOrientationsCallback& callback) override; + void SetApplicationSwitcherDescription( + ApplicationSwitcherDescriptionPtr description, + const SetApplicationSwitcherDescriptionCallback& callback) override; + void SetEnabledSystemUIOverlays( uint32_t overlays, const SetEnabledSystemUIOverlaysCallback& callback) override; + void SetSystemUIOverlayStyle( + SystemUIOverlayStyle style, + const SetSystemUIOverlayStyleCallback& callback) override; + private: mojo::StrongBinding binding_; @@ -34,6 +42,8 @@ class SystemChromeImpl : public SystemChrome { extern const char* const kOrientationUpdateNotificationName; extern const char* const kOrientationUpdateNotificationKey; +extern const char* const kOverlayStyleUpdateNotificationName; +extern const char* const kOverlayStyleUpdateNotificationKey; } // namespace platform } // namespace flutter diff --git a/sky/services/platform/ios/system_chrome_impl.mm b/sky/services/platform/ios/system_chrome_impl.mm index cadd139c4ec5f..4082bbc287879 100644 --- a/sky/services/platform/ios/system_chrome_impl.mm +++ b/sky/services/platform/ios/system_chrome_impl.mm @@ -4,6 +4,7 @@ #include "sky/services/platform/ios/system_chrome_impl.h" #include "base/mac/scoped_nsautorelease_pool.h" +#include #include namespace flutter { @@ -59,8 +60,15 @@ static constexpr bool IsSet(uint32_t mask, T orientation) { callback.Run(true); } +void SystemChromeImpl::SetApplicationSwitcherDescription( + ApplicationSwitcherDescriptionPtr description, + const SetApplicationSwitcherDescriptionCallback& callback) { + // No counterpart on iOS but is a benign operation. So no asserts. + callback.Run(true); +} + void SystemChromeImpl::SetEnabledSystemUIOverlays( - uint32_t overlays, + uint32_t overlay_mask, const SetEnabledSystemUIOverlaysCallback& callback) { // Checks if the top status bar should be visible. This platform ignores all // other overlays @@ -71,15 +79,56 @@ static constexpr bool IsSet(uint32_t mask, T orientation) { // to be able to modify this on the fly. The key used is // UIViewControllerBasedStatusBarAppearance [UIApplication sharedApplication].statusBarHidden = - !IsSet(overlays, SystemUIOverlay::Top); + !IsSet(overlay_mask, SystemUIOverlay::Top); + + callback.Run(true); +} + +void SystemChromeImpl::SetSystemUIOverlayStyle( + SystemUIOverlayStyle style, + const SetSystemUIOverlayStyleCallback& callback) { + base::mac::ScopedNSAutoreleasePool pool; + + UIStatusBarStyle statusBarStyle; + switch (style) { + case SystemUIOverlayStyle::Light: + statusBarStyle = UIStatusBarStyleLightContent; + break; + case SystemUIOverlayStyle::Dark: + statusBarStyle = UIStatusBarStyleDefault; + break; + } + + NSNumber* infoValue = [[NSBundle mainBundle] + objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"]; + Boolean delegateToViewController = + (infoValue == nil || [infoValue boolValue]); + + if (delegateToViewController) { + // This notification is respected by the iOS embedder + [[NSNotificationCenter defaultCenter] + postNotificationName:@(kOverlayStyleUpdateNotificationName) + 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]; + } callback.Run(true); } const char* const kOrientationUpdateNotificationName = - "SystemChromeOrientationNotificationName"; + "io.flutter.SystemChromeOrientationNotificationName"; const char* const kOrientationUpdateNotificationKey = - "SystemChromeOrientationNotificationName"; + "io.flutter.SystemChromeOrientationNotificationKey"; +const char* const kOverlayStyleUpdateNotificationName = + "io.flutter.SystemChromeOverlayNotificationName"; +const char* const kOverlayStyleUpdateNotificationKey = + "io.flutter.SystemChromeOverlayNotificationKey"; } // namespace platform } // namespace flutter diff --git a/sky/services/platform/src/org/domokit/platform/SystemChromeImpl.java b/sky/services/platform/src/org/domokit/platform/SystemChromeImpl.java index ea644ff79d1c9..53909c2fc7329 100644 --- a/sky/services/platform/src/org/domokit/platform/SystemChromeImpl.java +++ b/sky/services/platform/src/org/domokit/platform/SystemChromeImpl.java @@ -6,9 +6,11 @@ import android.app.Activity; import android.content.pm.ActivityInfo; +import android.os.Build; import android.view.View; import org.chromium.mojo.system.MojoException; +import org.chromium.mojom.flutter.platform.ApplicationSwitcherDescription; import org.chromium.mojom.flutter.platform.DeviceOrientation; import org.chromium.mojom.flutter.platform.SystemChrome; import org.chromium.mojom.flutter.platform.SystemUiOverlay; @@ -54,6 +56,31 @@ public void setPreferredOrientations(int deviceOrientationMask, callback.call(true); } + @Override + public void setApplicationSwitcherDescription( + ApplicationSwitcherDescription description, + SetApplicationSwitcherDescriptionResponse callback) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + callback.call(true); + return; + } + + int color = description.primaryColor; + if (color != 0) { // 0 means color isn't set, use system default + color = color | 0xFF000000; // color must be opaque if set + } + + mActivity.setTaskDescription( + new android.app.ActivityManager.TaskDescription( + description.label, + null, + color + ) + ); + + callback.call(true); + } + @Override public void setEnabledSystemUiOverlays(int overlays, SetEnabledSystemUiOverlaysResponse callback) { @@ -71,4 +98,12 @@ public void setEnabledSystemUiOverlays(int overlays, mActivity.getWindow().getDecorView().setSystemUiVisibility(flags); callback.call(true); } + + @Override + public void setSystemUiOverlayStyle(int style, SetSystemUiOverlayStyleResponse callback) { + // You can change the navigation bar color (including translucent colors) + // in Android, but you can't change the color of the navigation buttons, + // so LIGHT vs DARK effectively isn't supported in Android. + callback.call(true); + } } diff --git a/sky/services/platform/system_chrome.mojom b/sky/services/platform/system_chrome.mojom index b852f50a77f58..036181320d442 100644 --- a/sky/services/platform/system_chrome.mojom +++ b/sky/services/platform/system_chrome.mojom @@ -39,29 +39,66 @@ enum SystemUIOverlay { Bottom = 2, }; +/// Specifies a preference for the style of the system overlays. Certain +/// platforms may not respect this preference. +enum SystemUIOverlayStyle { + /// System overlays should be drawn with a light color. Intended for + /// applications with a dark background. + Light = 1, + + /// System overlays should be drawn with a dark color. Intended for + /// applications with a light background. + Dark = 2, +}; + +/// Specifies a description of the application that is pertinent to the +/// embedder's application switcher (a.k.a. "recent tasks") user interface. +struct ApplicationSwitcherDescription { + /// A label and description of the current state of the application. + string? label; + + /// The application's primary color. + uint32 primaryColor; +}; + /// Controls specific aspects of the embedder interface. [ServiceName="flutter::platform::SystemChrome"] interface SystemChrome { /// Specifies the set of orientations the application interface can /// be displayed in. /// - /// The value 0 is synonymous with having all options enabled. /// Arguments: - /// device_orientation_mask: A mask of `DeviceOrientation` enum values. + /// device_orientation_mask: A mask of `DeviceOrientation` enum values. + /// A value of 0 is synonymous with having all options enabled. /// /// Return Value: /// boolean indicating if the orientation mask is valid and the changes /// could be conveyed successfully to the embedder. SetPreferredOrientations(uint32 device_orientation_mask) => (bool success); + /// Specifies the description of the application within the embedder's + /// application switcher (a.k.a. "recent tasks") user interface. + /// + /// Arguments: + /// description: The description of the current state of the application. + /// + /// Return value: + /// boolean indicating if the preference was conveyed successfully to the + /// embedder. + /// + /// Platform Specific Notes: + /// If application switcher metadata cannot be manually set on the platform, + /// specifying such metadata is a no-op and always return true. + SetApplicationSwitcherDescription(ApplicationSwitcherDescription description) => (bool success); /// Specifies the set of overlays visible on the embedder when the /// application is running. The embedder may choose to ignore unsupported /// overlays /// /// Arguments: - /// style: A mask of `SystemUIOverlay` enum values that denotes the overlays - /// to show. + /// overlay_mask: A mask of `SystemUIOverlay` enum values that denotes the + /// overlays to show. A value of 0 is synonymous with showing no + /// overlays. /// /// Return Value: /// boolean indicating if the preference was conveyed successfully to the @@ -70,5 +107,20 @@ interface SystemChrome { /// Platform Specific Notes: /// If the overlay is unsupported on the platform, enabling or disabling /// that overlay is a no-op and always return true. - SetEnabledSystemUIOverlays(uint32 overlays) => (bool success); + SetEnabledSystemUIOverlays(uint32 overlay_mask) => (bool success); + + /// Specifies the style of the overlays that are visible on the embedder when + /// the applicatiomn is running. + /// + /// Arguments: + /// style: A `SystemUIOverlayStyle` enum value that denotes the style + /// + /// Return value: + /// boolean indicating if the preference was conveyed successfully to the + /// embedder. + /// + /// Platform Specific Notes: + /// If overlay style is unsupported on the platform, specifying a style is + /// a no-op and always return true. + SetSystemUIOverlayStyle(SystemUIOverlayStyle style) => (bool success); }; diff --git a/sky/shell/platform/ios/framework/Source/FlutterViewController.mm b/sky/shell/platform/ios/framework/Source/FlutterViewController.mm index da70ff76c1e69..544ecf3e63a60 100644 --- a/sky/shell/platform/ios/framework/Source/FlutterViewController.mm +++ b/sky/shell/platform/ios/framework/Source/FlutterViewController.mm @@ -43,6 +43,7 @@ void FlutterInit(int argc, const char* argv[]) { @implementation FlutterViewController { base::scoped_nsprotocol _dartProject; UIInterfaceOrientationMask _orientationPreferences; + UIStatusBarStyle _statusBarStyle; base::scoped_nsprotocol _dynamicServiceLoader; sky::ViewportMetricsPtr _viewportMetrics; sky::shell::TouchMapper _touchMapper; @@ -87,6 +88,7 @@ - (void)performCommonViewControllerInitialization { _initialized = YES; _orientationPreferences = UIInterfaceOrientationMaskAll; + _statusBarStyle = UIStatusBarStyleDefault; _dynamicServiceLoader.reset([[FlutterDynamicServiceLoader alloc] init]); _viewportMetrics = sky::ViewportMetrics::New(); _shellView = @@ -116,6 +118,11 @@ - (void)setupNotificationCenterObservers { name:@(flutter::platform::kOrientationUpdateNotificationName) object:nil]; + [center addObserver:self + selector:@selector(onPreferredStatusBarStyleUpdated:) + name:@(flutter::platform::kOverlayStyleUpdateNotificationName) + object:nil]; + [center addObserver:self selector:@selector(applicationBecameActive:) name:UIApplicationDidBecomeActiveNotification @@ -450,15 +457,38 @@ - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; } -- (UIStatusBarStyle)preferredStatusBarStyle { - return UIStatusBarStyleLightContent; -} - - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } +#pragma mark - Status bar style + +- (UIStatusBarStyle)preferredStatusBarStyle { + return _statusBarStyle; +} + +- (void)onPreferredStatusBarStyleUpdated:(NSNotification*)notification { + // Notifications may not be on the iOS UI thread + dispatch_async(dispatch_get_main_queue(), ^{ + NSDictionary* info = notification.userInfo; + + NSNumber* update = + info[@(flutter::platform::kOverlayStyleUpdateNotificationKey)]; + + if (update == nil) { + return; + } + + NSInteger style = update.integerValue; + + if (style != _statusBarStyle) { + _statusBarStyle = static_cast(style); + [self setNeedsStatusBarAppearanceUpdate]; + } + }); +} + #pragma mark - Application Messages - (void)sendString:(NSString*)message withMessageName:(NSString*)messageName { @@ -497,14 +527,16 @@ - (void)removeMessageListener:(NSObject*)listener { _appMessageReceiver.SetMessageListener(messageName.UTF8String, nil); } -- (void)addAsyncMessageListener:(NSObject*)listener { +- (void)addAsyncMessageListener: + (NSObject*)listener { NSAssert(listener, @"The listener must not be null"); NSString* messageName = listener.messageName; NSAssert(messageName, @"The messageName must not be null"); _appMessageReceiver.SetAsyncMessageListener(messageName.UTF8String, listener); } -- (void)removeAsyncMessageListener:(NSObject*)listener { +- (void)removeAsyncMessageListener: + (NSObject*)listener { NSAssert(listener, @"The listener must not be null"); NSString* messageName = listener.messageName; NSAssert(messageName, @"The messageName must not be null");