From 323490e3d9aab5099ab2599a6af27aa1bcaa8812 Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Mon, 18 Jan 2021 14:41:13 +0200 Subject: [PATCH 01/23] Add DisplayFeatures and DisplayCutout to viewport metrics --- lib/ui/hooks.dart | 6 + lib/ui/platform_dispatcher.dart | 145 ++++++++++++++++++ lib/ui/window.dart | 17 ++ lib/ui/window/viewport_metrics.cc | 21 ++- lib/ui/window/viewport_metrics.h | 9 +- lib/ui/window/window.cc | 3 + shell/common/shell_test.cc | 5 +- .../android/embedding_bundle/build.gradle | 1 + .../embedding/android/FlutterView.java | 96 ++++++++++++ .../flutter/embedding/engine/FlutterJNI.java | 16 +- .../engine/renderer/FlutterRenderer.java | 89 ++++++++++- .../android/io/flutter/view/FlutterView.java | 5 +- .../android/platform_view_android_jni_impl.cc | 25 ++- tools/androidx/files.json | 11 ++ 14 files changed, 435 insertions(+), 14 deletions(-) diff --git a/lib/ui/hooks.dart b/lib/ui/hooks.dart index 6ffcea0f93d72..1f415a6c07702 100644 --- a/lib/ui/hooks.dart +++ b/lib/ui/hooks.dart @@ -27,6 +27,9 @@ void _updateWindowMetrics( double systemGestureInsetRight, double systemGestureInsetBottom, double systemGestureInsetLeft, + List displayFeaturesBounds, + List displayFeaturesType, + List displayFeaturesState, ) { PlatformDispatcher.instance._updateWindowMetrics( id, @@ -45,6 +48,9 @@ void _updateWindowMetrics( systemGestureInsetRight, systemGestureInsetBottom, systemGestureInsetLeft, + displayFeaturesBounds, + displayFeaturesType, + displayFeaturesState, ); } diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index dc7223b79cc5d..b41c161393cc0 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -170,6 +170,9 @@ class PlatformDispatcher { double systemGestureInsetRight, double systemGestureInsetBottom, double systemGestureInsetLeft, + List displayFeaturesBounds, + List displayFeaturesType, + List displayFeaturesState, ) { final ViewConfiguration previousConfiguration = _viewConfigurations[id] ?? const ViewConfiguration(); @@ -204,10 +207,39 @@ class PlatformDispatcher { bottom: math.max(0.0, systemGestureInsetBottom), left: math.max(0.0, systemGestureInsetLeft), ), + displayFeatures: _decodeDisplayFeatures( + bounds: displayFeaturesBounds, + type: displayFeaturesType, + state: displayFeaturesState, + ), ); _invoke(onMetricsChanged, _onMetricsChangedZone); } + List _decodeDisplayFeatures({ + required List bounds, + required List type, + required List state, + }) { + final List result = []; + for(int i =0 ; i < type.length; i++){ + final int rectOffset = i * 4; + result.add(DisplayFeature( + rect: Rect.fromLTRB( + bounds[rectOffset], + bounds[rectOffset+1], + bounds[rectOffset+2], + bounds[rectOffset+3], + ), + 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 @@ -934,6 +966,7 @@ class ViewConfiguration { this.viewPadding = WindowPadding.zero, this.systemGestureInsets = WindowPadding.zero, this.padding = WindowPadding.zero, + this.displayFeatures = const [], }); /// Copy this configuration with some fields replaced. @@ -946,6 +979,7 @@ class ViewConfiguration { WindowPadding? viewPadding, WindowPadding? systemGestureInsets, WindowPadding? padding, + List? displayFeatures, }) { return ViewConfiguration( window: window ?? this.window, @@ -956,6 +990,7 @@ class ViewConfiguration { viewPadding: viewPadding ?? this.viewPadding, systemGestureInsets: systemGestureInsets ?? this.systemGestureInsets, padding: padding ?? this.padding, + displayFeatures: displayFeatures ?? this.displayFeatures, ); } @@ -1028,6 +1063,16 @@ class ViewConfiguration { /// phone sensor housings). final WindowPadding padding; + /// Areas of the display that are obstructed by hardware features. + /// + /// List of rectangle bounds, which the application can use to guide layout. + /// These areas may be obscured or not have touch capabilities. Each feature + /// has a type, which can be used to determine behaviour. + /// + /// For example, a hinge feature can be used to separate the layout into 2 + /// logical areas or panels in the application. + final List displayFeatures; + @override String toString() { return '$runtimeType[window: $window, geometry: $geometry]'; @@ -1251,6 +1296,106 @@ class WindowPadding { } } +/// Area of the display that is obstructed by a hardware feature. +/// +/// The bounds, which the application can use to guide layout, may be obscured +/// or not have touch capabilities. The type can be used to determine behaviour. +/// For example, a hinge feature can be used to separate the layout into 2 +/// logical areas or panels in the application. +class DisplayFeature { + const DisplayFeature({ + required this.rect, + required this.type, + required this.state, + }); + + DisplayFeature withDevicePixelRatio(double devicePixelRatio) { + return DisplayFeature( + rect : Rect.fromLTRB( + rect.left / devicePixelRatio, + rect.top / devicePixelRatio, + rect.right / devicePixelRatio, + rect.bottom / devicePixelRatio, + ), + type: type, + state: state, + ); + } + + /// Area of the view occupied by this display feature + final Rect rect; + + /// 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) => + identical(this, other) || + other is DisplayFeature && + runtimeType == other.runtimeType && + rect == other.rect && + type == other.type && + state == other.state; + + @override + int get hashCode => hashValues(rect, type, state); + + @override + String toString() { + return 'DisplayFeature(rect: $rect, type: $type, state: $state)'; + } +} + +/// Type of display feature (screen obstruction). This enum contains both +/// foldable obstructions and cutout obstructions. +/// +/// Some, 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. +enum DisplayFeatureType { + unknown, + /// A fold in the flexible screen without a physical gap. + fold, + /// A physical separation with a hinge that allows two display panels to fold. + hinge, + /// A non-functional area of the screen, usually housing cameras or sensors. + cutout, +} + +/// Display features for folds and hinges can have a state. Cutouts do not have +/// a state. +/// +/// * For cutouts, the state is [DisplayFeatureState.unknown]. +/// * For folds & hinges, the state is the posture. +/// TODO: Once WM is stable, values should be, in this order: unknown, flat, halfOpened, flipped, with no other values. +enum DisplayFeatureState { + unknown, + // TODO: Once WM is stalbe, this is removed. + closed, + /// 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 screen panels. See the + /// [Posture](https://developer.android.com/guide/topics/ui/foldables#postures) + /// section in the official documentation for visual samples and references. + /// TODO: Once WM is stable, this comes after flat. + halfOpened, + /// The foldable device is completely open, the screen space that is presented + /// to the user is flat. See the + /// [Posture](https://developer.android.com/guide/topics/ui/foldables#postures) + /// section in the official documentation for visual samples and references. + flat, + /// The foldable device is flipped with the flexible screen parts or physical + /// screens facing opposite directions. See the + /// [Posture](https://developer.android.com/guide/topics/ui/foldables#postures) + /// section in the official documentation for visual samples and references. + flipped, +} + /// 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 ac984a2a6581c..9c4815e58ed56 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -225,6 +225,23 @@ abstract class FlutterView { /// applications. WindowPadding get padding => viewConfiguration.padding; + /// Areas of the display that are obstructed by hardware features. + /// When this changes, [onMetricsChanged] is called. + /// + /// List of rectangle bounds, which the application can use to guide layout. + /// These areas may be obscured or not have touch capabilities. Each feature + /// has a type, which can be used to determine behaviour. + /// + /// For example, a hinge feature can be used to separate the layout into 2 + /// logical areas or panels in the application. + /// + /// See also: + /// + /// * [WidgetsBindingObserver], for a mechanism at the widgets layer to + /// observe when this value changes. + /// * [MediaQuery.of], a simpler mechanism for the same. + 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 d52bfb83756e4..e01c782814f96 100644 --- a/lib/ui/window/viewport_metrics.cc +++ b/lib/ui/window/viewport_metrics.cc @@ -31,7 +31,10 @@ ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, 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_system_gesture_inset_left, + std::vector p_physical_display_features_bounds, + std::vector p_physical_display_features_type, + std::vector p_physical_display_features_state) : device_pixel_ratio(p_device_pixel_ratio), physical_width(p_physical_width), physical_height(p_physical_height), @@ -48,7 +51,10 @@ ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, p_physical_system_gesture_inset_right), physical_system_gesture_inset_bottom( p_physical_system_gesture_inset_bottom), - physical_system_gesture_inset_left(p_physical_system_gesture_inset_left) { + physical_system_gesture_inset_left(p_physical_system_gesture_inset_left), + 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) { @@ -70,7 +76,13 @@ bool operator==(const ViewportMetrics& a, const ViewportMetrics& b) { a.physical_system_gesture_inset_bottom == b.physical_system_gesture_inset_bottom && a.physical_system_gesture_inset_left == - b.physical_system_gesture_inset_left; + b.physical_system_gesture_inset_left && + 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) { @@ -85,7 +97,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 size: " << 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 adce20a91cdc9..7fe85027fc083 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 { @@ -28,7 +29,10 @@ struct ViewportMetrics { 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_system_gesture_inset_left, + std::vector p_physical_display_features_bounds, + std::vector p_physical_display_features_type, + std::vector p_physical_display_features_state); double device_pixel_ratio = 1.0; double physical_width = 0; @@ -45,6 +49,9 @@ struct ViewportMetrics { double physical_system_gesture_inset_right = 0; double physical_system_gesture_inset_bottom = 0; double physical_system_gesture_inset_left = 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 082df1b823d32..a0ec1619e8e59 100644 --- a/lib/ui/window/window.cc +++ b/lib/ui/window/window.cc @@ -63,6 +63,9 @@ void Window::UpdateWindowMetrics(const ViewportMetrics& metrics) { 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_display_features_bounds), + tonic::ToDart(metrics.physical_display_features_type), + tonic::ToDart(metrics.physical_display_features_state), })); } diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index 7c455f1b77c95..4086c9c42893a 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -123,7 +123,10 @@ void ShellTest::SetViewportMetrics(Shell* shell, double width, double height) { 0, // gesture inset top 0, // gesture inset right 0, // gesture inset bottom - 0 // gesture inset left + 0, // gesture inset left + 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/embedding_bundle/build.gradle b/shell/platform/android/embedding_bundle/build.gradle index c3eea2b22fbf4..e4faf093a1142 100644 --- a/shell/platform/android/embedding_bundle/build.gradle +++ b/shell/platform/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:1.0.0-alpha01" def lifecycle_version = "2.2.0" embedding "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 2b886bc9c2475..f3bddce732930 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -33,9 +33,15 @@ 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.DeviceState; +import androidx.window.DisplayFeature; +import androidx.window.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.DisplayFeatureType; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.embedding.engine.renderer.RenderSurface; import io.flutter.embedding.engine.systemchannels.SettingsChannel; @@ -44,7 +50,9 @@ import io.flutter.plugin.mouse.MouseCursorPlugin; import io.flutter.plugin.platform.PlatformViewsController; import io.flutter.view.AccessibilityBridge; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -105,6 +113,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 androidx.window.WindowManager windowManager; // Directly implemented View behavior that communicates with Flutter. private final FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics(); @@ -139,6 +149,21 @@ public void onFlutterUiNoLongerDisplayed() { } }; + private final Consumer windowManagerDeviceStateListener = + new Consumer() { + @Override + public void accept(DeviceState deviceState) { + setWindowManagerDisplayFeatures(); + } + }; + private final Consumer windowManagerListener = + new Consumer() { + @Override + public void accept(WindowLayoutInfo layoutInfo) { + setWindowManagerDisplayFeatures(); + } + }; + /** * Constructs a {@code FlutterView} programmatically, without any XML attributes. * @@ -330,6 +355,8 @@ private void init() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS); } + + windowManager = new androidx.window.WindowManager(getContext(), null); } /** @@ -421,6 +448,75 @@ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) sendViewportMetricsToFlutter(); } + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + setWindowManagerDisplayFeatures(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + windowManager.registerLayoutChangeCallback(ContextCompat.getMainExecutor(getContext()), + windowManagerListener); + windowManager.registerDeviceStateChangeCallback(ContextCompat.getMainExecutor(getContext()), + windowManagerDeviceStateListener); + } + + @Override + protected void onDetachedFromWindow() { + windowManager.unregisterLayoutChangeCallback(windowManagerListener); + windowManager.unregisterDeviceStateChangeCallback(windowManagerDeviceStateListener); + super.onDetachedFromWindow(); + } + + /** + * Refresh {@link androidx.window.WindowManager} and {@link android.view.DisplayCutout} + * display features. Fold, hinge and cutout areas are populated here. + */ + private void setWindowManagerDisplayFeatures() { + List displayFeatures = windowManager.getWindowLayoutInfo().getDisplayFeatures(); + List result = new ArrayList<>(); + + // Data from androidx.window.WindowManager display features. Fold and hinge areas are + // populated here. + for (DisplayFeature displayFeature: displayFeatures){ + Log.v( + TAG, + "WindowManager Display Feature reported with bounds = " + + displayFeature.getBounds().toString() + + " and type = " + + displayFeature.getType()); + DisplayFeatureType type = DisplayFeatureType.UNKNOWN; + if (displayFeature.getType() == DisplayFeature.TYPE_FOLD) { + type = DisplayFeatureType.FOLD; + } else if (displayFeature.getType() == DisplayFeature.TYPE_HINGE) { + type = DisplayFeatureType.HINGE; + } + // In next versions of WindowManager, each DisplayFeature has its own posture. + // We're making the engine changes as close as possible to that for now. + final int posture = windowManager.getDeviceState().getPosture(); + result.add(new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, posture)); + } + + // Data from the DisplayCutout bounds. Cutouts for cameras and other sensors are + // populated here. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + 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/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index e122e2528a936..3df6881d54f29 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -22,6 +22,7 @@ import io.flutter.embedding.engine.dart.PlatformMessageHandler; import io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager; import io.flutter.embedding.engine.mutatorsstack.FlutterMutatorsStack; +import io.flutter.embedding.engine.renderer.FlutterRenderer.DisplayFeature; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.embedding.engine.renderer.RenderSurface; import io.flutter.embedding.engine.renderer.SurfaceTextureWrapper; @@ -442,7 +443,10 @@ public void setViewportMetrics( int systemGestureInsetTop, int systemGestureInsetRight, int systemGestureInsetBottom, - int systemGestureInsetLeft) { + int systemGestureInsetLeft, + int[] displayFeaturesBounds, + int[] displayFeaturesType, + int[] displayFeaturesState) { ensureRunningOnMainThread(); ensureAttachedToNative(); nativeSetViewportMetrics( @@ -461,7 +465,10 @@ public void setViewportMetrics( systemGestureInsetTop, systemGestureInsetRight, systemGestureInsetBottom, - systemGestureInsetLeft); + systemGestureInsetLeft, + displayFeaturesBounds, + displayFeaturesType, + displayFeaturesState); } private native void nativeSetViewportMetrics( @@ -480,7 +487,10 @@ private native void nativeSetViewportMetrics( int systemGestureInsetTop, int systemGestureInsetRight, int systemGestureInsetBottom, - int systemGestureInsetLeft); + int systemGestureInsetLeft, + 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 67570302bad28..543290184d401 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,7 @@ import io.flutter.embedding.engine.FlutterJNI; import io.flutter.view.TextureRegistry; import java.nio.ByteBuffer; +import java.util.List; import java.util.concurrent.atomic.AtomicLong; /** @@ -271,7 +273,23 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { + ", R: " + viewportMetrics.systemGestureInsetRight + ", B: " - + viewportMetrics.viewInsetBottom); + + viewportMetrics.systemGestureInsetRight + + "\n" + + "Display Features Count: " + + 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 displayFeatures = List.of(); + } + + /** + * Area of the viewport obstructed by a feature, eg. a hinge, a fold crease or camera cutout + * + *

Similar to {@link androidx.window.DisplayFeature}, used for reporting Fold / Hinge areas. + * The reason we define our own model is that we also want to support cutouts. + */ + public static final class DisplayFeature { + public final Rect bounds; + public final DisplayFeatureType type; + public final int state; + + public DisplayFeature(Rect bounds, DisplayFeatureType type, int state) { + this.bounds = bounds; + this.type = type; + this.state = state; + } + + public DisplayFeature(Rect bounds, DisplayFeatureType type) { + this.bounds = bounds; + this.type = type; + this.state = 0; // UNKNOWN + } + } + + /** + * Types of display features that can obstruct the viewport. + * + *

Some, like 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 {@link DisplayFeature} bounds + * can be 0-width in such cases. + */ + public enum DisplayFeatureType { + /** + * We do not know this type of display feature yet. This can happen if WindowManager is updated + * with new types. + */ + UNKNOWN(0), + + /** + * A fold in the flexible screen without a physical gap. + * Corresponds to {@link androidx.window.DisplayFeature.TYPE_FOLD} + */ + FOLD(1), + + /** + * A physical separation with a hinge that allows two display panels to fold. + * Corresponds to {@link androidx.window.DisplayFeature.TYPE_HINGE} + */ + HINGE(2), + + /** + * A non-functional area of the screen, usually housing cameras or sensors. + * Corresponds to {@link android.view.DisplayCutout} + */ + CUTOUT(3); + + public final int encodedValue; + + DisplayFeatureType(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 5cd0d66403edd..5232fba47884b 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -768,7 +768,10 @@ private void updateViewportMetrics() { mMetrics.systemGestureInsetTop, mMetrics.systemGestureInsetRight, mMetrics.systemGestureInsetBottom, - mMetrics.systemGestureInsetLeft); + mMetrics.systemGestureInsetLeft, + 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 1b1efa0d2efe9..f3b8f9a5c5fd5 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -261,7 +261,25 @@ static void SetViewportMetrics(JNIEnv* env, jint systemGestureInsetTop, jint systemGestureInsetRight, jint systemGestureInsetBottom, - jint systemGestureInsetLeft) { + jint systemGestureInsetLeft, + 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), @@ -278,6 +296,9 @@ static void SetViewportMetrics(JNIEnv* env, static_cast(systemGestureInsetRight), static_cast(systemGestureInsetBottom), static_cast(systemGestureInsetLeft), + displayFeaturesBounds, + displayFeaturesType, + displayFeaturesState }; ANDROID_SHELL_HOLDER->GetPlatformView()->SetViewportMetrics(metrics); @@ -661,7 +682,7 @@ bool RegisterApi(JNIEnv* env) { }, { .name = "nativeSetViewportMetrics", - .signature = "(JFIIIIIIIIIIIIII)V", + .signature = "(JFIIIIIIIIIIIIII[I[I[I)V", .fnPtr = reinterpret_cast(&SetViewportMetrics), }, { diff --git a/tools/androidx/files.json b/tools/androidx/files.json index e8939614a32b1..b063956760f78 100644 --- a/tools/androidx/files.json +++ b/tools/androidx/files.json @@ -49,5 +49,16 @@ "androidx.annotation.UiThread", "androidx.annotation.VisibleForTesting" ] + }, + { + "url": "https://maven.google.com/androidx/window/window/1.0.0-alpha01/window-1.0.0-aplha01.aar", + "out_file_name": "androidx_window.aar", + "maven_dependency": "androidx.window:window:1.0.0-alpha01", + "provides": [ + "androidx.window.DisplayFeature", + "androidx.window.FoldingFeature", + "androidx.window.WindowLayoutInfo", + "androidx.window.WindowManager" + ] } ] From 99f7bcde16e38cbf91fd3b592d827849d2972b4f Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Wed, 3 Mar 2021 15:07:49 +0200 Subject: [PATCH 02/23] WindowManager alpha03 and better documentation --- lib/ui/platform_dispatcher.dart | 142 +++++++++--------- lib/ui/window.dart | 27 ++-- lib/ui/window/viewport_metrics.cc | 6 +- lib/ui/window/viewport_metrics.h | 6 +- .../android/embedding_bundle/build.gradle | 2 +- .../embedding/android/FlutterView.java | 79 +++++----- .../engine/renderer/FlutterRenderer.java | 58 +++++-- tools/androidx/files.json | 4 +- 8 files changed, 194 insertions(+), 130 deletions(-) diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index b41c161393cc0..4cd203d5146da 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -211,6 +211,7 @@ class PlatformDispatcher { bounds: displayFeaturesBounds, type: displayFeaturesType, state: displayFeaturesState, + devicePixelRatio: devicePixelRatio, ), ); _invoke(onMetricsChanged, _onMetricsChangedZone); @@ -220,16 +221,19 @@ class PlatformDispatcher { required List bounds, required List type, required List state, + required double devicePixelRatio, }) { + assert(bounds.length / 4 == type.length); + assert(type.length == state.length); final List result = []; - for(int i =0 ; i < type.length; i++){ + for(int i = 0 ; i < type.length; i++){ final int rectOffset = i * 4; result.add(DisplayFeature( - rect: Rect.fromLTRB( - bounds[rectOffset], - bounds[rectOffset+1], - bounds[rectOffset+2], - bounds[rectOffset+3], + 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 @@ -1063,14 +1067,22 @@ class ViewConfiguration { /// phone sensor housings). final WindowPadding padding; - /// Areas of the display that are obstructed by hardware features. - /// - /// List of rectangle bounds, which the application can use to guide layout. - /// These areas may be obscured or not have touch capabilities. Each feature - /// has a type, which can be used to determine behaviour. - /// - /// For example, a hinge feature can be used to separate the layout into 2 - /// logical areas or panels in the application. + /// 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 space in which the [DisplayFeature.bounds] are defined includes all screens + /// and the space between them. For a dual-screen device, 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 more like 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. final List displayFeatures; @override @@ -1296,36 +1308,36 @@ class WindowPadding { } } -/// Area of the display that is obstructed by a hardware feature. +/// Area of the display that may be obstructed by a hardware feature.This is +/// populated only on Android. /// -/// The bounds, which the application can use to guide layout, may be obscured -/// or not have touch capabilities. The type can be used to determine behaviour. -/// For example, a hinge feature can be used to separate the layout into 2 -/// logical areas or panels in the application. +/// 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 screen +/// and expands to include the visual space between the screens and the right screen. +/// The [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] does not. class DisplayFeature { const DisplayFeature({ - required this.rect, + required this.bounds, required this.type, required this.state, }); - DisplayFeature withDevicePixelRatio(double devicePixelRatio) { - return DisplayFeature( - rect : Rect.fromLTRB( - rect.left / devicePixelRatio, - rect.top / devicePixelRatio, - rect.right / devicePixelRatio, - rect.bottom / devicePixelRatio, - ), - type: type, - state: state, - ); - } - - /// Area of the view occupied by this display feature - final Rect rect; + /// Area of the view occupied by this display feature, measured in logical pixels. + /// + /// On devices with two screens, the Flutter view spans from the top-right corner + /// of the left screen to the bottom-right corner of the right screen, including + /// the 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 + /// Type of display feature, e.g. hinge, fold, cutout. final DisplayFeatureType type; /// Posture of display feature, which is populated only for folds and hinges. @@ -1333,20 +1345,20 @@ class DisplayFeature { final DisplayFeatureState state; @override - bool operator ==(Object other) => - identical(this, other) || - other is DisplayFeature && - runtimeType == other.runtimeType && - rect == other.rect && - type == other.type && - state == other.state; + bool operator ==(Object other) { + return identical(this, other) || other is DisplayFeature && + runtimeType == other.runtimeType && + bounds == other.bounds && + type == other.type && + state == other.state; + } @override - int get hashCode => hashValues(rect, type, state); + int get hashCode => hashValues(bounds, type, state); @override String toString() { - return 'DisplayFeature(rect: $rect, type: $type, state: $state)'; + return 'DisplayFeature(rect: $bounds, type: $type, state: $state)'; } } @@ -1359,7 +1371,8 @@ class DisplayFeature { /// 0-width in such cases. enum DisplayFeatureType { unknown, - /// A fold in the flexible screen without a physical gap. + /// 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, @@ -1367,33 +1380,26 @@ enum DisplayFeatureType { cutout, } -/// Display features for folds and hinges can have a state. Cutouts do not have -/// a state. +/// State of the display feature. /// -/// * For cutouts, the state is [DisplayFeatureState.unknown]. -/// * For folds & hinges, the state is the posture. -/// TODO: Once WM is stable, values should be, in this order: unknown, flat, halfOpened, flipped, with no other values. +/// * For [DisplayFeatureType.fold]s & [DisplayFeatureType.hinge]s, the state is +/// the posture, corresponding to values found in +/// [Android Postures](https://developer.android.com/guide/topics/ui/foldables#postures). +/// * For [DisplayFeatureType.cutout]s, the state is [DisplayFeatureState.unknown]. enum DisplayFeatureState { + /// The display feature is a [DisplayFeatureType.cutout] or this state is new + /// and not yet known to Flutter. unknown, - // TODO: Once WM is stalbe, this is removed. - closed, + /// The foldable device is completely open. The screen space that is presented + /// to the user is flat. + postureFlat, /// 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 screen panels. See the - /// [Posture](https://developer.android.com/guide/topics/ui/foldables#postures) - /// section in the official documentation for visual samples and references. - /// TODO: Once WM is stable, this comes after flat. - halfOpened, - /// The foldable device is completely open, the screen space that is presented - /// to the user is flat. See the - /// [Posture](https://developer.android.com/guide/topics/ui/foldables#postures) - /// section in the official documentation for visual samples and references. - flat, + /// and closed state. There is a non-flat angle between parts of the flexible + /// screen or between physical screen panels. + postureHalfOpened, /// The foldable device is flipped with the flexible screen parts or physical - /// screens facing opposite directions. See the - /// [Posture](https://developer.android.com/guide/topics/ui/foldables#postures) - /// section in the official documentation for visual samples and references. - flipped, + /// screens facing opposite directions. + postureFlipped, } /// An identifier used to select a user's language and formatting preferences. diff --git a/lib/ui/window.dart b/lib/ui/window.dart index 9c4815e58ed56..dc6f3efdde663 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -225,21 +225,30 @@ abstract class FlutterView { /// applications. WindowPadding get padding => viewConfiguration.padding; - /// Areas of the display that are obstructed by hardware features. - /// When this changes, [onMetricsChanged] is called. - /// - /// List of rectangle bounds, which the application can use to guide layout. - /// These areas may be obscured or not have touch capabilities. Each feature - /// has a type, which can be used to determine behaviour. + /// 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 space in which the [DisplayFeature.bounds] are defined includes all screens + /// and the space between them. For a dual-screen device, 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 more like 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. /// - /// For example, a hinge feature can be used to separate the layout into 2 - /// logical areas or panels in the application. + /// 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 for the same. + /// * [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]. diff --git a/lib/ui/window/viewport_metrics.cc b/lib/ui/window/viewport_metrics.cc index e01c782814f96..6fb7c51301327 100644 --- a/lib/ui/window/viewport_metrics.cc +++ b/lib/ui/window/viewport_metrics.cc @@ -32,9 +32,9 @@ ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, double p_physical_system_gesture_inset_right, double p_physical_system_gesture_inset_bottom, double p_physical_system_gesture_inset_left, - std::vector p_physical_display_features_bounds, - std::vector p_physical_display_features_type, - std::vector p_physical_display_features_state) + 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), diff --git a/lib/ui/window/viewport_metrics.h b/lib/ui/window/viewport_metrics.h index 7fe85027fc083..91f1a0e9fc59a 100644 --- a/lib/ui/window/viewport_metrics.h +++ b/lib/ui/window/viewport_metrics.h @@ -30,9 +30,9 @@ struct ViewportMetrics { double p_physical_system_gesture_inset_right, double p_physical_system_gesture_inset_bottom, double p_physical_system_gesture_inset_left, - std::vector p_physical_display_features_bounds, - std::vector p_physical_display_features_type, - std::vector p_physical_display_features_state); + 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; diff --git a/shell/platform/android/embedding_bundle/build.gradle b/shell/platform/android/embedding_bundle/build.gradle index e4faf093a1142..323e414ea8bb0 100644 --- a/shell/platform/android/embedding_bundle/build.gradle +++ b/shell/platform/android/embedding_bundle/build.gradle @@ -42,7 +42,7 @@ android { dependencies { embedding "androidx.annotation:annotation:1.1.0" embedding "androidx.fragment:fragment:1.1.0" - embedding "androidx.window:window:1.0.0-alpha01" + embedding "androidx.window:window:1.0.0-alpha03" def lifecycle_version = "2.2.0" embedding "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index f3bddce732930..77ba3eea2d0b9 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -35,12 +35,13 @@ import androidx.annotation.VisibleForTesting; import androidx.core.content.ContextCompat; import androidx.core.util.Consumer; -import androidx.window.DeviceState; import androidx.window.DisplayFeature; +import androidx.window.FoldingFeature; import androidx.window.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; @@ -149,18 +150,11 @@ public void onFlutterUiNoLongerDisplayed() { } }; - private final Consumer windowManagerDeviceStateListener = - new Consumer() { - @Override - public void accept(DeviceState deviceState) { - setWindowManagerDisplayFeatures(); - } - }; private final Consumer windowManagerListener = new Consumer() { @Override public void accept(WindowLayoutInfo layoutInfo) { - setWindowManagerDisplayFeatures(); + setWindowManagerDisplayFeatures(layoutInfo); } }; @@ -448,25 +442,24 @@ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) sendViewportMetricsToFlutter(); } - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - setWindowManagerDisplayFeatures(); - } - + /** + * Invoked when this is attached to the window. + * + * AndroidX WindowManager alpha01 accepts callback registration only after the view is attached to + * the window. This is why we override this method. + * + * TODO: Refactor this once WindowManager goes to alpha02, as there is only 1 callback and it can be attached at any time. + */ @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); windowManager.registerLayoutChangeCallback(ContextCompat.getMainExecutor(getContext()), windowManagerListener); - windowManager.registerDeviceStateChangeCallback(ContextCompat.getMainExecutor(getContext()), - windowManagerDeviceStateListener); } @Override protected void onDetachedFromWindow() { windowManager.unregisterLayoutChangeCallback(windowManagerListener); - windowManager.unregisterDeviceStateChangeCallback(windowManagerDeviceStateListener); super.onDetachedFromWindow(); } @@ -474,8 +467,8 @@ protected void onDetachedFromWindow() { * Refresh {@link androidx.window.WindowManager} and {@link android.view.DisplayCutout} * display features. Fold, hinge and cutout areas are populated here. */ - private void setWindowManagerDisplayFeatures() { - List displayFeatures = windowManager.getWindowLayoutInfo().getDisplayFeatures(); + private void setWindowManagerDisplayFeatures(WindowLayoutInfo layoutInfo) { + List displayFeatures = layoutInfo.getDisplayFeatures(); List result = new ArrayList<>(); // Data from androidx.window.WindowManager display features. Fold and hinge areas are @@ -486,29 +479,47 @@ private void setWindowManagerDisplayFeatures() { "WindowManager Display Feature reported with bounds = " + displayFeature.getBounds().toString() + " and type = " - + displayFeature.getType()); - DisplayFeatureType type = DisplayFeatureType.UNKNOWN; - if (displayFeature.getType() == DisplayFeature.TYPE_FOLD) { - type = DisplayFeatureType.FOLD; - } else if (displayFeature.getType() == DisplayFeature.TYPE_HINGE) { - type = DisplayFeatureType.HINGE; + + displayFeature.getClass().getSimpleName()); + if (displayFeature instanceof FoldingFeature) { + DisplayFeatureType type = DisplayFeatureType.UNKNOWN; + DisplayFeatureState state = DisplayFeatureState.UNKNOWN; + final FoldingFeature feature = (FoldingFeature) displayFeature; + switch (feature.getType()) { + case FoldingFeature.TYPE_FOLD: + type = DisplayFeatureType.FOLD; + break; + case FoldingFeature.TYPE_HINGE: + type = DisplayFeatureType.HINGE; + break; + } + switch (feature.getState()) { + case FoldingFeature.STATE_FLAT: + state = DisplayFeatureState.POSTURE_FLAT; + break; + case FoldingFeature.STATE_HALF_OPENED: + state = DisplayFeatureState.POSTURE_HALF_OPENED; + break; + case FoldingFeature.STATE_FLIPPED: + state = DisplayFeatureState.POSTURE_FLIPPED; + break; + } + result.add(new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state)); + } else { + result.add(new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), DisplayFeatureType.UNKNOWN, + DisplayFeatureState.UNKNOWN)); } - // In next versions of WindowManager, each DisplayFeature has its own posture. - // We're making the engine changes as close as possible to that for now. - final int posture = windowManager.getDeviceState().getPosture(); - result.add(new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, posture)); } // Data from the DisplayCutout bounds. Cutouts for cameras and other sensors are // populated here. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { WindowInsets insets = getRootWindowInsets(); - if (insets!=null) { + if (insets != null) { DisplayCutout cutout = insets.getDisplayCutout(); - if (cutout!=null) { - for (Rect bounds: cutout.getBoundingRects()){ + 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)); + result.add(new FlutterRenderer.DisplayFeature(bounds, DisplayFeatureType.CUTOUT)); } } } 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 543290184d401..a06e8356edae1 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -17,6 +17,7 @@ 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; @@ -281,14 +282,14 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { 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 displayFeatures = List.of(); + public List displayFeatures = new ArrayList(); } /** @@ -393,9 +394,9 @@ public static final class ViewportMetrics { public static final class DisplayFeature { public final Rect bounds; public final DisplayFeatureType type; - public final int state; + public final DisplayFeatureState state; - public DisplayFeature(Rect bounds, DisplayFeatureType type, int state) { + public DisplayFeature(Rect bounds, DisplayFeatureType type, DisplayFeatureState state) { this.bounds = bounds; this.type = type; this.state = state; @@ -404,7 +405,7 @@ public DisplayFeature(Rect bounds, DisplayFeatureType type, int state) { public DisplayFeature(Rect bounds, DisplayFeatureType type) { this.bounds = bounds; this.type = type; - this.state = 0; // UNKNOWN + this.state = DisplayFeatureState.UNKNOWN; } } @@ -446,4 +447,41 @@ public enum DisplayFeatureType { this.encodedValue = encodedValue; } } + + /** + * State of the display feature. + * + *

For foldables, the state is the posture. For cutouts, this 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.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 screen panels. + * Corresponds to {@link androidx.window.FoldingFeature.STATE_HALF_OPENED} + */ + POSTURE_HALF_OPENED(2), + + /** + * The foldable device is flipped with the flexible screen parts or physical screens facing opposite directions. + * Corresponds to {@link androidx.window.FoldingFeature.STATE_FLIPPED} + */ + POSTURE_FLIPPED(3); + + public final int encodedValue; + + DisplayFeatureState(int encodedValue) { + this.encodedValue = encodedValue; + } + } } diff --git a/tools/androidx/files.json b/tools/androidx/files.json index b063956760f78..51b073b0ae00e 100644 --- a/tools/androidx/files.json +++ b/tools/androidx/files.json @@ -51,9 +51,9 @@ ] }, { - "url": "https://maven.google.com/androidx/window/window/1.0.0-alpha01/window-1.0.0-aplha01.aar", + "url": "https://maven.google.com/androidx/window/window/1.0.0-alpha03/window-1.0.0-alpha03.aar", "out_file_name": "androidx_window.aar", - "maven_dependency": "androidx.window:window:1.0.0-alpha01", + "maven_dependency": "androidx.window:window:1.0.0-alpha03", "provides": [ "androidx.window.DisplayFeature", "androidx.window.FoldingFeature", From 7ee3477ecaeda11986647aa85eb2ecf211bc68ec Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Tue, 16 Mar 2021 12:03:06 +0200 Subject: [PATCH 03/23] Fixed formatting --- lib/ui/window/viewport_metrics.cc | 46 +++++++++---------- shell/common/shell_test.cc | 36 +++++++-------- .../embedding/android/FlutterView.java | 27 ++++++----- .../flutter/embedding/engine/FlutterJNI.java | 1 - .../engine/renderer/FlutterRenderer.java | 30 ++++++------ .../android/platform_view_android_jni_impl.cc | 15 +++--- 6 files changed, 79 insertions(+), 76 deletions(-) diff --git a/lib/ui/window/viewport_metrics.cc b/lib/ui/window/viewport_metrics.cc index 6fb7c51301327..b7d6380d890af 100644 --- a/lib/ui/window/viewport_metrics.cc +++ b/lib/ui/window/viewport_metrics.cc @@ -17,24 +17,25 @@ ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, physical_width(p_physical_width), physical_height(p_physical_height) {} -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, - const std::vector p_physical_display_features_bounds, - const std::vector p_physical_display_features_type, - const std::vector p_physical_display_features_state) +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, + 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), @@ -54,8 +55,7 @@ ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, physical_system_gesture_inset_left(p_physical_system_gesture_inset_left), 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) { -} + 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 && @@ -79,10 +79,8 @@ bool operator==(const ViewportMetrics& a, const ViewportMetrics& b) { b.physical_system_gesture_inset_left && 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; + 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) { diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index 580ee17afb465..0224a58b9b953 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -109,24 +109,24 @@ 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 - std::vector(), // display features bounds - std::vector(), // display features type - std::vector() // display features state + 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 + 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/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index b5eac713b591c..6d562a95fd4ce 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -445,18 +445,20 @@ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) /** * Invoked when this is attached to the window. * - * AndroidX WindowManager alpha01 accepts callback registration only after the view is attached to - * the window. This is why we override this method. - * - * TODO: Refactor this once WindowManager goes to alpha02, as there is only 1 callback and it can be attached at any time. + *

We register for {@link androidx.window.WindowManager} updates. */ @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - windowManager.registerLayoutChangeCallback(ContextCompat.getMainExecutor(getContext()), - windowManagerListener); + windowManager.registerLayoutChangeCallback( + ContextCompat.getMainExecutor(getContext()), windowManagerListener); } + /** + * Invoked when this is detached from the window. + * + *

We unregister from {@link androidx.window.WindowManager} updates. + */ @Override protected void onDetachedFromWindow() { windowManager.unregisterLayoutChangeCallback(windowManagerListener); @@ -464,8 +466,8 @@ protected void onDetachedFromWindow() { } /** - * Refresh {@link androidx.window.WindowManager} and {@link android.view.DisplayCutout} - * display features. Fold, hinge and cutout areas are populated here. + * Refresh {@link androidx.window.WindowManager} and {@link android.view.DisplayCutout} display + * features. Fold, hinge and cutout areas are populated here. */ private void setWindowManagerDisplayFeatures(WindowLayoutInfo layoutInfo) { List displayFeatures = layoutInfo.getDisplayFeatures(); @@ -473,7 +475,7 @@ private void setWindowManagerDisplayFeatures(WindowLayoutInfo layoutInfo) { // Data from androidx.window.WindowManager display features. Fold and hinge areas are // populated here. - for (DisplayFeature displayFeature: displayFeatures){ + for (DisplayFeature displayFeature : displayFeatures) { Log.v( TAG, "WindowManager Display Feature reported with bounds = " @@ -505,8 +507,11 @@ private void setWindowManagerDisplayFeatures(WindowLayoutInfo layoutInfo) { } result.add(new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state)); } else { - result.add(new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), DisplayFeatureType.UNKNOWN, - DisplayFeatureState.UNKNOWN)); + result.add( + new FlutterRenderer.DisplayFeature( + displayFeature.getBounds(), + DisplayFeatureType.UNKNOWN, + DisplayFeatureState.UNKNOWN)); } } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index bb8d7123f1d08..5b8eba8d06489 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -22,7 +22,6 @@ import io.flutter.embedding.engine.dart.PlatformMessageHandler; import io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager; import io.flutter.embedding.engine.mutatorsstack.FlutterMutatorsStack; -import io.flutter.embedding.engine.renderer.FlutterRenderer.DisplayFeature; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.embedding.engine.renderer.RenderSurface; import io.flutter.embedding.engine.renderer.SurfaceTextureWrapper; 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 a06e8356edae1..81aebc388caaf 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -424,20 +424,20 @@ public enum DisplayFeatureType { UNKNOWN(0), /** - * A fold in the flexible screen without a physical gap. - * Corresponds to {@link androidx.window.DisplayFeature.TYPE_FOLD} + * A fold in the flexible screen without a physical gap. Corresponds to {@link + * androidx.window.DisplayFeature.TYPE_FOLD} */ FOLD(1), /** - * A physical separation with a hinge that allows two display panels to fold. - * Corresponds to {@link androidx.window.DisplayFeature.TYPE_HINGE} + * A physical separation with a hinge that allows two display panels to fold. Corresponds to + * {@link androidx.window.DisplayFeature.TYPE_HINGE} */ HINGE(2), /** - * A non-functional area of the screen, usually housing cameras or sensors. - * Corresponds to {@link android.view.DisplayCutout} + * A non-functional area of the screen, usually housing cameras or sensors. Corresponds to + * {@link android.view.DisplayCutout} */ CUTOUT(3); @@ -451,30 +451,28 @@ public enum DisplayFeatureType { /** * State of the display feature. * - *

For foldables, the state is the posture. For cutouts, this is {@link UNKNOWN} + *

For foldables, the state is the posture. For cutouts, this is {@link UNKNOWN} */ public enum DisplayFeatureState { - /** - * The display feature is a cutout or this state is new and not yet known to Flutter. - */ + /** 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.FoldingFeature.STATE_FLAT} + * The foldable device is completely open. The screen space that is presented to the user is + * flat. Corresponds to {@link androidx.window.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 screen panels. - * Corresponds to {@link androidx.window.FoldingFeature.STATE_HALF_OPENED} + * There is a non-flat angle between parts of the flexible screen or between physical screen + * panels. Corresponds to {@link androidx.window.FoldingFeature.STATE_HALF_OPENED} */ POSTURE_HALF_OPENED(2), /** - * The foldable device is flipped with the flexible screen parts or physical screens facing opposite directions. - * Corresponds to {@link androidx.window.FoldingFeature.STATE_FLIPPED} + * The foldable device is flipped with the flexible screen parts or physical screens facing + * opposite directions. Corresponds to {@link androidx.window.FoldingFeature.STATE_FLIPPED} */ POSTURE_FLIPPED(3); diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index ca03fe7f38be3..36e3829392dc6 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -300,16 +300,19 @@ static void SetViewportMetrics(JNIEnv* env, // 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()); - + 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]); + env->GetIntArrayRegion(javaDisplayFeaturesType, 0, typeSize, + &displayFeaturesType[0]); jsize stateSize = env->GetArrayLength(javaDisplayFeaturesState); std::vector displayFeaturesState(stateSize); - env->GetIntArrayRegion(javaDisplayFeaturesState, 0, stateSize, &displayFeaturesState[0]); + env->GetIntArrayRegion(javaDisplayFeaturesState, 0, stateSize, + &displayFeaturesState[0]); const flutter::ViewportMetrics metrics{ static_cast(devicePixelRatio), @@ -329,7 +332,7 @@ static void SetViewportMetrics(JNIEnv* env, static_cast(systemGestureInsetLeft), displayFeaturesBounds, displayFeaturesType, - displayFeaturesState + displayFeaturesState, }; ANDROID_SHELL_HOLDER->GetPlatformView()->SetViewportMetrics(metrics); From 62a1e2b6f0e711bc13e8e49cc8570dd9ebcb1cb6 Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Tue, 16 Mar 2021 17:07:46 +0200 Subject: [PATCH 04/23] Fixed failing tests --- .../lib/src/ui/platform_dispatcher.dart | 47 +++++++++++++++++++ lib/web_ui/lib/src/ui/window.dart | 1 + .../embedding/android/FlutterView.java | 5 +- .../embedding/android/FlutterViewTest.java | 32 +++++++------ .../plugin/editing/TextInputPluginTest.java | 14 +++--- .../plugin/mouse/MouseCursorPluginTest.java | 5 +- .../platform/PlatformViewsControllerTest.java | 9 +++- .../dart/window_hooks_integration_test.dart | 20 ++++++++ 8 files changed, 107 insertions(+), 26 deletions(-) diff --git a/lib/web_ui/lib/src/ui/platform_dispatcher.dart b/lib/web_ui/lib/src/ui/platform_dispatcher.dart index 87ee101c4d242..df75c8f67e0ad 100644 --- a/lib/web_ui/lib/src/ui/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/ui/platform_dispatcher.dart @@ -151,6 +151,7 @@ class ViewConfiguration { this.viewPadding = WindowPadding.zero, this.systemGestureInsets = WindowPadding.zero, this.padding = WindowPadding.zero, + this.displayFeatures = const [], }); ViewConfiguration copyWith({ @@ -162,6 +163,7 @@ class ViewConfiguration { WindowPadding? viewPadding, WindowPadding? systemGestureInsets, WindowPadding? padding, + List? displayFeatures, }) { return ViewConfiguration( window: window ?? this.window, @@ -172,6 +174,7 @@ class ViewConfiguration { viewPadding: viewPadding ?? this.viewPadding, systemGestureInsets: systemGestureInsets ?? this.systemGestureInsets, padding: padding ?? this.padding, + displayFeatures: displayFeatures ?? this.displayFeatures, ); } @@ -183,6 +186,7 @@ class ViewConfiguration { final WindowPadding viewPadding; final WindowPadding systemGestureInsets; final WindowPadding padding; + final List displayFeatures; @override String toString() { @@ -270,6 +274,49 @@ 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) { + return identical(this, other) || other is DisplayFeature && + runtimeType == other.runtimeType && + 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 06a8b75df8bf5..0946d8d2c5e2e 100644 --- a/lib/web_ui/lib/src/ui/window.dart +++ b/lib/web_ui/lib/src/ui/window.dart @@ -15,6 +15,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/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 50bb6849e94b5..b1f66f35d1e04 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -350,7 +350,7 @@ private void init() { setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS); } - windowManager = new androidx.window.WindowManager(getContext(), null); + windowManager = new androidx.window.WindowManager(getContext()); } /** @@ -469,6 +469,7 @@ protected void onDetachedFromWindow() { * Refresh {@link androidx.window.WindowManager} and {@link android.view.DisplayCutout} display * features. Fold, hinge and cutout areas are populated here. */ + @TargetApi(Build.VERSION_CODES.P) private void setWindowManagerDisplayFeatures(WindowLayoutInfo layoutInfo) { List displayFeatures = layoutInfo.getDisplayFeatures(); List result = new ArrayList<>(); @@ -517,7 +518,7 @@ private void setWindowManagerDisplayFeatures(WindowLayoutInfo layoutInfo) { // Data from the DisplayCutout bounds. Cutouts for cameras and other sensors are // populated here. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { WindowInsets insets = getRootWindowInsets(); if (insets != null) { DisplayCutout cutout = insets.getDisplayCutout(); 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 26e7341650379..9ffa7107d38f3 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -14,6 +14,7 @@ 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; @@ -44,6 +45,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; @@ -68,7 +70,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); @@ -80,7 +82,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); @@ -93,7 +95,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)); @@ -107,7 +109,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)); @@ -132,7 +134,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)); @@ -170,7 +172,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); @@ -222,7 +224,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)); @@ -264,7 +266,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)); @@ -302,7 +304,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 = @@ -343,7 +345,7 @@ public void reportSystemInsetWhenNotFullscreen() { @Config(sdk = 29) 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 = @@ -380,7 +382,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) @@ -427,7 +429,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) @@ -478,7 +480,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) @@ -530,7 +532,7 @@ public void systemInsetGetInsetsFullscreen() { @Config(sdk = 29) 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) @@ -581,7 +583,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) 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 19a8b1b5be5f2..7b037b25fb019 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java @@ -17,6 +17,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; @@ -64,6 +65,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @@ -634,7 +636,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)); @@ -709,7 +711,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)); @@ -755,7 +757,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)); @@ -874,7 +876,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)); @@ -960,7 +962,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)); @@ -1114,7 +1116,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 62806032cd092..eee54409b9849 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -5,6 +5,7 @@ import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; +import android.app.Activity; import android.content.Context; import android.content.res.AssetManager; import android.util.SparseArray; @@ -39,6 +40,7 @@ import org.junit.Ignore; 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; @@ -229,6 +231,7 @@ public void getPlatformViewById__hybridComposition() { platformViewsController.getRegistry().registerViewFactory("testType", viewFactory); FlutterJNI jni = new FlutterJNI(); + jni.attachToNative(false); attach(jni, platformViewsController); // Simulate create call from the framework. @@ -256,6 +259,7 @@ public void createPlatformViewMessage__initializesAndroidView() { platformViewsController.getRegistry().registerViewFactory("testType", viewFactory); FlutterJNI jni = new FlutterJNI(); + jni.attachToNative(false); attach(jni, platformViewsController); // Simulate create call from the framework. @@ -278,6 +282,7 @@ public void createPlatformViewMessage__throwsIfViewIsNull() { platformViewsController.getRegistry().registerViewFactory("testType", viewFactory); FlutterJNI jni = new FlutterJNI(); + jni.attachToNative(false); attach(jni, platformViewsController); // Simulate create call from the framework. @@ -308,6 +313,7 @@ public void createPlatformViewMessage__throwsIfViewHasParent() { platformViewsController.getRegistry().registerViewFactory("testType", viewFactory); FlutterJNI jni = new FlutterJNI(); + jni.attachToNative(false); attach(jni, platformViewsController); // Simulate create call from the framework. @@ -340,6 +346,7 @@ public void disposeAndroidView__hybridComposition() { platformViewsController.getRegistry().registerViewFactory("testType", viewFactory); FlutterJNI jni = new FlutterJNI(); + jni.attachToNative(false); attach(jni, platformViewsController); // Simulate create call from the framework. @@ -549,7 +556,7 @@ private static FlutterView attach( final DartExecutor executor = new DartExecutor(jni, mock(AssetManager.class)); executor.onAttachedToJNI(); - final Context context = RuntimeEnvironment.application.getApplicationContext(); + final Context context = Robolectric.setupActivity(Activity.class); platformViewsController.attach(context, null, executor); final FlutterView view = diff --git a/testing/dart/window_hooks_integration_test.dart b/testing/dart/window_hooks_integration_test.dart index 3a1aac710f9f9..492146fca40b5 100644 --- a/testing/dart/window_hooks_integration_test.dart +++ b/testing/dart/window_hooks_integration_test.dart @@ -53,6 +53,7 @@ void main() { WindowPadding? oldPadding; WindowPadding? oldInsets; WindowPadding? oldSystemGestureInsets; + List? oldDisplayFeatures; void setUp() { PlatformDispatcher.instance._viewConfigurations.clear(); @@ -65,6 +66,7 @@ void main() { oldPadding = window.viewPadding; oldInsets = window.viewInsets; oldSystemGestureInsets = window.systemGestureInsets; + oldDisplayFeatures = window.displayFeatures; originalOnMetricsChanged = window.onMetricsChanged; originalOnLocaleChanged = window.onLocaleChanged; @@ -96,6 +98,15 @@ void main() { oldSystemGestureInsets!.right, // system gesture inset right oldSystemGestureInsets!.bottom, // system gesture inset bottom oldSystemGestureInsets!.left, // system gesture inset left + oldDisplayFeatures!.expand((feature) => [ + feature.bounds.left, + feature.bounds.top, + feature.bounds.right, + feature.bounds.bottom, + ]).map( + (e) => e * oldDevicePixelRatio!).toList(), // display features bounds + oldDisplayFeatures!.map((e) => e.type.index).toList(), // display features types + oldDisplayFeatures!.map((e) => e.state.index).toList(), // display features states ); window.onMetricsChanged = originalOnMetricsChanged; window.onLocaleChanged = originalOnLocaleChanged; @@ -181,6 +192,9 @@ void main() { 0.0, // system gesture inset right 0.0, // system gesture inset bottom 0.0, // system gesture inset left + [], // display features bounds + [], // display features types + [], // display features states ); expectNotEquals(runZone, null); expectIdentical(runZone, innerZone); @@ -396,6 +410,9 @@ void main() { 0.0, // system gesture inset right 0.0, // system gesture inset bottom 0.0, // system gesture inset left + [], // display features bounds + [], // display features types + [], // display features states ); expectEquals(window.viewInsets.bottom, 0.0); @@ -420,6 +437,9 @@ void main() { 0.0, // system gesture inset right 44.0, // system gesture inset bottom 0.0, // system gesture inset left + [], // display features bounds + [], // display features types + [], // display features states ); expectEquals(window.viewInsets.bottom, 400.0); From 357f3166b47938daf0c43d03027425ba4a2f4a23 Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Tue, 16 Mar 2021 17:26:40 +0200 Subject: [PATCH 05/23] Replaced API version code P with 28 --- .../android/io/flutter/embedding/android/FlutterView.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index b1f66f35d1e04..2530ce6d0778a 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -11,6 +11,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.Build; +import android.os.Build.VERSION_CODES; import android.text.format.DateFormat; import android.util.AttributeSet; import android.util.SparseArray; @@ -469,7 +470,7 @@ protected void onDetachedFromWindow() { * Refresh {@link androidx.window.WindowManager} and {@link android.view.DisplayCutout} display * features. Fold, hinge and cutout areas are populated here. */ - @TargetApi(Build.VERSION_CODES.P) + @TargetApi(28) private void setWindowManagerDisplayFeatures(WindowLayoutInfo layoutInfo) { List displayFeatures = layoutInfo.getDisplayFeatures(); List result = new ArrayList<>(); @@ -517,8 +518,8 @@ private void setWindowManagerDisplayFeatures(WindowLayoutInfo layoutInfo) { } // Data from the DisplayCutout bounds. Cutouts for cameras and other sensors are - // populated here. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + // populated here. DisplayCutout was introduced in API 28. + if (Build.VERSION.SDK_INT >= 28) { WindowInsets insets = getRootWindowInsets(); if (insets != null) { DisplayCutout cutout = insets.getDisplayCutout(); From f91efa160578eed3332f1637a5fe13b983b6a725 Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Tue, 16 Mar 2021 17:35:06 +0200 Subject: [PATCH 06/23] Unnecessary import --- .../android/io/flutter/embedding/android/FlutterView.java | 1 - 1 file changed, 1 deletion(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 2530ce6d0778a..3bc16e71765f0 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -11,7 +11,6 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.Build; -import android.os.Build.VERSION_CODES; import android.text.format.DateFormat; import android.util.AttributeSet; import android.util.SparseArray; From e5b6188c8514fb651fc94ad5c4b323ecc167ca23 Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Tue, 16 Mar 2021 18:00:42 +0200 Subject: [PATCH 07/23] Fixed display features parameters missing --- .../plugin/platform/PlatformViewsControllerTest.java | 10 ++++------ shell/platform/fuchsia/flutter/platform_view.cc | 11 +++++++---- 2 files changed, 11 insertions(+), 10 deletions(-) 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 eee54409b9849..1cd0a32b6f0e6 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -231,7 +231,6 @@ public void getPlatformViewById__hybridComposition() { platformViewsController.getRegistry().registerViewFactory("testType", viewFactory); FlutterJNI jni = new FlutterJNI(); - jni.attachToNative(false); attach(jni, platformViewsController); // Simulate create call from the framework. @@ -259,7 +258,6 @@ public void createPlatformViewMessage__initializesAndroidView() { platformViewsController.getRegistry().registerViewFactory("testType", viewFactory); FlutterJNI jni = new FlutterJNI(); - jni.attachToNative(false); attach(jni, platformViewsController); // Simulate create call from the framework. @@ -282,7 +280,6 @@ public void createPlatformViewMessage__throwsIfViewIsNull() { platformViewsController.getRegistry().registerViewFactory("testType", viewFactory); FlutterJNI jni = new FlutterJNI(); - jni.attachToNative(false); attach(jni, platformViewsController); // Simulate create call from the framework. @@ -313,7 +310,6 @@ public void createPlatformViewMessage__throwsIfViewHasParent() { platformViewsController.getRegistry().registerViewFactory("testType", viewFactory); FlutterJNI jni = new FlutterJNI(); - jni.attachToNative(false); attach(jni, platformViewsController); // Simulate create call from the framework. @@ -346,7 +342,6 @@ public void disposeAndroidView__hybridComposition() { platformViewsController.getRegistry().registerViewFactory("testType", viewFactory); FlutterJNI jni = new FlutterJNI(); - jni.attachToNative(false); attach(jni, platformViewsController); // Simulate create call from the framework. @@ -631,7 +626,10 @@ public void setViewportMetrics( int systemGestureInsetTop, int systemGestureInsetRight, int systemGestureInsetBottom, - int systemGestureInsetLeft) {} + int systemGestureInsetLeft, + int[] displayFeaturesBounds, + int[] displayFeaturesType, + int[] displayFeaturesState) {} @Implementation public void invokePlatformMessageResponseCallback( diff --git a/shell/platform/fuchsia/flutter/platform_view.cc b/shell/platform/fuchsia/flutter/platform_view.cc index 2e74f2eac096d..60a6d05363b33 100644 --- a/shell/platform/fuchsia/flutter/platform_view.cc +++ b/shell/platform/fuchsia/flutter/platform_view.cc @@ -343,10 +343,13 @@ void PlatformView::OnScenicEvent( 0.0f, // physical_view_inset_right 0.0f, // physical_view_inset_bottom 0.0f, // physical_view_inset_left - 0.0f, // p_physical_system_gesture_inset_top - 0.0f, // p_physical_system_gesture_inset_right - 0.0f, // p_physical_system_gesture_inset_bottom - 0.0f, // p_physical_system_gesture_inset_left + 0.0f, // p_physical_system_gesture_inset_top + 0.0f, // p_physical_system_gesture_inset_right + 0.0f, // p_physical_system_gesture_inset_bottom + 0.0f, // p_physical_system_gesture_inset_left + std::vector(), // display features bounds + std::vector(), // display features type + std::vector() // display features state }); } } From be2e2917fbd948be06241a5660e110ff095b6693 Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Mon, 29 Mar 2021 15:33:59 +0300 Subject: [PATCH 08/23] Documentation changes --- lib/ui/platform_dispatcher.dart | 121 +++++++++++------- lib/ui/window.dart | 15 +-- .../engine/renderer/FlutterRenderer.java | 10 +- 3 files changed, 91 insertions(+), 55 deletions(-) diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index c5be54943df65..061041d9c356b 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -1134,14 +1134,13 @@ class ViewConfiguration { /// populated only on Android. If the device has no display features, this list /// is empty. /// - /// The space in which the [DisplayFeature.bounds] are defined includes all screens - /// and the space between them. For a dual-screen device, 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 more like a crease in the display. + /// The area in which the [DisplayFeature.bounds] are defined includes all screens + /// and the space between them. 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 @@ -1371,49 +1370,65 @@ class WindowPadding { } } -/// Area of the display that may be obstructed by a hardware feature.This is -/// populated only on Android. +/// 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 screen -/// and expands to include the visual space between the screens and the right screen. -/// The [type] can be used to determine if this display feature obstructs the screen or not. +/// 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 how much the [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(type != DisplayFeatureType.cutout || state == DisplayFeatureState.unknown); - /// Area of the view occupied by this display feature, measured in logical pixels. + /// 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-right corner - /// of the left screen to the bottom-right corner of the right screen, including - /// the area occupied by any display feature. Bounds of display features are - /// reported in this coordinate system. + /// 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. + /// * [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) { - return identical(this, other) || other is DisplayFeature && - runtimeType == other.runtimeType && - bounds == other.bounds && - type == other.type && - state == other.state; + 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 @@ -1425,17 +1440,29 @@ class DisplayFeature { } } -/// Type of display feature (screen obstruction). This enum contains both -/// foldable obstructions and cutout obstructions. +/// Type of [DisplayFeature], describing the [DisplayFeature] behaviour and how +/// much the 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 of the display feature for types [DisplayFeatureType.fold] and +/// [DisplayFeatureType.hinge] is called the posture and is exposed in +/// [DisplayFeature.state]. +/// +/// ![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) /// -/// Some, 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. +/// ![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. + /// 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, @@ -1443,22 +1470,28 @@ enum DisplayFeatureType { cutout, } -/// State of the display feature. +/// 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. Postures correspond to values found in +/// [Android Postures](https://developer.android.com/guide/topics/ui/foldables#postures). /// -/// * For [DisplayFeatureType.fold]s & [DisplayFeatureType.hinge]s, the state is -/// the posture, corresponding to values found in -/// [Android Postures](https://developer.android.com/guide/topics/ui/foldables#postures). -/// * For [DisplayFeatureType.cutout]s, the state is [DisplayFeatureState.unknown]. +/// * 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. + /// The foldable device is completely open. + /// + /// The screen space that is presented to the user is flat. postureFlat, - /// 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 screen panels. + /// Fold angle is in an intermediate position between [postureFlat] and closed state. + /// + /// There is a non-flat angle between parts of the flexible screen or between physical screen panels. postureHalfOpened, /// The foldable device is flipped with the flexible screen parts or physical /// screens facing opposite directions. diff --git a/lib/ui/window.dart b/lib/ui/window.dart index e09f9e1413efb..eb4b4ab43c178 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -229,14 +229,13 @@ abstract class FlutterView { /// populated only on Android. If the device has no display features, this list /// is empty. /// - /// The space in which the [DisplayFeature.bounds] are defined includes all screens - /// and the space between them. For a dual-screen device, 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 more like a crease in the display. + /// The area in which the [DisplayFeature.bounds] are defined includes all screens + /// and the space between them. 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 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 81aebc388caaf..b127abb8c7367 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -386,10 +386,14 @@ public static final class ViewportMetrics { } /** - * Area of the viewport obstructed by a feature, eg. a hinge, a fold crease or camera cutout + * Description of a physical feature on the display. * - *

Similar to {@link androidx.window.DisplayFeature}, used for reporting Fold / Hinge areas. - * The reason we define our own model is that we also want to support cutouts. + *

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.DisplayFeature}, with added support for cutouts. */ public static final class DisplayFeature { public final Rect bounds; From 84645dcb5dbee73b3105ffd43e5565285be81501 Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Mon, 29 Mar 2021 15:44:57 +0300 Subject: [PATCH 09/23] First paragraph is a sentence --- lib/ui/platform_dispatcher.dart | 7 ++++--- lib/ui/window.dart | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index 061041d9c356b..c40d42ce870b8 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -1130,9 +1130,10 @@ class ViewConfiguration { /// phone sensor housings). final WindowPadding padding; - /// 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. + /// 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 area in which the [DisplayFeature.bounds] are defined includes all screens /// and the space between them. This means that the space between the screens diff --git a/lib/ui/window.dart b/lib/ui/window.dart index eb4b4ab43c178..db407c9eb2652 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -225,9 +225,10 @@ abstract class FlutterView { /// applications. WindowPadding get padding => viewConfiguration.padding; - /// 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. + /// 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 area in which the [DisplayFeature.bounds] are defined includes all screens /// and the space between them. This means that the space between the screens From 8872e390bafc33acae2171a39edd5bc862f35aa7 Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Mon, 29 Mar 2021 18:17:31 +0300 Subject: [PATCH 10/23] Update androidx window library to version 1.0.0-alpha05 --- tools/androidx/files.json | 4 ++-- tools/cipd/android_embedding_bundle/build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/androidx/files.json b/tools/androidx/files.json index 51b073b0ae00e..afcf43a15e0a0 100644 --- a/tools/androidx/files.json +++ b/tools/androidx/files.json @@ -51,9 +51,9 @@ ] }, { - "url": "https://maven.google.com/androidx/window/window/1.0.0-alpha03/window-1.0.0-alpha03.aar", + "url": "https://maven.google.com/androidx/window/window/1.0.0-alpha05/window-1.0.0-alpha05.aar", "out_file_name": "androidx_window.aar", - "maven_dependency": "androidx.window:window:1.0.0-alpha03", + "maven_dependency": "androidx.window:window:1.0.0-alpha05", "provides": [ "androidx.window.DisplayFeature", "androidx.window.FoldingFeature", diff --git a/tools/cipd/android_embedding_bundle/build.gradle b/tools/cipd/android_embedding_bundle/build.gradle index 323e414ea8bb0..dd0d70ac4326f 100644 --- a/tools/cipd/android_embedding_bundle/build.gradle +++ b/tools/cipd/android_embedding_bundle/build.gradle @@ -42,7 +42,7 @@ android { dependencies { embedding "androidx.annotation:annotation:1.1.0" embedding "androidx.fragment:fragment:1.1.0" - embedding "androidx.window:window:1.0.0-alpha03" + embedding "androidx.window:window:1.0.0-alpha05" def lifecycle_version = "2.2.0" embedding "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" From 284a029e46f8e58a0294bdf9ad7198130d5b4772 Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Mon, 5 Apr 2021 14:18:09 +0300 Subject: [PATCH 11/23] Improve documentation, remove deprecated display feature call --- lib/ui/platform_dispatcher.dart | 19 ++++++++++++------- lib/ui/window.dart | 17 +---------------- .../lib/src/ui/platform_dispatcher.dart | 11 ++++++----- .../embedding/android/FlutterView.java | 13 +++++-------- 4 files changed, 24 insertions(+), 36 deletions(-) diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index c40d42ce870b8..e0f3895e9b5e0 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -1130,12 +1130,14 @@ class ViewConfiguration { /// phone sensor housings). final WindowPadding padding; + + /// {@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 area in which the [DisplayFeature.bounds] are defined includes all screens + /// The coordinate space in which the [DisplayFeature.bounds] are defined includes all screens /// and the space between them. 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 @@ -1146,6 +1148,7 @@ class ViewConfiguration { /// 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 @@ -1379,7 +1382,7 @@ class WindowPadding { /// 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 how much the [DisplayFeature] obstructs the display. +/// 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. /// @@ -1441,17 +1444,19 @@ class DisplayFeature { } } -/// Type of [DisplayFeature], describing the [DisplayFeature] behaviour and how -/// much the it obstructs the display. +/// 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 of the display feature for types [DisplayFeatureType.fold] and +/// The shape formed by the screens for types [DisplayFeatureType.fold] and /// [DisplayFeatureType.hinge] is called the posture and is exposed in -/// [DisplayFeature.state]. +/// [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) /// @@ -1490,7 +1495,7 @@ enum DisplayFeatureState { /// /// The screen space that is presented to the user is flat. postureFlat, - /// Fold angle is in an intermediate position between [postureFlat] and closed state. + /// 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. postureHalfOpened, diff --git a/lib/ui/window.dart b/lib/ui/window.dart index db407c9eb2652..b758288d09ad2 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -225,22 +225,7 @@ abstract class FlutterView { /// applications. WindowPadding get padding => viewConfiguration.padding; - /// 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 area in which the [DisplayFeature.bounds] are defined includes all screens - /// and the space between them. 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. + /// {@macro dart.ui.ViewConfiguration.displayFeatures} /// /// When this changes, [onMetricsChanged] is called. /// diff --git a/lib/web_ui/lib/src/ui/platform_dispatcher.dart b/lib/web_ui/lib/src/ui/platform_dispatcher.dart index df75c8f67e0ad..bea1ca3960dd4 100644 --- a/lib/web_ui/lib/src/ui/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/ui/platform_dispatcher.dart @@ -287,11 +287,12 @@ class DisplayFeature { @override bool operator ==(Object other) { - return identical(this, other) || other is DisplayFeature && - runtimeType == other.runtimeType && - bounds == other.bounds && - type == other.type && - state == other.state; + 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 diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 3bc16e71765f0..bf60628c0099e 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -484,16 +484,13 @@ private void setWindowManagerDisplayFeatures(WindowLayoutInfo layoutInfo) { + " and type = " + displayFeature.getClass().getSimpleName()); if (displayFeature instanceof FoldingFeature) { - DisplayFeatureType type = DisplayFeatureType.UNKNOWN; + DisplayFeatureType type; DisplayFeatureState state = DisplayFeatureState.UNKNOWN; final FoldingFeature feature = (FoldingFeature) displayFeature; - switch (feature.getType()) { - case FoldingFeature.TYPE_FOLD: - type = DisplayFeatureType.FOLD; - break; - case FoldingFeature.TYPE_HINGE: - type = DisplayFeatureType.HINGE; - break; + if (feature.getOcclusionMode() == FoldingFeature.OCCLUSION_NONE) { + type = DisplayFeatureType.FOLD; + } else { + type = DisplayFeatureType.HINGE; } switch (feature.getState()) { case FoldingFeature.STATE_FLAT: From 17fd7a6ba27f1b13784e33623ecf581cdb9da30c Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Wed, 7 Apr 2021 20:16:41 +0300 Subject: [PATCH 12/23] Write tests and improve documentation --- lib/ui/platform_dispatcher.dart | 18 +-- lib/ui/window/viewport_metrics.cc | 2 +- .../embedding/android/FlutterView.java | 11 +- .../engine/renderer/FlutterRenderer.java | 2 +- .../embedding/android/FlutterViewTest.java | 134 ++++++++++++++++++ .../engine/renderer/FlutterRendererTest.java | 62 ++++++++ .../dart/window_hooks_integration_test.dart | 57 +++++++- 7 files changed, 271 insertions(+), 15 deletions(-) diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index e0f3895e9b5e0..5ada4998622e3 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -229,10 +229,10 @@ class PlatformDispatcher { required List state, required double devicePixelRatio, }) { - assert(bounds.length / 4 == type.length); + 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++){ + for(int i = 0; i < type.length; i++){ final int rectOffset = i * 4; result.add(DisplayFeature( bounds: Rect.fromLTRB( @@ -1137,8 +1137,8 @@ class ViewConfiguration { /// 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 includes all screens - /// and the space between them. This means that the space between the screens + /// 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. @@ -1472,7 +1472,7 @@ enum DisplayFeatureType { fold, /// A physical separation with a hinge that allows two display panels to fold. hinge, - /// A non-functional area of the screen, usually housing cameras or sensors. + /// A non-displaying area of the screen, usually housing cameras or sensors. cutout, } @@ -1480,7 +1480,7 @@ enum DisplayFeatureType { /// for foldable features. /// /// The posture is the shape made by the parts of the flexible screen or -/// physical screen panels. Postures correspond to values found in +/// 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 @@ -1497,10 +1497,12 @@ enum DisplayFeatureState { 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. + /// 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, /// The foldable device is flipped with the flexible screen parts or physical - /// screens facing opposite directions. + /// screens facing opposite directions. Not all postures are possible on all + /// foldable devices. Some do not support the flipped posture. postureFlipped, } diff --git a/lib/ui/window/viewport_metrics.cc b/lib/ui/window/viewport_metrics.cc index b7d6380d890af..161cf55c5b02f 100644 --- a/lib/ui/window/viewport_metrics.cc +++ b/lib/ui/window/viewport_metrics.cc @@ -96,7 +96,7 @@ std::ostream& operator<<(std::ostream& os, const ViewportMetrics& a) { << a.physical_system_gesture_inset_right << "R " << a.physical_system_gesture_inset_bottom << "B " << a.physical_system_gesture_inset_left << "L] " - << "Display Features size: " << a.physical_display_features_type.size(); + << "Display Features: " << a.physical_display_features_type.size(); return os; } diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index bf60628c0099e..4e5985a3ab3cd 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -349,8 +349,6 @@ private void init() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS); } - - windowManager = new androidx.window.WindowManager(getContext()); } /** @@ -442,6 +440,11 @@ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) sendViewportMetricsToFlutter(); } + @VisibleForTesting() + protected androidx.window.WindowManager createWindowManager() { + return new androidx.window.WindowManager(getContext()); + } + /** * Invoked when this is attached to the window. * @@ -450,6 +453,7 @@ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); + this.windowManager = createWindowManager(); windowManager.registerLayoutChangeCallback( ContextCompat.getMainExecutor(getContext()), windowManagerListener); } @@ -462,6 +466,7 @@ protected void onAttachedToWindow() { @Override protected void onDetachedFromWindow() { windowManager.unregisterLayoutChangeCallback(windowManagerListener); + this.windowManager = null; super.onDetachedFromWindow(); } @@ -470,7 +475,7 @@ protected void onDetachedFromWindow() { * features. Fold, hinge and cutout areas are populated here. */ @TargetApi(28) - private void setWindowManagerDisplayFeatures(WindowLayoutInfo layoutInfo) { + protected void setWindowManagerDisplayFeatures(WindowLayoutInfo layoutInfo) { List displayFeatures = layoutInfo.getDisplayFeatures(); List result = new ArrayList<>(); 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 b127abb8c7367..e539513fecdaa 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -276,7 +276,7 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { + ", B: " + viewportMetrics.systemGestureInsetRight + "\n" - + "Display Features Count: " + + "Display Features: " + viewportMetrics.displayFeatures.size()); int[] displayFeaturesBounds = new int[viewportMetrics.displayFeatures.size() * 4]; 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 9ffa7107d38f3..d6d0ff4504fd8 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -20,6 +20,7 @@ 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,12 +30,16 @@ import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowManager; +import androidx.core.util.Consumer; +import androidx.window.FoldingFeature; +import androidx.window.WindowLayoutInfo; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.systemchannels.SettingsChannel; import io.flutter.plugin.platform.PlatformViewsController; +import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; import org.junit.Before; import org.junit.Test; @@ -637,6 +642,135 @@ 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()); + when(flutterView.getContext()).thenReturn(context); + androidx.window.WindowManager windowManager = mock(androidx.window.WindowManager.class); + when(flutterView.createWindowManager()).thenReturn(windowManager); + + // When a new FlutterView is attached to the window + flutterView.onAttachedToWindow(); + + // Then the WindowManager callback is registered + verify(windowManager, times(1)).registerLayoutChangeCallback(any(), any()); + + // When the FlutterView is detached from the window + flutterView.onDetachedFromWindow(); + + // Then the WindowManager callback is unregistered + verify(windowManager, times(1)).unregisterLayoutChangeCallback(any()); + } + + @Test + @TargetApi(30) + @Config(sdk = 30) + public void itSendsCutoutDisplayFeatureToFlutter() { + 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); + androidx.window.WindowManager windowManager = mock(androidx.window.WindowManager.class); + when(flutterView.createWindowManager()).thenReturn(windowManager); + FlutterEngine flutterEngine = + spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); + FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); + when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + + // When FlutterView is attached to the engine and window, and a cutout exists + WindowInsets windowInsets = mock(WindowInsets.class); + DisplayCutout displayCutout = mock(DisplayCutout.class); + when(displayCutout.getBoundingRects()).thenReturn(Arrays.asList(new Rect(0, 0, 100, 100))); + when(windowInsets.getDisplayCutout()).thenReturn(displayCutout); + when(flutterView.getRootWindowInsets()).thenReturn(windowInsets); + + 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(windowManager).registerLayoutChangeCallback(any(), wmConsumerCaptor.capture()); + Consumer wmConsumer = wmConsumerCaptor.getValue(); + + wmConsumer.accept(new WindowLayoutInfo.Builder().build()); + + // Then the Renderer receives the display feature + verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); + assertEquals( + FlutterRenderer.DisplayFeatureType.CUTOUT, + viewportMetricsCaptor.getValue().displayFeatures.get(0).type); + assertEquals( + FlutterRenderer.DisplayFeatureState.UNKNOWN, + viewportMetricsCaptor.getValue().displayFeatures.get(0).state); + assertEquals( + new Rect(0, 0, 100, 100), viewportMetricsCaptor.getValue().displayFeatures.get(0).bounds); + } + + @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); + androidx.window.WindowManager windowManager = mock(androidx.window.WindowManager.class); + when(flutterView.createWindowManager()).thenReturn(windowManager); + FlutterEngine flutterEngine = + spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); + FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); + when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + + WindowLayoutInfo testWindowLayout = + new WindowLayoutInfo.Builder() + .setDisplayFeatures( + Arrays.asList( + new FoldingFeature( + new Rect(0, 0, 100, 100), + FoldingFeature.TYPE_HINGE, + FoldingFeature.STATE_FLAT))) + .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(windowManager).registerLayoutChangeCallback(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 185ed43fa08c2..bd1893e6563ce 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,15 +1,20 @@ package io.flutter.embedding.engine.renderer; +import static org.junit.Assert.assertArrayEquals; +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; import static org.mockito.Mockito.verify; +import android.graphics.Rect; import android.view.Surface; import io.flutter.embedding.engine.FlutterJNI; 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; @@ -116,4 +121,61 @@ public void itStopsSurfaceTextureCallbackWhenDetached() { // Verify behavior under test. verify(fakeFlutterJNI, times(0)).markTextureFrameAvailable(eq(entry.id())); } + + @Test + public void itConvertsDisplayFeatureArrayToPrimitiveArrays() { + // Setup the test. + FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI); + FlutterRenderer.ViewportMetrics metrics = new FlutterRenderer.ViewportMetrics(); + metrics.displayFeatures.add( + new FlutterRenderer.DisplayFeature( + new Rect(10, 20, 30, 40), + FlutterRenderer.DisplayFeatureType.FOLD, + FlutterRenderer.DisplayFeatureState.POSTURE_FLIPPED)); + 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(), + 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_FLIPPED.encodedValue, + FlutterRenderer.DisplayFeatureState.UNKNOWN.encodedValue + }, + stateCaptor.getValue()); + } } diff --git a/testing/dart/window_hooks_integration_test.dart b/testing/dart/window_hooks_integration_test.dart index 492146fca40b5..331160e909b6a 100644 --- a/testing/dart/window_hooks_integration_test.dart +++ b/testing/dart/window_hooks_integration_test.dart @@ -395,7 +395,7 @@ void main() { test('Window padding/insets/viewPadding/systemGestureInsets', () { _updateWindowMetrics( 0, // window id - 0, // screen id + 0, // device pixel ratio 800.0, // width 600.0, // height 50.0, // padding top @@ -422,7 +422,7 @@ void main() { _updateWindowMetrics( 0, // window id - 0, // screen id + 0, // device pixel ratio 800.0, // width 600.0, // height 50.0, // padding top @@ -448,6 +448,59 @@ void main() { expectEquals(window.systemGestureInsets.bottom, 44.0); }); + test('Window displayFeatures updates decode properly', () { + _updateWindowMetrics( + 0, // window id + 2, // device pixel ratio + 800.0, // width + 600.0, // height + 50.0, // padding top + 0.0, // padding right + 40.0, // padding bottom + 0.0, // padding left + 0.0, // inset top + 0.0, // inset right + 0.0, // inset bottom + 0.0, // inset left + 0.0, // system gesture inset top + 0.0, // system gesture inset right + 0.0, // system gesture inset bottom + 0.0, // system gesture inset left + [], // display features bounds + [], // display features types + [], // display features states + ); + + expectIterablesEqual(window.displayFeatures, []); + + _updateWindowMetrics( + 0, // window id + 2, // device pixel ratio + 800.0, // width + 600.0, // height + 50.0, // padding top + 0.0, // padding right + 40.0, // padding bottom + 0.0, // padding left + 0.0, // inset top + 0.0, // inset right + 400.0, // inset bottom + 0.0, // inset left + 0.0, // system gesture inset top + 0.0, // system gesture inset right + 44.0, // system gesture inset bottom + 0.0, // system gesture inset left + [0, 0, 100, 100], // display features bounds + [2], // display features types + [3], // display features states + ); + + // Display feature bounds is measured in logical pixels + expectEquals(window.displayFeatures[0].bounds, Rect.fromLTRB(0, 0, 50, 50)); + expectEquals(window.displayFeatures[0].type, DisplayFeatureType.hinge); + expectEquals(window.displayFeatures[0].state, DisplayFeatureState.postureFlipped); + }); + test('PlatformDispatcher.locale returns unknown locale when locales is set to empty list', () { late Locale locale; runZoned(() { From a73cfe5f418f2d2196ddd4235bad56cebec7e65f Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Wed, 23 Jun 2021 12:05:35 +0300 Subject: [PATCH 13/23] Migrate to Window Manager alpha08 --- lib/ui/platform_dispatcher.dart | 6 +----- .../io/flutter/embedding/android/FlutterView.java | 3 --- .../embedding/engine/renderer/FlutterRenderer.java | 8 +------- tools/androidx/files.json | 9 +++++---- tools/cipd/android_embedding_bundle/build.gradle | 2 +- 5 files changed, 8 insertions(+), 20 deletions(-) diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index 5ada4998622e3..c5851becc0154 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -1402,7 +1402,7 @@ class DisplayFeature { required this.bounds, required this.type, required this.state, - }) : assert(type != DisplayFeatureType.cutout || state == DisplayFeatureState.unknown); + }) : assert(!identical(type, DisplayFeatureType.cutout) || identical(state, DisplayFeatureState.unknown)); /// The area of the flutter view occupied by this display feature, measured in logical pixels. /// @@ -1500,10 +1500,6 @@ enum DisplayFeatureState { /// 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, - /// The foldable device is flipped with the flexible screen parts or physical - /// screens facing opposite directions. Not all postures are possible on all - /// foldable devices. Some do not support the flipped posture. - postureFlipped, } /// An identifier used to select a user's language and formatting preferences. diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 4e5985a3ab3cd..f6da85a2f791d 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -504,9 +504,6 @@ protected void setWindowManagerDisplayFeatures(WindowLayoutInfo layoutInfo) { case FoldingFeature.STATE_HALF_OPENED: state = DisplayFeatureState.POSTURE_HALF_OPENED; break; - case FoldingFeature.STATE_FLIPPED: - state = DisplayFeatureState.POSTURE_FLIPPED; - break; } result.add(new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state)); } else { 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 e539513fecdaa..cb33838658dc0 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -472,13 +472,7 @@ public enum DisplayFeatureState { * There is a non-flat angle between parts of the flexible screen or between physical screen * panels. Corresponds to {@link androidx.window.FoldingFeature.STATE_HALF_OPENED} */ - POSTURE_HALF_OPENED(2), - - /** - * The foldable device is flipped with the flexible screen parts or physical screens facing - * opposite directions. Corresponds to {@link androidx.window.FoldingFeature.STATE_FLIPPED} - */ - POSTURE_FLIPPED(3); + POSTURE_HALF_OPENED(2); public final int encodedValue; diff --git a/tools/androidx/files.json b/tools/androidx/files.json index afcf43a15e0a0..ccd162e781f2c 100644 --- a/tools/androidx/files.json +++ b/tools/androidx/files.json @@ -51,14 +51,15 @@ ] }, { - "url": "https://maven.google.com/androidx/window/window/1.0.0-alpha05/window-1.0.0-alpha05.aar", - "out_file_name": "androidx_window.aar", - "maven_dependency": "androidx.window:window:1.0.0-alpha05", + "url": "https://maven.google.com/androidx/window/window-java/1.0.0-alpha08/window-java-1.0.0-alpha08.aar", + "out_file_name": "androidx_window_java.aar", + "maven_dependency": "androidx.window:window-java:1.0.0-alpha08", "provides": [ "androidx.window.DisplayFeature", "androidx.window.FoldingFeature", "androidx.window.WindowLayoutInfo", - "androidx.window.WindowManager" + "androidx.window.WindowManager", + "androidx.window.WindowInfoRepo" ] } ] diff --git a/tools/cipd/android_embedding_bundle/build.gradle b/tools/cipd/android_embedding_bundle/build.gradle index dd0d70ac4326f..f071b5d0de8a7 100644 --- a/tools/cipd/android_embedding_bundle/build.gradle +++ b/tools/cipd/android_embedding_bundle/build.gradle @@ -42,7 +42,7 @@ android { dependencies { embedding "androidx.annotation:annotation:1.1.0" embedding "androidx.fragment:fragment:1.1.0" - embedding "androidx.window:window:1.0.0-alpha05" + embedding "androidx.window:window-java:1.0.0-alpha08" def lifecycle_version = "2.2.0" embedding "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" From de52872bc90da3acf082cd16f4a45e24b228a25e Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Fri, 20 Aug 2021 18:40:31 +0300 Subject: [PATCH 14/23] Update androidx window to beta01 --- .../embedding/android/FlutterView.java | 67 ++++++++++--------- .../engine/renderer/FlutterRenderer.java | 32 ++++----- .../embedding/android/FlutterViewTest.java | 52 +++++++------- .../engine/renderer/FlutterRendererTest.java | 4 +- .../dart/window_hooks_integration_test.dart | 4 +- testing/scenario_app/android/app/build.gradle | 2 +- tools/androidx/files.json | 16 +++-- .../android_embedding_bundle/build.gradle | 4 +- 8 files changed, 97 insertions(+), 84 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 60f216b023369..6a71ebeee5bfa 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; @@ -36,9 +37,13 @@ import androidx.annotation.VisibleForTesting; import androidx.core.content.ContextCompat; import androidx.core.util.Consumer; -import androidx.window.DisplayFeature; -import androidx.window.FoldingFeature; -import androidx.window.WindowLayoutInfo; +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; @@ -119,7 +124,7 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC @Nullable private AccessibilityBridge accessibilityBridge; // Provides access to foldable/hinge information - @Nullable private androidx.window.WindowManager windowManager; + @Nullable private WindowInfoRepositoryCallbackAdapter windowInfoRepo; // Directly implemented View behavior that communicates with Flutter. private final FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics(); @@ -154,11 +159,11 @@ public void onFlutterUiNoLongerDisplayed() { } }; - private final Consumer windowManagerListener = + private final Consumer windowInfoListener = new Consumer() { @Override public void accept(WindowLayoutInfo layoutInfo) { - setWindowManagerDisplayFeatures(layoutInfo); + setWindowInfoListenerDisplayFeatures(layoutInfo); } }; @@ -445,69 +450,69 @@ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) } @VisibleForTesting() - protected androidx.window.WindowManager createWindowManager() { - return new androidx.window.WindowManager(getContext()); + protected WindowInfoRepositoryCallbackAdapter createWindowInfoRepo() { + return new WindowInfoRepositoryCallbackAdapter( + WindowInfoRepository.getOrCreate((Activity) getContext())); } /** * Invoked when this is attached to the window. * - *

We register for {@link androidx.window.WindowManager} updates. + *

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

We unregister from {@link androidx.window.WindowManager} updates. + *

We unregister from {@link androidx.window.layout.WindowInfoRepository} updates. */ @Override protected void onDetachedFromWindow() { - windowManager.unregisterLayoutChangeCallback(windowManagerListener); - this.windowManager = null; + windowInfoRepo.removeWindowLayoutInfoListener(windowInfoListener); + this.windowInfoRepo = null; super.onDetachedFromWindow(); } /** - * Refresh {@link androidx.window.WindowManager} and {@link android.view.DisplayCutout} display - * features. Fold, hinge and cutout areas are populated here. + * 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 setWindowManagerDisplayFeatures(WindowLayoutInfo layoutInfo) { + protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo) { List displayFeatures = layoutInfo.getDisplayFeatures(); List result = new ArrayList<>(); - // Data from androidx.window.WindowManager display features. Fold and hinge areas are + // Data from WindowInfoRepository display features. Fold and hinge areas are // populated here. for (DisplayFeature displayFeature : displayFeatures) { Log.v( TAG, - "WindowManager Display Feature reported with bounds = " + "WindowInfoRepository Display Feature reported with bounds = " + displayFeature.getBounds().toString() + " and type = " + displayFeature.getClass().getSimpleName()); if (displayFeature instanceof FoldingFeature) { DisplayFeatureType type; - DisplayFeatureState state = DisplayFeatureState.UNKNOWN; + DisplayFeatureState state; final FoldingFeature feature = (FoldingFeature) displayFeature; - if (feature.getOcclusionMode() == FoldingFeature.OCCLUSION_NONE) { - type = DisplayFeatureType.FOLD; - } else { + if (feature.getOcclusionType() == OcclusionType.FULL) { type = DisplayFeatureType.HINGE; + } else { + type = DisplayFeatureType.FOLD; } - switch (feature.getState()) { - case FoldingFeature.STATE_FLAT: - state = DisplayFeatureState.POSTURE_FLAT; - break; - case FoldingFeature.STATE_HALF_OPENED: - state = DisplayFeatureState.POSTURE_HALF_OPENED; - break; + 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 { 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 302d18fc31603..f87e5e15285a7 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -393,7 +393,7 @@ public static final class ViewportMetrics { * visual or touch discontinuity, make some area invisible or create a logical divider or * separation in the screen space. * - *

Based on {@link androidx.window.DisplayFeature}, with added support for cutouts. + *

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

Some, like 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 {@link DisplayFeature} bounds + *

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 { /** - * We do not know this type of display feature yet. This can happen if WindowManager is updated - * with new types. + * 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 screen without a physical gap. Corresponds to {@link - * androidx.window.FoldingFeature#TYPE_FOLD} + * A fold in the flexible display that does not occlude the screen. Corresponds to {@link + * androidx.window.layout.FoldingFeature.OcclusionType#NONE} */ FOLD(1), /** - * A physical separation with a hinge that allows two display panels to fold. Corresponds to - * {@link androidx.window.FoldingFeature#TYPE_HINGE} + * Splits the display in two separate panels that can fold. Occludes the screen. Corresponds to + * {@link androidx.window.layout.FoldingFeature.OcclusionType#FULL} */ HINGE(2), /** - * A non-functional area of the screen, usually housing cameras or sensors. Corresponds to - * {@link android.view.DisplayCutout} + * Area of the screen that usually houses cameras or sensors. Occludes the screen. Corresponds + * to {@link android.view.DisplayCutout} */ CUTOUT(3); @@ -455,7 +455,7 @@ public enum DisplayFeatureType { /** * State of the display feature. * - *

For foldables, the state is the posture. For cutouts, this is {@link UNKNOWN} + *

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. */ @@ -463,14 +463,14 @@ public enum DisplayFeatureState { /** * The foldable device is completely open. The screen space that is presented to the user is - * flat. Corresponds to {@link androidx.window.FoldingFeature#STATE_FLAT} + * 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 screen - * panels. Corresponds to {@link androidx.window.FoldingFeature#STATE_HALF_OPENED} + * 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); 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 d4239230d3b73..1789387b76597 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; @@ -32,8 +33,10 @@ import android.view.WindowManager; import android.widget.FrameLayout; import androidx.core.util.Consumer; -import androidx.window.FoldingFeature; -import androidx.window.WindowLayoutInfo; +import androidx.window.java.layout.WindowInfoRepositoryCallbackAdapter; +import androidx.window.layout.FoldingFeature; +import androidx.window.layout.HardwareFoldingFeature; +import androidx.window.layout.WindowLayoutInfo; import io.flutter.TestUtils; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterJNI; @@ -654,21 +657,23 @@ public void itRegistersAndUnregistersToWindowManager() { ((WindowManager) RuntimeEnvironment.systemContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay()); - when(flutterView.getContext()).thenReturn(context); - androidx.window.WindowManager windowManager = mock(androidx.window.WindowManager.class); - when(flutterView.createWindowManager()).thenReturn(windowManager); + WindowInfoRepositoryCallbackAdapter windowInfoRepo = + mock(WindowInfoRepositoryCallbackAdapter.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(windowManager, times(1)).registerLayoutChangeCallback(any(), any()); + verify(windowInfoRepo, times(1)).addWindowLayoutInfoListener(any(), any()); // When the FlutterView is detached from the window flutterView.onDetachedFromWindow(); // Then the WindowManager callback is unregistered - verify(windowManager, times(1)).unregisterLayoutChangeCallback(any()); + verify(windowInfoRepo, times(1)).removeWindowLayoutInfoListener(any()); } @Test @@ -682,13 +687,15 @@ public void itSendsCutoutDisplayFeatureToFlutter() { ((WindowManager) RuntimeEnvironment.systemContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay()); - when(flutterView.getContext()).thenReturn(context); - androidx.window.WindowManager windowManager = mock(androidx.window.WindowManager.class); - when(flutterView.createWindowManager()).thenReturn(windowManager); + WindowInfoRepositoryCallbackAdapter windowInfoRepo = + mock(WindowInfoRepositoryCallbackAdapter.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(); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - when(flutterEngine.getRenderer()).thenReturn(flutterRenderer); + doReturn(flutterRenderer).when(flutterEngine).getRenderer(); // When FlutterView is attached to the engine and window, and a cutout exists WindowInsets windowInsets = mock(WindowInsets.class); @@ -705,7 +712,7 @@ public void itSendsCutoutDisplayFeatureToFlutter() { flutterView.onAttachedToWindow(); ArgumentCaptor> wmConsumerCaptor = ArgumentCaptor.forClass((Class) Consumer.class); - verify(windowManager).registerLayoutChangeCallback(any(), wmConsumerCaptor.capture()); + verify(windowInfoRepo).addWindowLayoutInfoListener(any(), wmConsumerCaptor.capture()); Consumer wmConsumer = wmConsumerCaptor.getValue(); wmConsumer.accept(new WindowLayoutInfo.Builder().build()); @@ -732,22 +739,21 @@ public void itSendsHingeDisplayFeatureToFlutter() { RuntimeEnvironment.systemContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay()); when(flutterView.getContext()).thenReturn(context); - androidx.window.WindowManager windowManager = mock(androidx.window.WindowManager.class); - when(flutterView.createWindowManager()).thenReturn(windowManager); + WindowInfoRepositoryCallbackAdapter windowInfoRepo = + mock(WindowInfoRepositoryCallbackAdapter.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( - new FoldingFeature( - new Rect(0, 0, 100, 100), - FoldingFeature.TYPE_HINGE, - FoldingFeature.STATE_FLAT))) - .build(); + 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); @@ -758,7 +764,7 @@ public void itSendsHingeDisplayFeatureToFlutter() { flutterView.onAttachedToWindow(); ArgumentCaptor> wmConsumerCaptor = ArgumentCaptor.forClass((Class) Consumer.class); - verify(windowManager).registerLayoutChangeCallback(any(), wmConsumerCaptor.capture()); + verify(windowInfoRepo).addWindowLayoutInfoListener(any(), wmConsumerCaptor.capture()); Consumer wmConsumer = wmConsumerCaptor.getValue(); wmConsumer.accept(testWindowLayout); 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 bd1893e6563ce..ff8576a8df35d 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 @@ -131,7 +131,7 @@ public void itConvertsDisplayFeatureArrayToPrimitiveArrays() { new FlutterRenderer.DisplayFeature( new Rect(10, 20, 30, 40), FlutterRenderer.DisplayFeatureType.FOLD, - FlutterRenderer.DisplayFeatureState.POSTURE_FLIPPED)); + FlutterRenderer.DisplayFeatureState.POSTURE_HALF_OPENED)); metrics.displayFeatures.add( new FlutterRenderer.DisplayFeature( new Rect(50, 60, 70, 80), FlutterRenderer.DisplayFeatureType.CUTOUT)); @@ -173,7 +173,7 @@ public void itConvertsDisplayFeatureArrayToPrimitiveArrays() { typeCaptor.getValue()); assertArrayEquals( new int[] { - FlutterRenderer.DisplayFeatureState.POSTURE_FLIPPED.encodedValue, + FlutterRenderer.DisplayFeatureState.POSTURE_HALF_OPENED.encodedValue, FlutterRenderer.DisplayFeatureState.UNKNOWN.encodedValue }, stateCaptor.getValue()); diff --git a/testing/dart/window_hooks_integration_test.dart b/testing/dart/window_hooks_integration_test.dart index 331160e909b6a..6e7fe542a1029 100644 --- a/testing/dart/window_hooks_integration_test.dart +++ b/testing/dart/window_hooks_integration_test.dart @@ -492,13 +492,13 @@ void main() { 0.0, // system gesture inset left [0, 0, 100, 100], // display features bounds [2], // display features types - [3], // display features states + [2], // display features states ); // Display feature bounds is measured in logical pixels expectEquals(window.displayFeatures[0].bounds, Rect.fromLTRB(0, 0, 50, 50)); expectEquals(window.displayFeatures[0].type, DisplayFeatureType.hinge); - expectEquals(window.displayFeatures[0].state, DisplayFeatureState.postureFlipped); + expectEquals(window.displayFeatures[0].state, DisplayFeatureState.postureHalfOpened); }); test('PlatformDispatcher.locale returns unknown locale when locales is set to empty list', () { diff --git a/testing/scenario_app/android/app/build.gradle b/testing/scenario_app/android/app/build.gradle index 16288dd35f2cb..12ff5196d800b 100644 --- a/testing/scenario_app/android/app/build.gradle +++ b/testing/scenario_app/android/app/build.gradle @@ -9,7 +9,7 @@ screenshots { } android { - compileSdkVersion 30 + compileSdkVersion 31 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 diff --git a/tools/androidx/files.json b/tools/androidx/files.json index ccd162e781f2c..8ba4fdf16a01f 100644 --- a/tools/androidx/files.json +++ b/tools/androidx/files.json @@ -51,15 +51,17 @@ ] }, { - "url": "https://maven.google.com/androidx/window/window-java/1.0.0-alpha08/window-java-1.0.0-alpha08.aar", + "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-alpha08", + "maven_dependency": "androidx.window:window-java:1.0.0-beta01", "provides": [ - "androidx.window.DisplayFeature", - "androidx.window.FoldingFeature", - "androidx.window.WindowLayoutInfo", - "androidx.window.WindowManager", - "androidx.window.WindowInfoRepo" + "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 f071b5d0de8a7..455a0c605f385 100644 --- a/tools/cipd/android_embedding_bundle/build.gradle +++ b/tools/cipd/android_embedding_bundle/build.gradle @@ -37,12 +37,12 @@ configurations { } android { - compileSdkVersion 30 + compileSdkVersion 31 dependencies { embedding "androidx.annotation:annotation:1.1.0" embedding "androidx.fragment:fragment:1.1.0" - embedding "androidx.window:window-java:1.0.0-alpha08" + embedding "androidx.window:window-java:1.0.0-beta01" def lifecycle_version = "2.2.0" embedding "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" From 3dc358ca2135ab85e2875731aaaba9a8128af485 Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Fri, 20 Aug 2021 18:42:34 +0300 Subject: [PATCH 15/23] Remove unused import --- .../test/io/flutter/embedding/android/FlutterViewTest.java | 1 - 1 file changed, 1 deletion(-) 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 1789387b76597..d3de34f152751 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -35,7 +35,6 @@ import androidx.core.util.Consumer; import androidx.window.java.layout.WindowInfoRepositoryCallbackAdapter; import androidx.window.layout.FoldingFeature; -import androidx.window.layout.HardwareFoldingFeature; import androidx.window.layout.WindowLayoutInfo; import io.flutter.TestUtils; import io.flutter.embedding.engine.FlutterEngine; From 5452a010f5720d6c614afd6560a7fa31970322bf Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Fri, 20 Aug 2021 23:17:05 +0300 Subject: [PATCH 16/23] Remove window_hook_integration_test.dart --- .../dart/window_hooks_integration_test.dart | 516 ------------------ 1 file changed, 516 deletions(-) delete mode 100644 testing/dart/window_hooks_integration_test.dart diff --git a/testing/dart/window_hooks_integration_test.dart b/testing/dart/window_hooks_integration_test.dart deleted file mode 100644 index 6e7fe542a1029..0000000000000 --- a/testing/dart/window_hooks_integration_test.dart +++ /dev/null @@ -1,516 +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. - -// @dart = 2.12 - -// HACK: pretend to be dart.ui in order to access its internals -library dart.ui; - -import 'dart:async'; -// this needs to be imported because painting.dart expects it this way -import 'dart:collection' as collection; -import 'dart:convert'; -import 'dart:developer' as developer; -import 'dart:math' as math; -import 'dart:nativewrappers'; // ignore: unused_import -import 'dart:typed_data'; - - -// HACK: these parts are to get access to private functions tested here. -part '../../lib/ui/annotations.dart'; -part '../../lib/ui/channel_buffers.dart'; -part '../../lib/ui/compositing.dart'; -part '../../lib/ui/geometry.dart'; -part '../../lib/ui/hash_codes.dart'; -part '../../lib/ui/hooks.dart'; -part '../../lib/ui/key.dart'; -part '../../lib/ui/lerp.dart'; -part '../../lib/ui/natives.dart'; -part '../../lib/ui/painting.dart'; -part '../../lib/ui/platform_dispatcher.dart'; -part '../../lib/ui/pointer.dart'; -part '../../lib/ui/semantics.dart'; -part '../../lib/ui/text.dart'; -part '../../lib/ui/window.dart'; - -void main() { - VoidCallback? originalOnMetricsChanged; - VoidCallback? originalOnLocaleChanged; - FrameCallback? originalOnBeginFrame; - VoidCallback? originalOnDrawFrame; - TimingsCallback? originalOnReportTimings; - PointerDataPacketCallback? originalOnPointerDataPacket; - VoidCallback? originalOnSemanticsEnabledChanged; - SemanticsActionCallback? originalOnSemanticsAction; - PlatformMessageCallback? originalOnPlatformMessage; - VoidCallback? originalOnTextScaleFactorChanged; - - Object? oldWindowId; - double? oldDevicePixelRatio; - Rect? oldGeometry; - - WindowPadding? oldPadding; - WindowPadding? oldInsets; - WindowPadding? oldSystemGestureInsets; - List? oldDisplayFeatures; - - void setUp() { - PlatformDispatcher.instance._viewConfigurations.clear(); - PlatformDispatcher.instance._views.clear(); - PlatformDispatcher.instance._viewConfigurations[0] = const ViewConfiguration(); - PlatformDispatcher.instance._views[0] = FlutterWindow._(0, PlatformDispatcher.instance); - oldWindowId = window._windowId; - oldDevicePixelRatio = window.devicePixelRatio; - oldGeometry = window.viewConfiguration.geometry; - oldPadding = window.viewPadding; - oldInsets = window.viewInsets; - oldSystemGestureInsets = window.systemGestureInsets; - oldDisplayFeatures = window.displayFeatures; - - originalOnMetricsChanged = window.onMetricsChanged; - originalOnLocaleChanged = window.onLocaleChanged; - originalOnBeginFrame = window.onBeginFrame; - originalOnDrawFrame = window.onDrawFrame; - originalOnReportTimings = window.onReportTimings; - originalOnPointerDataPacket = window.onPointerDataPacket; - originalOnSemanticsEnabledChanged = window.onSemanticsEnabledChanged; - originalOnSemanticsAction = window.onSemanticsAction; - originalOnPlatformMessage = window.onPlatformMessage; - originalOnTextScaleFactorChanged = window.onTextScaleFactorChanged; - } - - tearDown() { - _updateWindowMetrics( - oldWindowId!, // window id - oldDevicePixelRatio!, // device pixel ratio - oldGeometry!.width, // width - oldGeometry!.height, // height - oldPadding!.top, // padding top - oldPadding!.right, // padding right - oldPadding!.bottom, // padding bottom - oldPadding!.left, // padding left - oldInsets!.top, // inset top - oldInsets!.right, // inset right - oldInsets!.bottom, // inset bottom - oldInsets!.left, // inset left - oldSystemGestureInsets!.top, // system gesture inset top - oldSystemGestureInsets!.right, // system gesture inset right - oldSystemGestureInsets!.bottom, // system gesture inset bottom - oldSystemGestureInsets!.left, // system gesture inset left - oldDisplayFeatures!.expand((feature) => [ - feature.bounds.left, - feature.bounds.top, - feature.bounds.right, - feature.bounds.bottom, - ]).map( - (e) => e * oldDevicePixelRatio!).toList(), // display features bounds - oldDisplayFeatures!.map((e) => e.type.index).toList(), // display features types - oldDisplayFeatures!.map((e) => e.state.index).toList(), // display features states - ); - window.onMetricsChanged = originalOnMetricsChanged; - window.onLocaleChanged = originalOnLocaleChanged; - window.onBeginFrame = originalOnBeginFrame; - window.onDrawFrame = originalOnDrawFrame; - window.onReportTimings = originalOnReportTimings; - window.onPointerDataPacket = originalOnPointerDataPacket; - window.onSemanticsEnabledChanged = originalOnSemanticsEnabledChanged; - window.onSemanticsAction = originalOnSemanticsAction; - window.onPlatformMessage = originalOnPlatformMessage; - window.onTextScaleFactorChanged = originalOnTextScaleFactorChanged; - } - - void test(String description, void Function() testFunction) { - print(description); - setUp(); - testFunction(); - tearDown(); - } - - void expectEquals(dynamic actual, dynamic expected) { - if (actual != expected) { - throw Exception('Equality check failed:\n Expected: $expected\n Actual: $actual'); - } - } - - void expectIterablesEqual(Iterable actual, Iterable expected) { - expectEquals(actual.length, expected.length); - final Iterator actualIter = actual.iterator; - final Iterator expectedIter = expected.iterator; - while (expectedIter.moveNext()) { - expectEquals(actualIter.moveNext(), true); - expectEquals(actualIter.current, expectedIter.current); - } - expectEquals(actualIter.moveNext(), false); - } - - void expectNotEquals(dynamic actual, dynamic expected) { - if (actual == expected) { - throw Exception('Inequality check failed:\n Expected: $expected\n Actual: $actual'); - } - } - - void expectIdentical(dynamic actual, dynamic expected) { - if (!identical(actual, expected)) { - throw Exception('Identity check failed:\n Expected: $expected\n Actual: $actual'); - } - } - - test('updateUserSettings can handle an empty object', () { - // this should not throw. - _updateUserSettingsData('{}'); - }); - - test('onMetricsChanged preserves callback zone', () { - late Zone innerZone; - late Zone runZone; - late double devicePixelRatio; - - runZoned(() { - innerZone = Zone.current; - window.onMetricsChanged = () { - runZone = Zone.current; - devicePixelRatio = window.devicePixelRatio; - }; - }); - - window.onMetricsChanged!(); - _updateWindowMetrics( - 0, // window id - 0.1234, // device pixel ratio - 0.0, // width - 0.0, // height - 0.0, // padding top - 0.0, // padding right - 0.0, // padding bottom - 0.0, // padding left - 0.0, // inset top - 0.0, // inset right - 0.0, // inset bottom - 0.0, // inset left - 0.0, // system gesture inset top - 0.0, // system gesture inset right - 0.0, // system gesture inset bottom - 0.0, // system gesture inset left - [], // display features bounds - [], // display features types - [], // display features states - ); - expectNotEquals(runZone, null); - expectIdentical(runZone, innerZone); - expectEquals(devicePixelRatio, 0.1234); - }); - - test('onLocaleChanged preserves callback zone', () { - late Zone innerZone; - late Zone runZone; - Locale? locale; - - runZoned(() { - innerZone = Zone.current; - window.onLocaleChanged = () { - runZone = Zone.current; - locale = window.locale; - }; - }); - - _updateLocales(['en', 'US', '', '']); - expectNotEquals(runZone, null); - expectIdentical(runZone, innerZone); - expectEquals(locale, const Locale('en', 'US')); - }); - - test('onBeginFrame preserves callback zone', () { - late Zone innerZone; - late Zone runZone; - late Duration start; - - runZoned(() { - innerZone = Zone.current; - window.onBeginFrame = (Duration value) { - runZone = Zone.current; - start = value; - }; - }); - - _beginFrame(1234); - expectNotEquals(runZone, null); - expectIdentical(runZone, innerZone); - expectEquals(start, const Duration(microseconds: 1234)); - }); - - test('onDrawFrame preserves callback zone', () { - late Zone innerZone; - late Zone runZone; - - runZoned(() { - innerZone = Zone.current; - window.onDrawFrame = () { - runZone = Zone.current; - }; - }); - - _drawFrame(); - expectNotEquals(runZone, null); - expectIdentical(runZone, innerZone); - }); - - test('onReportTimings preserves callback zone', () { - late Zone innerZone; - late Zone runZone; - - PlatformDispatcher.instance._setNeedsReportTimings = (bool _) {}; - - runZoned(() { - innerZone = Zone.current; - window.onReportTimings = (List timings) { - runZone = Zone.current; - }; - }); - - _reportTimings([]); - expectNotEquals(runZone, null); - expectIdentical(runZone, innerZone); - }); - - test('onPointerDataPacket preserves callback zone', () { - late Zone innerZone; - late Zone runZone; - late PointerDataPacket data; - - runZoned(() { - innerZone = Zone.current; - window.onPointerDataPacket = (PointerDataPacket value) { - runZone = Zone.current; - data = value; - }; - }); - - final ByteData testData = ByteData.view(Uint8List(0).buffer); - _dispatchPointerDataPacket(testData); - expectNotEquals(runZone, null); - expectIdentical(runZone, innerZone); - expectIterablesEqual(data.data, PlatformDispatcher._unpackPointerDataPacket(testData).data); - }); - - test('onSemanticsEnabledChanged preserves callback zone', () { - late Zone innerZone; - late Zone runZone; - late bool enabled; - - runZoned(() { - innerZone = Zone.current; - window.onSemanticsEnabledChanged = () { - runZone = Zone.current; - enabled = window.semanticsEnabled; - }; - }); - - final bool newValue = !window.semanticsEnabled; // needed? - _updateSemanticsEnabled(newValue); - expectNotEquals(runZone, null); - expectIdentical(runZone, innerZone); - expectNotEquals(enabled, null); - expectEquals(enabled, newValue); - }); - - test('onSemanticsAction preserves callback zone', () { - late Zone innerZone; - late Zone runZone; - late int id; - late int action; - - runZoned(() { - innerZone = Zone.current; - window.onSemanticsAction = (int i, SemanticsAction a, ByteData? _) { - runZone = Zone.current; - action = a.index; - id = i; - }; - }); - - _dispatchSemanticsAction(1234, 4, null); - expectNotEquals(runZone, null); - expectIdentical(runZone, innerZone); - expectEquals(id, 1234); - expectEquals(action, 4); - }); - - test('onPlatformMessage preserves callback zone', () { - late Zone innerZone; - late Zone runZone; - late String name; - - runZoned(() { - innerZone = Zone.current; - window.onPlatformMessage = (String value, _, __) { - runZone = Zone.current; - name = value; - }; - }); - - _dispatchPlatformMessage('testName', null, 123456789); - expectNotEquals(runZone, null); - expectIdentical(runZone, innerZone); - expectEquals(name, 'testName'); - }); - - test('onTextScaleFactorChanged preserves callback zone', () { - late Zone innerZone; - late Zone runZoneTextScaleFactor; - late Zone runZonePlatformBrightness; - late double? textScaleFactor; - late Brightness? platformBrightness; - - runZoned(() { - innerZone = Zone.current; - window.onTextScaleFactorChanged = () { - runZoneTextScaleFactor = Zone.current; - textScaleFactor = window.textScaleFactor; - }; - window.onPlatformBrightnessChanged = () { - runZonePlatformBrightness = Zone.current; - platformBrightness = window.platformBrightness; - }; - }); - - window.onTextScaleFactorChanged!(); - - _updateUserSettingsData('{"textScaleFactor": 0.5, "platformBrightness": "light", "alwaysUse24HourFormat": true}'); - expectNotEquals(runZoneTextScaleFactor, null); - expectIdentical(runZoneTextScaleFactor, innerZone); - expectEquals(textScaleFactor, 0.5); - - textScaleFactor = null; - platformBrightness = null; - - window.onPlatformBrightnessChanged!(); - _updateUserSettingsData('{"textScaleFactor": 0.5, "platformBrightness": "dark", "alwaysUse24HourFormat": true}'); - expectNotEquals(runZonePlatformBrightness, null); - expectIdentical(runZonePlatformBrightness, innerZone); - expectEquals(platformBrightness, Brightness.dark); - - }); - - test('Window padding/insets/viewPadding/systemGestureInsets', () { - _updateWindowMetrics( - 0, // window id - 0, // device pixel ratio - 800.0, // width - 600.0, // height - 50.0, // padding top - 0.0, // padding right - 40.0, // padding bottom - 0.0, // padding left - 0.0, // inset top - 0.0, // inset right - 0.0, // inset bottom - 0.0, // inset left - 0.0, // system gesture inset top - 0.0, // system gesture inset right - 0.0, // system gesture inset bottom - 0.0, // system gesture inset left - [], // display features bounds - [], // display features types - [], // display features states - ); - - expectEquals(window.viewInsets.bottom, 0.0); - expectEquals(window.viewPadding.bottom, 40.0); - expectEquals(window.padding.bottom, 40.0); - expectEquals(window.systemGestureInsets.bottom, 0.0); - - _updateWindowMetrics( - 0, // window id - 0, // device pixel ratio - 800.0, // width - 600.0, // height - 50.0, // padding top - 0.0, // padding right - 40.0, // padding bottom - 0.0, // padding left - 0.0, // inset top - 0.0, // inset right - 400.0, // inset bottom - 0.0, // inset left - 0.0, // system gesture inset top - 0.0, // system gesture inset right - 44.0, // system gesture inset bottom - 0.0, // system gesture inset left - [], // display features bounds - [], // display features types - [], // display features states - ); - - expectEquals(window.viewInsets.bottom, 400.0); - expectEquals(window.viewPadding.bottom, 40.0); - expectEquals(window.padding.bottom, 0.0); - expectEquals(window.systemGestureInsets.bottom, 44.0); - }); - - test('Window displayFeatures updates decode properly', () { - _updateWindowMetrics( - 0, // window id - 2, // device pixel ratio - 800.0, // width - 600.0, // height - 50.0, // padding top - 0.0, // padding right - 40.0, // padding bottom - 0.0, // padding left - 0.0, // inset top - 0.0, // inset right - 0.0, // inset bottom - 0.0, // inset left - 0.0, // system gesture inset top - 0.0, // system gesture inset right - 0.0, // system gesture inset bottom - 0.0, // system gesture inset left - [], // display features bounds - [], // display features types - [], // display features states - ); - - expectIterablesEqual(window.displayFeatures, []); - - _updateWindowMetrics( - 0, // window id - 2, // device pixel ratio - 800.0, // width - 600.0, // height - 50.0, // padding top - 0.0, // padding right - 40.0, // padding bottom - 0.0, // padding left - 0.0, // inset top - 0.0, // inset right - 400.0, // inset bottom - 0.0, // inset left - 0.0, // system gesture inset top - 0.0, // system gesture inset right - 44.0, // system gesture inset bottom - 0.0, // system gesture inset left - [0, 0, 100, 100], // display features bounds - [2], // display features types - [2], // display features states - ); - - // Display feature bounds is measured in logical pixels - expectEquals(window.displayFeatures[0].bounds, Rect.fromLTRB(0, 0, 50, 50)); - expectEquals(window.displayFeatures[0].type, DisplayFeatureType.hinge); - expectEquals(window.displayFeatures[0].state, DisplayFeatureState.postureHalfOpened); - }); - - test('PlatformDispatcher.locale returns unknown locale when locales is set to empty list', () { - late Locale locale; - runZoned(() { - window.onLocaleChanged = () { - locale = PlatformDispatcher.instance.locale; - }; - }); - - _updateLocales([]); - expectEquals(locale, const Locale.fromSubtags()); - expectEquals(locale.languageCode, 'und'); - }); -} From 0e6fe2a0cc38172335e9e1e7ea6b27d55808bf8e Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Fri, 20 Aug 2021 23:17:43 +0300 Subject: [PATCH 17/23] Fix tests --- .../embedding/android/FlutterView.java | 24 +++++++++++++++---- .../engine/renderer/FlutterRendererTest.java | 3 +++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 125418b1562f8..c56a98d594650 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -452,8 +452,18 @@ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) @VisibleForTesting() protected WindowInfoRepositoryCallbackAdapter createWindowInfoRepo() { - return new WindowInfoRepositoryCallbackAdapter( - WindowInfoRepository.getOrCreate((Activity) getContext())); + try { + return 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; + } } /** @@ -465,8 +475,10 @@ protected WindowInfoRepositoryCallbackAdapter createWindowInfoRepo() { protected void onAttachedToWindow() { super.onAttachedToWindow(); this.windowInfoRepo = createWindowInfoRepo(); - windowInfoRepo.addWindowLayoutInfoListener( - ContextCompat.getMainExecutor(getContext()), windowInfoListener); + if (windowInfoRepo != null) { + windowInfoRepo.addWindowLayoutInfoListener( + ContextCompat.getMainExecutor(getContext()), windowInfoListener); + } } /** @@ -476,7 +488,9 @@ protected void onAttachedToWindow() { */ @Override protected void onDetachedFromWindow() { - windowInfoRepo.removeWindowLayoutInfoListener(windowInfoListener); + if (windowInfoRepo != null) { + windowInfoRepo.removeWindowLayoutInfoListener(windowInfoListener); + } this.windowInfoRepo = null; super.onDetachedFromWindow(); } 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 bb45b04d4ab82..60f31767f36ce 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 @@ -127,6 +127,9 @@ 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), From 888ee953d9ccef309acc6d37f4efa8e9793f28c7 Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Thu, 26 Aug 2021 23:43:05 +0300 Subject: [PATCH 18/23] Update android_embedding_dependencies version to include androidx.window --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 522f8416ccfb6..e153084fe6d05 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', From d22920e51ab8753447de7ad85851bd81c18b297d Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Fri, 27 Aug 2021 00:22:32 +0300 Subject: [PATCH 19/23] Add androidx.window to test_runner build --- shell/platform/android/test_runner/build.gradle | 1 + 1 file changed, 1 insertion(+) 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" From 3fd06991c8a7134025efd0f909b8932b32042123 Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Fri, 27 Aug 2021 19:44:15 +0300 Subject: [PATCH 20/23] Add empty display features arrays to fuchsia platform view --- shell/platform/fuchsia/flutter/flatland_platform_view.cc | 3 +++ shell/platform/fuchsia/flutter/gfx_platform_view.cc | 3 +++ 2 files changed, 6 insertions(+) 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 }); } } From 58d3eb2e86ecc1bc41766beaefa9e6cc21836e18 Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Sat, 28 Aug 2021 13:13:31 +0300 Subject: [PATCH 21/23] Fix tests using final class mocking --- shell/platform/android/BUILD.gn | 1 + .../embedding/android/FlutterView.java | 9 +-- ...wInfoRepositoryCallbackAdapterWrapper.java | 26 ++++++++ .../embedding/android/FlutterViewTest.java | 62 ++----------------- 4 files changed, 36 insertions(+), 62 deletions(-) create mode 100644 shell/platform/android/io/flutter/embedding/android/WindowInfoRepositoryCallbackAdapterWrapper.java 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 c56a98d594650..f0167a5d39827 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -125,7 +125,7 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC @Nullable private AccessibilityBridge accessibilityBridge; // Provides access to foldable/hinge information - @Nullable private WindowInfoRepositoryCallbackAdapter windowInfoRepo; + @Nullable private WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo; // Directly implemented View behavior that communicates with Flutter. private final FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics(); @@ -451,10 +451,11 @@ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) } @VisibleForTesting() - protected WindowInfoRepositoryCallbackAdapter createWindowInfoRepo() { + protected WindowInfoRepositoryCallbackAdapterWrapper createWindowInfoRepo() { try { - return new WindowInfoRepositoryCallbackAdapter( - WindowInfoRepository.getOrCreate((Activity) getContext())); + 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. 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..e0e4d539e7cb3 --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/android/WindowInfoRepositoryCallbackAdapterWrapper.java @@ -0,0 +1,26 @@ +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/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index 8e2eb296596bb..b9f4006914f31 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -33,7 +33,6 @@ import android.view.WindowManager; import android.widget.FrameLayout; import androidx.core.util.Consumer; -import androidx.window.java.layout.WindowInfoRepositoryCallbackAdapter; import androidx.window.layout.FoldingFeature; import androidx.window.layout.WindowLayoutInfo; import io.flutter.TestUtils; @@ -655,8 +654,8 @@ public void itRegistersAndUnregistersToWindowManager() { ((WindowManager) RuntimeEnvironment.systemContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay()); - WindowInfoRepositoryCallbackAdapter windowInfoRepo = - mock(WindowInfoRepositoryCallbackAdapter.class); + 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(); @@ -674,59 +673,6 @@ public void itRegistersAndUnregistersToWindowManager() { verify(windowInfoRepo, times(1)).removeWindowLayoutInfoListener(any()); } - @Test - @TargetApi(30) - @Config(sdk = 30) - public void itSendsCutoutDisplayFeatureToFlutter() { - Context context = Robolectric.setupActivity(Activity.class); - FlutterView flutterView = spy(new FlutterView(context)); - ShadowDisplay display = - Shadows.shadowOf( - ((WindowManager) - RuntimeEnvironment.systemContext.getSystemService(Context.WINDOW_SERVICE)) - .getDefaultDisplay()); - WindowInfoRepositoryCallbackAdapter windowInfoRepo = - mock(WindowInfoRepositoryCallbackAdapter.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(); - FlutterEngine flutterEngine = - spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); - FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni)); - doReturn(flutterRenderer).when(flutterEngine).getRenderer(); - - // When FlutterView is attached to the engine and window, and a cutout exists - WindowInsets windowInsets = mock(WindowInsets.class); - DisplayCutout displayCutout = mock(DisplayCutout.class); - when(displayCutout.getBoundingRects()).thenReturn(Arrays.asList(new Rect(0, 0, 100, 100))); - when(windowInsets.getDisplayCutout()).thenReturn(displayCutout); - when(flutterView.getRootWindowInsets()).thenReturn(windowInsets); - - 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(new WindowLayoutInfo.Builder().build()); - - // Then the Renderer receives the display feature - verify(flutterRenderer).setViewportMetrics(viewportMetricsCaptor.capture()); - assertEquals( - FlutterRenderer.DisplayFeatureType.CUTOUT, - viewportMetricsCaptor.getValue().displayFeatures.get(0).type); - assertEquals( - FlutterRenderer.DisplayFeatureState.UNKNOWN, - viewportMetricsCaptor.getValue().displayFeatures.get(0).state); - assertEquals( - new Rect(0, 0, 100, 100), viewportMetricsCaptor.getValue().displayFeatures.get(0).bounds); - } - @Test public void itSendsHingeDisplayFeatureToFlutter() { Context context = Robolectric.setupActivity(Activity.class); @@ -737,8 +683,8 @@ public void itSendsHingeDisplayFeatureToFlutter() { RuntimeEnvironment.systemContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay()); when(flutterView.getContext()).thenReturn(context); - WindowInfoRepositoryCallbackAdapter windowInfoRepo = - mock(WindowInfoRepositoryCallbackAdapter.class); + WindowInfoRepositoryCallbackAdapterWrapper windowInfoRepo = + mock(WindowInfoRepositoryCallbackAdapterWrapper.class); doReturn(windowInfoRepo).when(flutterView).createWindowInfoRepo(); FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); From e12f54af8570db03158a01ad44a6f4a4defc4d06 Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Sat, 28 Aug 2021 13:27:59 +0300 Subject: [PATCH 22/23] Add missing license header --- .../android/WindowInfoRepositoryCallbackAdapterWrapper.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shell/platform/android/io/flutter/embedding/android/WindowInfoRepositoryCallbackAdapterWrapper.java b/shell/platform/android/io/flutter/embedding/android/WindowInfoRepositoryCallbackAdapterWrapper.java index e0e4d539e7cb3..3d5bc3043da00 100644 --- a/shell/platform/android/io/flutter/embedding/android/WindowInfoRepositoryCallbackAdapterWrapper.java +++ b/shell/platform/android/io/flutter/embedding/android/WindowInfoRepositoryCallbackAdapterWrapper.java @@ -1,3 +1,7 @@ +// 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; From afce6629ec41fe37bd0edf44aed5715ef2507db7 Mon Sep 17 00:00:00 2001 From: Andrei Diaconu Date: Sat, 28 Aug 2021 22:47:58 +0300 Subject: [PATCH 23/23] Update licenses_golden --- ci/licenses_golden/licenses_flutter | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 15e97c6b8f784..8b0aa9cab122f 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -821,6 +821,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