From 3f66fc34402f965731ee21938bb400351e310c6c Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Wed, 26 Apr 2023 14:59:04 -0700 Subject: [PATCH 01/21] Adding app lifecycle notification to macOS and linux, and add the hidden state. --- ci/licenses_golden/licenses_flutter | 12 ++ lib/ui/platform_dispatcher.dart | 108 ++++++++----- lib/web_ui/lib/platform_dispatcher.dart | 5 +- .../lib/src/engine/platform_dispatcher.dart | 44 ++++-- runtime/platform_data.h | 2 +- ...lutterActivityAndFragmentDelegateTest.java | 14 ++ shell/platform/common/BUILD.gn | 7 +- .../platform/common/application_lifecycle.cc | 51 +++++++ shell/platform/common/application_lifecycle.h | 85 +++++++++++ shell/platform/darwin/macos/BUILD.gn | 3 + .../framework/Headers/FlutterAppDelegate.h | 19 ++- .../Headers/FlutterAppLifecycleDelegate.h | 137 +++++++++++++++++ .../macos/framework/Headers/FlutterEngine.h | 4 +- .../macos/framework/Headers/FlutterMacOS.h | 1 + .../framework/Headers/FlutterPluginMacOS.h | 9 +- .../framework/Source/FlutterAppDelegate.mm | 17 ++- .../Source/FlutterAppLifecycleDelegate.mm | 143 ++++++++++++++++++ .../FlutterAppLifecycleDelegate_Internal.h | 21 +++ .../macos/framework/Source/FlutterEngine.mm | 77 +++++++++- .../framework/Source/FlutterEngineTest.mm | 68 +++++++++ .../framework/Source/FlutterEngine_Internal.h | 7 + shell/platform/linux/BUILD.gn | 2 + shell/platform/linux/fl_engine.cc | 35 +++++ shell/platform/linux/fl_engine_private.h | 12 ++ shell/platform/linux/fl_engine_test.cc | 35 +++++ shell/platform/linux/fl_view.cc | 47 +++++- shell/platform/windows/BUILD.gn | 1 + .../windows/flutter_windows_engine.cc | 9 ++ .../platform/windows/flutter_windows_engine.h | 4 + 29 files changed, 912 insertions(+), 67 deletions(-) create mode 100644 shell/platform/common/application_lifecycle.cc create mode 100644 shell/platform/common/application_lifecycle.h create mode 100644 shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 893d479e0dbe4..46bd0b150f66f 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2463,6 +2463,8 @@ ORIGIN: ../../../flutter/shell/platform/common/accessibility_bridge.cc + ../../. ORIGIN: ../../../flutter/shell/platform/common/accessibility_bridge.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/alert_platform_node_delegate.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/alert_platform_node_delegate.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/common/application_lifecycle.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/common/application_lifecycle.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/client_wrapper/byte_buffer_streams.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/client_wrapper/core_implementations.cc + ../../../flutter/LICENSE @@ -2680,6 +2682,8 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.mm + ../../ ORIGIN: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterDartProject.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterPlatformViews.h + ../../../flutter/LICENSE @@ -2691,6 +2695,8 @@ ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/Accessibil ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacTest.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h + ../../../flutter/LICENSE @@ -5130,6 +5136,8 @@ FILE: ../../../flutter/shell/platform/common/accessibility_bridge.cc FILE: ../../../flutter/shell/platform/common/accessibility_bridge.h FILE: ../../../flutter/shell/platform/common/alert_platform_node_delegate.cc FILE: ../../../flutter/shell/platform/common/alert_platform_node_delegate.h +FILE: ../../../flutter/shell/platform/common/application_lifecycle.cc +FILE: ../../../flutter/shell/platform/common/application_lifecycle.h FILE: ../../../flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h FILE: ../../../flutter/shell/platform/common/client_wrapper/byte_buffer_streams.h FILE: ../../../flutter/shell/platform/common/client_wrapper/core_implementations.cc @@ -5349,6 +5357,8 @@ FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.mm FILE: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.h FILE: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterDartProject.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterPlatformViews.h @@ -5361,6 +5371,8 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/Accessibilit FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacTest.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index 0e26b0ca215a1..93cc78bf57b67 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -946,7 +946,7 @@ class PlatformDispatcher { return _initialLifecycleState; } - late String _initialLifecycleState; + String _initialLifecycleState = 'AppLifecycleState.detached'; /// Tracks if the initial state has been accessed. Once accessed, we will stop /// updating the [initialLifecycleState], as it is not the preferred way to @@ -1679,13 +1679,11 @@ class FrameTiming { } } -/// States that an application can be in. +/// States that an application can be in once it is running. /// -/// The values below describe notifications from the operating system. -/// Applications should not expect to always receive all possible notifications. -/// For example, if the users pulls out the battery from the device, no -/// notification will be sent before the application is suddenly terminated, -/// along with the rest of the operating system. +/// States not supported on a platform will be synthesized by the framework when +/// transitioning between states which are supported, so that all +/// implementations share the same state machine. /// /// For historical and name collision reasons, Flutter's application state names /// do not correspond one to one with the state names on all platforms. On @@ -1696,6 +1694,19 @@ class FrameTiming { /// Flutter enters the [paused] state. See the individual state's documentation /// for descriptions of what they mean on each platform. /// +/// The current application state can be obtained from +/// [SchedulerBinding.instance.lifecycleState], and changes to the state can be +/// observed by creating an [AppLifecycleListener], or by using a +/// [WidgetsBindingObserver] by overriding the +/// [WidgetsBindingObserver.didChangeAppLifecycleState] method. +/// +/// Applications should not rely on always receiving all possible notifications. +/// +/// For example, if the application is killed with a task manager, a kill +/// signal, the user pulls the power from the device, or there is a rapid +/// unscheduled disassembly of the device, no notification will be sent before +/// the application is suddenly terminated, and some states may be skipped. +/// /// See also: /// /// * [WidgetsBindingObserver], for a mechanism to observe the lifecycle state @@ -1704,7 +1715,22 @@ class FrameTiming { /// * Android's [activity lifecycle](https://developer.android.com/guide/components/activities/activity-lifecycle) documentation. /// * macOS's [AppKit activity lifecycle](https://developer.apple.com/documentation/appkit/nsapplicationdelegate?language=objc) documentation. enum AppLifecycleState { - /// The application is visible and responsive to user input. + /// The application is still hosted by a Flutter engine but is detached from + /// any host views. + /// + /// The application defaults to this state before it initializes, and can be + /// in this state (on Android and iOS only) after all views have been + /// detached. + /// + /// When the application is in this state, the engine is running without a + /// view. + /// + /// This state is only entered on iOS and Android, although on all platforms + /// it is the default state before the application begins running. + detached, + + /// The application is in the default running mode for a running application + /// that has input focus and is visible. /// /// On Android, this state corresponds to the Flutter host view having focus /// ([`Activity.onWindowFocusChanged`](https://developer.android.com/reference/android/app/Activity#onWindowFocusChanged(boolean)) @@ -1719,16 +1745,23 @@ enum AppLifecycleState { /// called on it. resumed, - /// The application is in an inactive state and is not receiving user input. + /// At least one view of the application is visible, but none have input + /// focus. The application is otherwise running normally. /// - /// On iOS, this state corresponds to an app or the Flutter host view running - /// in the foreground inactive state. Apps transition to this state when in a - /// phone call, responding to a TouchID request, when entering the app - /// switcher or the control center, or when the UIViewController hosting the - /// Flutter app is transitioning. + /// On non-web desktop platforms, this corresponds to an application that is + /// not in the foreground, but still has visible windows. /// - /// On Android, this corresponds to an app or the Flutter host view running in - /// Android's paused state (i.e. + /// On the web, this corresponds to an application that is not running in a + /// window or tab that has input focus. + /// + /// On iOS, this state corresponds to the Flutter host view running in the + /// foreground inactive state. Apps transition to this state when in a phone + /// call, when responding to a TouchID request, when entering the app switcher + /// or the control center, or when the UIViewController hosting the Flutter + /// app is transitioning. + /// + /// On Android, this corresponds to the Flutter host view running in Android's + /// paused state (i.e. /// [`Activity.onPause`](https://developer.android.com/reference/android/app/Activity#onPause()) /// has been called), or in Android's "resumed" state (i.e. /// [`Activity.onResume`](https://developer.android.com/reference/android/app/Activity#onResume()) @@ -1738,33 +1771,39 @@ enum AppLifecycleState { /// picture-in-picture app, a system dialog, another view, when the /// notification window shade is down, or the application switcher is visible. /// - /// Apps in this state should assume that they may be [paused] at any time. + /// On Android and iOS, apps in this state should assume that they may be + /// [hidden] and [paused] at any time. inactive, - /// The application is not currently visible to the user, not responding to - /// user input, and running in the background. + /// All views of an application are hidden, either because the application is + /// about to be paused (on iOS and Android), or because it has been minimized + /// or placed on a desktop that is no longer visible (on non-web desktop), or + /// is running in a window or tab that is no longer visible (on the web). + /// + /// On iOS and Android, in order to keep the state machine the same on all + /// platforms, a transition to this state is synthesized before the [paused] + /// state is entered when coming from [inactive], and before the [inactive] + /// state is entered when coming from [paused]. This allows cross-platform + /// implementations that want to know when an app is conceptually "hidden" to + /// only write one handler. + hidden, + + /// The application is not currently visible to the user, and not responding + /// to user input. /// /// When the application is in this state, the engine will not call the /// [PlatformDispatcher.onBeginFrame] and [PlatformDispatcher.onDrawFrame] /// callbacks. - paused, - - /// The application is still hosted on a flutter engine but is detached from - /// any host views. /// - /// When the application is in this state, the engine is running without - /// a view. It can either be in the progress of attaching a view when engine - /// was first initializes, or after the view being destroyed due to a Navigator - /// pop. - detached, + /// This state is only entered on iOS and Android. + paused, } /// The possible responses to a request to exit the application. /// -/// The request is typically responded to by a [WidgetsBindingObserver]. -// TODO(gspencergoog): Insert doc references here to AppLifecycleListener and to -// the actual function called on WidgetsBindingObserver once those have landed -// in the framework. https://github.com/flutter/flutter/issues/121721 +/// The request is typically responded to by creating an [AppLifecycleListener] +/// and supplying an [AppLifecycleListener.onExitRequested] callback, or by +/// overriding [WidgetsBindingObserver.didRequestAppExit]. enum AppExitResponse { /// Exiting the application can proceed. exit, @@ -1773,10 +1812,7 @@ enum AppExitResponse { } /// The type of application exit to perform when calling -/// `ServicesBinding.exitApplication`. -// TODO(gspencergoog): Insert doc references here to -// ServicesBinding.exitApplication that has landed in the framework. -// https://github.com/flutter/flutter/issues/121721 +/// [ServicesBinding.exitApplication]. enum AppExitType { /// Requests that the application start an orderly exit, sending a request /// back to the framework through the [WidgetsBinding]. If that responds diff --git a/lib/web_ui/lib/platform_dispatcher.dart b/lib/web_ui/lib/platform_dispatcher.dart index 2546404fbacce..2059122d8c8a5 100644 --- a/lib/web_ui/lib/platform_dispatcher.dart +++ b/lib/web_ui/lib/platform_dispatcher.dart @@ -104,7 +104,7 @@ abstract class PlatformDispatcher { VoidCallback? get onLocaleChanged; set onLocaleChanged(VoidCallback? callback); - String get initialLifecycleState => 'AppLifecycleState.resumed'; + String get initialLifecycleState => ''; bool get alwaysUse24HourFormat; @@ -247,10 +247,11 @@ class FrameTiming { } enum AppLifecycleState { + detached, resumed, inactive, + hidden, paused, - detached, } enum AppExitResponse { diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 7ff4c674121f4..544a42847b189 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -104,6 +104,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { _addFontSizeObserver(); _addLocaleChangedListener(); registerHotRestartListener(dispose); + _setAppLifecycleState(ui.AppLifecycleState.resumed); } /// The [EnginePlatformDispatcher] singleton. @@ -471,6 +472,19 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { PlatformViewMessageHandler? _platformViewMessageHandler; + static const String _kSkiaChannel = 'flutter/skia'; + static const String _kAssetsChannel = 'flutter/assets'; + static const String _kPlatformChannel = 'flutter/platform'; + static const String _kServiceWorkerChannel = 'flutter/service_worker'; + static const String _kTextInputChannel = 'flutter/textinput'; + static const String _kContextMenuChannel = 'flutter/contextmenu'; + static const String _kMouseCursorChannel = 'flutter/mousecursor'; + static const String _kWebTestE2EChannel = 'flutter/web_test_e2e'; + static const String _kPlatformViewsChannel = 'flutter/platform_views'; + static const String _kAccessibilityChannel = 'flutter/accessibility'; + static const String _kNavigationChannel = 'flutter/navigation'; + static const String _kLifecycleChannel = 'flutter/lifecycle'; + void _sendPlatformMessage( String name, ByteData? data, @@ -508,7 +522,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { switch (name) { /// This should be in sync with shell/common/shell.cc - case 'flutter/skia': + case _kSkiaChannel: const MethodCodec codec = JSONMethodCodec(); final MethodCall decoded = codec.decodeMethodCall(data); switch (decoded.method) { @@ -529,12 +543,12 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { } return; - case 'flutter/assets': + case _kAssetsChannel: final String url = utf8.decode(data!.buffer.asUint8List()); _handleFlutterAssetsMessage(url, callback); return; - case 'flutter/platform': + case _kPlatformChannel: const MethodCodec codec = JSONMethodCodec(); final MethodCall decoded = codec.decodeMethodCall(data); switch (decoded.method) { @@ -590,15 +604,15 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { } // Dispatched by the bindings to delay service worker initialization. - case 'flutter/service_worker': + case _kServiceWorkerChannel: domWindow.dispatchEvent(createDomEvent('Event', 'flutter-first-frame')); return; - case 'flutter/textinput': + case _kTextInputChannel: textEditing.channel.handleTextInput(data, callback); return; - case 'flutter/contextmenu': + case _kContextMenuChannel: const MethodCodec codec = JSONMethodCodec(); final MethodCall decoded = codec.decodeMethodCall(data); switch (decoded.method) { @@ -613,7 +627,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { } return; - case 'flutter/mousecursor': + case _kMouseCursorChannel: const MethodCodec codec = StandardMethodCodec(); final MethodCall decoded = codec.decodeMethodCall(data); final Map arguments = decoded.arguments as Map; @@ -623,7 +637,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { } return; - case 'flutter/web_test_e2e': + case _kWebTestE2EChannel: const MethodCodec codec = JSONMethodCodec(); replyToPlatformMessage( callback, @@ -631,7 +645,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { _handleWebTestEnd2EndMessage(codec, data))); return; - case 'flutter/platform_views': + case _kPlatformViewsChannel: _platformViewMessageHandler ??= PlatformViewMessageHandler( contentManager: platformViewManager, contentHandler: (DomElement content) { @@ -641,14 +655,14 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { _platformViewMessageHandler!.handlePlatformViewCall(data, callback!); return; - case 'flutter/accessibility': + case _kAccessibilityChannel: // In widget tests we want to bypass processing of platform messages. const StandardMessageCodec codec = StandardMessageCodec(); accessibilityAnnouncements.handleMessage(codec, data); replyToPlatformMessage(callback, codec.encodeMessage(true)); return; - case 'flutter/navigation': + case _kNavigationChannel: // TODO(a-wallen): As multi-window support expands, the navigation call // will need to include the view ID. Right now only one view is // supported. @@ -1005,6 +1019,14 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { _fontSizeObserver = null; } + void _setAppLifecycleState(ui.AppLifecycleState state) { + sendPlatformMessage( + _kLifecycleChannel, + Uint8List.fromList(utf8.encode(state.toString())).buffer.asByteData(), + null, + ); + } + /// A callback that is invoked whenever [textScaleFactor] changes value. /// /// The framework invokes this callback in the same zone in which the diff --git a/runtime/platform_data.h b/runtime/platform_data.h index b6d4c65f94d19..1bb6cc02322b8 100644 --- a/runtime/platform_data.h +++ b/runtime/platform_data.h @@ -39,7 +39,7 @@ struct PlatformData { std::string variant_code; std::vector locale_data; std::string user_settings_data = "{}"; - std::string lifecycle_state; + std::string lifecycle_state = ""; bool semantics_enabled = false; bool assistive_technology_enabled = false; int32_t accessibility_feature_flags_ = 0; diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java index 91669d276f254..f6d1eb04e5b28 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -152,6 +152,20 @@ public void itSendsLifecycleEventsToFlutter() { verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused(); verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached(); + // When the app regains focus, it should go to resumed again. + delegate.onWindowFocusChanged(true); + verify(mockFlutterEngine.getLifecycleChannel(), times(2)).appIsResumed(); + verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive(); + verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused(); + verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached(); + + // When the Activity/Fragment is paused, an inactive message should have been sent to Flutter. + delegate.onPause(); + verify(mockFlutterEngine.getLifecycleChannel(), times(2)).appIsResumed(); + verify(mockFlutterEngine.getLifecycleChannel(), times(2)).appIsInactive(); + verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused(); + verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached(); + // When the Activity/Fragment is stopped, a paused message should have been sent to Flutter. // Notice that Flutter uses the term "paused" in a different way, and at a different time // than the Android OS. diff --git a/shell/platform/common/BUILD.gn b/shell/platform/common/BUILD.gn index 0d45a8ad1eb5f..bdf220e65b2b0 100644 --- a/shell/platform/common/BUILD.gn +++ b/shell/platform/common/BUILD.gn @@ -58,7 +58,12 @@ source_set("common_cpp_input") { } source_set("common_cpp_enums") { - public = [ "platform_provided_menu.h" ] + public = [ + "application_lifecycle.h", + "platform_provided_menu.h", + ] + + sources = [ "application_lifecycle.cc" ] public_configs = [ "//flutter:config", diff --git a/shell/platform/common/application_lifecycle.cc b/shell/platform/common/application_lifecycle.cc new file mode 100644 index 0000000000000..5f42c9b9a6627 --- /dev/null +++ b/shell/platform/common/application_lifecycle.cc @@ -0,0 +1,51 @@ +// 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. + +#include "flutter/shell/platform/common/application_lifecycle.h" + +#include +#include + +namespace flutter { + +const char* AppLifecycleStateToString(AppLifecycleState state) { + switch (state) { + case kAppLifecycleStateDetached: + return "AppLifecycleState.detached"; + case kAppLifecycleStateResumed: + return "AppLifecycleState.resumed"; + case kAppLifecycleStateInactive: + return "AppLifecycleState.inactive"; + case kAppLifecycleStateHidden: + return "AppLifecycleState.hidden"; + case kAppLifecycleStatePaused: + return "AppLifecycleState.paused"; + default: + assert(false && "Lifecycle state not recognized"); + break; + } +} + +AppLifecycleState StringToAppLifecycleState(const char* char_value) { + std::string value = char_value; + if (value == "AppLifecycleState.detached") { + return kAppLifecycleStateDetached; + } + if (value == "AppLifecycleState.resumed") { + return kAppLifecycleStateResumed; + } + if (value == "AppLifecycleState.inactive") { + return kAppLifecycleStateInactive; + } + if (value == "AppLifecycleState.hidden") { + return kAppLifecycleStateHidden; + } + if (value == "AppLifecycleState.paused") { + return kAppLifecycleStatePaused; + } + assert(false && "Lifecycle state string not recognized"); + return kAppLifecycleStateDetached; +} + +} // namespace flutter diff --git a/shell/platform/common/application_lifecycle.h b/shell/platform/common/application_lifecycle.h new file mode 100644 index 0000000000000..0296f56cc3a47 --- /dev/null +++ b/shell/platform/common/application_lifecycle.h @@ -0,0 +1,85 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_COMMON_APPLICATION_LIFECYCLE_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_APPLICATION_LIFECYCLE_H_ + +namespace flutter { + +/** + * This enum describes the possible lifecycle states of the application. It + * must be kept up to date with changes in the framework's AppLifecycleState + * enum. It is passed to the embedder's |SetLifecycleState| function. + * + * States not supported on a platform will be synthesized by the framework when + * transitioning between states which are supported, so that all + * implementations share the same state machine. + * + * Here is the state machine: + * + * +-----------+ +-----------+ + * | detached |------------------------------>| resumed | + * +-----------+ +-----------+ + * ^ ^ + * | | + * | v + * +-----------+ +--------------+ +-----------+ + * | paused |<------>| hidden |<----->| inactive | + * +-----------+ +--------------+ +-----------+ + */ +typedef enum { + /** + * Corresponds to AppLifecycleState.detached: The initial state of the state + * machine. On Android and iOS, also the final state of the state machine + * when all views are detached. Other platforms do not enter this state again + * after initially leaving it. + */ + kAppLifecycleStateDetached = 0x0, + + /** + * Corresponds to AppLifecycleState.resumed: The nominal "running" state of + * the + * application. The application is visible, has input focus, and is running. + */ + kAppLifecycleStateResumed = 0x1, + + /** + * Corresponds to AppLifecycleState.inactive: At least one view of the + * application is visible, but none have input focus. The application is + * otherwise running normally. + */ + kAppLifecycleStateInactive = 0x2, + + /** + * Corresponds to AppLifecycleState.hidden: All views of an application are + * hidden, either because the application is being stopped (on iOS and + * Android), or because it is being minimized or on a desktop that is no + * longer visible (on desktop), or on a tab that is no longer visible (on + * web). + */ + kAppLifecycleStateHidden = 0x3, + + /** + * Corresponds to AppLifecycleState.paused: The application is not running, + * and can be detached or started again at any time. This state is typically + * only entered into on iOS and Android. + */ + kAppLifecycleStatePaused = 0x4, +} AppLifecycleState; + +/** + * Converts an AppLifecycleState enum value to a string. + */ +const char* AppLifecycleStateToString(AppLifecycleState state); + +/** + * Converts an AppLifecycleState string value to the corresponding enum value. + * + * Will assert if the string doesn't represent an enum value. + */ +AppLifecycleState StringToAppLifecycleState(const char* char_value); + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_APPLICATION_LIFECYCLE_H_ diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index 16ffb24cd3f19..5beb9e9b0d821 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -38,6 +38,8 @@ _framework_binary_subpath = "Versions/A/$_flutter_framework_name" # the Flutter engine source root. _flutter_framework_headers = [ "framework/Headers/FlutterAppDelegate.h", + "framework/Headers/FlutterAppLifecycleDelegate.h", + "framework/Headers/FlutterDartProject.h", "framework/Headers/FlutterEngine.h", "framework/Headers/FlutterMacOS.h", "framework/Headers/FlutterPlatformViews.h", @@ -56,6 +58,7 @@ source_set("flutter_framework_source") { "framework/Source/AccessibilityBridgeMac.h", "framework/Source/AccessibilityBridgeMac.mm", "framework/Source/FlutterAppDelegate.mm", + "framework/Source/FlutterAppLifecycleDelegate.mm", "framework/Source/FlutterBackingStore.h", "framework/Source/FlutterBackingStore.mm", "framework/Source/FlutterChannelKeyResponder.h", diff --git a/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h b/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h index 092c0115d077f..c7aaeca37e6cc 100644 --- a/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h +++ b/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h @@ -7,10 +7,11 @@ #import +#import "FlutterAppLifecycleDelegate.h" #import "FlutterMacros.h" /** - * `NSApplicationDelegate` subclass for simple apps that want default behavior. + * |NSApplicationDelegate| subclass for simple apps that want default behavior. * * This class implements the following behaviors: * * Updates the application name of items in the application menu to match the name in @@ -33,11 +34,23 @@ FLUTTER_DARWIN_EXPORT @property(weak, nonatomic) IBOutlet NSMenu* applicationMenu; /** - * The primary application window containing a FlutterViewController. This is primarily intended - * for use in single-window applications. + * The primary application window containing a FlutterViewController. This is + * primarily intended for use in single-window applications. */ @property(weak, nonatomic) IBOutlet NSWindow* mainFlutterWindow; +/** + * Adds an object implementing |FlutterAppLifecycleDelegate| to the list of + * delegates to be informed of application lifecycle events. + */ +- (void)addApplicationLifecycleDelegate:(NSObject*)delegate; + +/** + * Removes an object implementing |FlutterAppLifecycleDelegate| to the list of + * delegates to be informed of application lifecycle events. + */ +- (void)removeApplicationLifecycleDelegate:(NSObject*)delegate; + @end #endif // FLUTTER_FLUTTERAPPDELEGATE_H_ diff --git a/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h b/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h new file mode 100644 index 0000000000000..fd8d52de20e52 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h @@ -0,0 +1,137 @@ +// 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. + +#ifndef FLUTTER_FLUTTERAPPLIFECYCLEDELEGATE_H_ +#define FLUTTER_FLUTTERAPPLIFECYCLEDELEGATE_H_ + +#import +#include + +#import "FlutterMacros.h" +#import "FlutterPluginMacOS.h" + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - +/** + * Protocol for listener of lifecycle events from the NSApplication, typically a + * FlutterPlugin. + */ +@protocol FlutterAppLifecycleDelegate + +@optional +/** + * Called when the |FlutterAppDelegate| gets the applicationWillFinishLaunching + * notification. + */ +- (void)handleWillFinishLaunching:(NSNotification*)notification; + +/** + * Called when the |FlutterAppDelegate| gets the applicationDidFinishLaunching + * notification. + */ +- (void)handleDidFinishLaunching:(NSNotification*)notification; + +/** + * Called when the |FlutterAppDelegate| gets the applicationWillBecomeActive + * notification. + */ +- (void)handleWillBecomeActive:(NSNotification*)notification; + +/** + * Called when the |FlutterAppDelegate| gets the applicationDidBecomeActive + * notification. + */ +- (void)handleDidBecomeActive:(NSNotification*)notification; + +/** + * Called when the |FlutterAppDelegate| gets the applicationWillResignActive + * notification. + */ +- (void)handleWillResignActive:(NSNotification*)notification; + +/** + * Called when the |FlutterAppDelegate| gets the applicationWillResignActive + * notification. + */ +- (void)handleDidResignActive:(NSNotification*)notification; + +/** + * Called when the |FlutterAppDelegate| gets the applicationWillHide + * notification. + */ +- (void)handleWillHide:(NSNotification*)notification; + +/** + * Called when the |FlutterAppDelegate| gets the applicationDidHide + * notification. + */ +- (void)handleDidHide:(NSNotification*)notification; + +/** + * Called when the |FlutterAppDelegate| gets the applicationWillUnhide + * notification. + */ +- (void)handleWillUnhide:(NSNotification*)notification; + +/** + * Called when the |FlutterAppDelegate| gets the applicationDidUnhide + * notification. + */ +- (void)handleDidUnhide:(NSNotification*)notification; + +/** + * Called when the |FlutterAppDelegate| gets the applicationDidUnhide + * notification. + */ +- (void)handleDidChangeScreenParameters:(NSNotification*)notification; + +/** + * Called when the |FlutterAppDelegate| gets the applicationDidUnhide + * notification. + */ +- (void)handleDidChangeOcclusionState:(NSNotification*)notification API_AVAILABLE(macos(10.9)); + +/** + * Called when the |FlutterAppDelegate| gets the applicationWillTerminate + * notification. + * + * Applications should not rely on always receiving all possible notifications. + * + * For example, if the application is killed with a task manager, a kill signal, + * the user pulls the power from the device, or there is a rapid unscheduled + * disassembly of the device, no notification will be sent before the + * application is suddenly terminated, and this notification may be skipped. + */ +- (void)handleWillTerminate:(NSNotification*)notification; +@end + +#pragma mark - + +/** + * Propagates `NSAppDelegate` callbacks to registered delegates. + */ +FLUTTER_DARWIN_EXPORT +@interface FlutterAppLifecycleRegistrar : NSObject + +/** + * Registers `delegate` to receive lifecycle callbacks via this + * FlutterAppLifecycleDelegate as long as it is alive. + * + * `delegate` will only be referenced weakly. + */ +- (void)addDelegate:(NSObject*)delegate; + +/** + * Unregisters `delegate` so that it will no longer receive life cycle callbacks + * via this FlutterAppLifecycleDelegate. + * + * `delegate` will only be referenced weakly. + */ +- (void)removeDelegate:(NSObject*)delegate; +@end + +NS_ASSUME_NONNULL_END + +#endif // FLUTTER_FLUTTERAPPLIFECYCLEDELEGATE_H_ diff --git a/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h b/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h index f854976ebd611..bb12d5cd2ae76 100644 --- a/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h +++ b/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h @@ -9,6 +9,7 @@ #include +#import "FlutterAppLifecycleDelegate.h" #import "FlutterBinaryMessenger.h" #import "FlutterDartProject.h" #import "FlutterMacros.h" @@ -26,7 +27,8 @@ * code. */ FLUTTER_DARWIN_EXPORT -@interface FlutterEngine : NSObject +@interface FlutterEngine + : NSObject /** * Initializes an engine with the given project. diff --git a/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h b/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h index 5fc794b92995d..a7753e578a3fd 100644 --- a/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h +++ b/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h @@ -3,6 +3,7 @@ // found in the LICENSE file. #import "FlutterAppDelegate.h" +#import "FlutterAppLifecycleDelegate.h" #import "FlutterBinaryMessenger.h" #import "FlutterChannels.h" #import "FlutterCodecs.h" diff --git a/shell/platform/darwin/macos/framework/Headers/FlutterPluginMacOS.h b/shell/platform/darwin/macos/framework/Headers/FlutterPluginMacOS.h index cbedb0566a767..af8e0dd686a25 100644 --- a/shell/platform/darwin/macos/framework/Headers/FlutterPluginMacOS.h +++ b/shell/platform/darwin/macos/framework/Headers/FlutterPluginMacOS.h @@ -8,8 +8,7 @@ #import "FlutterCodecs.h" #import "FlutterMacros.h" -// TODO: Merge this file and FlutterPluginRegistrarMacOS.h with the iOS FlutterPlugin.h, sharing -// all but the platform-specific methods. +NS_ASSUME_NONNULL_BEGIN @protocol FlutterPluginRegistrar; @@ -29,7 +28,7 @@ FLUTTER_DARWIN_EXPORT * Creates an instance of the plugin to register with |registrar| using the desired * FlutterPluginRegistrar methods. */ -+ (void)registerWithRegistrar:(nonnull id)registrar; ++ (void)registerWithRegistrar:(id)registrar; @optional @@ -44,6 +43,8 @@ FLUTTER_DARWIN_EXPORT * - Any other value (including nil) to indicate success. The value will * be returned to the Flutter caller, and must be serializable to JSON. */ -- (void)handleMethodCall:(nonnull FlutterMethodCall*)call result:(nonnull FlutterResult)result; +- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result; + +NS_ASSUME_NONNULL_END @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm b/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm index e934c0a6b663e..782266419edba 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm @@ -8,6 +8,7 @@ #import #include "flutter/fml/logging.h" +#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h" #include "flutter/shell/platform/embedder/embedder.h" @interface FlutterAppDelegate () @@ -17,17 +18,15 @@ @interface FlutterAppDelegate () */ - (NSString*)applicationName; +@property(nonatomic) FlutterAppLifecycleRegistrar* lifecycleRegistrar; @end @implementation FlutterAppDelegate -// TODO(gspencergoog): Implement application lifecycle forwarding to plugins here, as is done -// on iOS. Currently macOS plugins don't have access to lifecycle messages. -// https://github.com/flutter/flutter/issues/30735 - - (instancetype)init { if (self = [super init]) { _terminationHandler = nil; + _lifecycleRegistrar = [[FlutterAppLifecycleRegistrar alloc] init]; } return self; } @@ -42,6 +41,16 @@ - (void)applicationWillFinishLaunching:(NSNotification*)notification { } } +#pragma mark - Delegate handling + +- (void)addApplicationLifecycleDelegate:(NSObject*)delegate { + [[self lifecycleRegistrar] addDelegate:delegate]; +} + +- (void)removeApplicationLifecycleDelegate:(NSObject*)delegate { + [[self lifecycleRegistrar] removeDelegate:delegate]; +} + #pragma mark Private Methods - (NSString*)applicationName { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm new file mode 100644 index 0000000000000..bc27f96062b03 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm @@ -0,0 +1,143 @@ +// 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/macos/framework/Headers/FlutterAppLifecycleDelegate.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h" + +#include +#include +#include +#include + +#include "flutter/fml/logging.h" +#include "flutter/fml/paths.h" + +@interface FlutterAppLifecycleRegistrar () +@end + +@implementation FlutterAppLifecycleRegistrar { + NSMutableArray* _notificationUnsubscribers; + + // Weak references to registered plugins. + NSPointerArray* _delegates; +} + +- (void)addObserverFor:(NSString*)name selector:(SEL)selector { + [[NSNotificationCenter defaultCenter] addObserver:self selector:selector name:name object:nil]; + __block NSObject* blockSelf = self; + dispatch_block_t unsubscribe = ^{ + [[NSNotificationCenter defaultCenter] removeObserver:blockSelf name:name object:nil]; + }; + [_notificationUnsubscribers addObject:[unsubscribe copy]]; +} + +- (instancetype)init { + if (self = [super init]) { + _notificationUnsubscribers = [[NSMutableArray alloc] init]; + +// Using a macro to avoid errors where the notification doesn't match the +// selector. +#ifdef OBSERVE_NOTIFICATION +#error OBSERVE_NOTIFICATION ALREADY DEFINED! +#else +#define OBSERVE_NOTIFICATION(SELECTOR) \ + [self addObserverFor:NSApplication##SELECTOR##Notification selector:@selector(handle##SELECTOR:)] +#endif + + OBSERVE_NOTIFICATION(WillFinishLaunching); + OBSERVE_NOTIFICATION(DidFinishLaunching); + OBSERVE_NOTIFICATION(WillBecomeActive); + OBSERVE_NOTIFICATION(DidBecomeActive); + OBSERVE_NOTIFICATION(WillResignActive); + OBSERVE_NOTIFICATION(DidResignActive); + OBSERVE_NOTIFICATION(WillTerminate); + OBSERVE_NOTIFICATION(WillHide); + OBSERVE_NOTIFICATION(DidHide); + OBSERVE_NOTIFICATION(WillUnhide); + OBSERVE_NOTIFICATION(DidUnhide); + OBSERVE_NOTIFICATION(DidChangeScreenParameters); + OBSERVE_NOTIFICATION(DidChangeOcclusionState); + +#undef OBSERVE_NOTIFICATION + + _delegates = [NSPointerArray weakObjectsPointerArray]; + } + return self; +} + +- (void)dealloc { + for (dispatch_block_t unsubscribe in _notificationUnsubscribers) { + unsubscribe(); + } + [_notificationUnsubscribers removeAllObjects]; + _delegates = nil; + _notificationUnsubscribers = nil; +} + +static BOOL IsPowerOfTwo(NSUInteger x) { + return x != 0 && (x & (x - 1)) == 0; +} + +- (BOOL)hasDelegateThatRespondsToSelector:(SEL)selector { + for (NSObject* delegate in [_delegates allObjects]) { + if (!delegate) { + continue; + } + if ([delegate respondsToSelector:selector]) { + return YES; + } + } + return NO; +} + +- (void)addDelegate:(NSObject*)delegate { + [_delegates addPointer:(__bridge void*)delegate]; + if (IsPowerOfTwo([_delegates count])) { + [_delegates compact]; + } +} + +- (void)removeDelegate:(NSObject*)delegate { + NSUInteger index = [[_delegates allObjects] indexOfObject:delegate]; + if (index >= 0) { + [_delegates removePointerAtIndex:index]; + } +} + +// This isn't done via performSelector because that can cause leaks due to the +// selector not being known. Using a macro to avoid mismatch errors between the +// notification and the selector. +#ifdef DISTRIBUTE_NOTIFICATION +#error DISTRIBUTE_NOTIFICATION ALREADY DEFINED! +#else +#define DISTRIBUTE_NOTIFICATION(SELECTOR) \ + -(void)handle##SELECTOR : (NSNotification*)notification { \ + for (NSObject * delegate in _delegates) { \ + if (!delegate) { \ + continue; \ + } \ + if ([delegate respondsToSelector:@selector(handle##SELECTOR:)]) { \ + [delegate handle##SELECTOR:notification]; \ + } \ + } \ + } +#endif + +DISTRIBUTE_NOTIFICATION(WillFinishLaunching) +DISTRIBUTE_NOTIFICATION(DidFinishLaunching) +DISTRIBUTE_NOTIFICATION(WillBecomeActive) +DISTRIBUTE_NOTIFICATION(DidBecomeActive) +DISTRIBUTE_NOTIFICATION(WillResignActive) +DISTRIBUTE_NOTIFICATION(DidResignActive) +DISTRIBUTE_NOTIFICATION(WillTerminate) +DISTRIBUTE_NOTIFICATION(WillHide) +DISTRIBUTE_NOTIFICATION(WillUnhide) +DISTRIBUTE_NOTIFICATION(DidHide) +DISTRIBUTE_NOTIFICATION(DidUnhide) +DISTRIBUTE_NOTIFICATION(DidChangeScreenParameters) +DISTRIBUTE_NOTIFICATION(DidChangeOcclusionState) + +#undef DISTRIBUTE_NOTIFICATION + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h new file mode 100644 index 0000000000000..fa3b70f4aff8c --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h @@ -0,0 +1,21 @@ +// 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. + +#ifndef FLUTTER_FLUTTERAPPLIFECYCLEDELEGATE_INTERNAL_H_ +#define FLUTTER_FLUTTERAPPLIFECYCLEDELEGATE_INTERNAL_H_ + +#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h" + +#include "flutter/shell/platform/common/application_lifecycle.h" + +@interface FlutterAppLifecycleRegistrar () + +/** + * Check whether there is at least one plugin responds to the selector. + */ +- (BOOL)hasDelegateThatRespondsToSelector:(SEL)selector; + +@end + +#endif // FLUTTER_FLUTTERAPPLIFECYCLEDELEGATE_INTERNAL_H_ diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index d4f194cfca01e..74a6bc84764aa 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -9,6 +9,7 @@ #include #include +#include "flutter/shell/platform/common/application_lifecycle.h" #include "flutter/shell/platform/common/engine_switches.h" #include "flutter/shell/platform/embedder/embedder.h" @@ -24,6 +25,8 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewEngineProvider.h" NSString* const kFlutterPlatformChannel = @"flutter/platform"; +NSString* const kFlutterSettingsChannel = @"flutter/settings"; +NSString* const kFlutterLifecycleChannel = @"flutter/lifecycle"; /** * Constructs and returns a FlutterLocale struct corresponding to |locale|, which must outlive @@ -390,7 +393,14 @@ @implementation FlutterEngine { FlutterThreadSynchronizer* _threadSynchronizer; + // The next available view ID. int _nextViewId; + + // Whether the application is currently the active application. + BOOL _active; + + // Whether any portion of the application is currently visible. + BOOL _visible; } - (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project { @@ -402,7 +412,8 @@ - (instancetype)initWithName:(NSString*)labelPrefix allowHeadlessExecution:(BOOL)allowHeadlessExecution { self = [super init]; NSAssert(self, @"Super init cannot be nil"); - + _active = NO; + _visible = NO; _project = project ?: [[FlutterDartProject alloc] init]; _messengerHandlers = [[NSMutableDictionary alloc] init]; _currentMessengerConnection = 1; @@ -433,11 +444,17 @@ - (instancetype)initWithName:(NSString*)labelPrefix [self setUpPlatformViewChannel]; [self setUpAccessibilityChannel]; [self setUpNotificationCenterListeners]; + FlutterAppDelegate* appDelegate = (FlutterAppDelegate*)[NSApp delegate]; + [appDelegate addApplicationLifecycleDelegate:self]; return self; } - (void)dealloc { + FlutterAppDelegate* appDelegate = (FlutterAppDelegate*)[NSApp delegate]; + if (appDelegate != nil) { + [appDelegate removeApplicationLifecycleDelegate:self]; + } @synchronized(_isResponseValid) { [_isResponseValid removeAllObjects]; [_isResponseValid addObject:@NO]; @@ -997,11 +1014,11 @@ - (void)addInternalPlugins { [FlutterMouseCursorPlugin registerWithRegistrar:[self registrarForPlugin:@"mousecursor"]]; [FlutterMenuPlugin registerWithRegistrar:[self registrarForPlugin:@"menu"]]; _settingsChannel = - [FlutterBasicMessageChannel messageChannelWithName:@"flutter/settings" + [FlutterBasicMessageChannel messageChannelWithName:kFlutterSettingsChannel binaryMessenger:self.binaryMessenger codec:[FlutterJSONMessageCodec sharedInstance]]; _platformChannel = - [FlutterMethodChannel methodChannelWithName:@"flutter/platform" + [FlutterMethodChannel methodChannelWithName:kFlutterPlatformChannel binaryMessenger:self.binaryMessenger codec:[FlutterJSONMethodCodec sharedInstance]]; [_platformChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { @@ -1121,6 +1138,60 @@ - (FlutterThreadSynchronizer*)testThreadSynchronizer { return _threadSynchronizer; } +#pragma mark - FlutterAppLifecycleDelegate + +- (void)setApplicationState:(flutter::AppLifecycleState)state { + NSString* nextState = + [[NSString alloc] initWithCString:flutter::AppLifecycleStateToString(state)]; + [self sendOnChannel:kFlutterLifecycleChannel + message:[nextState dataUsingEncoding:NSUTF8StringEncoding]]; +} + +/** + * Called when the |FlutterAppDelegate| gets the applicationWillBecomeActive + * notification. + */ +- (void)handleWillBecomeActive:(NSNotification*)notification { + _active = YES; + if (!_visible) { + [self setApplicationState:flutter::kAppLifecycleStateHidden]; + } else { + [self setApplicationState:flutter::kAppLifecycleStateResumed]; + } +} + +/** + * Called when the |FlutterAppDelegate| gets the applicationWillResignActive + * notification. + */ +- (void)handleWillResignActive:(NSNotification*)notification { + _active = NO; + if (!_visible) { + [self setApplicationState:flutter::kAppLifecycleStateHidden]; + } else { + [self setApplicationState:flutter::kAppLifecycleStateInactive]; + } +} + +/** + * Called when the |FlutterAppDelegate| gets the applicationDidUnhide + * notification. + */ +- (void)handleDidChangeOcclusionState:(NSNotification*)notification API_AVAILABLE(macos(10.9)) { + NSApplicationOcclusionState occlusionState = [[NSApplication sharedApplication] occlusionState]; + if (occlusionState & NSApplicationOcclusionStateVisible) { + _visible = YES; + if (_active) { + [self setApplicationState:flutter::kAppLifecycleStateResumed]; + } else { + [self setApplicationState:flutter::kAppLifecycleStateInactive]; + } + } else { + _visible = NO; + [self setApplicationState:flutter::kAppLifecycleStateHidden]; + } +} + #pragma mark - FlutterBinaryMessenger - (void)sendOnChannel:(nonnull NSString*)channel message:(nullable NSData*)message { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm index cf81a8ad798c2..d888f71b0e048 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm @@ -856,6 +856,74 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable EXPECT_TRUE(updated); } +TEST_F(FlutterEngineTest, HandleLifecycleStates) { + __block flutter::AppLifecycleState sentState; + id engineMock = CreateMockFlutterEngine(nil); + + // Have to enumerate all the values because OCMStub can't capture + // non-Objective-C object arguments. + OCMStub([engineMock setApplicationState:flutter::kAppLifecycleStateDetached]) + .andDo((^(NSInvocation* invocation) { + sentState = kAppLifecycleStateHidden; + })); + OCMStub([engineMock setApplicationState:flutter::kAppLifecycleStateResumed]) + .andDo((^(NSInvocation* invocation) { + sentState = kAppLifecycleStateResumed; + })); + OCMStub([engineMock setApplicationState:flutter::kAppLifecycleStateInactive]) + .andDo((^(NSInvocation* invocation) { + sentState = kAppLifecycleStateInactive; + })); + OCMStub([engineMock setApplicationState:flutter::kAppLifecycleStateHidden]) + .andDo((^(NSInvocation* invocation) { + sentState = kAppLifecycleStateHidden; + })); + OCMStub([engineMock setApplicationState:flutter::kAppLifecycleStatePaused]) + .andDo((^(NSInvocation* invocation) { + sentState = kAppLifecycleStatePaused; + })); + + __block NSApplicationOcclusionState visibility = NSApplicationOcclusionStateVisible; + id mockApplication = OCMPartialMock([NSApplication sharedApplication]); + OCMStub((NSApplicationOcclusionState)[mockApplication occlusionState]) + .andDo(^(NSInvocation* invocation) { + [invocation setReturnValue:&visibility]; + }); + NSApp = mockApplication; + + NSNotification* willBecomeActive = + [[NSNotification alloc] initWithName:NSApplicationWillBecomeActiveNotification + object:nil + userInfo:nil]; + NSNotification* willResignActive = + [[NSNotification alloc] initWithName:NSApplicationWillResignActiveNotification + object:nil + userInfo:nil]; + NSNotification* didChangeOcclusionState = + [[NSNotification alloc] initWithName:NSApplicationDidChangeOcclusionStateNotification + object:nil + userInfo:nil]; + + [engineMock handleDidChangeOcclusionState:didChangeOcclusionState]; + EXPECT_EQ(sentState, flutter::kAppLifecycleStateInactive); + + [engineMock handleWillBecomeActive:willBecomeActive]; + EXPECT_EQ(sentState, flutter::kAppLifecycleStateResumed); + + [engineMock handleWillResignActive:willResignActive]; + EXPECT_EQ(sentState, flutter::kAppLifecycleStateInactive); + + visibility = 0; + [engineMock handleDidChangeOcclusionState:didChangeOcclusionState]; + EXPECT_EQ(sentState, flutter::kAppLifecycleStateHidden); + + [engineMock handleWillBecomeActive:willBecomeActive]; + EXPECT_EQ(sentState, flutter::kAppLifecycleStateHidden); + + [engineMock handleWillResignActive:willResignActive]; + EXPECT_EQ(sentState, flutter::kAppLifecycleStateHidden); +} + } // namespace flutter::testing // NOLINTEND(clang-analyzer-core.StackAddressEscape) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h index be973222ca95d..062dc631e722c 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h @@ -8,6 +8,8 @@ #include +#include "flutter/shell/platform/common/application_lifecycle.h" + #import "flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h" @@ -172,6 +174,11 @@ typedef NS_ENUM(NSInteger, FlutterAppExitResponse) { - (nonnull FlutterPlatformViewController*)platformViewController; +/** + * Handles changes to the application state, sending them to the framework. + */ +- (void)setApplicationState:(flutter::AppLifecycleState)state; + // Accessibility API. /** diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 5cb9f2839aaa8..4af1f89fe6bd3 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -158,6 +158,7 @@ source_set("flutter_linux_sources") { ] deps = [ + "//flutter/shell/platform/common:common_cpp_enums", "//flutter/shell/platform/common:common_cpp_input", "//flutter/shell/platform/common:common_cpp_switches", "//flutter/shell/platform/embedder:embedder_headers", @@ -257,6 +258,7 @@ executable("flutter_linux_unittests") { ":flutter_linux_gschemas", ":flutter_linux_sources", "//flutter/runtime:libdart", + "//flutter/shell/platform/common:common_cpp_enums", "//flutter/shell/platform/embedder:embedder_headers", "//flutter/shell/platform/embedder:embedder_test_utils", "//flutter/testing", diff --git a/shell/platform/linux/fl_engine.cc b/shell/platform/linux/fl_engine.cc index 0ae331b328375..a7ce20871348e 100644 --- a/shell/platform/linux/fl_engine.cc +++ b/shell/platform/linux/fl_engine.cc @@ -10,6 +10,7 @@ #include #include +#include "flutter/shell/platform/common/application_lifecycle.h" #include "flutter/shell/platform/common/engine_switches.h" #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/linux/fl_binary_messenger_private.h" @@ -23,6 +24,7 @@ #include "flutter/shell/platform/linux/fl_texture_gl_private.h" #include "flutter/shell/platform/linux/fl_texture_registrar_private.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_plugin_registry.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_string_codec.h" // Unique number associated with platform tasks. static constexpr size_t kPlatformTaskRunnerIdentifier = 1; @@ -32,6 +34,8 @@ static constexpr size_t kPlatformTaskRunnerIdentifier = 1; static constexpr int32_t kMousePointerDeviceId = 0; static constexpr int32_t kPointerPanZoomDeviceId = 1; +static constexpr const char* kFlutterLifecycleChannel = "flutter/lifecycle"; + struct _FlEngine { GObject parent_instance; @@ -122,6 +126,25 @@ static void parse_locale(const gchar* locale, } } +static void set_app_lifecycle_state(FlEngine* self, + flutter::AppLifecycleState state) { + FlBinaryMessenger* binary_messenger = fl_engine_get_binary_messenger(self); + + g_autoptr(FlValue) value = + fl_value_new_string(flutter::AppLifecycleStateToString(state)); + g_autoptr(FlStringCodec) codec = fl_string_codec_new(); + g_autoptr(GBytes) message = + fl_message_codec_encode_message(FL_MESSAGE_CODEC(codec), value, nullptr); + + if (message == nullptr) { + return; + } + + fl_binary_messenger_send_on_channel(binary_messenger, + kFlutterLifecycleChannel, message, + nullptr, nullptr, nullptr); +} + // Passes locale information to the Flutter engine. static void setup_locales(FlEngine* self) { const gchar* const* languages = g_get_language_names(); @@ -721,6 +744,18 @@ GBytes* fl_engine_send_platform_message_finish(FlEngine* self, return static_cast(g_task_propagate_pointer(G_TASK(result), error)); } +void fl_engine_send_window_state_event(FlEngine* self, + gboolean visible, + gboolean focused) { + if (visible && focused) { + set_app_lifecycle_state(self, flutter::kAppLifecycleStateResumed); + } else if (visible) { + set_app_lifecycle_state(self, flutter::kAppLifecycleStateInactive); + } else { + set_app_lifecycle_state(self, flutter::kAppLifecycleStateHidden); + } +} + void fl_engine_send_window_metrics_event(FlEngine* self, size_t width, size_t height, diff --git a/shell/platform/linux/fl_engine_private.h b/shell/platform/linux/fl_engine_private.h index 0d1dd1c958436..4b0227a62750a 100644 --- a/shell/platform/linux/fl_engine_private.h +++ b/shell/platform/linux/fl_engine_private.h @@ -167,6 +167,18 @@ void fl_engine_send_window_metrics_event(FlEngine* engine, size_t height, double pixel_ratio); +/** + * fl_engine_send_window_state_event: + * @engine: an #FlEngine. + * @visible: whether the window is currently visible or not. + * @focused: whether the window is currently focused or not. + * + * Sends a window state event to the engine. + */ +void fl_engine_send_window_state_event(FlEngine* engine, + gboolean visible, + gboolean focused); + /** * fl_engine_send_mouse_pointer_event: * @engine: an #FlEngine. diff --git a/shell/platform/linux/fl_engine_test.cc b/shell/platform/linux/fl_engine_test.cc index a1d6e1d650eaf..e5684165e6f78 100644 --- a/shell/platform/linux/fl_engine_test.cc +++ b/shell/platform/linux/fl_engine_test.cc @@ -5,10 +5,12 @@ // Included first as it collides with the X11 headers. #include "gtest/gtest.h" +#include "flutter/shell/platform/common/application_lifecycle.h" #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" #include "flutter/shell/platform/linux/fl_engine_private.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_string_codec.h" #include "flutter/shell/platform/linux/testing/fl_test.h" // MOCK_ENGINE_PROC is leaky by design @@ -423,6 +425,39 @@ TEST(FlEngineTest, SwitchesEmpty) { EXPECT_EQ(switches->len, 0U); } +TEST(FlEngineTest, SendWindowStateEvent) { + g_autoptr(FlEngine) engine = make_mock_engine(); + FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); + + bool called = false; + flutter::AppLifecycleState state = flutter::kAppLifecycleStateDetached; + embedder_api->SendPlatformMessage = MOCK_ENGINE_PROC( + SendPlatformMessage, + ([&called, &state](auto engine, const FlutterPlatformMessage* message) { + EXPECT_STREQ(message->channel, "flutter/lifecycle"); + called = true; + g_autoptr(FlStringCodec) codec = fl_string_codec_new(); + g_autoptr(GBytes) data = + g_bytes_new(message->message, message->message_size); + g_autoptr(GError) error = nullptr; + g_autoptr(FlValue) parsed_state = fl_message_codec_decode_message( + FL_MESSAGE_CODEC(codec), data, &error); + + state = flutter::StringToAppLifecycleState( + fl_value_get_string(parsed_state)); + return kSuccess; + })); + fl_engine_send_window_state_event(engine, false, false); + EXPECT_EQ(state, flutter::kAppLifecycleStateHidden); + fl_engine_send_window_state_event(engine, false, true); + EXPECT_EQ(state, flutter::kAppLifecycleStateHidden); + fl_engine_send_window_state_event(engine, true, false); + EXPECT_EQ(state, flutter::kAppLifecycleStateInactive); + fl_engine_send_window_state_event(engine, true, true); + EXPECT_EQ(state, flutter::kAppLifecycleStateResumed); + EXPECT_TRUE(called); +} + #ifndef FLUTTER_RELEASE TEST(FlEngineTest, Switches) { g_autoptr(FlEngine) engine = make_mock_engine(); diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index 58f11ebf68b10..46540d3b60c43 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -42,6 +42,9 @@ struct _FlView { // Pointer button state recorded for sending status updates. int64_t button_state; + // Current state information for the window associated with this view. + GdkWindowState window_state; + // Flutter system channel handlers. FlAccessibilityPlugin* accessibility_plugin; FlKeyboardManager* keyboard_manager; @@ -59,7 +62,9 @@ struct _FlView { /* FlKeyboardViewDelegate related properties */ KeyboardLayoutNotifier keyboard_layout_notifier; GdkKeymap* keymap; - gulong keymap_keys_changed_cb_id; // Signal connection ID. + gulong keymap_keys_changed_cb_id; // Signal connection ID for + // keymap-keys-changed + gulong window_state_cb_id; // Signal connection ID for window-state-changed }; enum { kPropFlutterProject = 1, kPropLast }; @@ -235,6 +240,8 @@ static void on_pre_engine_restart_cb(FlEngine* engine, gpointer user_data) { g_clear_object(&self->scrolling_manager); init_keyboard(self); init_scrolling(self); + self->window_state = + gdk_window_get_state(gtk_widget_get_window(GTK_WIDGET(self))); } // Implements FlPluginRegistry::get_registrar_for_plugin. @@ -480,12 +487,42 @@ static void gesture_zoom_end_cb(GtkGestureZoom* gesture, fl_scrolling_manager_handle_zoom_end(self->scrolling_manager); } +static gboolean window_state_event_cb(GtkWidget* widget, + GdkEvent* event, + gpointer user_data) { + g_return_val_if_fail(FL_IS_VIEW(user_data), FALSE); + g_return_val_if_fail(FL_IS_ENGINE(FL_VIEW(user_data)->engine), FALSE); + FlView* self = FL_VIEW(user_data); + GdkWindowState state = event->window_state.new_window_state; + GdkWindowState previous_state = self->window_state; + self->window_state = state; + bool was_visible = !((previous_state & GDK_WINDOW_STATE_WITHDRAWN) || + (previous_state & GDK_WINDOW_STATE_ICONIFIED)); + bool is_visible = !((state & GDK_WINDOW_STATE_WITHDRAWN) || + (state & GDK_WINDOW_STATE_ICONIFIED)); + bool was_focused = (previous_state & GDK_WINDOW_STATE_FOCUSED); + bool is_focused = (state & GDK_WINDOW_STATE_FOCUSED); + if (was_visible != is_visible || was_focused != is_focused) { + if (self->engine != nullptr) { + fl_engine_send_window_state_event(FL_ENGINE(self->engine), is_visible, + is_focused); + } + } + return FALSE; +} + static void realize_cb(GtkWidget* widget) { FlView* self = FL_VIEW(widget); g_autoptr(GError) error = nullptr; // Handle requests by the user to close the application. GtkWidget* toplevel_window = gtk_widget_get_toplevel(GTK_WIDGET(self)); + + // Listen to window state changes. + self->window_state_cb_id = + g_signal_connect(toplevel_window, "window-state-event", + G_CALLBACK(window_state_event_cb), self); + g_signal_connect(toplevel_window, "delete-event", G_CALLBACK(window_delete_event_cb), self); @@ -624,6 +661,12 @@ static void fl_view_dispose(GObject* object) { nullptr); } + if (self->window_state_cb_id != 0) { + GtkWidget* toplevel_window = gtk_widget_get_toplevel(GTK_WIDGET(self)); + g_signal_handler_disconnect(toplevel_window, self->window_state_cb_id); + self->window_state_cb_id = 0; + } + g_clear_object(&self->project); g_clear_object(&self->renderer); g_clear_object(&self->engine); @@ -683,6 +726,8 @@ static void fl_view_class_init(FlViewClass* klass) { static void fl_view_init(FlView* self) { gtk_widget_set_can_focus(GTK_WIDGET(self), TRUE); + self->window_state = gdk_window_get_state( + gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(self)))); } G_MODULE_EXPORT FlView* fl_view_new(FlDartProject* project) { diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index db9053a34e453..0effa94ed4dc8 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -129,6 +129,7 @@ source_set("flutter_windows_source") { public_deps = [ "//flutter/fml:string_conversion", "//flutter/shell/platform/common:common_cpp_accessibility", + "//flutter/shell/platform/common:common_cpp_enums", ] deps = [ diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index 8d8f3f15d358d..5b9106f62f743 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -397,6 +397,7 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) { displays.data(), displays.size()); SendSystemLocales(); + SetLifecycleState(flutter::kAppLifecycleStateResumed); settings_plugin_->StartWatching(); settings_plugin_->SendSettings(); @@ -562,6 +563,14 @@ void FlutterWindowsEngine::SetNextFrameCallback(fml::closure callback) { this); } +void FlutterWindowsEngine::SetLifecycleState(flutter::AppLifecycleState state) { + const std::string& state_str = flutter::AppLifecycleStateToString(state); + + SendPlatformMessage("flutter/lifecycle", + reinterpret_cast(state_str.c_str()), + state_str.size(), nullptr, nullptr); +} + void FlutterWindowsEngine::SendSystemLocales() { std::vector languages = GetPreferredLanguageInfo(*windows_registry_); diff --git a/shell/platform/windows/flutter_windows_engine.h b/shell/platform/windows/flutter_windows_engine.h index 36d21f822df54..5209e02140d34 100644 --- a/shell/platform/windows/flutter_windows_engine.h +++ b/shell/platform/windows/flutter_windows_engine.h @@ -16,6 +16,7 @@ #include "flutter/fml/closure.h" #include "flutter/fml/macros.h" #include "flutter/shell/platform/common/accessibility_bridge.h" +#include "flutter/shell/platform/common/application_lifecycle.h" #include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h" #include "flutter/shell/platform/common/client_wrapper/include/flutter/basic_message_channel.h" #include "flutter/shell/platform/common/incoming_message_dispatcher.h" @@ -306,6 +307,9 @@ class FlutterWindowsEngine { // system changes. void SendSystemLocales(); + // Sends the current lifecycle state to the framework. + void SetLifecycleState(flutter::AppLifecycleState state); + // Create the keyboard & text input sub-systems. // // This requires that a view is attached to the engine. From 710998f8310cd904daadfb2ce750c96354c901b6 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Thu, 27 Apr 2023 16:15:21 -0700 Subject: [PATCH 02/21] Fix tests --- .../FlutterActivityAndFragmentDelegateTest.java | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java index f6d1eb04e5b28..b95c3c66ee310 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -143,7 +143,8 @@ public void itSendsLifecycleEventsToFlutter() { verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused(); verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached(); - // When the Activity/Fragment is paused, an inactive message should have been sent to Flutter. + // When the Activity/Fragment is paused, an inactive message (not paused) + // should have been sent to Flutter. delegate.onPause(); verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused(); verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused(); @@ -152,20 +153,6 @@ public void itSendsLifecycleEventsToFlutter() { verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused(); verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached(); - // When the app regains focus, it should go to resumed again. - delegate.onWindowFocusChanged(true); - verify(mockFlutterEngine.getLifecycleChannel(), times(2)).appIsResumed(); - verify(mockFlutterEngine.getLifecycleChannel(), times(1)).appIsInactive(); - verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused(); - verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached(); - - // When the Activity/Fragment is paused, an inactive message should have been sent to Flutter. - delegate.onPause(); - verify(mockFlutterEngine.getLifecycleChannel(), times(2)).appIsResumed(); - verify(mockFlutterEngine.getLifecycleChannel(), times(2)).appIsInactive(); - verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused(); - verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached(); - // When the Activity/Fragment is stopped, a paused message should have been sent to Flutter. // Notice that Flutter uses the term "paused" in a different way, and at a different time // than the Android OS. From 9bc54d2c94bc7c2062d2b22712f732763430ce90 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 28 Apr 2023 09:39:45 -0700 Subject: [PATCH 03/21] Add a test for FlutterAppLifecycleDelegate.mm --- runtime/platform_data.h | 2 +- ...lutterActivityAndFragmentDelegateTest.java | 3 +- shell/platform/darwin/macos/BUILD.gn | 1 + .../Source/FlutterAppLifecycleDelegateTest.mm | 222 ++++++++++++++++++ 4 files changed, 225 insertions(+), 3 deletions(-) create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm diff --git a/runtime/platform_data.h b/runtime/platform_data.h index 1bb6cc02322b8..b6d4c65f94d19 100644 --- a/runtime/platform_data.h +++ b/runtime/platform_data.h @@ -39,7 +39,7 @@ struct PlatformData { std::string variant_code; std::vector locale_data; std::string user_settings_data = "{}"; - std::string lifecycle_state = ""; + std::string lifecycle_state; bool semantics_enabled = false; bool assistive_technology_enabled = false; int32_t accessibility_feature_flags_ = 0; diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java index b95c3c66ee310..91669d276f254 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -143,8 +143,7 @@ public void itSendsLifecycleEventsToFlutter() { verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsPaused(); verify(mockFlutterEngine.getLifecycleChannel(), never()).appIsDetached(); - // When the Activity/Fragment is paused, an inactive message (not paused) - // should have been sent to Flutter. + // When the Activity/Fragment is paused, an inactive message should have been sent to Flutter. delegate.onPause(); verify(mockFlutterEngine.getLifecycleChannel(), times(1)).aWindowIsFocused(); verify(mockFlutterEngine.getLifecycleChannel(), times(1)).noWindowsAreFocused(); diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index 5beb9e9b0d821..a3f9b08578c26 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -170,6 +170,7 @@ executable("flutter_desktop_darwin_unittests") { sources = [ "framework/Source/AccessibilityBridgeMacTest.mm", + "framework/Source/FlutterAppLifecycleDelegateTest.mm", "framework/Source/FlutterChannelKeyResponderTest.mm", "framework/Source/FlutterCompositorTest.mm", "framework/Source/FlutterEmbedderExternalTextureTest.mm", diff --git a/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm new file mode 100644 index 0000000000000..8a8bd5e121bb7 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm @@ -0,0 +1,222 @@ +// 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/macos/framework/Headers/FlutterAppLifecycleDelegate.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h" + +#import "flutter/testing/testing.h" +#include "third_party/googletest/googletest/include/gtest/gtest.h" + +@interface TestFlutterAppLifecycleDelegate : NSObject +@property(nonatomic, readwrite, nullable) NSNotification* lastNotification; +@end + +@implementation TestFlutterAppLifecycleDelegate + +- (void)setNotification:(NSNotification*)notification { + self.lastNotification = notification; +} + +- (void)handleWillFinishLaunching:(NSNotification*)notification { + [self setNotification:notification]; +} + +- (void)handleDidFinishLaunching:(NSNotification*)notification { + [self setNotification:notification]; +} + +- (void)handleWillBecomeActive:(NSNotification*)notification { + [self setNotification:notification]; +} + +- (void)handleDidBecomeActive:(NSNotification*)notification { + [self setNotification:notification]; +} + +- (void)handleWillResignActive:(NSNotification*)notification { + [self setNotification:notification]; +} + +- (void)handleDidResignActive:(NSNotification*)notification { + [self setNotification:notification]; +} + +- (void)handleWillHide:(NSNotification*)notification { + [self setNotification:notification]; +} + +- (void)handleDidHide:(NSNotification*)notification { + [self setNotification:notification]; +} + +- (void)handleWillUnhide:(NSNotification*)notification { + [self setNotification:notification]; +} + +- (void)handleDidUnhide:(NSNotification*)notification { + [self setNotification:notification]; +} + +- (void)handleDidChangeScreenParameters:(NSNotification*)notification { + [self setNotification:notification]; +} + +- (void)handleDidChangeOcclusionState:(NSNotification*)notification API_AVAILABLE(macos(10.9)) { + [self setNotification:notification]; +} + +- (void)handleWillTerminate:(NSNotification*)notification { + [self setNotification:notification]; +} + +@end + +namespace flutter::testing { + +TEST(FlutterAppLifecycleDelegateTest, RespondsToWillFinishLaunching) { + FlutterAppLifecycleRegistrar* registrar = [[FlutterAppLifecycleRegistrar alloc] init]; + TestFlutterAppLifecycleDelegate* delegate = [[TestFlutterAppLifecycleDelegate alloc] init]; + [registrar addDelegate:delegate]; + + NSNotification* willFinishLaunching = + [NSNotification notificationWithName:NSApplicationWillFinishLaunchingNotification object:nil]; + [registrar handleWillFinishLaunching:willFinishLaunching]; + EXPECT_EQ([delegate lastNotification], willFinishLaunching); +} + +TEST(FlutterAppLifecycleDelegateTest, RespondsToDidFinishLaunching) { + FlutterAppLifecycleRegistrar* registrar = [[FlutterAppLifecycleRegistrar alloc] init]; + TestFlutterAppLifecycleDelegate* delegate = [[TestFlutterAppLifecycleDelegate alloc] init]; + [registrar addDelegate:delegate]; + + NSNotification* didFinishLaunching = + [NSNotification notificationWithName:NSApplicationDidFinishLaunchingNotification object:nil]; + [registrar handleDidFinishLaunching:didFinishLaunching]; + EXPECT_EQ([delegate lastNotification], didFinishLaunching); +} + +TEST(FlutterAppLifecycleDelegateTest, RespondsToWillBecomeActive) { + FlutterAppLifecycleRegistrar* registrar = [[FlutterAppLifecycleRegistrar alloc] init]; + TestFlutterAppLifecycleDelegate* delegate = [[TestFlutterAppLifecycleDelegate alloc] init]; + [registrar addDelegate:delegate]; + + NSNotification* willBecomeActive = + [NSNotification notificationWithName:NSApplicationWillBecomeActiveNotification object:nil]; + [registrar handleWillBecomeActive:willBecomeActive]; + EXPECT_EQ([delegate lastNotification], willBecomeActive); +} + +TEST(FlutterAppLifecycleDelegateTest, RespondsToDidBecomeActive) { + FlutterAppLifecycleRegistrar* registrar = [[FlutterAppLifecycleRegistrar alloc] init]; + TestFlutterAppLifecycleDelegate* delegate = [[TestFlutterAppLifecycleDelegate alloc] init]; + [registrar addDelegate:delegate]; + + NSNotification* didBecomeActive = + [NSNotification notificationWithName:NSApplicationDidBecomeActiveNotification object:nil]; + [registrar handleDidBecomeActive:didBecomeActive]; + EXPECT_EQ([delegate lastNotification], didBecomeActive); +} + +TEST(FlutterAppLifecycleDelegateTest, RespondsToWillResignActive) { + FlutterAppLifecycleRegistrar* registrar = [[FlutterAppLifecycleRegistrar alloc] init]; + TestFlutterAppLifecycleDelegate* delegate = [[TestFlutterAppLifecycleDelegate alloc] init]; + [registrar addDelegate:delegate]; + + NSNotification* willResignActive = + [NSNotification notificationWithName:NSApplicationWillResignActiveNotification object:nil]; + [registrar handleWillResignActive:willResignActive]; + EXPECT_EQ([delegate lastNotification], willResignActive); +} + +TEST(FlutterAppLifecycleDelegateTest, RespondsToDidResignActive) { + FlutterAppLifecycleRegistrar* registrar = [[FlutterAppLifecycleRegistrar alloc] init]; + TestFlutterAppLifecycleDelegate* delegate = [[TestFlutterAppLifecycleDelegate alloc] init]; + [registrar addDelegate:delegate]; + + NSNotification* didResignActive = + [NSNotification notificationWithName:NSApplicationDidResignActiveNotification object:nil]; + [registrar handleDidResignActive:didResignActive]; + EXPECT_EQ([delegate lastNotification], didResignActive); +} + +TEST(FlutterAppLifecycleDelegateTest, RespondsToWillTerminate) { + FlutterAppLifecycleRegistrar* registrar = [[FlutterAppLifecycleRegistrar alloc] init]; + TestFlutterAppLifecycleDelegate* delegate = [[TestFlutterAppLifecycleDelegate alloc] init]; + [registrar addDelegate:delegate]; + + NSNotification* applicationWillTerminate = + [NSNotification notificationWithName:NSApplicationWillTerminateNotification object:nil]; + [registrar handleWillTerminate:applicationWillTerminate]; + EXPECT_EQ([delegate lastNotification], applicationWillTerminate); +} + +TEST(FlutterAppLifecycleDelegateTest, RespondsToWillHide) { + FlutterAppLifecycleRegistrar* registrar = [[FlutterAppLifecycleRegistrar alloc] init]; + TestFlutterAppLifecycleDelegate* delegate = [[TestFlutterAppLifecycleDelegate alloc] init]; + [registrar addDelegate:delegate]; + + NSNotification* willHide = [NSNotification notificationWithName:NSApplicationWillHideNotification + object:nil]; + [registrar handleWillHide:willHide]; + EXPECT_EQ([delegate lastNotification], willHide); +} + +TEST(FlutterAppLifecycleDelegateTest, RespondsToWillUnhide) { + FlutterAppLifecycleRegistrar* registrar = [[FlutterAppLifecycleRegistrar alloc] init]; + TestFlutterAppLifecycleDelegate* delegate = [[TestFlutterAppLifecycleDelegate alloc] init]; + [registrar addDelegate:delegate]; + + NSNotification* willUnhide = + [NSNotification notificationWithName:NSApplicationWillUnhideNotification object:nil]; + [registrar handleWillUnhide:willUnhide]; + EXPECT_EQ([delegate lastNotification], willUnhide); +} + +TEST(FlutterAppLifecycleDelegateTest, RespondsToDidHide) { + FlutterAppLifecycleRegistrar* registrar = [[FlutterAppLifecycleRegistrar alloc] init]; + TestFlutterAppLifecycleDelegate* delegate = [[TestFlutterAppLifecycleDelegate alloc] init]; + [registrar addDelegate:delegate]; + + NSNotification* didHide = [NSNotification notificationWithName:NSApplicationDidHideNotification + object:nil]; + [registrar handleDidHide:didHide]; + EXPECT_EQ([delegate lastNotification], didHide); +} + +TEST(FlutterAppLifecycleDelegateTest, RespondsToDidUnhide) { + FlutterAppLifecycleRegistrar* registrar = [[FlutterAppLifecycleRegistrar alloc] init]; + TestFlutterAppLifecycleDelegate* delegate = [[TestFlutterAppLifecycleDelegate alloc] init]; + [registrar addDelegate:delegate]; + + NSNotification* didUnhide = + [NSNotification notificationWithName:NSApplicationDidUnhideNotification object:nil]; + [registrar handleDidUnhide:didUnhide]; + EXPECT_EQ([delegate lastNotification], didUnhide); +} + +TEST(FlutterAppLifecycleDelegateTest, RespondsToDidChangeScreenParameters) { + FlutterAppLifecycleRegistrar* registrar = [[FlutterAppLifecycleRegistrar alloc] init]; + TestFlutterAppLifecycleDelegate* delegate = [[TestFlutterAppLifecycleDelegate alloc] init]; + [registrar addDelegate:delegate]; + + NSNotification* didChangeScreenParameters = + [NSNotification notificationWithName:NSApplicationDidChangeScreenParametersNotification + object:nil]; + [registrar handleDidChangeScreenParameters:didChangeScreenParameters]; + EXPECT_EQ([delegate lastNotification], didChangeScreenParameters); +} + +TEST(FlutterAppLifecycleDelegateTest, RespondsToDidChangeOcclusionState) { + FlutterAppLifecycleRegistrar* registrar = [[FlutterAppLifecycleRegistrar alloc] init]; + TestFlutterAppLifecycleDelegate* delegate = [[TestFlutterAppLifecycleDelegate alloc] init]; + [registrar addDelegate:delegate]; + + NSNotification* didChangeOcclusionState = + [NSNotification notificationWithName:NSApplicationDidChangeOcclusionStateNotification + object:nil]; + [registrar handleDidChangeOcclusionState:didChangeOcclusionState]; + EXPECT_EQ([delegate lastNotification], didChangeOcclusionState); +} + +} // namespace flutter::testing From a7b20cd6e1a46cc0a1c6dfb091943aa1b887bd72 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 28 Apr 2023 13:17:07 -0700 Subject: [PATCH 04/21] Update some docs --- lib/ui/platform_dispatcher.dart | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index 93cc78bf57b67..622cad30556a9 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -1729,8 +1729,9 @@ enum AppLifecycleState { /// it is the default state before the application begins running. detached, - /// The application is in the default running mode for a running application - /// that has input focus and is visible. + /// On all platforms, this state indicates that the application is in the + /// default running mode for a running application that has input focus and is + /// visible. /// /// On Android, this state corresponds to the Flutter host view having focus /// ([`Activity.onWindowFocusChanged`](https://developer.android.com/reference/android/app/Activity#onWindowFocusChanged(boolean)) @@ -1743,6 +1744,9 @@ enum AppLifecycleState { /// was called with false), but hasn't had /// [`Activity.onPause`](https://developer.android.com/reference/android/app/Activity#onPause()) /// called on it. + /// + /// On iOS and macOS, this corresponds to the app running in the foreground + /// active state. resumed, /// At least one view of the application is visible, but none have input @@ -1751,10 +1755,10 @@ enum AppLifecycleState { /// On non-web desktop platforms, this corresponds to an application that is /// not in the foreground, but still has visible windows. /// - /// On the web, this corresponds to an application that is not running in a - /// window or tab that has input focus. + /// On the web, this corresponds to an application that is running in a + /// window or tab that does not have input focus. /// - /// On iOS, this state corresponds to the Flutter host view running in the + /// On iOS and macOS, this state corresponds to the Flutter host view running in the /// foreground inactive state. Apps transition to this state when in a phone /// call, when responding to a TouchID request, when entering the app switcher /// or the control center, or when the UIViewController hosting the Flutter @@ -1765,10 +1769,11 @@ enum AppLifecycleState { /// [`Activity.onPause`](https://developer.android.com/reference/android/app/Activity#onPause()) /// has been called), or in Android's "resumed" state (i.e. /// [`Activity.onResume`](https://developer.android.com/reference/android/app/Activity#onResume()) - /// has been called) but it has lost window focus. Examples of when apps + /// has been called) but does not have window focus. Examples of when apps /// transition to this state include when the app is partially obscured or - /// another activity is focused, such as: a split-screen app, a phone call, a - /// picture-in-picture app, a system dialog, another view, when the + /// another activity is focused, a app running in a split screen that isn't + /// the current app, an app interrupted by a phone call, a picture-in-picture + /// app, a system dialog, another view. It will also be inactive when the /// notification window shade is down, or the application switcher is visible. /// /// On Android and iOS, apps in this state should assume that they may be From 8fc97515e6c2def41db0b5eccd67113b7de03286 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 28 Apr 2023 14:57:00 -0700 Subject: [PATCH 05/21] Fix licenses --- ci/licenses_golden/licenses_flutter | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 46bd0b150f66f..c70a21f8233f3 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2696,6 +2696,7 @@ ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/Accessibil ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.mm + ../../../flutter/LICENSE @@ -5372,6 +5373,7 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/Accessibilit FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.mm From 963444af9b66d5984ff7fd55a84e8fc0863b7e96 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Tue, 2 May 2023 15:20:51 -0700 Subject: [PATCH 06/21] Minor doc change --- lib/ui/platform_dispatcher.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index 622cad30556a9..e9f2fe4eed22f 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -1709,6 +1709,8 @@ class FrameTiming { /// /// See also: /// +/// * [AppLifecycleListener], an object used observe the lifecycle state +/// that provides state transition callbacks. /// * [WidgetsBindingObserver], for a mechanism to observe the lifecycle state /// from the widgets layer. /// * iOS's [IOKit activity lifecycle](https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle?language=objc) documentation. From ac5533d450a7b6cee5990472ee5962a82c0036e1 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Wed, 3 May 2023 11:58:30 -0700 Subject: [PATCH 07/21] Fix tests --- .../Source/FlutterAppLifecycleDelegateTest.mm | 6 +- .../framework/Source/FlutterEngineTest.mm | 87 +++++-------------- 2 files changed, 26 insertions(+), 67 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm index 8a8bd5e121bb7..1e3fc2b0413fc 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm @@ -215,8 +215,10 @@ - (void)handleWillTerminate:(NSNotification*)notification { NSNotification* didChangeOcclusionState = [NSNotification notificationWithName:NSApplicationDidChangeOcclusionStateNotification object:nil]; - [registrar handleDidChangeOcclusionState:didChangeOcclusionState]; - EXPECT_EQ([delegate lastNotification], didChangeOcclusionState); + if ([registrar respondsToSelector:@selector(handleDidChangeOcclusionState:)]) { + [registrar handleDidChangeOcclusionState:didChangeOcclusionState]; + EXPECT_EQ([delegate lastNotification], didChangeOcclusionState); + } } } // namespace flutter::testing diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm index d888f71b0e048..598985e5c3b17 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm @@ -805,58 +805,7 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable EXPECT_TRUE(announced); } -TEST_F(FlutterEngineTest, RunWithEntrypointUpdatesDisplayConfig) { - BOOL updated = NO; - FlutterEngine* engine = GetFlutterEngine(); - auto original_update_displays = engine.embedderAPI.NotifyDisplayUpdate; - engine.embedderAPI.NotifyDisplayUpdate = MOCK_ENGINE_PROC( - NotifyDisplayUpdate, ([&updated, &original_update_displays]( - auto engine, auto update_type, auto* displays, auto display_count) { - updated = YES; - return original_update_displays(engine, update_type, displays, display_count); - })); - - EXPECT_TRUE([engine runWithEntrypoint:@"main"]); - EXPECT_TRUE(updated); - - updated = NO; - [[NSNotificationCenter defaultCenter] - postNotificationName:NSApplicationDidChangeScreenParametersNotification - object:nil]; - EXPECT_TRUE(updated); -} - -TEST_F(FlutterEngineTest, NotificationsUpdateDisplays) { - BOOL updated = NO; - FlutterEngine* engine = GetFlutterEngine(); - auto original_set_viewport_metrics = engine.embedderAPI.SendWindowMetricsEvent; - engine.embedderAPI.SendWindowMetricsEvent = MOCK_ENGINE_PROC( - SendWindowMetricsEvent, - ([&updated, &original_set_viewport_metrics](auto engine, auto* window_metrics) { - updated = YES; - return original_set_viewport_metrics(engine, window_metrics); - })); - - EXPECT_TRUE([engine runWithEntrypoint:@"main"]); - - updated = NO; - [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidChangeScreenNotification - object:nil]; - // No VC. - EXPECT_FALSE(updated); - - FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine - nibName:nil - bundle:nil]; - [viewController loadView]; - viewController.flutterView.frame = CGRectMake(0, 0, 800, 600); - - [[NSNotificationCenter defaultCenter] postNotificationName:NSWindowDidChangeScreenNotification - object:nil]; - EXPECT_TRUE(updated); -} - -TEST_F(FlutterEngineTest, HandleLifecycleStates) { +TEST_F(FlutterEngineTest, HandleLifecycleStates) API_AVAILABLE(macos(10.9)) { __block flutter::AppLifecycleState sentState; id engineMock = CreateMockFlutterEngine(nil); @@ -885,10 +834,12 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable __block NSApplicationOcclusionState visibility = NSApplicationOcclusionStateVisible; id mockApplication = OCMPartialMock([NSApplication sharedApplication]); - OCMStub((NSApplicationOcclusionState)[mockApplication occlusionState]) - .andDo(^(NSInvocation* invocation) { - [invocation setReturnValue:&visibility]; - }); + if ([mockApplication respondsToSelector:@selector(occlusionState)]) { + OCMStub((NSApplicationOcclusionState)[mockApplication occlusionState]) + .andDo(^(NSInvocation* invocation) { + [invocation setReturnValue:&visibility]; + }); + } NSApp = mockApplication; NSNotification* willBecomeActive = @@ -899,13 +850,17 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable [[NSNotification alloc] initWithName:NSApplicationWillResignActiveNotification object:nil userInfo:nil]; - NSNotification* didChangeOcclusionState = - [[NSNotification alloc] initWithName:NSApplicationDidChangeOcclusionStateNotification - object:nil - userInfo:nil]; - [engineMock handleDidChangeOcclusionState:didChangeOcclusionState]; - EXPECT_EQ(sentState, flutter::kAppLifecycleStateInactive); + NSNotification* didChangeOcclusionState; + if ([mockApplication respondsToSelector:@selector(occlusionState)]) { + didChangeOcclusionState = + [[NSNotification alloc] initWithName:NSApplicationDidChangeOcclusionStateNotification + object:nil + userInfo:nil]; + + [engineMock handleDidChangeOcclusionState:didChangeOcclusionState]; + EXPECT_EQ(sentState, flutter::kAppLifecycleStateInactive); + } [engineMock handleWillBecomeActive:willBecomeActive]; EXPECT_EQ(sentState, flutter::kAppLifecycleStateResumed); @@ -913,9 +868,11 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable [engineMock handleWillResignActive:willResignActive]; EXPECT_EQ(sentState, flutter::kAppLifecycleStateInactive); - visibility = 0; - [engineMock handleDidChangeOcclusionState:didChangeOcclusionState]; - EXPECT_EQ(sentState, flutter::kAppLifecycleStateHidden); + if ([mockApplication respondsToSelector:@selector(occlusionState)]) { + visibility = 0; + [engineMock handleDidChangeOcclusionState:didChangeOcclusionState]; + EXPECT_EQ(sentState, flutter::kAppLifecycleStateHidden); + } [engineMock handleWillBecomeActive:willBecomeActive]; EXPECT_EQ(sentState, flutter::kAppLifecycleStateHidden); From dc17f5b0e01d766ccdffca77413484b623c14315 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Wed, 3 May 2023 12:03:28 -0700 Subject: [PATCH 08/21] Fix bad merge --- ci/licenses_golden/licenses_flutter | 2 -- shell/platform/darwin/macos/BUILD.gn | 1 - 2 files changed, 3 deletions(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index c70a21f8233f3..eda9805149d13 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2683,7 +2683,6 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.h + . ORIGIN: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterDartProject.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterPlatformViews.h + ../../../flutter/LICENSE @@ -5359,7 +5358,6 @@ FILE: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.h FILE: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterDartProject.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterPlatformViews.h diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index a3f9b08578c26..86f681b21fe57 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -39,7 +39,6 @@ _framework_binary_subpath = "Versions/A/$_flutter_framework_name" _flutter_framework_headers = [ "framework/Headers/FlutterAppDelegate.h", "framework/Headers/FlutterAppLifecycleDelegate.h", - "framework/Headers/FlutterDartProject.h", "framework/Headers/FlutterEngine.h", "framework/Headers/FlutterMacOS.h", "framework/Headers/FlutterPlatformViews.h", From 829d050fdbade985d18ddbe480a4b20afce10095 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Wed, 3 May 2023 12:56:29 -0700 Subject: [PATCH 09/21] Try to fix macOS tests --- .../platform/darwin/macos/framework/Source/FlutterEngine.mm | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 74a6bc84764aa..4307443cb34d1 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -176,11 +176,9 @@ - (instancetype)initWithEngine:(FlutterEngine*)engine _terminator = terminator ? terminator : ^(id sender) { // Default to actually terminating the application. The terminator exists to // allow tests to override it so that an actual exit doesn't occur. - NSApplication* flutterApp = [NSApplication sharedApplication]; - [flutterApp terminate:sender]; + [NSApp terminate:sender]; }; - FlutterAppDelegate* appDelegate = - (FlutterAppDelegate*)[[NSApplication sharedApplication] delegate]; + FlutterAppDelegate* appDelegate = (FlutterAppDelegate*)[NSApp delegate]; appDelegate.terminationHandler = self; return self; } From bfcdd4ffa924717770b8e813181e387f66adc49a Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Wed, 3 May 2023 17:54:00 -0700 Subject: [PATCH 10/21] Stop mocking NSApplication, it doesn't like it if you don't --- .../framework/Source/FlutterEngineTest.mm | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm index 598985e5c3b17..b6c3e0ad0c458 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm @@ -834,13 +834,10 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable __block NSApplicationOcclusionState visibility = NSApplicationOcclusionStateVisible; id mockApplication = OCMPartialMock([NSApplication sharedApplication]); - if ([mockApplication respondsToSelector:@selector(occlusionState)]) { - OCMStub((NSApplicationOcclusionState)[mockApplication occlusionState]) - .andDo(^(NSInvocation* invocation) { - [invocation setReturnValue:&visibility]; - }); - } - NSApp = mockApplication; + OCMStub((NSApplicationOcclusionState)[mockApplication occlusionState]) + .andDo(^(NSInvocation* invocation) { + [invocation setReturnValue:&visibility]; + }); NSNotification* willBecomeActive = [[NSNotification alloc] initWithName:NSApplicationWillBecomeActiveNotification @@ -852,15 +849,13 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable userInfo:nil]; NSNotification* didChangeOcclusionState; - if ([mockApplication respondsToSelector:@selector(occlusionState)]) { - didChangeOcclusionState = - [[NSNotification alloc] initWithName:NSApplicationDidChangeOcclusionStateNotification - object:nil - userInfo:nil]; - - [engineMock handleDidChangeOcclusionState:didChangeOcclusionState]; - EXPECT_EQ(sentState, flutter::kAppLifecycleStateInactive); - } + didChangeOcclusionState = + [[NSNotification alloc] initWithName:NSApplicationDidChangeOcclusionStateNotification + object:nil + userInfo:nil]; + + [engineMock handleDidChangeOcclusionState:didChangeOcclusionState]; + EXPECT_EQ(sentState, flutter::kAppLifecycleStateInactive); [engineMock handleWillBecomeActive:willBecomeActive]; EXPECT_EQ(sentState, flutter::kAppLifecycleStateResumed); @@ -868,17 +863,17 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable [engineMock handleWillResignActive:willResignActive]; EXPECT_EQ(sentState, flutter::kAppLifecycleStateInactive); - if ([mockApplication respondsToSelector:@selector(occlusionState)]) { - visibility = 0; - [engineMock handleDidChangeOcclusionState:didChangeOcclusionState]; - EXPECT_EQ(sentState, flutter::kAppLifecycleStateHidden); - } + visibility = 0; + [engineMock handleDidChangeOcclusionState:didChangeOcclusionState]; + EXPECT_EQ(sentState, flutter::kAppLifecycleStateHidden); [engineMock handleWillBecomeActive:willBecomeActive]; EXPECT_EQ(sentState, flutter::kAppLifecycleStateHidden); [engineMock handleWillResignActive:willResignActive]; EXPECT_EQ(sentState, flutter::kAppLifecycleStateHidden); + + [mockApplication stopMocking]; } } // namespace flutter::testing From 36a8f119bb158ff271e51e34b225104b540d21f0 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Wed, 3 May 2023 17:56:37 -0700 Subject: [PATCH 11/21] Stop using NSApp global, use [NSApplication sharedApplication] instead --- .../darwin/macos/framework/Source/FlutterEngine.mm | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 4307443cb34d1..29cb80b4f33fd 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -176,9 +176,10 @@ - (instancetype)initWithEngine:(FlutterEngine*)engine _terminator = terminator ? terminator : ^(id sender) { // Default to actually terminating the application. The terminator exists to // allow tests to override it so that an actual exit doesn't occur. - [NSApp terminate:sender]; + [[NSApplication sharedApplication] terminate:sender]; }; - FlutterAppDelegate* appDelegate = (FlutterAppDelegate*)[NSApp delegate]; + FlutterAppDelegate* appDelegate = + (FlutterAppDelegate*)[[NSApplication sharedApplication] delegate]; appDelegate.terminationHandler = self; return self; } @@ -442,14 +443,16 @@ - (instancetype)initWithName:(NSString*)labelPrefix [self setUpPlatformViewChannel]; [self setUpAccessibilityChannel]; [self setUpNotificationCenterListeners]; - FlutterAppDelegate* appDelegate = (FlutterAppDelegate*)[NSApp delegate]; + FlutterAppDelegate* appDelegate = + (FlutterAppDelegate*)[[NSApplication sharedApplication] delegate]; [appDelegate addApplicationLifecycleDelegate:self]; return self; } - (void)dealloc { - FlutterAppDelegate* appDelegate = (FlutterAppDelegate*)[NSApp delegate]; + FlutterAppDelegate* appDelegate = + (FlutterAppDelegate*)[[NSApplication sharedApplication] delegate]; if (appDelegate != nil) { [appDelegate removeApplicationLifecycleDelegate:self]; } @@ -1074,7 +1077,7 @@ - (void)announceAccessibilityMessage:(NSString*)message } - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { if ([call.method isEqualToString:@"SystemNavigator.pop"]) { - [NSApp terminate:self]; + [[NSApplication sharedApplication] terminate:self]; result(nil); } else if ([call.method isEqualToString:@"SystemSound.play"]) { [self playSystemSound:call.arguments]; From 81bbf7148aa68cfa79ba0baed6a822e84b8cccce Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Thu, 4 May 2023 08:52:09 -0700 Subject: [PATCH 12/21] Revert some unneeded refactoring --- .../lib/src/engine/platform_dispatcher.dart | 37 ++++++------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 544a42847b189..0d54cba677de7 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -472,19 +472,6 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { PlatformViewMessageHandler? _platformViewMessageHandler; - static const String _kSkiaChannel = 'flutter/skia'; - static const String _kAssetsChannel = 'flutter/assets'; - static const String _kPlatformChannel = 'flutter/platform'; - static const String _kServiceWorkerChannel = 'flutter/service_worker'; - static const String _kTextInputChannel = 'flutter/textinput'; - static const String _kContextMenuChannel = 'flutter/contextmenu'; - static const String _kMouseCursorChannel = 'flutter/mousecursor'; - static const String _kWebTestE2EChannel = 'flutter/web_test_e2e'; - static const String _kPlatformViewsChannel = 'flutter/platform_views'; - static const String _kAccessibilityChannel = 'flutter/accessibility'; - static const String _kNavigationChannel = 'flutter/navigation'; - static const String _kLifecycleChannel = 'flutter/lifecycle'; - void _sendPlatformMessage( String name, ByteData? data, @@ -522,7 +509,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { switch (name) { /// This should be in sync with shell/common/shell.cc - case _kSkiaChannel: + case 'flutter/skia': const MethodCodec codec = JSONMethodCodec(); final MethodCall decoded = codec.decodeMethodCall(data); switch (decoded.method) { @@ -543,12 +530,12 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { } return; - case _kAssetsChannel: + case 'flutter/assets': final String url = utf8.decode(data!.buffer.asUint8List()); _handleFlutterAssetsMessage(url, callback); return; - case _kPlatformChannel: + case 'flutter/platform': const MethodCodec codec = JSONMethodCodec(); final MethodCall decoded = codec.decodeMethodCall(data); switch (decoded.method) { @@ -604,15 +591,15 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { } // Dispatched by the bindings to delay service worker initialization. - case _kServiceWorkerChannel: + case 'flutter/service_worker': domWindow.dispatchEvent(createDomEvent('Event', 'flutter-first-frame')); return; - case _kTextInputChannel: + case 'flutter/textinput': textEditing.channel.handleTextInput(data, callback); return; - case _kContextMenuChannel: + case 'flutter/contextmenu': const MethodCodec codec = JSONMethodCodec(); final MethodCall decoded = codec.decodeMethodCall(data); switch (decoded.method) { @@ -627,7 +614,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { } return; - case _kMouseCursorChannel: + case 'flutter/mousecursor': const MethodCodec codec = StandardMethodCodec(); final MethodCall decoded = codec.decodeMethodCall(data); final Map arguments = decoded.arguments as Map; @@ -637,7 +624,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { } return; - case _kWebTestE2EChannel: + case 'flutter/web_test_e2e': const MethodCodec codec = JSONMethodCodec(); replyToPlatformMessage( callback, @@ -645,7 +632,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { _handleWebTestEnd2EndMessage(codec, data))); return; - case _kPlatformViewsChannel: + case 'flutter/platform_views': _platformViewMessageHandler ??= PlatformViewMessageHandler( contentManager: platformViewManager, contentHandler: (DomElement content) { @@ -655,14 +642,14 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { _platformViewMessageHandler!.handlePlatformViewCall(data, callback!); return; - case _kAccessibilityChannel: + case 'flutter/accessibility': // In widget tests we want to bypass processing of platform messages. const StandardMessageCodec codec = StandardMessageCodec(); accessibilityAnnouncements.handleMessage(codec, data); replyToPlatformMessage(callback, codec.encodeMessage(true)); return; - case _kNavigationChannel: + case 'flutter/navigation': // TODO(a-wallen): As multi-window support expands, the navigation call // will need to include the view ID. Right now only one view is // supported. @@ -1021,7 +1008,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { void _setAppLifecycleState(ui.AppLifecycleState state) { sendPlatformMessage( - _kLifecycleChannel, + 'flutter/lifecycle', Uint8List.fromList(utf8.encode(state.toString())).buffer.asByteData(), null, ); From bcdbc6b2e16046a902b8abf946f840d17648f79b Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Thu, 4 May 2023 13:07:13 -0700 Subject: [PATCH 13/21] Remove lifecycle enum in favor of string constants --- shell/platform/common/BUILD.gn | 2 - .../platform/common/application_lifecycle.cc | 51 ------------ shell/platform/common/application_lifecycle.h | 82 ++++++++----------- .../macos/framework/Source/FlutterEngine.mm | 5 +- .../framework/Source/FlutterEngineTest.mm | 2 +- .../framework/Source/FlutterEngine_Internal.h | 5 +- shell/platform/linux/fl_engine.cc | 6 +- shell/platform/linux/fl_engine_test.cc | 13 ++- 8 files changed, 50 insertions(+), 116 deletions(-) delete mode 100644 shell/platform/common/application_lifecycle.cc diff --git a/shell/platform/common/BUILD.gn b/shell/platform/common/BUILD.gn index bdf220e65b2b0..7ee769c9ead8d 100644 --- a/shell/platform/common/BUILD.gn +++ b/shell/platform/common/BUILD.gn @@ -63,8 +63,6 @@ source_set("common_cpp_enums") { "platform_provided_menu.h", ] - sources = [ "application_lifecycle.cc" ] - public_configs = [ "//flutter:config", "//flutter/common:flutter_config", diff --git a/shell/platform/common/application_lifecycle.cc b/shell/platform/common/application_lifecycle.cc deleted file mode 100644 index 5f42c9b9a6627..0000000000000 --- a/shell/platform/common/application_lifecycle.cc +++ /dev/null @@ -1,51 +0,0 @@ -// 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. - -#include "flutter/shell/platform/common/application_lifecycle.h" - -#include -#include - -namespace flutter { - -const char* AppLifecycleStateToString(AppLifecycleState state) { - switch (state) { - case kAppLifecycleStateDetached: - return "AppLifecycleState.detached"; - case kAppLifecycleStateResumed: - return "AppLifecycleState.resumed"; - case kAppLifecycleStateInactive: - return "AppLifecycleState.inactive"; - case kAppLifecycleStateHidden: - return "AppLifecycleState.hidden"; - case kAppLifecycleStatePaused: - return "AppLifecycleState.paused"; - default: - assert(false && "Lifecycle state not recognized"); - break; - } -} - -AppLifecycleState StringToAppLifecycleState(const char* char_value) { - std::string value = char_value; - if (value == "AppLifecycleState.detached") { - return kAppLifecycleStateDetached; - } - if (value == "AppLifecycleState.resumed") { - return kAppLifecycleStateResumed; - } - if (value == "AppLifecycleState.inactive") { - return kAppLifecycleStateInactive; - } - if (value == "AppLifecycleState.hidden") { - return kAppLifecycleStateHidden; - } - if (value == "AppLifecycleState.paused") { - return kAppLifecycleStatePaused; - } - assert(false && "Lifecycle state string not recognized"); - return kAppLifecycleStateDetached; -} - -} // namespace flutter diff --git a/shell/platform/common/application_lifecycle.h b/shell/platform/common/application_lifecycle.h index 0296f56cc3a47..78b906d0ee163 100644 --- a/shell/platform/common/application_lifecycle.h +++ b/shell/platform/common/application_lifecycle.h @@ -8,13 +8,14 @@ namespace flutter { /** - * This enum describes the possible lifecycle states of the application. It - * must be kept up to date with changes in the framework's AppLifecycleState - * enum. It is passed to the embedder's |SetLifecycleState| function. + * These constants describe the possible lifecycle states of the application. + * They must be kept up to date with changes in the framework's + * AppLifecycleState enum. They are passed to the embedder's |SetLifecycleState| + * function. * * States not supported on a platform will be synthesized by the framework when - * transitioning between states which are supported, so that all - * implementations share the same state machine. + * transitioning between states which are supported, so that all implementations + * share the same state machine. * * Here is the state machine: * @@ -28,57 +29,44 @@ namespace flutter { * | paused |<------>| hidden |<----->| inactive | * +-----------+ +--------------+ +-----------+ */ -typedef enum { - /** - * Corresponds to AppLifecycleState.detached: The initial state of the state - * machine. On Android and iOS, also the final state of the state machine - * when all views are detached. Other platforms do not enter this state again - * after initially leaving it. - */ - kAppLifecycleStateDetached = 0x0, - /** - * Corresponds to AppLifecycleState.resumed: The nominal "running" state of - * the - * application. The application is visible, has input focus, and is running. - */ - kAppLifecycleStateResumed = 0x1, - - /** - * Corresponds to AppLifecycleState.inactive: At least one view of the - * application is visible, but none have input focus. The application is - * otherwise running normally. - */ - kAppLifecycleStateInactive = 0x2, +/** + * Corresponds to AppLifecycleState.detached: The initial state of the state + * machine. On Android and iOS, also the final state of the state machine + * when all views are detached. Other platforms do not enter this state again + * after initially leaving it. + */ +constexpr const char* kAppLifecycleStateDetached = "AppLifecycleState.detached"; - /** - * Corresponds to AppLifecycleState.hidden: All views of an application are - * hidden, either because the application is being stopped (on iOS and - * Android), or because it is being minimized or on a desktop that is no - * longer visible (on desktop), or on a tab that is no longer visible (on - * web). - */ - kAppLifecycleStateHidden = 0x3, +/** + * Corresponds to AppLifecycleState.resumed: The nominal "running" state of + * the + * application. The application is visible, has input focus, and is running. + */ +constexpr const char* kAppLifecycleStateResumed = "AppLifecycleState.resumed"; - /** - * Corresponds to AppLifecycleState.paused: The application is not running, - * and can be detached or started again at any time. This state is typically - * only entered into on iOS and Android. - */ - kAppLifecycleStatePaused = 0x4, -} AppLifecycleState; +/** + * Corresponds to AppLifecycleState.inactive: At least one view of the + * application is visible, but none have input focus. The application is + * otherwise running normally. + */ +constexpr const char* kAppLifecycleStateInactive = "AppLifecycleState.inactive"; /** - * Converts an AppLifecycleState enum value to a string. + * Corresponds to AppLifecycleState.hidden: All views of an application are + * hidden, either because the application is being stopped (on iOS and + * Android), or because it is being minimized or on a desktop that is no + * longer visible (on desktop), or on a tab that is no longer visible (on + * web). */ -const char* AppLifecycleStateToString(AppLifecycleState state); +constexpr const char* kAppLifecycleStateHidden = "AppLifecycleState.hidden"; /** - * Converts an AppLifecycleState string value to the corresponding enum value. - * - * Will assert if the string doesn't represent an enum value. + * Corresponds to AppLifecycleState.paused: The application is not running, + * and can be detached or started again at any time. This state is typically + * only entered into on iOS and Android. */ -AppLifecycleState StringToAppLifecycleState(const char* char_value); +constexpr const char* kAppLifecycleStatePaused = "AppLifecycleState.paused"; } // namespace flutter diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 29cb80b4f33fd..c3b13016780d3 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -1141,9 +1141,8 @@ - (FlutterThreadSynchronizer*)testThreadSynchronizer { #pragma mark - FlutterAppLifecycleDelegate -- (void)setApplicationState:(flutter::AppLifecycleState)state { - NSString* nextState = - [[NSString alloc] initWithCString:flutter::AppLifecycleStateToString(state)]; +- (void)setApplicationState:(const char*)state { + NSString* nextState = [[NSString alloc] initWithCString:state]; [self sendOnChannel:kFlutterLifecycleChannel message:[nextState dataUsingEncoding:NSUTF8StringEncoding]]; } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm index b6c3e0ad0c458..9a9efc7f9c1a7 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm @@ -806,7 +806,7 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable } TEST_F(FlutterEngineTest, HandleLifecycleStates) API_AVAILABLE(macos(10.9)) { - __block flutter::AppLifecycleState sentState; + __block const char* sentState; id engineMock = CreateMockFlutterEngine(nil); // Have to enumerate all the values because OCMStub can't capture diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h index 062dc631e722c..d01fa45f7e1db 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h @@ -176,8 +176,11 @@ typedef NS_ENUM(NSInteger, FlutterAppExitResponse) { /** * Handles changes to the application state, sending them to the framework. + * + * @param state One of the lifecycle constants in application_lifecycle.h, + * corresponding to the Dart enum AppLifecycleState. */ -- (void)setApplicationState:(flutter::AppLifecycleState)state; +- (void)setApplicationState:(const char*)state; // Accessibility API. diff --git a/shell/platform/linux/fl_engine.cc b/shell/platform/linux/fl_engine.cc index a7ce20871348e..b6976b68199cd 100644 --- a/shell/platform/linux/fl_engine.cc +++ b/shell/platform/linux/fl_engine.cc @@ -126,12 +126,10 @@ static void parse_locale(const gchar* locale, } } -static void set_app_lifecycle_state(FlEngine* self, - flutter::AppLifecycleState state) { +static void set_app_lifecycle_state(FlEngine* self, const gchar* state) { FlBinaryMessenger* binary_messenger = fl_engine_get_binary_messenger(self); - g_autoptr(FlValue) value = - fl_value_new_string(flutter::AppLifecycleStateToString(state)); + g_autoptr(FlValue) value = fl_value_new_string(state); g_autoptr(FlStringCodec) codec = fl_string_codec_new(); g_autoptr(GBytes) message = fl_message_codec_encode_message(FL_MESSAGE_CODEC(codec), value, nullptr); diff --git a/shell/platform/linux/fl_engine_test.cc b/shell/platform/linux/fl_engine_test.cc index e5684165e6f78..075c5e30a3626 100644 --- a/shell/platform/linux/fl_engine_test.cc +++ b/shell/platform/linux/fl_engine_test.cc @@ -430,7 +430,7 @@ TEST(FlEngineTest, SendWindowStateEvent) { FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); bool called = false; - flutter::AppLifecycleState state = flutter::kAppLifecycleStateDetached; + const char* state = flutter::kAppLifecycleStateDetached; embedder_api->SendPlatformMessage = MOCK_ENGINE_PROC( SendPlatformMessage, ([&called, &state](auto engine, const FlutterPlatformMessage* message) { @@ -443,18 +443,17 @@ TEST(FlEngineTest, SendWindowStateEvent) { g_autoptr(FlValue) parsed_state = fl_message_codec_decode_message( FL_MESSAGE_CODEC(codec), data, &error); - state = flutter::StringToAppLifecycleState( - fl_value_get_string(parsed_state)); + state = fl_value_get_string(parsed_state); return kSuccess; })); fl_engine_send_window_state_event(engine, false, false); - EXPECT_EQ(state, flutter::kAppLifecycleStateHidden); + EXPECT_STREQ(state, flutter::kAppLifecycleStateHidden); fl_engine_send_window_state_event(engine, false, true); - EXPECT_EQ(state, flutter::kAppLifecycleStateHidden); + EXPECT_STREQ(state, flutter::kAppLifecycleStateHidden); fl_engine_send_window_state_event(engine, true, false); - EXPECT_EQ(state, flutter::kAppLifecycleStateInactive); + EXPECT_STREQ(state, flutter::kAppLifecycleStateInactive); fl_engine_send_window_state_event(engine, true, true); - EXPECT_EQ(state, flutter::kAppLifecycleStateResumed); + EXPECT_STREQ(state, flutter::kAppLifecycleStateResumed); EXPECT_TRUE(called); } From 5d103c0727a4c48b70f5bd8987e83096f8ed41ce Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 5 May 2023 15:00:12 -0700 Subject: [PATCH 14/21] Clean up for review --- lib/ui/platform_dispatcher.dart | 2 +- .../Source/FlutterAppLifecycleDelegate.mm | 13 ------------ .../Source/FlutterAppLifecycleDelegateTest.mm | 1 - .../FlutterAppLifecycleDelegate_Internal.h | 21 ------------------- .../macos/framework/Source/FlutterEngine.mm | 4 ++-- 5 files changed, 3 insertions(+), 38 deletions(-) delete mode 100644 shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index e9f2fe4eed22f..0401915e1b747 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -946,7 +946,7 @@ class PlatformDispatcher { return _initialLifecycleState; } - String _initialLifecycleState = 'AppLifecycleState.detached'; + late String _initialLifecycleState; /// Tracks if the initial state has been accessed. Once accessed, we will stop /// updating the [initialLifecycleState], as it is not the preferred way to diff --git a/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm index bc27f96062b03..4b552b94ca1a0 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm @@ -3,7 +3,6 @@ // found in the LICENSE file. #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h" -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h" #include #include @@ -79,18 +78,6 @@ static BOOL IsPowerOfTwo(NSUInteger x) { return x != 0 && (x & (x - 1)) == 0; } -- (BOOL)hasDelegateThatRespondsToSelector:(SEL)selector { - for (NSObject* delegate in [_delegates allObjects]) { - if (!delegate) { - continue; - } - if ([delegate respondsToSelector:selector]) { - return YES; - } - } - return NO; -} - - (void)addDelegate:(NSObject*)delegate { [_delegates addPointer:(__bridge void*)delegate]; if (IsPowerOfTwo([_delegates count])) { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm index 1e3fc2b0413fc..079ddbb7ff26b 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm @@ -3,7 +3,6 @@ // found in the LICENSE file. #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h" -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h" #import "flutter/testing/testing.h" #include "third_party/googletest/googletest/include/gtest/gtest.h" diff --git a/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h deleted file mode 100644 index fa3b70f4aff8c..0000000000000 --- a/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h +++ /dev/null @@ -1,21 +0,0 @@ -// 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. - -#ifndef FLUTTER_FLUTTERAPPLIFECYCLEDELEGATE_INTERNAL_H_ -#define FLUTTER_FLUTTERAPPLIFECYCLEDELEGATE_INTERNAL_H_ - -#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h" - -#include "flutter/shell/platform/common/application_lifecycle.h" - -@interface FlutterAppLifecycleRegistrar () - -/** - * Check whether there is at least one plugin responds to the selector. - */ -- (BOOL)hasDelegateThatRespondsToSelector:(SEL)selector; - -@end - -#endif // FLUTTER_FLUTTERAPPLIFECYCLEDELEGATE_INTERNAL_H_ diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index c3b13016780d3..d53f14c3166ef 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -444,7 +444,7 @@ - (instancetype)initWithName:(NSString*)labelPrefix [self setUpAccessibilityChannel]; [self setUpNotificationCenterListeners]; FlutterAppDelegate* appDelegate = - (FlutterAppDelegate*)[[NSApplication sharedApplication] delegate]; + reinterpret_cast([[NSApplication sharedApplication] delegate]); [appDelegate addApplicationLifecycleDelegate:self]; return self; @@ -452,7 +452,7 @@ - (instancetype)initWithName:(NSString*)labelPrefix - (void)dealloc { FlutterAppDelegate* appDelegate = - (FlutterAppDelegate*)[[NSApplication sharedApplication] delegate]; + reinterpret_cast([[NSApplication sharedApplication] delegate]); if (appDelegate != nil) { [appDelegate removeApplicationLifecycleDelegate:self]; } From f0ee110dd83f15aadf9afa34ad5a33e0f4462b91 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 5 May 2023 15:43:34 -0700 Subject: [PATCH 15/21] Fix license --- ci/licenses_golden/licenses_flutter | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index eda9805149d13..c4af114bb531b 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2463,7 +2463,6 @@ ORIGIN: ../../../flutter/shell/platform/common/accessibility_bridge.cc + ../../. ORIGIN: ../../../flutter/shell/platform/common/accessibility_bridge.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/alert_platform_node_delegate.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/alert_platform_node_delegate.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/common/application_lifecycle.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/application_lifecycle.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/client_wrapper/byte_buffer_streams.h + ../../../flutter/LICENSE @@ -2696,7 +2695,6 @@ ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterApp ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h + ../../../flutter/LICENSE @@ -5136,7 +5134,6 @@ FILE: ../../../flutter/shell/platform/common/accessibility_bridge.cc FILE: ../../../flutter/shell/platform/common/accessibility_bridge.h FILE: ../../../flutter/shell/platform/common/alert_platform_node_delegate.cc FILE: ../../../flutter/shell/platform/common/alert_platform_node_delegate.h -FILE: ../../../flutter/shell/platform/common/application_lifecycle.cc FILE: ../../../flutter/shell/platform/common/application_lifecycle.h FILE: ../../../flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h FILE: ../../../flutter/shell/platform/common/client_wrapper/byte_buffer_streams.h @@ -5372,7 +5369,6 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDe FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h From e1d08b2cc207eb74b9240a4123410390ee5bae1a Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 5 May 2023 15:58:23 -0700 Subject: [PATCH 16/21] Fix Linux test --- shell/platform/linux/fl_engine_test.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shell/platform/linux/fl_engine_test.cc b/shell/platform/linux/fl_engine_test.cc index 075c5e30a3626..29a405bdc7355 100644 --- a/shell/platform/linux/fl_engine_test.cc +++ b/shell/platform/linux/fl_engine_test.cc @@ -430,7 +430,7 @@ TEST(FlEngineTest, SendWindowStateEvent) { FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); bool called = false; - const char* state = flutter::kAppLifecycleStateDetached; + std::string state = flutter::kAppLifecycleStateDetached; embedder_api->SendPlatformMessage = MOCK_ENGINE_PROC( SendPlatformMessage, ([&called, &state](auto engine, const FlutterPlatformMessage* message) { @@ -447,13 +447,13 @@ TEST(FlEngineTest, SendWindowStateEvent) { return kSuccess; })); fl_engine_send_window_state_event(engine, false, false); - EXPECT_STREQ(state, flutter::kAppLifecycleStateHidden); + EXPECT_STREQ(state.c_str(), flutter::kAppLifecycleStateHidden); fl_engine_send_window_state_event(engine, false, true); - EXPECT_STREQ(state, flutter::kAppLifecycleStateHidden); + EXPECT_STREQ(state.c_str(), flutter::kAppLifecycleStateHidden); fl_engine_send_window_state_event(engine, true, false); - EXPECT_STREQ(state, flutter::kAppLifecycleStateInactive); + EXPECT_STREQ(state.c_str(), flutter::kAppLifecycleStateInactive); fl_engine_send_window_state_event(engine, true, true); - EXPECT_STREQ(state, flutter::kAppLifecycleStateResumed); + EXPECT_STREQ(state.c_str(), flutter::kAppLifecycleStateResumed); EXPECT_TRUE(called); } From 87974bc3a9f71830adbb72bfc3c4fdd47f27a00e Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Wed, 10 May 2023 15:04:11 -0700 Subject: [PATCH 17/21] Remove enum from Windows implementation --- shell/platform/windows/flutter_windows_engine.cc | 8 +++----- shell/platform/windows/flutter_windows_engine.h | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index 5b9106f62f743..0d482e6e04681 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -563,12 +563,10 @@ void FlutterWindowsEngine::SetNextFrameCallback(fml::closure callback) { this); } -void FlutterWindowsEngine::SetLifecycleState(flutter::AppLifecycleState state) { - const std::string& state_str = flutter::AppLifecycleStateToString(state); - +void FlutterWindowsEngine::SetLifecycleState(const char* state) { SendPlatformMessage("flutter/lifecycle", - reinterpret_cast(state_str.c_str()), - state_str.size(), nullptr, nullptr); + reinterpret_cast(state), strlen(state), + nullptr, nullptr); } void FlutterWindowsEngine::SendSystemLocales() { diff --git a/shell/platform/windows/flutter_windows_engine.h b/shell/platform/windows/flutter_windows_engine.h index 5209e02140d34..4f2de2e108801 100644 --- a/shell/platform/windows/flutter_windows_engine.h +++ b/shell/platform/windows/flutter_windows_engine.h @@ -308,7 +308,7 @@ class FlutterWindowsEngine { void SendSystemLocales(); // Sends the current lifecycle state to the framework. - void SetLifecycleState(flutter::AppLifecycleState state); + void SetLifecycleState(const char* state); // Create the keyboard & text input sub-systems. // From f1f39caee6b2af9f3f0550fd1268e55caa3ac978 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 19 May 2023 11:42:09 -0700 Subject: [PATCH 18/21] Restore AppLifecycleState enum, add apicheck tests --- .../systemchannels/LifecycleChannel.java | 77 +++++++++++------ shell/platform/common/application_lifecycle.h | 86 ++++++++++++------- .../macos/framework/Source/FlutterEngine.mm | 19 ++-- .../framework/Source/FlutterEngineTest.mm | 34 ++++---- .../framework/Source/FlutterEngine_Internal.h | 2 +- shell/platform/linux/fl_engine.cc | 12 +-- shell/platform/linux/fl_engine_test.cc | 14 +-- .../windows/flutter_windows_engine.cc | 9 +- .../platform/windows/flutter_windows_engine.h | 2 +- tools/api_check/test/apicheck_test.dart | 34 ++++++-- 10 files changed, 183 insertions(+), 106 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/LifecycleChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/LifecycleChannel.java index 6ea0814fe0ba5..2e6858baf67f6 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/LifecycleChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/LifecycleChannel.java @@ -22,14 +22,21 @@ public class LifecycleChannel { private static final String TAG = "LifecycleChannel"; private static final String CHANNEL_NAME = "flutter/lifecycle"; - // These should stay in sync with the AppLifecycleState enum in the framework. - private static final String RESUMED = "AppLifecycleState.resumed"; - private static final String INACTIVE = "AppLifecycleState.inactive"; - private static final String PAUSED = "AppLifecycleState.paused"; - private static final String DETACHED = "AppLifecycleState.detached"; - - private String lastAndroidState = ""; - private String lastFlutterState = ""; + // This enum should match the Dart enum of the same name. + // + // HIDDEN isn't used on Android (it's synthesized in the Framework code). It's + // only listed here so that apicheck_test.dart can make sure that the states here + // match the Dart code. + private enum AppLifecycleState { + DETACHED, + RESUMED, + INACTIVE, + HIDDEN, + PAUSED, + }; + + private AppLifecycleState lastAndroidState = null; + private AppLifecycleState lastFlutterState = null; private boolean lastFocus = true; @NonNull private final BasicMessageChannel channel; @@ -55,21 +62,39 @@ public LifecycleChannel(@NonNull BasicMessageChannel channel) { // | Stopped | false | paused | // | Detached | true | detached | // | Detached | false | detached | - private void sendState(String state, boolean hasFocus) { + // + // The hidden state isn't used on Android, it's synthesized in the Framework + // code when transitioning between paused and inactive in either direction. + private void sendState(AppLifecycleState state, boolean hasFocus) { if (lastAndroidState == state && hasFocus == lastFocus) { // No inputs changed, so Flutter state could not have changed. return; } - String newState; - if (state == RESUMED) { - // Focus is only taken into account when the Android state is "Resumed". - // In all other states, focus is ignored, because we can't know what order - // Android lifecycle notifications and window focus notifications events - // will arrive in, and those states don't send input events anyhow. - newState = hasFocus ? RESUMED : INACTIVE; - } else { - newState = state; + if (state == null && lastAndroidState == null) { + // If we're responding to a focus change before the state is set, just + // keep the last reported focus state and don't send anything to the + // framework. This could happen if focus events and lifecycle events are + // delivered out of the expected order. + lastFocus = hasFocus; + return; + } + AppLifecycleState newState = null; + switch (state) { + case RESUMED: + // Focus is only taken into account when the Android state is "Resumed". + // In all other states, focus is ignored, because we can't know what order + // Android lifecycle notifications and window focus notifications events + // will arrive in, and those states don't send input events anyhow. + newState = hasFocus ? AppLifecycleState.RESUMED : AppLifecycleState.INACTIVE; + break; + case INACTIVE: + case HIDDEN: + case PAUSED: + case DETACHED: + newState = state; + break; } + // Keep the last reported values for future updates. lastAndroidState = state; lastFocus = hasFocus; @@ -77,12 +102,14 @@ private void sendState(String state, boolean hasFocus) { // No change in the resulting Flutter state, so don't report anything. return; } - Log.v(TAG, "Sending " + newState + " message."); - channel.send(newState); + String message = "AppLifecycleState." + newState.name().toLowerCase(); + Log.v(TAG, "Sending " + message + " message."); + channel.send(message); lastFlutterState = newState; } - // Called if at least one window in the app has focus. + // Called if at least one window in the app has focus, even if the focused + // window doesn't contain a Flutter view. public void aWindowIsFocused() { sendState(lastAndroidState, true); } @@ -93,18 +120,18 @@ public void noWindowsAreFocused() { } public void appIsResumed() { - sendState(RESUMED, lastFocus); + sendState(AppLifecycleState.RESUMED, lastFocus); } public void appIsInactive() { - sendState(INACTIVE, lastFocus); + sendState(AppLifecycleState.INACTIVE, lastFocus); } public void appIsPaused() { - sendState(PAUSED, lastFocus); + sendState(AppLifecycleState.PAUSED, lastFocus); } public void appIsDetached() { - sendState(DETACHED, lastFocus); + sendState(AppLifecycleState.DETACHED, lastFocus); } } diff --git a/shell/platform/common/application_lifecycle.h b/shell/platform/common/application_lifecycle.h index 78b906d0ee163..50cc67f316f11 100644 --- a/shell/platform/common/application_lifecycle.h +++ b/shell/platform/common/application_lifecycle.h @@ -5,6 +5,10 @@ #ifndef FLUTTER_SHELL_PLATFORM_COMMON_APPLICATION_LIFECYCLE_H_ #define FLUTTER_SHELL_PLATFORM_COMMON_APPLICATION_LIFECYCLE_H_ +#include +#include +#include + namespace flutter { /** @@ -29,44 +33,60 @@ namespace flutter { * | paused |<------>| hidden |<----->| inactive | * +-----------+ +--------------+ +-----------+ */ +enum class AppLifecycleState { + /** + * Corresponds to the Framework's AppLifecycleState.detached: The initial + * state of the state machine. On Android and iOS, also the final state of the + * state machine when all views are detached. Other platforms do not enter + * this state again after initially leaving it. + */ + kDetached, -/** - * Corresponds to AppLifecycleState.detached: The initial state of the state - * machine. On Android and iOS, also the final state of the state machine - * when all views are detached. Other platforms do not enter this state again - * after initially leaving it. - */ -constexpr const char* kAppLifecycleStateDetached = "AppLifecycleState.detached"; + /** + * Corresponds to the Framework's AppLifecycleState.resumed: The nominal + * "running" state of the application. The application is visible, has input + * focus, and is running. + */ + kResumed, -/** - * Corresponds to AppLifecycleState.resumed: The nominal "running" state of - * the - * application. The application is visible, has input focus, and is running. - */ -constexpr const char* kAppLifecycleStateResumed = "AppLifecycleState.resumed"; + /** + * Corresponds to the Framework's AppLifecycleState.inactive: At least one + * view of the application is visible, but none have input focus. The + * application is otherwise running normally. + */ + kInactive, -/** - * Corresponds to AppLifecycleState.inactive: At least one view of the - * application is visible, but none have input focus. The application is - * otherwise running normally. - */ -constexpr const char* kAppLifecycleStateInactive = "AppLifecycleState.inactive"; + /** + * Corresponds to the Framework's AppLifecycleState.hidden: All views of an + * application are hidden, either because the application is being stopped (on + * iOS and Android), or because it is being minimized or on a desktop that is + * no longer visible (on desktop), or on a tab that is no longer visible (on + * web). + */ + kHidden, -/** - * Corresponds to AppLifecycleState.hidden: All views of an application are - * hidden, either because the application is being stopped (on iOS and - * Android), or because it is being minimized or on a desktop that is no - * longer visible (on desktop), or on a tab that is no longer visible (on - * web). - */ -constexpr const char* kAppLifecycleStateHidden = "AppLifecycleState.hidden"; + /** + * Corresponds to the Framework's AppLifecycleState.paused: The application is + * not running, and can be detached or started again at any time. This state + * is typically only entered into on iOS and Android. + */ + kPaused, +}; -/** - * Corresponds to AppLifecycleState.paused: The application is not running, - * and can be detached or started again at any time. This state is typically - * only entered into on iOS and Android. - */ -constexpr const char* kAppLifecycleStatePaused = "AppLifecycleState.paused"; +constexpr const char* AppLifecycleStateToString(AppLifecycleState state) { + switch (state) { + case AppLifecycleState::kDetached: + return "AppLifecycleState.detached"; + case AppLifecycleState::kResumed: + return "AppLifecycleState.resumed"; + case AppLifecycleState::kInactive: + return "AppLifecycleState.inactive"; + case AppLifecycleState::kHidden: + return "AppLifecycleState.hidden"; + case AppLifecycleState::kPaused: + return "AppLifecycleState.paused"; + } +} } // namespace flutter diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index d53f14c3166ef..7f79d5ddc24aa 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -1141,8 +1141,9 @@ - (FlutterThreadSynchronizer*)testThreadSynchronizer { #pragma mark - FlutterAppLifecycleDelegate -- (void)setApplicationState:(const char*)state { - NSString* nextState = [[NSString alloc] initWithCString:state]; +- (void)setApplicationState:(flutter::AppLifecycleState)state { + NSString* nextState = + [[NSString alloc] initWithCString:flutter::AppLifecycleStateToString(state)]; [self sendOnChannel:kFlutterLifecycleChannel message:[nextState dataUsingEncoding:NSUTF8StringEncoding]]; } @@ -1154,9 +1155,9 @@ - (void)setApplicationState:(const char*)state { - (void)handleWillBecomeActive:(NSNotification*)notification { _active = YES; if (!_visible) { - [self setApplicationState:flutter::kAppLifecycleStateHidden]; + [self setApplicationState:flutter::AppLifecycleState::kHidden]; } else { - [self setApplicationState:flutter::kAppLifecycleStateResumed]; + [self setApplicationState:flutter::AppLifecycleState::kResumed]; } } @@ -1167,9 +1168,9 @@ - (void)handleWillBecomeActive:(NSNotification*)notification { - (void)handleWillResignActive:(NSNotification*)notification { _active = NO; if (!_visible) { - [self setApplicationState:flutter::kAppLifecycleStateHidden]; + [self setApplicationState:flutter::AppLifecycleState::kHidden]; } else { - [self setApplicationState:flutter::kAppLifecycleStateInactive]; + [self setApplicationState:flutter::AppLifecycleState::kInactive]; } } @@ -1182,13 +1183,13 @@ - (void)handleDidChangeOcclusionState:(NSNotification*)notification API_AVAILABL if (occlusionState & NSApplicationOcclusionStateVisible) { _visible = YES; if (_active) { - [self setApplicationState:flutter::kAppLifecycleStateResumed]; + [self setApplicationState:flutter::AppLifecycleState::kResumed]; } else { - [self setApplicationState:flutter::kAppLifecycleStateInactive]; + [self setApplicationState:flutter::AppLifecycleState::kInactive]; } } else { _visible = NO; - [self setApplicationState:flutter::kAppLifecycleStateHidden]; + [self setApplicationState:flutter::AppLifecycleState::kHidden]; } } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm index 9a9efc7f9c1a7..60e8bc363f8df 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm @@ -806,30 +806,30 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable } TEST_F(FlutterEngineTest, HandleLifecycleStates) API_AVAILABLE(macos(10.9)) { - __block const char* sentState; + __block flutter::AppLifecycleState sentState; id engineMock = CreateMockFlutterEngine(nil); // Have to enumerate all the values because OCMStub can't capture // non-Objective-C object arguments. - OCMStub([engineMock setApplicationState:flutter::kAppLifecycleStateDetached]) + OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kDetached]) .andDo((^(NSInvocation* invocation) { - sentState = kAppLifecycleStateHidden; + sentState = flutter::AppLifecycleState::kDetached; })); - OCMStub([engineMock setApplicationState:flutter::kAppLifecycleStateResumed]) + OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kResumed]) .andDo((^(NSInvocation* invocation) { - sentState = kAppLifecycleStateResumed; + sentState = flutter::AppLifecycleState::kResumed; })); - OCMStub([engineMock setApplicationState:flutter::kAppLifecycleStateInactive]) + OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kInactive]) .andDo((^(NSInvocation* invocation) { - sentState = kAppLifecycleStateInactive; + sentState = flutter::AppLifecycleState::kInactive; })); - OCMStub([engineMock setApplicationState:flutter::kAppLifecycleStateHidden]) + OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kHidden]) .andDo((^(NSInvocation* invocation) { - sentState = kAppLifecycleStateHidden; + sentState = flutter::AppLifecycleState::kHidden; })); - OCMStub([engineMock setApplicationState:flutter::kAppLifecycleStatePaused]) + OCMStub([engineMock setApplicationState:flutter::AppLifecycleState::kPaused]) .andDo((^(NSInvocation* invocation) { - sentState = kAppLifecycleStatePaused; + sentState = flutter::AppLifecycleState::kPaused; })); __block NSApplicationOcclusionState visibility = NSApplicationOcclusionStateVisible; @@ -855,23 +855,23 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable userInfo:nil]; [engineMock handleDidChangeOcclusionState:didChangeOcclusionState]; - EXPECT_EQ(sentState, flutter::kAppLifecycleStateInactive); + EXPECT_EQ(sentState, flutter::AppLifecycleState::kInactive); [engineMock handleWillBecomeActive:willBecomeActive]; - EXPECT_EQ(sentState, flutter::kAppLifecycleStateResumed); + EXPECT_EQ(sentState, flutter::AppLifecycleState::kResumed); [engineMock handleWillResignActive:willResignActive]; - EXPECT_EQ(sentState, flutter::kAppLifecycleStateInactive); + EXPECT_EQ(sentState, flutter::AppLifecycleState::kInactive); visibility = 0; [engineMock handleDidChangeOcclusionState:didChangeOcclusionState]; - EXPECT_EQ(sentState, flutter::kAppLifecycleStateHidden); + EXPECT_EQ(sentState, flutter::AppLifecycleState::kHidden); [engineMock handleWillBecomeActive:willBecomeActive]; - EXPECT_EQ(sentState, flutter::kAppLifecycleStateHidden); + EXPECT_EQ(sentState, flutter::AppLifecycleState::kHidden); [engineMock handleWillResignActive:willResignActive]; - EXPECT_EQ(sentState, flutter::kAppLifecycleStateHidden); + EXPECT_EQ(sentState, flutter::AppLifecycleState::kHidden); [mockApplication stopMocking]; } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h index d01fa45f7e1db..8fbdb42ca3c7a 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h @@ -180,7 +180,7 @@ typedef NS_ENUM(NSInteger, FlutterAppExitResponse) { * @param state One of the lifecycle constants in application_lifecycle.h, * corresponding to the Dart enum AppLifecycleState. */ -- (void)setApplicationState:(const char*)state; +- (void)setApplicationState:(flutter::AppLifecycleState)state; // Accessibility API. diff --git a/shell/platform/linux/fl_engine.cc b/shell/platform/linux/fl_engine.cc index b6976b68199cd..c861eeb90e9f6 100644 --- a/shell/platform/linux/fl_engine.cc +++ b/shell/platform/linux/fl_engine.cc @@ -126,10 +126,12 @@ static void parse_locale(const gchar* locale, } } -static void set_app_lifecycle_state(FlEngine* self, const gchar* state) { +static void set_app_lifecycle_state(FlEngine* self, + const flutter::AppLifecycleState state) { FlBinaryMessenger* binary_messenger = fl_engine_get_binary_messenger(self); - g_autoptr(FlValue) value = fl_value_new_string(state); + g_autoptr(FlValue) value = + fl_value_new_string(flutter::AppLifecycleStateToString(state)); g_autoptr(FlStringCodec) codec = fl_string_codec_new(); g_autoptr(GBytes) message = fl_message_codec_encode_message(FL_MESSAGE_CODEC(codec), value, nullptr); @@ -746,11 +748,11 @@ void fl_engine_send_window_state_event(FlEngine* self, gboolean visible, gboolean focused) { if (visible && focused) { - set_app_lifecycle_state(self, flutter::kAppLifecycleStateResumed); + set_app_lifecycle_state(self, flutter::AppLifecycleState::kResumed); } else if (visible) { - set_app_lifecycle_state(self, flutter::kAppLifecycleStateInactive); + set_app_lifecycle_state(self, flutter::AppLifecycleState::kInactive); } else { - set_app_lifecycle_state(self, flutter::kAppLifecycleStateHidden); + set_app_lifecycle_state(self, flutter::AppLifecycleState::kHidden); } } diff --git a/shell/platform/linux/fl_engine_test.cc b/shell/platform/linux/fl_engine_test.cc index 29a405bdc7355..d546328bb5407 100644 --- a/shell/platform/linux/fl_engine_test.cc +++ b/shell/platform/linux/fl_engine_test.cc @@ -430,7 +430,7 @@ TEST(FlEngineTest, SendWindowStateEvent) { FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); bool called = false; - std::string state = flutter::kAppLifecycleStateDetached; + std::string state; embedder_api->SendPlatformMessage = MOCK_ENGINE_PROC( SendPlatformMessage, ([&called, &state](auto engine, const FlutterPlatformMessage* message) { @@ -447,13 +447,17 @@ TEST(FlEngineTest, SendWindowStateEvent) { return kSuccess; })); fl_engine_send_window_state_event(engine, false, false); - EXPECT_STREQ(state.c_str(), flutter::kAppLifecycleStateHidden); + EXPECT_STREQ(state.c_str(), flutter::AppLifecycleStateToString( + flutter::AppLifecycleState::kHidden)); fl_engine_send_window_state_event(engine, false, true); - EXPECT_STREQ(state.c_str(), flutter::kAppLifecycleStateHidden); + EXPECT_STREQ(state.c_str(), flutter::AppLifecycleStateToString( + flutter::AppLifecycleState::kHidden)); fl_engine_send_window_state_event(engine, true, false); - EXPECT_STREQ(state.c_str(), flutter::kAppLifecycleStateInactive); + EXPECT_STREQ(state.c_str(), flutter::AppLifecycleStateToString( + flutter::AppLifecycleState::kInactive)); fl_engine_send_window_state_event(engine, true, true); - EXPECT_STREQ(state.c_str(), flutter::kAppLifecycleStateResumed); + EXPECT_STREQ(state.c_str(), flutter::AppLifecycleStateToString( + flutter::AppLifecycleState::kResumed)); EXPECT_TRUE(called); } diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index 0d482e6e04681..c6c46d7301776 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -397,7 +397,7 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) { displays.data(), displays.size()); SendSystemLocales(); - SetLifecycleState(flutter::kAppLifecycleStateResumed); + SetLifecycleState(flutter::AppLifecycleState::kResumed); settings_plugin_->StartWatching(); settings_plugin_->SendSettings(); @@ -563,10 +563,11 @@ void FlutterWindowsEngine::SetNextFrameCallback(fml::closure callback) { this); } -void FlutterWindowsEngine::SetLifecycleState(const char* state) { +void FlutterWindowsEngine::SetLifecycleState(flutter::AppLifecycleState state) { + const char* state_name = flutter::AppLifecycleStateToString(state); SendPlatformMessage("flutter/lifecycle", - reinterpret_cast(state), strlen(state), - nullptr, nullptr); + reinterpret_cast(state_name), + strlen(state_name), nullptr, nullptr); } void FlutterWindowsEngine::SendSystemLocales() { diff --git a/shell/platform/windows/flutter_windows_engine.h b/shell/platform/windows/flutter_windows_engine.h index 4f2de2e108801..5209e02140d34 100644 --- a/shell/platform/windows/flutter_windows_engine.h +++ b/shell/platform/windows/flutter_windows_engine.h @@ -308,7 +308,7 @@ class FlutterWindowsEngine { void SendSystemLocales(); // Sends the current lifecycle state to the framework. - void SetLifecycleState(const char* state); + void SetLifecycleState(flutter::AppLifecycleState state); // Create the keyboard & text input sub-systems. // diff --git a/tools/api_check/test/apicheck_test.dart b/tools/api_check/test/apicheck_test.dart index 22dea01379613..1d1a516b791f7 100644 --- a/tools/api_check/test/apicheck_test.dart +++ b/tools/api_check/test/apicheck_test.dart @@ -43,10 +43,6 @@ void checkApiConsistency(String flutterRoot) { sourcePath: path.join(flutterRoot, 'lib', 'ui', 'window.dart'), className: 'AccessibilityFeatures', ); - final List webuiFields = getDartClassFields( - sourcePath: path.join(flutterRoot, 'lib', 'ui', 'window.dart'), - className: 'AccessibilityFeatures', - ); // C values: kFlutterAccessibilityFeatureFooBar = 1 << N, final List embedderEnumValues = getCppEnumValues( sourcePath: path.join(flutterRoot, 'shell', 'platform', 'embedder', 'embedder.h'), @@ -64,7 +60,6 @@ void checkApiConsistency(String flutterRoot) { enumName: 'AccessibilityFeature', ).map(allCapsToCamelCase).toList(); - expect(webuiFields, uiFields); expect(embedderEnumValues, uiFields); expect(internalEnumValues, uiFields); expect(javaEnumValues, uiFields); @@ -77,7 +72,7 @@ void checkApiConsistency(String flutterRoot) { className: 'SemanticsAction', ); final List webuiFields = getDartClassFields( - sourcePath: path.join(flutterRoot, 'lib', 'ui', 'semantics.dart'), + sourcePath: path.join(flutterRoot, 'lib', 'web_ui', 'lib', 'semantics.dart'), className: 'SemanticsAction', ); // C values: kFlutterSemanticsActionFooBar = 1 << N. @@ -103,6 +98,33 @@ void checkApiConsistency(String flutterRoot) { expect(javaEnumValues, uiFields); }); + test('AppLifecycleState enums match', () { + // Dart values: _kFooBarIndex = 1 << N. + final List uiFields = getDartClassFields( + sourcePath: path.join(flutterRoot, 'lib', 'ui', 'platform_dispatcher.dart'), + className: 'AppLifecycleState', + ); + final List webuiFields = getDartClassFields( + sourcePath: path.join(flutterRoot, 'lib', 'web_ui', 'lib', 'platform_dispatcher.dart'), + className: 'AppLifecycleState', + ); + // C++ values: kFooBar = 1 << N. + final List internalEnumValues = getCppEnumClassValues( + sourcePath: path.join(flutterRoot, 'shell', 'platform', 'common', 'application_lifecycle.h'), + enumName: 'AppLifecycleState', + ); + // Java values: FOO_BAR(1 << N). + final List javaEnumValues = getJavaEnumValues( + sourcePath: path.join(flutterRoot, 'shell', 'platform', 'android', 'io', + 'flutter', 'embedding', 'engine', 'systemchannels', 'LifecycleChannel.java'), + enumName: 'AppLifecycleState', + ).map(allCapsToCamelCase).toList(); + + expect(webuiFields, uiFields); + expect(internalEnumValues, uiFields); + expect(javaEnumValues, uiFields); + }); + test('SemanticsFlag enums match', () { // Dart values: _kFooBarIndex = 1 << N. final List uiFields = getDartClassFields( From fecb1d59b6587d5277350bd34390719d7ebd1f13 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 19 May 2023 15:23:00 -0700 Subject: [PATCH 19/21] Add comment --- lib/ui/platform_dispatcher.dart | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index 0401915e1b747..42a56a36c345a 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -1685,6 +1685,10 @@ class FrameTiming { /// transitioning between states which are supported, so that all /// implementations share the same state machine. /// +/// The initial value for the state is the [detached] state, updated to the +/// current state (usually [resumed]) as soon as the first lifecycle update is +/// received from the platform. +/// /// For historical and name collision reasons, Flutter's application state names /// do not correspond one to one with the state names on all platforms. On /// Android, for instance, when the OS calls @@ -1709,13 +1713,19 @@ class FrameTiming { /// /// See also: /// -/// * [AppLifecycleListener], an object used observe the lifecycle state -/// that provides state transition callbacks. +/// * [AppLifecycleListener], an object used observe the lifecycle state that +/// provides state transition callbacks. /// * [WidgetsBindingObserver], for a mechanism to observe the lifecycle state /// from the widgets layer. -/// * iOS's [IOKit activity lifecycle](https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle?language=objc) documentation. -/// * Android's [activity lifecycle](https://developer.android.com/guide/components/activities/activity-lifecycle) documentation. -/// * macOS's [AppKit activity lifecycle](https://developer.apple.com/documentation/appkit/nsapplicationdelegate?language=objc) documentation. +/// * iOS's [IOKit activity +/// lifecycle](https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle?language=objc) +/// documentation. +/// * Android's [activity +/// lifecycle](https://developer.android.com/guide/components/activities/activity-lifecycle) +/// documentation. +/// * macOS's [AppKit activity +/// lifecycle](https://developer.apple.com/documentation/appkit/nsapplicationdelegate?language=objc) +/// documentation. enum AppLifecycleState { /// The application is still hosted by a Flutter engine but is detached from /// any host views. From 2252f7fd3b47ee6e862a42a4a2cff8521218c005 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 19 May 2023 16:17:49 -0700 Subject: [PATCH 20/21] Rename application_lifecycle.h to app_lifecycle_state.h --- ci/licenses_golden/licenses_flutter | 4 ++-- shell/platform/common/BUILD.gn | 2 +- .../{application_lifecycle.h => app_lifecycle_state.h} | 6 +++--- .../platform/darwin/macos/framework/Source/FlutterEngine.mm | 2 +- .../darwin/macos/framework/Source/FlutterEngine_Internal.h | 4 ++-- shell/platform/linux/fl_engine.cc | 2 +- shell/platform/linux/fl_engine_test.cc | 2 +- shell/platform/windows/flutter_windows_engine.h | 2 +- tools/api_check/test/apicheck_test.dart | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) rename shell/platform/common/{application_lifecycle.h => app_lifecycle_state.h} (94%) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index c4af114bb531b..7d0eab4a8cada 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2463,7 +2463,7 @@ ORIGIN: ../../../flutter/shell/platform/common/accessibility_bridge.cc + ../../. ORIGIN: ../../../flutter/shell/platform/common/accessibility_bridge.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/alert_platform_node_delegate.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/alert_platform_node_delegate.h + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/common/application_lifecycle.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/common/app_lifecycle_state.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/client_wrapper/byte_buffer_streams.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/common/client_wrapper/core_implementations.cc + ../../../flutter/LICENSE @@ -5134,7 +5134,7 @@ FILE: ../../../flutter/shell/platform/common/accessibility_bridge.cc FILE: ../../../flutter/shell/platform/common/accessibility_bridge.h FILE: ../../../flutter/shell/platform/common/alert_platform_node_delegate.cc FILE: ../../../flutter/shell/platform/common/alert_platform_node_delegate.h -FILE: ../../../flutter/shell/platform/common/application_lifecycle.h +FILE: ../../../flutter/shell/platform/common/app_lifecycle_state.h FILE: ../../../flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h FILE: ../../../flutter/shell/platform/common/client_wrapper/byte_buffer_streams.h FILE: ../../../flutter/shell/platform/common/client_wrapper/core_implementations.cc diff --git a/shell/platform/common/BUILD.gn b/shell/platform/common/BUILD.gn index 7ee769c9ead8d..13f2a51e20e97 100644 --- a/shell/platform/common/BUILD.gn +++ b/shell/platform/common/BUILD.gn @@ -59,7 +59,7 @@ source_set("common_cpp_input") { source_set("common_cpp_enums") { public = [ - "application_lifecycle.h", + "app_lifecycle_state.h", "platform_provided_menu.h", ] diff --git a/shell/platform/common/application_lifecycle.h b/shell/platform/common/app_lifecycle_state.h similarity index 94% rename from shell/platform/common/application_lifecycle.h rename to shell/platform/common/app_lifecycle_state.h index 50cc67f316f11..0afa88827a4a5 100644 --- a/shell/platform/common/application_lifecycle.h +++ b/shell/platform/common/app_lifecycle_state.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef FLUTTER_SHELL_PLATFORM_COMMON_APPLICATION_LIFECYCLE_H_ -#define FLUTTER_SHELL_PLATFORM_COMMON_APPLICATION_LIFECYCLE_H_ +#ifndef FLUTTER_SHELL_PLATFORM_COMMON_APP_LIFECYCLE_STATE_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_APP_LIFECYCLE_STATE_H_ #include #include @@ -90,4 +90,4 @@ constexpr const char* AppLifecycleStateToString(AppLifecycleState state) { } // namespace flutter -#endif // FLUTTER_SHELL_PLATFORM_COMMON_APPLICATION_LIFECYCLE_H_ +#endif // FLUTTER_SHELL_PLATFORM_COMMON_APP_LIFECYCLE_STATE_H_ diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 7f79d5ddc24aa..2be2f6b70af8f 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -9,7 +9,7 @@ #include #include -#include "flutter/shell/platform/common/application_lifecycle.h" +#include "flutter/shell/platform/common/app_lifecycle_state.h" #include "flutter/shell/platform/common/engine_switches.h" #include "flutter/shell/platform/embedder/embedder.h" diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h index 8fbdb42ca3c7a..71fe5481cf72b 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h @@ -8,7 +8,7 @@ #include -#include "flutter/shell/platform/common/application_lifecycle.h" +#include "flutter/shell/platform/common/app_lifecycle_state.h" #import "flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h" @@ -177,7 +177,7 @@ typedef NS_ENUM(NSInteger, FlutterAppExitResponse) { /** * Handles changes to the application state, sending them to the framework. * - * @param state One of the lifecycle constants in application_lifecycle.h, + * @param state One of the lifecycle constants in app_lifecycle_state.h, * corresponding to the Dart enum AppLifecycleState. */ - (void)setApplicationState:(flutter::AppLifecycleState)state; diff --git a/shell/platform/linux/fl_engine.cc b/shell/platform/linux/fl_engine.cc index c861eeb90e9f6..507d9e2271470 100644 --- a/shell/platform/linux/fl_engine.cc +++ b/shell/platform/linux/fl_engine.cc @@ -10,7 +10,7 @@ #include #include -#include "flutter/shell/platform/common/application_lifecycle.h" +#include "flutter/shell/platform/common/app_lifecycle_state.h" #include "flutter/shell/platform/common/engine_switches.h" #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/linux/fl_binary_messenger_private.h" diff --git a/shell/platform/linux/fl_engine_test.cc b/shell/platform/linux/fl_engine_test.cc index d546328bb5407..a7329a6997b0f 100644 --- a/shell/platform/linux/fl_engine_test.cc +++ b/shell/platform/linux/fl_engine_test.cc @@ -5,7 +5,7 @@ // Included first as it collides with the X11 headers. #include "gtest/gtest.h" -#include "flutter/shell/platform/common/application_lifecycle.h" +#include "flutter/shell/platform/common/app_lifecycle_state.h" #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" #include "flutter/shell/platform/linux/fl_engine_private.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h" diff --git a/shell/platform/windows/flutter_windows_engine.h b/shell/platform/windows/flutter_windows_engine.h index 5209e02140d34..9fd7df9f4985a 100644 --- a/shell/platform/windows/flutter_windows_engine.h +++ b/shell/platform/windows/flutter_windows_engine.h @@ -16,7 +16,7 @@ #include "flutter/fml/closure.h" #include "flutter/fml/macros.h" #include "flutter/shell/platform/common/accessibility_bridge.h" -#include "flutter/shell/platform/common/application_lifecycle.h" +#include "flutter/shell/platform/common/app_lifecycle_state.h" #include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h" #include "flutter/shell/platform/common/client_wrapper/include/flutter/basic_message_channel.h" #include "flutter/shell/platform/common/incoming_message_dispatcher.h" diff --git a/tools/api_check/test/apicheck_test.dart b/tools/api_check/test/apicheck_test.dart index 1d1a516b791f7..f32bb339f8f8a 100644 --- a/tools/api_check/test/apicheck_test.dart +++ b/tools/api_check/test/apicheck_test.dart @@ -110,7 +110,7 @@ void checkApiConsistency(String flutterRoot) { ); // C++ values: kFooBar = 1 << N. final List internalEnumValues = getCppEnumClassValues( - sourcePath: path.join(flutterRoot, 'shell', 'platform', 'common', 'application_lifecycle.h'), + sourcePath: path.join(flutterRoot, 'shell', 'platform', 'common', 'app_lifecycle_state.h'), enumName: 'AppLifecycleState', ); // Java values: FOO_BAR(1 << N). From 49513daa993ca0669357aeebd4c4878aeb604062 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Fri, 26 May 2023 13:14:45 -0700 Subject: [PATCH 21/21] Remove unneeded includes --- shell/platform/common/app_lifecycle_state.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/shell/platform/common/app_lifecycle_state.h b/shell/platform/common/app_lifecycle_state.h index 0afa88827a4a5..a3d79dcb74c81 100644 --- a/shell/platform/common/app_lifecycle_state.h +++ b/shell/platform/common/app_lifecycle_state.h @@ -5,10 +5,6 @@ #ifndef FLUTTER_SHELL_PLATFORM_COMMON_APP_LIFECYCLE_STATE_H_ #define FLUTTER_SHELL_PLATFORM_COMMON_APP_LIFECYCLE_STATE_H_ -#include -#include -#include - namespace flutter { /**