diff --git a/DEPS b/DEPS index e72634ef8347d..c7c59c19e5ea8 100644 --- a/DEPS +++ b/DEPS @@ -549,7 +549,7 @@ deps = { 'packages': [ { 'package': 'flutter/android/embedding_bundle', - 'version': 'last_updated:2021-08-10T22:12:57-0700' + 'version': 'last_updated:2021-08-26T13:35:58-0700' } ], 'condition': 'download_android_deps', diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 1d5cee942105c..346243c96495b 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -824,6 +824,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/Rende FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/SplashScreen.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/SplashScreenProvider.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/TransparencyMode.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/WindowInfoRepositoryCallbackAdapterWrapper.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineCache.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java diff --git a/lib/ui/fixtures/ui_test.dart b/lib/ui/fixtures/ui_test.dart index b5eac66aed490..ad560db96ba30 100644 --- a/lib/ui/fixtures/ui_test.dart +++ b/lib/ui/fixtures/ui_test.dart @@ -339,7 +339,7 @@ void hooksTests() { window.onMetricsChanged!(); _callHook( '_updateWindowMetrics', - 17, + 20, 0, // window Id 0.1234, // device pixel ratio 0.0, // width @@ -355,8 +355,11 @@ void hooksTests() { 0.0, // system gesture inset top 0.0, // system gesture inset right 0.0, // system gesture inset bottom - 0.0, // system gesture inset left, + 0.0, // system gesture inset left 22.0, // physicalTouchSlop + [], // display features bounds + [], // display features types + [], // display features states ); expectIdentical(originalZone, callbackZone); @@ -403,7 +406,7 @@ void hooksTests() { test('Window padding/insets/viewPadding/systemGestureInsets', () { _callHook( '_updateWindowMetrics', - 17, + 20, 0, // window Id 1.0, // devicePixelRatio 800.0, // width @@ -421,6 +424,9 @@ void hooksTests() { 0.0, // systemGestureInsetBottom 0.0, // systemGestureInsetLeft 22.0, // physicalTouchSlop + [], // display features bounds + [], // display features types + [], // display features states ); expectEquals(window.viewInsets.bottom, 0.0); @@ -430,7 +436,7 @@ void hooksTests() { _callHook( '_updateWindowMetrics', - 17, + 20, 0, // window Id 1.0, // devicePixelRatio 800.0, // width @@ -448,6 +454,9 @@ void hooksTests() { 44.0, // systemGestureInsetBottom 0.0, // systemGestureInsetLeft 22.0, // physicalTouchSlop + [], // display features bounds + [], // display features types + [], // display features states ); expectEquals(window.viewInsets.bottom, 400.0); @@ -459,7 +468,7 @@ void hooksTests() { test('Window physical touch slop', () { _callHook( '_updateWindowMetrics', - 17, + 20, 0, // window Id 1.0, // devicePixelRatio 800.0, // width @@ -477,6 +486,9 @@ void hooksTests() { 0.0, // systemGestureInsetBottom 0.0, // systemGestureInsetLeft 11.0, // physicalTouchSlop + [], // display features bounds + [], // display features types + [], // display features states ); expectEquals(window.viewConfiguration.gestureSettings, @@ -484,7 +496,7 @@ void hooksTests() { _callHook( '_updateWindowMetrics', - 17, + 20, 0, // window Id 1.0, // devicePixelRatio 800.0, // width @@ -502,6 +514,9 @@ void hooksTests() { 44.0, // systemGestureInsetBottom 0.0, // systemGestureInsetLeft -1.0, // physicalTouchSlop + [], // display features bounds + [], // display features types + [], // display features states ); expectEquals(window.viewConfiguration.gestureSettings, @@ -509,7 +524,7 @@ void hooksTests() { _callHook( '_updateWindowMetrics', - 17, + 20, 0, // window Id 1.0, // devicePixelRatio 800.0, // width @@ -527,6 +542,9 @@ void hooksTests() { 44.0, // systemGestureInsetBottom 0.0, // systemGestureInsetLeft 22.0, // physicalTouchSlop + [], // display features bounds + [], // display features types + [], // display features states ); expectEquals(window.viewConfiguration.gestureSettings, @@ -752,4 +770,7 @@ void _callHook( Object? arg15, Object? arg16, Object? arg17, + Object? arg18, + Object? arg19, + Object? arg20, ]) native 'CallHook'; diff --git a/lib/ui/hooks.dart b/lib/ui/hooks.dart index c0b78e7a291cf..e2628e939c209 100644 --- a/lib/ui/hooks.dart +++ b/lib/ui/hooks.dart @@ -28,6 +28,9 @@ void _updateWindowMetrics( double systemGestureInsetBottom, double systemGestureInsetLeft, double physicalTouchSlop, + List displayFeaturesBounds, + List displayFeaturesType, + List displayFeaturesState, ) { PlatformDispatcher.instance._updateWindowMetrics( id, @@ -47,6 +50,9 @@ void _updateWindowMetrics( systemGestureInsetBottom, systemGestureInsetLeft, physicalTouchSlop, + displayFeaturesBounds, + displayFeaturesType, + displayFeaturesState, ); } diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index 99a3bd3d77f57..6964a66b6d363 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -183,6 +183,9 @@ class PlatformDispatcher { double systemGestureInsetBottom, double systemGestureInsetLeft, double physicalTouchSlop, + List displayFeaturesBounds, + List displayFeaturesType, + List displayFeaturesState, ) { final ViewConfiguration previousConfiguration = _viewConfigurations[id] ?? const ViewConfiguration(); @@ -221,10 +224,43 @@ class PlatformDispatcher { gestureSettings: GestureSettings( physicalTouchSlop: physicalTouchSlop == _kUnsetGestureSetting ? null : physicalTouchSlop, ), + displayFeatures: _decodeDisplayFeatures( + bounds: displayFeaturesBounds, + type: displayFeaturesType, + state: displayFeaturesState, + devicePixelRatio: devicePixelRatio, + ), ); _invoke(onMetricsChanged, _onMetricsChangedZone); } + List _decodeDisplayFeatures({ + required List bounds, + required List type, + required List state, + required double devicePixelRatio, + }) { + assert(bounds.length / 4 == type.length, 'Bounds are rectangles, requiring 4 measurements each'); + assert(type.length == state.length); + final List result = []; + for(int i = 0; i < type.length; i++){ + final int rectOffset = i * 4; + result.add(DisplayFeature( + bounds: Rect.fromLTRB( + bounds[rectOffset] / devicePixelRatio, + bounds[rectOffset + 1] / devicePixelRatio, + bounds[rectOffset + 2] / devicePixelRatio, + bounds[rectOffset + 3] / devicePixelRatio, + ), + type: DisplayFeatureType.values[type[i]], + state: state[i] < DisplayFeatureState.values.length + ? DisplayFeatureState.values[state[i]] + : DisplayFeatureState.unknown, + )); + } + return result; + } + /// A callback invoked when any view begins a frame. /// /// A callback that is invoked to notify the application that it is an @@ -1035,6 +1071,7 @@ class ViewConfiguration { this.systemGestureInsets = WindowPadding.zero, this.padding = WindowPadding.zero, this.gestureSettings = const GestureSettings(), + this.displayFeatures = const [], }); /// Copy this configuration with some fields replaced. @@ -1047,7 +1084,8 @@ class ViewConfiguration { WindowPadding? viewPadding, WindowPadding? systemGestureInsets, WindowPadding? padding, - GestureSettings? gestureSettings + GestureSettings? gestureSettings, + List? displayFeatures, }) { return ViewConfiguration( window: window ?? this.window, @@ -1059,6 +1097,7 @@ class ViewConfiguration { systemGestureInsets: systemGestureInsets ?? this.systemGestureInsets, padding: padding ?? this.padding, gestureSettings: gestureSettings ?? this.gestureSettings, + displayFeatures: displayFeatures ?? this.displayFeatures, ); } @@ -1138,6 +1177,26 @@ class ViewConfiguration { /// touch slop constant. final GestureSettings gestureSettings; + /// {@template dart.ui.ViewConfiguration.displayFeatures} + /// Areas of the display that are obstructed by hardware features. + /// + /// This list is populated only on Android. If the device has no display + /// features, this list is empty. + /// + /// The coordinate space in which the [DisplayFeature.bounds] are defined spans + /// across the screens currently in use. This means that the space between the screens + /// is virtually part of the Flutter view space, with the [DisplayFeature.bounds] + /// of the display feature as an obstructed area. The [DisplayFeature.type] can + /// be used to determine if this display feature obstructs the screen or not. + /// For example, [DisplayFeatureType.hinge] and [DisplayFeatureType.cutout] both + /// obstruct the display, while [DisplayFeatureType.fold] is a crease in the display. + /// + /// Folding [DisplayFeature]s like the [DisplayFeatureType.hinge] and + /// [DisplayFeatureType.fold] also have a [DisplayFeature.state] which can be + /// used to determine the posture the device is in. + /// {@endtemplate} + final List displayFeatures; + @override String toString() { return '$runtimeType[window: $window, geometry: $geometry]'; @@ -1376,6 +1435,134 @@ class WindowPadding { } } +/// Area of the display that may be obstructed by a hardware feature. +/// +/// This is populated only on Android. +/// +/// The [bounds] are measured in logical pixels. On devices with two screens the +/// coordinate system starts with [0,0] in the top-left corner of the left or top screen +/// and expands to include both screens and the visual space between them. +/// +/// The [type] describes the behaviour and if [DisplayFeature] obstructs the display. +/// For example, [DisplayFeatureType.hinge] and [DisplayFeatureType.cutout] both obstruct the display, +/// while [DisplayFeatureType.fold] does not. +/// +/// ![Device with a hinge display feature](https://flutter.github.io/assets-for-api-docs/assets/hardware/display_feature_hinge.png) +/// +/// ![Device with a fold display feature](https://flutter.github.io/assets-for-api-docs/assets/hardware/display_feature_fold.png) +/// +/// ![Device with a cutout display feature](https://flutter.github.io/assets-for-api-docs/assets/hardware/display_feature_cutout.png) +/// +/// The [state] contains information about the posture for foldable features +/// ([DisplayFeatureType.hinge] and [DisplayFeatureType.fold]). The posture is +/// the shape of the display, for example [DisplayFeatureState.postureFlat] or +/// [DisplayFeatureState.postureHalfOpened]. For [DisplayFeatureType.cutout], +/// the state is not used and has the [DisplayFeatureState.unknown] value. +class DisplayFeature { + const DisplayFeature({ + required this.bounds, + required this.type, + required this.state, + }) : assert(!identical(type, DisplayFeatureType.cutout) || identical(state, DisplayFeatureState.unknown)); + + /// The area of the flutter view occupied by this display feature, measured in logical pixels. + /// + /// On devices with two screens, the Flutter view spans from the top-left corner + /// of the left or top screen to the bottom-right corner of the right or bottom screen, + /// including the visual area occupied by any display feature. Bounds of display + /// features are reported in this coordinate system. + /// + /// For example, on a dual screen device in portrait mode: + /// + /// * [bounds.left] gives you the size of left screen, in logical pixels. + /// * [bounds.right] gives you the size of the left screen + the hinge width. + final Rect bounds; + + /// Type of display feature, e.g. hinge, fold, cutout. + final DisplayFeatureType type; + + /// Posture of display feature, which is populated only for folds and hinges. + /// + /// For cutouts, this is [DisplayFeatureState.unknown] + final DisplayFeatureState state; + + @override + bool operator ==(Object other) { + if (identical(this, other)) + return true; + if (other.runtimeType != runtimeType) + return false; + return other is DisplayFeature && bounds == other.bounds && + type == other.type && state == other.state; + } + + @override + int get hashCode => hashValues(bounds, type, state); + + @override + String toString() { + return 'DisplayFeature(rect: $bounds, type: $type, state: $state)'; + } +} + +/// Type of [DisplayFeature], describing the [DisplayFeature] behaviour and if +/// it obstructs the display. +/// +/// Some types of [DisplayFeature], like [DisplayFeatureType.fold], can be +/// reported without actually impeding drawing on the screen. They are useful +/// for knowing where the display is bent or has a crease. The +/// [DisplayFeature.bounds] can be 0-width in such cases. +/// +/// The shape formed by the screens for types [DisplayFeatureType.fold] and +/// [DisplayFeatureType.hinge] is called the posture and is exposed in +/// [DisplayFeature.state]. For example, the [DisplayFeatureState.postureFlat] posture +/// means the screens form a flat surface, while [DisplayFeatureState.postureFlipped] +/// posture means the screens are facing opposite directions. +/// +/// ![Device with a hinge display feature](https://flutter.github.io/assets-for-api-docs/assets/hardware/display_feature_hinge.png) +/// +/// ![Device with a fold display feature](https://flutter.github.io/assets-for-api-docs/assets/hardware/display_feature_fold.png) +/// +/// ![Device with a cutout display feature](https://flutter.github.io/assets-for-api-docs/assets/hardware/display_feature_cutout.png) +enum DisplayFeatureType { + /// [DisplayFeature] type is new and not yet known to Flutter. + unknown, + /// A fold in the flexible screen without a physical gap. + /// + /// The bounds for this display feature type indicate where the display makes a crease. + fold, + /// A physical separation with a hinge that allows two display panels to fold. + hinge, + /// A non-displaying area of the screen, usually housing cameras or sensors. + cutout, +} + +/// State of the display feature, which contains information about the posture +/// for foldable features. +/// +/// The posture is the shape made by the parts of the flexible screen or +/// physical screen panels. They are inspired by and similar to +/// [Android Postures](https://developer.android.com/guide/topics/ui/foldables#postures). +/// +/// * For [DisplayFeatureType.fold]s & [DisplayFeatureType.hinge]s, the state is +/// the posture. +/// * For [DisplayFeatureType.cutout]s, the state is not used and has the +/// [DisplayFeatureState.unknown] value. +enum DisplayFeatureState { + /// The display feature is a [DisplayFeatureType.cutout] or this state is new + /// and not yet known to Flutter. + unknown, + /// The foldable device is completely open. + /// + /// The screen space that is presented to the user is flat. + postureFlat, + /// Fold angle is in an intermediate position between opened and closed state. + /// + /// There is a non-flat angle between parts of the flexible screen or between + /// physical screen panels such that the screens start to face each other. + postureHalfOpened, +} + /// An identifier used to select a user's language and formatting preferences. /// /// This represents a [Unicode Language diff --git a/lib/ui/window.dart b/lib/ui/window.dart index 8ddb931d184ea..31e63036081b4 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -225,6 +225,17 @@ abstract class FlutterView { /// applications. WindowPadding get padding => viewConfiguration.padding; + /// {@macro dart.ui.ViewConfiguration.displayFeatures} + /// + /// When this changes, [onMetricsChanged] is called. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this value changes. + /// * [MediaQuery.of], a simpler mechanism to access this data. + List get displayFeatures => viewConfiguration.displayFeatures; + /// Updates the view's rendering on the GPU with the newly provided [Scene]. /// /// This function must be called within the scope of the diff --git a/lib/ui/window/viewport_metrics.cc b/lib/ui/window/viewport_metrics.cc index 961e29dd4cf2e..82867c2e6c917 100644 --- a/lib/ui/window/viewport_metrics.cc +++ b/lib/ui/window/viewport_metrics.cc @@ -19,22 +19,26 @@ ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, physical_height(p_physical_height), physical_touch_slop(p_physical_touch_slop) {} -ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, - double p_physical_width, - double p_physical_height, - double p_physical_padding_top, - double p_physical_padding_right, - double p_physical_padding_bottom, - double p_physical_padding_left, - double p_physical_view_inset_top, - double p_physical_view_inset_right, - double p_physical_view_inset_bottom, - double p_physical_view_inset_left, - double p_physical_system_gesture_inset_top, - double p_physical_system_gesture_inset_right, - double p_physical_system_gesture_inset_bottom, - double p_physical_system_gesture_inset_left, - double p_physical_touch_slop) +ViewportMetrics::ViewportMetrics( + double p_device_pixel_ratio, + double p_physical_width, + double p_physical_height, + double p_physical_padding_top, + double p_physical_padding_right, + double p_physical_padding_bottom, + double p_physical_padding_left, + double p_physical_view_inset_top, + double p_physical_view_inset_right, + double p_physical_view_inset_bottom, + double p_physical_view_inset_left, + double p_physical_system_gesture_inset_top, + double p_physical_system_gesture_inset_right, + double p_physical_system_gesture_inset_bottom, + double p_physical_system_gesture_inset_left, + double p_physical_touch_slop, + const std::vector p_physical_display_features_bounds, + const std::vector p_physical_display_features_type, + const std::vector p_physical_display_features_state) : device_pixel_ratio(p_device_pixel_ratio), physical_width(p_physical_width), physical_height(p_physical_height), @@ -52,7 +56,10 @@ ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, physical_system_gesture_inset_bottom( p_physical_system_gesture_inset_bottom), physical_system_gesture_inset_left(p_physical_system_gesture_inset_left), - physical_touch_slop(p_physical_touch_slop) {} + physical_touch_slop(p_physical_touch_slop), + physical_display_features_bounds(p_physical_display_features_bounds), + physical_display_features_type(p_physical_display_features_type), + physical_display_features_state(p_physical_display_features_state) {} bool operator==(const ViewportMetrics& a, const ViewportMetrics& b) { return a.device_pixel_ratio == b.device_pixel_ratio && @@ -74,7 +81,11 @@ bool operator==(const ViewportMetrics& a, const ViewportMetrics& b) { b.physical_system_gesture_inset_bottom && a.physical_system_gesture_inset_left == b.physical_system_gesture_inset_left && - a.physical_touch_slop == b.physical_touch_slop; + a.physical_touch_slop == b.physical_touch_slop && + a.physical_display_features_bounds == + b.physical_display_features_bounds && + a.physical_display_features_type == b.physical_display_features_type && + a.physical_display_features_state == b.physical_display_features_state; } std::ostream& operator<<(std::ostream& os, const ViewportMetrics& a) { @@ -89,7 +100,8 @@ std::ostream& operator<<(std::ostream& os, const ViewportMetrics& a) { << "Gesture Insets: [" << a.physical_system_gesture_inset_top << "T " << a.physical_system_gesture_inset_right << "R " << a.physical_system_gesture_inset_bottom << "B " - << a.physical_system_gesture_inset_left << "L]"; + << a.physical_system_gesture_inset_left << "L] " + << "Display Features: " << a.physical_display_features_type.size(); return os; } diff --git a/lib/ui/window/viewport_metrics.h b/lib/ui/window/viewport_metrics.h index 3224b2930609a..1dab8927afb38 100644 --- a/lib/ui/window/viewport_metrics.h +++ b/lib/ui/window/viewport_metrics.h @@ -6,6 +6,7 @@ #define FLUTTER_LIB_UI_WINDOW_VIEWPORT_METRICS_H_ #include +#include namespace flutter { @@ -30,7 +31,10 @@ struct ViewportMetrics { double p_physical_system_gesture_inset_right, double p_physical_system_gesture_inset_bottom, double p_physical_system_gesture_inset_left, - double p_physical_touch_slop); + double p_physical_touch_slop, + const std::vector p_physical_display_features_bounds, + const std::vector p_physical_display_features_type, + const std::vector p_physical_display_features_state); double device_pixel_ratio = 1.0; double physical_width = 0; @@ -48,6 +52,9 @@ struct ViewportMetrics { double physical_system_gesture_inset_bottom = 0; double physical_system_gesture_inset_left = 0; double physical_touch_slop = -1.0; + std::vector physical_display_features_bounds; + std::vector physical_display_features_type; + std::vector physical_display_features_state; }; bool operator==(const ViewportMetrics& a, const ViewportMetrics& b); diff --git a/lib/ui/window/window.cc b/lib/ui/window/window.cc index 66f5e2e7dbef0..23a04398e2600 100644 --- a/lib/ui/window/window.cc +++ b/lib/ui/window/window.cc @@ -64,22 +64,28 @@ void Window::UpdateWindowMetrics(const ViewportMetrics& metrics) { tonic::DartState::Scope scope(dart_state); tonic::LogIfError(tonic::DartInvokeField( library_.value(), "_updateWindowMetrics", - {tonic::ToDart(window_id_), tonic::ToDart(metrics.device_pixel_ratio), - tonic::ToDart(metrics.physical_width), - tonic::ToDart(metrics.physical_height), - tonic::ToDart(metrics.physical_padding_top), - tonic::ToDart(metrics.physical_padding_right), - tonic::ToDart(metrics.physical_padding_bottom), - tonic::ToDart(metrics.physical_padding_left), - tonic::ToDart(metrics.physical_view_inset_top), - tonic::ToDart(metrics.physical_view_inset_right), - tonic::ToDart(metrics.physical_view_inset_bottom), - tonic::ToDart(metrics.physical_view_inset_left), - tonic::ToDart(metrics.physical_system_gesture_inset_top), - tonic::ToDart(metrics.physical_system_gesture_inset_right), - tonic::ToDart(metrics.physical_system_gesture_inset_bottom), - tonic::ToDart(metrics.physical_system_gesture_inset_left), - tonic::ToDart(metrics.physical_touch_slop)})); + { + tonic::ToDart(window_id_), + tonic::ToDart(metrics.device_pixel_ratio), + tonic::ToDart(metrics.physical_width), + tonic::ToDart(metrics.physical_height), + tonic::ToDart(metrics.physical_padding_top), + tonic::ToDart(metrics.physical_padding_right), + tonic::ToDart(metrics.physical_padding_bottom), + tonic::ToDart(metrics.physical_padding_left), + tonic::ToDart(metrics.physical_view_inset_top), + tonic::ToDart(metrics.physical_view_inset_right), + tonic::ToDart(metrics.physical_view_inset_bottom), + tonic::ToDart(metrics.physical_view_inset_left), + tonic::ToDart(metrics.physical_system_gesture_inset_top), + tonic::ToDart(metrics.physical_system_gesture_inset_right), + tonic::ToDart(metrics.physical_system_gesture_inset_bottom), + tonic::ToDart(metrics.physical_system_gesture_inset_left), + tonic::ToDart(metrics.physical_touch_slop), + tonic::ToDart(metrics.physical_display_features_bounds), + tonic::ToDart(metrics.physical_display_features_type), + tonic::ToDart(metrics.physical_display_features_state), + })); } } // namespace flutter diff --git a/lib/web_ui/lib/src/ui/platform_dispatcher.dart b/lib/web_ui/lib/src/ui/platform_dispatcher.dart index 8408c11865d96..37b21bad57c0f 100644 --- a/lib/web_ui/lib/src/ui/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/ui/platform_dispatcher.dart @@ -156,6 +156,7 @@ class ViewConfiguration { this.systemGestureInsets = WindowPadding.zero, this.padding = WindowPadding.zero, this.gestureSettings = const GestureSettings(), + this.displayFeatures = const [], }); ViewConfiguration copyWith({ @@ -168,6 +169,7 @@ class ViewConfiguration { WindowPadding? systemGestureInsets, WindowPadding? padding, GestureSettings? gestureSettings, + List? displayFeatures, }) { return ViewConfiguration( window: window ?? this.window, @@ -179,6 +181,7 @@ class ViewConfiguration { systemGestureInsets: systemGestureInsets ?? this.systemGestureInsets, padding: padding ?? this.padding, gestureSettings: gestureSettings ?? this.gestureSettings, + displayFeatures: displayFeatures ?? this.displayFeatures, ); } @@ -191,6 +194,7 @@ class ViewConfiguration { final WindowPadding systemGestureInsets; final WindowPadding padding; final GestureSettings gestureSettings; + final List displayFeatures; @override String toString() { @@ -285,6 +289,50 @@ abstract class WindowPadding { } } +class DisplayFeature { + const DisplayFeature({ + required this.bounds, + required this.type, + required this.state, + }); + + final Rect bounds; + final DisplayFeatureType type; + final DisplayFeatureState state; + + @override + bool operator ==(Object other) { + if (identical(this, other)) + return true; + if (other.runtimeType != runtimeType) + return false; + return other is DisplayFeature && bounds == other.bounds && + type == other.type && state == other.state; + } + + @override + int get hashCode => hashValues(bounds, type, state); + + @override + String toString() { + return 'DisplayFeature(rect: $bounds, type: $type, state: $state)'; + } +} + +enum DisplayFeatureType { + unknown, + fold, + hinge, + cutout, +} + +enum DisplayFeatureState { + unknown, + postureFlat, + postureHalfOpened, + postureFlipped, +} + class Locale { const Locale( this._languageCode, [ diff --git a/lib/web_ui/lib/src/ui/window.dart b/lib/web_ui/lib/src/ui/window.dart index b69d30765e4ef..d88e6dc5a56fa 100644 --- a/lib/web_ui/lib/src/ui/window.dart +++ b/lib/web_ui/lib/src/ui/window.dart @@ -14,6 +14,7 @@ abstract class FlutterView { WindowPadding get viewPadding => viewConfiguration.viewPadding; WindowPadding get systemGestureInsets => viewConfiguration.systemGestureInsets; WindowPadding get padding => viewConfiguration.padding; + List get displayFeatures => viewConfiguration.displayFeatures; void render(Scene scene) => platformDispatcher.render(scene, this); } diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index 3e55654a19c88..fd0ade059dab4 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -111,22 +111,25 @@ void ShellTest::VSyncFlush(Shell* shell, bool& will_draw_new_frame) { void ShellTest::SetViewportMetrics(Shell* shell, double width, double height) { flutter::ViewportMetrics viewport_metrics = { - 1, // device pixel ratio - width, // physical width - height, // physical height - 0, // padding top - 0, // padding right - 0, // padding bottom - 0, // padding left - 0, // view inset top - 0, // view inset right - 0, // view inset bottom - 0, // view inset left - 0, // gesture inset top - 0, // gesture inset right - 0, // gesture inset bottom - 0, // gesture inset left - 22 // physical touch slop + 1, // device pixel ratio + width, // physical width + height, // physical height + 0, // padding top + 0, // padding right + 0, // padding bottom + 0, // padding left + 0, // view inset top + 0, // view inset right + 0, // view inset bottom + 0, // view inset left + 0, // gesture inset top + 0, // gesture inset right + 0, // gesture inset bottom + 0, // gesture inset left + 22, // physical touch slop + std::vector(), // display features bounds + std::vector(), // display features type + std::vector() // display features state }; // Set viewport to nonempty, and call Animator::BeginFrame to make the layer // tree pipeline nonempty. Without either of this, the layer tree below diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 6d3ffa76c953c..53cc97f8862c3 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -158,6 +158,7 @@ android_java_sources = [ "io/flutter/embedding/android/SplashScreen.java", "io/flutter/embedding/android/SplashScreenProvider.java", "io/flutter/embedding/android/TransparencyMode.java", + "io/flutter/embedding/android/WindowInfoRepositoryCallbackAdapterWrapper.java", "io/flutter/embedding/engine/FlutterEngine.java", "io/flutter/embedding/engine/FlutterEngineCache.java", "io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java", diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index a224d34b26b10..f0167a5d39827 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -6,6 +6,7 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; +import android.app.Activity; import android.content.Context; import android.content.res.Configuration; import android.graphics.Insets; @@ -35,9 +36,20 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; +import androidx.core.content.ContextCompat; +import androidx.core.util.Consumer; +import androidx.window.java.layout.WindowInfoRepositoryCallbackAdapter; +import androidx.window.layout.DisplayFeature; +import androidx.window.layout.FoldingFeature; +import androidx.window.layout.FoldingFeature.OcclusionType; +import androidx.window.layout.FoldingFeature.State; +import androidx.window.layout.WindowInfoRepository; +import androidx.window.layout.WindowLayoutInfo; import io.flutter.Log; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.renderer.FlutterRenderer; +import io.flutter.embedding.engine.renderer.FlutterRenderer.DisplayFeatureState; +import io.flutter.embedding.engine.renderer.FlutterRenderer.DisplayFeatureType; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.embedding.engine.renderer.RenderSurface; import io.flutter.embedding.engine.systemchannels.SettingsChannel; @@ -48,7 +60,9 @@ import io.flutter.view.AccessibilityBridge; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -110,6 +124,8 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC @Nullable private AndroidTouchProcessor androidTouchProcessor; @Nullable private AccessibilityBridge accessibilityBridge; + // Provides access to foldable/hinge information + @Nullable private WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo; // Directly implemented View behavior that communicates with Flutter. private final FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics(); @@ -144,6 +160,14 @@ public void onFlutterUiNoLongerDisplayed() { } }; + private final Consumer windowInfoListener = + new Consumer() { + @Override + public void accept(WindowLayoutInfo layoutInfo) { + setWindowInfoListenerDisplayFeatures(layoutInfo); + } + }; + /** * Constructs a {@code FlutterView} programmatically, without any XML attributes. * @@ -426,6 +450,114 @@ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) sendViewportMetricsToFlutter(); } + @VisibleForTesting() + protected WindowInfoRepositoryCallbackAdapterWrapper createWindowInfoRepo() { + try { + return new WindowInfoRepositoryCallbackAdapterWrapper( + new WindowInfoRepositoryCallbackAdapter( + WindowInfoRepository.getOrCreate((Activity) getContext()))); + } catch (NoClassDefFoundError noClassDefFoundError) { + // Testing environment uses gn/javac, which does not work with aar files. This is why aar + // are converted to jar files, losing resources and other android-specific files. + // androidx.window does contain resources, which causes it to fail during testing, since the + // class androidx.window.R is not found. + // This method is mocked in the tests involving androidx.window, but this catch block is + // needed for other tests, which would otherwise fail during onAttachedToWindow(). + return null; + } + } + + /** + * Invoked when this is attached to the window. + * + *

We register for {@link androidx.window.layout.WindowInfoRepository} updates. + */ + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + this.windowInfoRepo = createWindowInfoRepo(); + if (windowInfoRepo != null) { + windowInfoRepo.addWindowLayoutInfoListener( + ContextCompat.getMainExecutor(getContext()), windowInfoListener); + } + } + + /** + * Invoked when this is detached from the window. + * + *

We unregister from {@link androidx.window.layout.WindowInfoRepository} updates. + */ + @Override + protected void onDetachedFromWindow() { + if (windowInfoRepo != null) { + windowInfoRepo.removeWindowLayoutInfoListener(windowInfoListener); + } + this.windowInfoRepo = null; + super.onDetachedFromWindow(); + } + + /** + * Refresh {@link androidx.window.layout.WindowInfoRepository} and {@link + * android.view.DisplayCutout} display features. Fold, hinge and cutout areas are populated here. + */ + @TargetApi(28) + protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo) { + List displayFeatures = layoutInfo.getDisplayFeatures(); + List result = new ArrayList<>(); + + // Data from WindowInfoRepository display features. Fold and hinge areas are + // populated here. + for (DisplayFeature displayFeature : displayFeatures) { + Log.v( + TAG, + "WindowInfoRepository Display Feature reported with bounds = " + + displayFeature.getBounds().toString() + + " and type = " + + displayFeature.getClass().getSimpleName()); + if (displayFeature instanceof FoldingFeature) { + DisplayFeatureType type; + DisplayFeatureState state; + final FoldingFeature feature = (FoldingFeature) displayFeature; + if (feature.getOcclusionType() == OcclusionType.FULL) { + type = DisplayFeatureType.HINGE; + } else { + type = DisplayFeatureType.FOLD; + } + if (feature.getState() == State.FLAT) { + state = DisplayFeatureState.POSTURE_FLAT; + } else if (feature.getState() == State.HALF_OPENED) { + state = DisplayFeatureState.POSTURE_HALF_OPENED; + } else { + state = DisplayFeatureState.UNKNOWN; + } + result.add(new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state)); + } else { + result.add( + new FlutterRenderer.DisplayFeature( + displayFeature.getBounds(), + DisplayFeatureType.UNKNOWN, + DisplayFeatureState.UNKNOWN)); + } + } + + // Data from the DisplayCutout bounds. Cutouts for cameras and other sensors are + // populated here. DisplayCutout was introduced in API 28. + if (Build.VERSION.SDK_INT >= 28) { + WindowInsets insets = getRootWindowInsets(); + if (insets != null) { + DisplayCutout cutout = insets.getDisplayCutout(); + if (cutout != null) { + for (Rect bounds : cutout.getBoundingRects()) { + Log.v(TAG, "DisplayCutout area reported with bounds = " + bounds.toString()); + result.add(new FlutterRenderer.DisplayFeature(bounds, DisplayFeatureType.CUTOUT)); + } + } + } + } + viewportMetrics.displayFeatures = result; + sendViewportMetricsToFlutter(); + } + // TODO(garyq): Add support for notch cutout API: https://github.com/flutter/flutter/issues/56592 // Decide if we want to zero the padding of the sides. When in Landscape orientation, // android may decide to place the software navigation bars on the side. When the nav diff --git a/shell/platform/android/io/flutter/embedding/android/WindowInfoRepositoryCallbackAdapterWrapper.java b/shell/platform/android/io/flutter/embedding/android/WindowInfoRepositoryCallbackAdapterWrapper.java new file mode 100644 index 0000000000000..3d5bc3043da00 --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/android/WindowInfoRepositoryCallbackAdapterWrapper.java @@ -0,0 +1,30 @@ +// 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. + +package io.flutter.embedding.android; + +import androidx.core.util.Consumer; +import androidx.window.java.layout.WindowInfoRepositoryCallbackAdapter; +import androidx.window.layout.WindowLayoutInfo; +import java.util.concurrent.Executor; + +/** + * Wraps {@link WindowInfoRepositoryCallbackAdapter} in order to be able to mock it during testing. + */ +public class WindowInfoRepositoryCallbackAdapterWrapper { + + final WindowInfoRepositoryCallbackAdapter adapter; + + public WindowInfoRepositoryCallbackAdapterWrapper(WindowInfoRepositoryCallbackAdapter adapter) { + this.adapter = adapter; + } + + public void addWindowLayoutInfoListener(Executor executor, Consumer consumer) { + adapter.addWindowLayoutInfoListener(executor, consumer); + } + + public void removeWindowLayoutInfoListener(Consumer consumer) { + adapter.removeWindowLayoutInfoListener(consumer); + } +} diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 3a2d4fcd48f57..919dcc0e477ab 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -564,7 +564,10 @@ public void setViewportMetrics( int systemGestureInsetRight, int systemGestureInsetBottom, int systemGestureInsetLeft, - int physicalTouchSlop) { + int physicalTouchSlop, + int[] displayFeaturesBounds, + int[] displayFeaturesType, + int[] displayFeaturesState) { ensureRunningOnMainThread(); ensureAttachedToNative(); nativeSetViewportMetrics( @@ -584,7 +587,10 @@ public void setViewportMetrics( systemGestureInsetRight, systemGestureInsetBottom, systemGestureInsetLeft, - physicalTouchSlop); + physicalTouchSlop, + displayFeaturesBounds, + displayFeaturesType, + displayFeaturesState); } private native void nativeSetViewportMetrics( @@ -604,7 +610,10 @@ private native void nativeSetViewportMetrics( int systemGestureInsetRight, int systemGestureInsetBottom, int systemGestureInsetLeft, - int physicalTouchSlop); + int physicalTouchSlop, + int[] displayFeaturesBounds, + int[] displayFeaturesType, + int[] displayFeaturesState); // ----- End Render Surface Support ----- // ------ Start Touch Interaction Support --- diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index f079c3614b272..d68b17bdf7c44 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -6,6 +6,7 @@ import android.annotation.TargetApi; import android.graphics.Bitmap; +import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.os.Build; import android.os.Handler; @@ -16,6 +17,8 @@ import io.flutter.embedding.engine.FlutterJNI; import io.flutter.view.TextureRegistry; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.atomic.AtomicLong; /** @@ -326,7 +329,23 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { + ", R: " + viewportMetrics.systemGestureInsetRight + ", B: " - + viewportMetrics.viewInsetBottom); + + viewportMetrics.systemGestureInsetRight + + "\n" + + "Display Features: " + + viewportMetrics.displayFeatures.size()); + + int[] displayFeaturesBounds = new int[viewportMetrics.displayFeatures.size() * 4]; + int[] displayFeaturesType = new int[viewportMetrics.displayFeatures.size()]; + int[] displayFeaturesState = new int[viewportMetrics.displayFeatures.size()]; + for (int i = 0; i < viewportMetrics.displayFeatures.size(); i++) { + DisplayFeature displayFeature = viewportMetrics.displayFeatures.get(i); + displayFeaturesBounds[4 * i] = displayFeature.bounds.left; + displayFeaturesBounds[4 * i + 1] = displayFeature.bounds.top; + displayFeaturesBounds[4 * i + 2] = displayFeature.bounds.right; + displayFeaturesBounds[4 * i + 3] = displayFeature.bounds.bottom; + displayFeaturesType[i] = displayFeature.type.encodedValue; + displayFeaturesState[i] = displayFeature.state.encodedValue; + } flutterJNI.setViewportMetrics( viewportMetrics.devicePixelRatio, @@ -344,7 +363,10 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { viewportMetrics.systemGestureInsetRight, viewportMetrics.systemGestureInsetBottom, viewportMetrics.systemGestureInsetLeft, - viewportMetrics.physicalTouchSlop); + viewportMetrics.physicalTouchSlop, + displayFeaturesBounds, + displayFeaturesType, + displayFeaturesState); } // TODO(mattcarroll): describe the native behavior that this invokes @@ -429,5 +451,103 @@ public static final class ViewportMetrics { boolean validate() { return width > 0 && height > 0 && devicePixelRatio > 0; } + + public List displayFeatures = new ArrayList(); + } + + /** + * Description of a physical feature on the display. + * + *

A display feature is a distinctive physical attribute located within the display panel of + * the device. It can intrude into the application window space and create a visual distortion, + * visual or touch discontinuity, make some area invisible or create a logical divider or + * separation in the screen space. + * + *

Based on {@link androidx.window.layout.DisplayFeature}, with added support for cutouts. + */ + public static final class DisplayFeature { + public final Rect bounds; + public final DisplayFeatureType type; + public final DisplayFeatureState state; + + public DisplayFeature(Rect bounds, DisplayFeatureType type, DisplayFeatureState state) { + this.bounds = bounds; + this.type = type; + this.state = state; + } + + public DisplayFeature(Rect bounds, DisplayFeatureType type) { + this.bounds = bounds; + this.type = type; + this.state = DisplayFeatureState.UNKNOWN; + } + } + + /** + * Types of display features that can appear on the viewport. + * + *

Some, like {@link #FOLD}, can be reported without actually occluding the screen. They are + * useful for knowing where the display is bent or has a crease. The {@link DisplayFeature#bounds} + * can be 0-width in such cases. + */ + public enum DisplayFeatureType { + /** + * Type of display feature not yet known to Flutter. This can happen if WindowManager is updated + * with new types. The {@link DisplayFeature#bounds} is the only known property. + */ + UNKNOWN(0), + + /** + * A fold in the flexible display that does not occlude the screen. Corresponds to {@link + * androidx.window.layout.FoldingFeature.OcclusionType#NONE} + */ + FOLD(1), + + /** + * Splits the display in two separate panels that can fold. Occludes the screen. Corresponds to + * {@link androidx.window.layout.FoldingFeature.OcclusionType#FULL} + */ + HINGE(2), + + /** + * Area of the screen that usually houses cameras or sensors. Occludes the screen. Corresponds + * to {@link android.view.DisplayCutout} + */ + CUTOUT(3); + + public final int encodedValue; + + DisplayFeatureType(int encodedValue) { + this.encodedValue = encodedValue; + } + } + + /** + * State of the display feature. + * + *

For foldables, the state is the posture. For cutouts, this property is {@link #UNKNOWN} + */ + public enum DisplayFeatureState { + /** The display feature is a cutout or this state is new and not yet known to Flutter. */ + UNKNOWN(0), + + /** + * The foldable device is completely open. The screen space that is presented to the user is + * flat. Corresponds to {@link androidx.window.layout.FoldingFeature.State#FLAT} + */ + POSTURE_FLAT(1), + + /** + * The foldable device's hinge is in an intermediate position between opened and closed state. + * There is a non-flat angle between parts of the flexible screen or between physical display + * panels. Corresponds to {@link androidx.window.layout.FoldingFeature.State#HALF_OPENED} + */ + POSTURE_HALF_OPENED(2); + + public final int encodedValue; + + DisplayFeatureState(int encodedValue) { + this.encodedValue = encodedValue; + } } } diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 4ed83c0d55292..2b965c0aa5035 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -766,7 +766,10 @@ private void updateViewportMetrics() { mMetrics.systemGestureInsetRight, mMetrics.systemGestureInsetBottom, mMetrics.systemGestureInsetLeft, - mMetrics.physicalTouchSlop); + mMetrics.physicalTouchSlop, + new int[0], + new int[0], + new int[0]); } // Called by FlutterNativeView to notify first Flutter frame rendered. diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 840f71317bd98..4df01c9271acd 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -282,7 +282,28 @@ static void SetViewportMetrics(JNIEnv* env, jint systemGestureInsetRight, jint systemGestureInsetBottom, jint systemGestureInsetLeft, - jint physicalTouchSlop) { + jint physicalTouchSlop, + jintArray javaDisplayFeaturesBounds, + jintArray javaDisplayFeaturesType, + jintArray javaDisplayFeaturesState) { + // Convert java->c++. javaDisplayFeaturesBounds, javaDisplayFeaturesType and + // javaDisplayFeaturesState cannot be null + jsize rectSize = env->GetArrayLength(javaDisplayFeaturesBounds); + std::vector boundsIntVector(rectSize); + env->GetIntArrayRegion(javaDisplayFeaturesBounds, 0, rectSize, + &boundsIntVector[0]); + std::vector displayFeaturesBounds(boundsIntVector.begin(), + boundsIntVector.end()); + jsize typeSize = env->GetArrayLength(javaDisplayFeaturesType); + std::vector displayFeaturesType(typeSize); + env->GetIntArrayRegion(javaDisplayFeaturesType, 0, typeSize, + &displayFeaturesType[0]); + + jsize stateSize = env->GetArrayLength(javaDisplayFeaturesState); + std::vector displayFeaturesState(stateSize); + env->GetIntArrayRegion(javaDisplayFeaturesState, 0, stateSize, + &displayFeaturesState[0]); + const flutter::ViewportMetrics metrics{ static_cast(devicePixelRatio), static_cast(physicalWidth), @@ -300,6 +321,9 @@ static void SetViewportMetrics(JNIEnv* env, static_cast(systemGestureInsetBottom), static_cast(systemGestureInsetLeft), static_cast(physicalTouchSlop), + displayFeaturesBounds, + displayFeaturesType, + displayFeaturesState, }; ANDROID_SHELL_HOLDER->GetPlatformView()->SetViewportMetrics(metrics); @@ -689,7 +713,7 @@ bool RegisterApi(JNIEnv* env) { }, { .name = "nativeSetViewportMetrics", - .signature = "(JFIIIIIIIIIIIIIII)V", + .signature = "(JFIIIIIIIIIIIIIII[I[I[I)V", .fnPtr = reinterpret_cast(&SetViewportMetrics), }, { diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 9aa0ec9c0156b..b9f4006914f31 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -6,6 +6,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -14,11 +15,13 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; +import android.app.Activity; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Insets; +import android.graphics.Rect; import android.graphics.Region; import android.media.Image; import android.media.Image.Plane; @@ -29,6 +32,9 @@ import android.view.WindowInsets; import android.view.WindowManager; import android.widget.FrameLayout; +import androidx.core.util.Consumer; +import androidx.window.layout.FoldingFeature; +import androidx.window.layout.WindowLayoutInfo; import io.flutter.TestUtils; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterJNI; @@ -37,6 +43,7 @@ import io.flutter.embedding.engine.systemchannels.SettingsChannel; import io.flutter.plugin.platform.PlatformViewsController; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; import org.junit.Before; import org.junit.Test; @@ -47,6 +54,7 @@ import org.mockito.Spy; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.Shadows; @@ -71,7 +79,7 @@ public void setUp() { @Test public void attachToFlutterEngine_alertsPlatformViews() { - FlutterView flutterView = new FlutterView(RuntimeEnvironment.application); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); @@ -83,7 +91,7 @@ public void attachToFlutterEngine_alertsPlatformViews() { @Test public void detachFromFlutterEngine_alertsPlatformViews() { - FlutterView flutterView = new FlutterView(RuntimeEnvironment.application); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); @@ -96,7 +104,7 @@ public void detachFromFlutterEngine_alertsPlatformViews() { @Test public void detachFromFlutterEngine_turnsOffA11y() { - FlutterView flutterView = new FlutterView(RuntimeEnvironment.application); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); @@ -110,7 +118,7 @@ public void detachFromFlutterEngine_turnsOffA11y() { @Test public void onConfigurationChanged_fizzlesWhenNullEngine() { - FlutterView flutterView = new FlutterView(RuntimeEnvironment.application); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -135,7 +143,7 @@ public void itSendsLightPlatformBrightnessToFlutter() { new AtomicReference<>(); // FYI - The default brightness is LIGHT, which is why we don't need to configure it. - FlutterView flutterView = new FlutterView(RuntimeEnvironment.application); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); @@ -173,7 +181,7 @@ public void itSendsDarkPlatformBrightnessToFlutter() { AtomicReference reportedBrightness = new AtomicReference<>(); - Context spiedContext = spy(RuntimeEnvironment.application); + Context spiedContext = spy(Robolectric.setupActivity(Activity.class)); Resources spiedResources = spy(spiedContext.getResources()); when(spiedContext.getResources()).thenReturn(spiedResources); @@ -225,7 +233,7 @@ public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) FlutterViewTest.ShadowFullscreenViewGroup.class }) public void setPaddingTopToZeroForFullscreenMode() { - FlutterView flutterView = new FlutterView(RuntimeEnvironment.application); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); @@ -268,7 +276,7 @@ public void setPaddingTopToZeroForFullscreenMode() { FlutterViewTest.ShadowFullscreenViewGroup.class }) public void setPaddingTopToZeroForFullscreenModeLegacy() { - FlutterView flutterView = new FlutterView(RuntimeEnvironment.application); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); @@ -306,7 +314,7 @@ public void setPaddingTopToZeroForFullscreenModeLegacy() { @Config(sdk = 30) public void reportSystemInsetWhenNotFullscreen() { // Without custom shadows, the default system ui visibility flags is 0. - FlutterView flutterView = new FlutterView(RuntimeEnvironment.application); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); assertEquals(0, flutterView.getSystemUiVisibility()); FlutterEngine flutterEngine = @@ -348,7 +356,7 @@ public void reportSystemInsetWhenNotFullscreen() { @Config(sdk = 28) public void reportSystemInsetWhenNotFullscreenLegacy() { // Without custom shadows, the default system ui visibility flags is 0. - FlutterView flutterView = new FlutterView(RuntimeEnvironment.application); + FlutterView flutterView = new FlutterView(Robolectric.setupActivity(Activity.class)); assertEquals(0, flutterView.getSystemUiVisibility()); FlutterEngine flutterEngine = @@ -385,7 +393,7 @@ public void reportSystemInsetWhenNotFullscreenLegacy() { @Test public void systemInsetHandlesFullscreenNavbarRight() { RuntimeEnvironment.setQualifiers("+land"); - FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); + FlutterView flutterView = spy(new FlutterView(Robolectric.setupActivity(Activity.class))); ShadowDisplay display = Shadows.shadowOf( ((WindowManager) @@ -432,7 +440,7 @@ public void systemInsetHandlesFullscreenNavbarRight() { @Test public void systemInsetHandlesFullscreenNavbarLeft() { RuntimeEnvironment.setQualifiers("+land"); - FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); + FlutterView flutterView = spy(new FlutterView(Robolectric.setupActivity(Activity.class))); ShadowDisplay display = Shadows.shadowOf( ((WindowManager) @@ -483,7 +491,7 @@ public void systemInsetHandlesFullscreenNavbarLeft() { @Config(sdk = 30) public void systemInsetGetInsetsFullscreen() { RuntimeEnvironment.setQualifiers("+land"); - FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); + FlutterView flutterView = spy(new FlutterView(Robolectric.setupActivity(Activity.class))); ShadowDisplay display = Shadows.shadowOf( ((WindowManager) @@ -532,7 +540,7 @@ public void systemInsetGetInsetsFullscreen() { @Config(sdk = 28) public void systemInsetGetInsetsFullscreenLegacy() { RuntimeEnvironment.setQualifiers("+land"); - FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); + FlutterView flutterView = spy(new FlutterView(Robolectric.setupActivity(Activity.class))); ShadowDisplay display = Shadows.shadowOf( ((WindowManager) @@ -583,7 +591,7 @@ public void systemInsetGetInsetsFullscreenLegacy() { @Config(sdk = 30) public void systemInsetDisplayCutoutSimple() { RuntimeEnvironment.setQualifiers("+land"); - FlutterView flutterView = spy(new FlutterView(RuntimeEnvironment.systemContext)); + FlutterView flutterView = spy(new FlutterView(Robolectric.setupActivity(Activity.class))); ShadowDisplay display = Shadows.shadowOf( ((WindowManager) @@ -637,6 +645,85 @@ public void systemInsetDisplayCutoutSimple() { assertEquals(100, viewportMetricsCaptor.getValue().viewInsetTop); } + @Test + public void itRegistersAndUnregistersToWindowManager() { + Context context = Robolectric.setupActivity(Activity.class); + FlutterView flutterView = spy(new FlutterView(context)); + ShadowDisplay display = + Shadows.shadowOf( + ((WindowManager) + RuntimeEnvironment.systemContext.getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay()); + WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo = + mock(WindowInfoRepositoryCallbackAdapterWrapper.class); + // For reasoning behing using doReturn instead of when, read "Important gotcha" at + // https://www.javadoc.io/doc/org.mockito/mockito-core/1.10.19/org/mockito/Mockito.html#13 + doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo(); + + // When a new FlutterView is attached to the window + flutterView.onAttachedToWindow(); + + // Then the WindowManager callback is registered + verify(windowInfoRepo, times(1)).addWindowLayoutInfoListener(any(), any()); + + // When the FlutterView is detached from the window + flutterView.onDetachedFromWindow(); + + // Then the WindowManager callback is unregistered + verify(windowInfoRepo, times(1)).removeWindowLayoutInfoListener(any()); + } + + @Test + public void itSendsHingeDisplayFeatureToFlutter() { + Context context = Robolectric.setupActivity(Activity.class); + FlutterView flutterView = spy(new FlutterView(context)); + ShadowDisplay display = + Shadows.shadowOf( + ((WindowManager) + RuntimeEnvironment.systemContext.getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay()); + when(flutterView.getContext()).thenReturn(context); + WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo = + mock(WindowInfoRepositoryCallbackAdapterWrapper.class); + doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo(); + FlutterEngine flutterEngine = + spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); + FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); + when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + + FoldingFeature displayFeature = mock(FoldingFeature.class); + when(displayFeature.getBounds()).thenReturn(new Rect(0, 0, 100, 100)); + when(displayFeature.getOcclusionType()).thenReturn(FoldingFeature.OcclusionType.FULL); + when(displayFeature.getState()).thenReturn(FoldingFeature.State.FLAT); + + WindowLayoutInfo testWindowLayout = + new WindowLayoutInfo.Builder().setDisplayFeatures(Arrays.asList(displayFeature)).build(); + + // When FlutterView is attached to the engine and window, and a hinge display feature exists + flutterView.attachToFlutterEngine(flutterEngine); + ArgumentCaptor viewportMetricsCaptor = + ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class); + verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); + assertEquals(Arrays.asList(), viewportMetricsCaptor.getValue().displayFeatures); + flutterView.onAttachedToWindow(); + ArgumentCaptor> wmConsumerCaptor = + ArgumentCaptor.forClass((Class) Consumer.class); + verify(windowInfoRepo).addWindowLayoutInfoListener(any(), wmConsumerCaptor.capture()); + Consumer wmConsumer = wmConsumerCaptor.getValue(); + wmConsumer.accept(testWindowLayout); + + // Then the Renderer receives the display feature + verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); + assertEquals( + FlutterRenderer.DisplayFeatureType.HINGE, + viewportMetricsCaptor.getValue().displayFeatures.get(0).type); + assertEquals( + FlutterRenderer.DisplayFeatureState.POSTURE_FLAT, + viewportMetricsCaptor.getValue().displayFeatures.get(0).state); + assertEquals( + new Rect(0, 0, 100, 100), viewportMetricsCaptor.getValue().displayFeatures.get(0).bounds); + } + @Test public void flutterImageView_acquiresImageAndInvalidates() { final ImageReader mockReader = mock(ImageReader.class); diff --git a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java index f2e22a06e78f8..23b0aaa9a1876 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java @@ -1,6 +1,9 @@ package io.flutter.embedding.engine.renderer; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyFloat; +import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -8,6 +11,7 @@ import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; +import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.os.Looper; import android.view.Surface; @@ -17,6 +21,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @@ -219,4 +224,65 @@ public void run() { // do nothing } } + + @Test + public void itConvertsDisplayFeatureArrayToPrimitiveArrays() { + // Setup the test. + FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI); + FlutterRenderer.ViewportMetrics metrics = new FlutterRenderer.ViewportMetrics(); + metrics.width = 1000; + metrics.height = 1000; + metrics.devicePixelRatio = 2; + metrics.displayFeatures.add( + new FlutterRenderer.DisplayFeature( + new Rect(10, 20, 30, 40), + FlutterRenderer.DisplayFeatureType.FOLD, + FlutterRenderer.DisplayFeatureState.POSTURE_HALF_OPENED)); + metrics.displayFeatures.add( + new FlutterRenderer.DisplayFeature( + new Rect(50, 60, 70, 80), FlutterRenderer.DisplayFeatureType.CUTOUT)); + + // Execute the behavior under test. + flutterRenderer.setViewportMetrics(metrics); + + // Verify behavior under test. + ArgumentCaptor boundsCaptor = ArgumentCaptor.forClass(int[].class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(int[].class); + ArgumentCaptor stateCaptor = ArgumentCaptor.forClass(int[].class); + verify(fakeFlutterJNI) + .setViewportMetrics( + anyFloat(), + anyInt(), + anyInt(), + anyInt(), + anyInt(), + anyInt(), + anyInt(), + anyInt(), + anyInt(), + anyInt(), + anyInt(), + anyInt(), + anyInt(), + anyInt(), + anyInt(), + anyInt(), + boundsCaptor.capture(), + typeCaptor.capture(), + stateCaptor.capture()); + + assertArrayEquals(new int[] {10, 20, 30, 40, 50, 60, 70, 80}, boundsCaptor.getValue()); + assertArrayEquals( + new int[] { + FlutterRenderer.DisplayFeatureType.FOLD.encodedValue, + FlutterRenderer.DisplayFeatureType.CUTOUT.encodedValue + }, + typeCaptor.getValue()); + assertArrayEquals( + new int[] { + FlutterRenderer.DisplayFeatureState.POSTURE_HALF_OPENED.encodedValue, + FlutterRenderer.DisplayFeatureState.UNKNOWN.encodedValue + }, + stateCaptor.getValue()); + } } diff --git a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java index eab8ce9d545cd..5216cf8b6982f 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java @@ -18,6 +18,7 @@ import static org.mockito.Mockito.when; import android.annotation.TargetApi; +import android.app.Activity; import android.content.Context; import android.content.res.AssetManager; import android.graphics.Insets; @@ -67,6 +68,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @@ -672,7 +674,7 @@ public void autofill_onProvideVirtualViewStructure() { return; } - FlutterView testView = new FlutterView(RuntimeEnvironment.application); + FlutterView testView = new FlutterView(Robolectric.setupActivity(Activity.class)); TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class)); TextInputPlugin textInputPlugin = new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class)); @@ -750,7 +752,7 @@ public void autofill_onProvideVirtualViewStructure_single() { return; } - FlutterView testView = new FlutterView(RuntimeEnvironment.application); + FlutterView testView = new FlutterView(Robolectric.setupActivity(Activity.class)); TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class)); TextInputPlugin textInputPlugin = new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class)); @@ -797,7 +799,7 @@ public void autofill_testLifeCycle() { TestAfm testAfm = Shadow.extract(RuntimeEnvironment.application.getSystemService(AutofillManager.class)); - FlutterView testView = new FlutterView(RuntimeEnvironment.application); + FlutterView testView = new FlutterView(Robolectric.setupActivity(Activity.class)); TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class)); TextInputPlugin textInputPlugin = new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class)); @@ -920,7 +922,7 @@ public void autofill_testAutofillUpdatesTheFramework() { TestAfm testAfm = Shadow.extract(RuntimeEnvironment.application.getSystemService(AutofillManager.class)); - FlutterView testView = new FlutterView(RuntimeEnvironment.application); + FlutterView testView = new FlutterView(Robolectric.setupActivity(Activity.class)); TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class))); TextInputPlugin textInputPlugin = new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class)); @@ -1009,7 +1011,7 @@ public void autofill_testSetTextIpnutClientUpdatesSideFields() { TestAfm testAfm = Shadow.extract(RuntimeEnvironment.application.getSystemService(AutofillManager.class)); - FlutterView testView = new FlutterView(RuntimeEnvironment.application); + FlutterView testView = new FlutterView(Robolectric.setupActivity(Activity.class)); TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class)); TextInputPlugin textInputPlugin = new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class)); @@ -1166,7 +1168,7 @@ public void sendAppPrivateCommand_hasData() throws JSONException { @TargetApi(30) @Config(sdk = 30) public void ime_windowInsetsSync() { - FlutterView testView = new FlutterView(RuntimeEnvironment.application); + FlutterView testView = new FlutterView(Robolectric.setupActivity(Activity.class)); TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class)); TextInputPlugin textInputPlugin = new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class)); diff --git a/shell/platform/android/test/io/flutter/plugin/mouse/MouseCursorPluginTest.java b/shell/platform/android/test/io/flutter/plugin/mouse/MouseCursorPluginTest.java index 60c275bd1ec49..72659ec0193a0 100644 --- a/shell/platform/android/test/io/flutter/plugin/mouse/MouseCursorPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/mouse/MouseCursorPluginTest.java @@ -8,6 +8,7 @@ import static org.mockito.Mockito.verify; import android.annotation.TargetApi; +import android.app.Activity; import android.view.PointerIcon; import io.flutter.embedding.android.FlutterView; import io.flutter.embedding.engine.dart.DartExecutor; @@ -18,8 +19,8 @@ import org.json.JSONException; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @Config( @@ -31,7 +32,7 @@ public class MouseCursorPluginTest { @Test public void mouseCursorPlugin_SetsSystemCursorOnRequest() throws JSONException { // Initialize a general MouseCursorPlugin. - FlutterView testView = spy(new FlutterView(RuntimeEnvironment.application)); + FlutterView testView = spy(new FlutterView(Robolectric.setupActivity(Activity.class))); MouseCursorChannel mouseCursorChannel = new MouseCursorChannel(mock(DartExecutor.class)); MouseCursorPlugin mouseCursorPlugin = new MouseCursorPlugin(testView, mouseCursorChannel); diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java index 81d884411c0b9..f25d07c4263ab 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -946,7 +946,10 @@ public void setViewportMetrics( int systemGestureInsetRight, int systemGestureInsetBottom, int systemGestureInsetLeft, - int physicalTouchSlop) {} + int physicalTouchSlop, + int[] displayFeaturesBounds, + int[] displayFeaturesType, + int[] displayFeaturesState) {} @Implementation public void invokePlatformMessageResponseCallback( diff --git a/shell/platform/android/test_runner/build.gradle b/shell/platform/android/test_runner/build.gradle index 704dc7c5b10c9..01a4015ed0af0 100644 --- a/shell/platform/android/test_runner/build.gradle +++ b/shell/platform/android/test_runner/build.gradle @@ -41,6 +41,7 @@ android { testImplementation "androidx.lifecycle:lifecycle-runtime:2.2.0" testImplementation "androidx.lifecycle:lifecycle-common-java8:2.2.0" testImplementation "androidx.test:core:1.4.0" + testImplementation "androidx.window:window-java:1.0.0-beta01" testImplementation "com.google.android.play:core:1.8.0" testImplementation "com.ibm.icu:icu4j:69.1" testImplementation "org.mockito:mockito-core:3.11.2" diff --git a/shell/platform/fuchsia/flutter/flatland_platform_view.cc b/shell/platform/fuchsia/flutter/flatland_platform_view.cc index 57206e1fd7418..5fe2a96d83f33 100644 --- a/shell/platform/fuchsia/flutter/flatland_platform_view.cc +++ b/shell/platform/fuchsia/flutter/flatland_platform_view.cc @@ -89,6 +89,9 @@ void FlatlandPlatformView::OnGetLayout( 0.0f, // p_physical_system_gesture_inset_bottom 0.0f, // p_physical_system_gesture_inset_left, -1.0, // p_physical_touch_slop, + {}, // p_physical_display_features_bounds + {}, // p_physical_display_features_type + {}, // p_physical_display_features_state }); parent_viewport_watcher_->GetLayout( diff --git a/shell/platform/fuchsia/flutter/gfx_platform_view.cc b/shell/platform/fuchsia/flutter/gfx_platform_view.cc index e89a06ef46fdf..5cdae343793f6 100644 --- a/shell/platform/fuchsia/flutter/gfx_platform_view.cc +++ b/shell/platform/fuchsia/flutter/gfx_platform_view.cc @@ -254,6 +254,9 @@ void GfxPlatformView::OnScenicEvent( 0.0f, // p_physical_system_gesture_inset_bottom 0.0f, // p_physical_system_gesture_inset_left, -1.0, // p_physical_touch_slop, + {}, // p_physical_display_features_bounds + {}, // p_physical_display_features_type + {}, // p_physical_display_features_state }); } } diff --git a/tools/androidx/files.json b/tools/androidx/files.json index e8939614a32b1..8ba4fdf16a01f 100644 --- a/tools/androidx/files.json +++ b/tools/androidx/files.json @@ -49,5 +49,19 @@ "androidx.annotation.UiThread", "androidx.annotation.VisibleForTesting" ] + }, + { + "url": "https://maven.google.com/androidx/window/window-java/1.0.0-beta01/window-java-1.0.0-beta01.aar", + "out_file_name": "androidx_window_java.aar", + "maven_dependency": "androidx.window:window-java:1.0.0-beta01", + "provides": [ + "androidx.window.java.layout.WindowInfoRepositoryCallbackAdapter", + "androidx.window.layout.DisplayFeature", + "androidx.window.layout.FoldingFeature", + "androidx.window.layout.FoldingFeature.OcclusionType", + "androidx.window.layout.FoldingFeature.State", + "androidx.window.layout.WindowLayoutInfo", + "androidx.window.layout.WindowInfoRepository" + ] } ] diff --git a/tools/cipd/android_embedding_bundle/build.gradle b/tools/cipd/android_embedding_bundle/build.gradle index e93098cf2ed7e..fca896a0136b2 100644 --- a/tools/cipd/android_embedding_bundle/build.gradle +++ b/tools/cipd/android_embedding_bundle/build.gradle @@ -42,6 +42,7 @@ android { dependencies { embedding "androidx.annotation:annotation:1.1.0" embedding "androidx.fragment:fragment:1.1.0" + embedding "androidx.window:window-java:1.0.0-beta01" def lifecycle_version = "2.2.0" embedding "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"