diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 97d427091be24..b8b483f1a39e4 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -36291,8 +36291,11 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/Platf ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewFakeWindowViewGroup.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewWindowManager.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/WindowManagerHandler.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/text/ProcessTextPlugin.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/util/HandlerCompat.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/util/PathUtils.java + ../../../flutter/LICENSE @@ -39156,10 +39159,13 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/Platfor FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewFakeWindowViewGroup.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java +FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewWindowManager.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SurfaceProducerPlatformViewRenderTarget.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SurfaceTexturePlatformViewRenderTarget.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java +FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/WindowManagerHandler.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/text/ProcessTextPlugin.java FILE: ../../../flutter/shell/platform/android/io/flutter/util/HandlerCompat.java FILE: ../../../flutter/shell/platform/android/io/flutter/util/PathUtils.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 8d7d5e96686a6..97eaf285ec2b3 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -324,10 +324,13 @@ android_java_sources = [ "io/flutter/plugin/platform/PlatformViewWrapper.java", "io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java", "io/flutter/plugin/platform/PlatformViewsController.java", + "io/flutter/plugin/platform/SingleViewFakeWindowViewGroup.java", "io/flutter/plugin/platform/SingleViewPresentation.java", + "io/flutter/plugin/platform/SingleViewWindowManager.java", "io/flutter/plugin/platform/SurfaceProducerPlatformViewRenderTarget.java", "io/flutter/plugin/platform/SurfaceTexturePlatformViewRenderTarget.java", "io/flutter/plugin/platform/VirtualDisplayController.java", + "io/flutter/plugin/platform/WindowManagerHandler.java", "io/flutter/plugin/text/ProcessTextPlugin.java", "io/flutter/util/HandlerCompat.java", "io/flutter/util/PathUtils.java", diff --git a/shell/platform/android/io/flutter/plugin/platform/SingleViewFakeWindowViewGroup.java b/shell/platform/android/io/flutter/plugin/platform/SingleViewFakeWindowViewGroup.java new file mode 100644 index 0000000000000..fa16b525435ef --- /dev/null +++ b/shell/platform/android/io/flutter/plugin/platform/SingleViewFakeWindowViewGroup.java @@ -0,0 +1,65 @@ +// 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.plugin.platform; + +import android.content.Context; +import android.graphics.Rect; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; + +/* + * A view group that implements the same layout protocol that exist between the WindowManager and its direct + * children. + * + * Currently only a subset of the protocol is supported (gravity, x, and y). + */ +class SingleViewFakeWindowViewGroup extends ViewGroup { + // Used in onLayout to keep the bounds of the current view. + // We keep it as a member to avoid object allocations during onLayout which are discouraged. + private final Rect viewBounds; + + // Used in onLayout to keep the bounds of the child views. + // We keep it as a member to avoid object allocations during onLayout which are discouraged. + private final Rect childRect; + + public SingleViewFakeWindowViewGroup(Context context) { + super(context); + viewBounds = new Rect(); + childRect = new Rect(); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + WindowManager.LayoutParams params = (WindowManager.LayoutParams) child.getLayoutParams(); + viewBounds.set(l, t, r, b); + Gravity.apply( + params.gravity, + child.getMeasuredWidth(), + child.getMeasuredHeight(), + viewBounds, + params.x, + params.y, + childRect); + child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + child.measure(atMost(widthMeasureSpec), atMost(heightMeasureSpec)); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + private static int atMost(int measureSpec) { + return MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(measureSpec), MeasureSpec.AT_MOST); + } +} diff --git a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java index c272f859286ee..82a8013db30c4 100644 --- a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java +++ b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java @@ -12,13 +12,10 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.MutableContextWrapper; -import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.view.Display; -import android.view.Gravity; import android.view.View; -import android.view.ViewGroup; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.inputmethod.InputMethodManager; @@ -27,10 +24,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.Log; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; /* * A presentation used for hosting a single Android view in a virtual display. @@ -68,7 +61,7 @@ static class PresentationState { // Contains views that were added directly to the window manager (e.g // android.widget.PopupWindow). - private FakeWindowViewGroup fakeWindowViewGroup; + private SingleViewFakeWindowViewGroup fakeWindowViewGroup; } // A reference to the current accessibility bridge to which accessibility events will be @@ -153,7 +146,7 @@ protected void onCreate(Bundle savedInstanceState) { // This makes sure we preserve alpha for the VD's content. getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT)); if (state.fakeWindowViewGroup == null) { - state.fakeWindowViewGroup = new FakeWindowViewGroup(getContext()); + state.fakeWindowViewGroup = new SingleViewFakeWindowViewGroup(getContext()); } if (state.windowManagerHandler == null) { WindowManager windowManagerDelegate = @@ -223,59 +216,6 @@ public PlatformView getView() { return state.platformView; } - /* - * A view group that implements the same layout protocol that exist between the WindowManager and its direct - * children. - * - * Currently only a subset of the protocol is supported (gravity, x, and y). - */ - static class FakeWindowViewGroup extends ViewGroup { - // Used in onLayout to keep the bounds of the current view. - // We keep it as a member to avoid object allocations during onLayout which are discouraged. - private final Rect viewBounds; - - // Used in onLayout to keep the bounds of the child views. - // We keep it as a member to avoid object allocations during onLayout which are discouraged. - private final Rect childRect; - - public FakeWindowViewGroup(Context context) { - super(context); - viewBounds = new Rect(); - childRect = new Rect(); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - WindowManager.LayoutParams params = (WindowManager.LayoutParams) child.getLayoutParams(); - viewBounds.set(l, t, r, b); - Gravity.apply( - params.gravity, - child.getMeasuredWidth(), - child.getMeasuredHeight(), - viewBounds, - params.x, - params.y, - childRect); - child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - child.measure(atMost(widthMeasureSpec), atMost(heightMeasureSpec)); - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - private static int atMost(int measureSpec) { - return MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(measureSpec), MeasureSpec.AT_MOST); - } - } - /** Answers calls for {@link InputMethodManager} with an instance cached at creation time. */ // TODO(mklim): This caches the IMM at construction time and won't pick up any changes. In rare // cases where the FlutterView changes windows this will return an outdated instance. This @@ -354,7 +294,7 @@ public Object getSystemService(String name) { private WindowManager getWindowManager() { if (windowManager == null) { - windowManager = windowManagerHandler.getWindowManager(); + windowManager = windowManagerHandler; } return windowManager; } @@ -371,101 +311,6 @@ private boolean isCalledFromAlertDialog() { } } - /* - * A dynamic proxy handler for a WindowManager with custom overrides. - * - * The presentation's window manager delegates all calls to the default window manager. - * WindowManager#addView calls triggered by views that are attached to the virtual display are crashing - * (see: https://github.com/flutter/flutter/issues/20714). This was triggered when selecting text in an embedded - * WebView (as the selection handles are implemented as popup windows). - * - * This dynamic proxy overrides the addView, removeView, removeViewImmediate, and updateViewLayout methods - * to prevent these crashes. - * - * This will be more efficient as a static proxy that's not using reflection, but as the engine is currently - * not being built against the latest Android SDK we cannot override all relevant method. - * Tracking issue for upgrading the engine's Android sdk: https://github.com/flutter/flutter/issues/20717 - */ - static class WindowManagerHandler implements InvocationHandler { - private static final String TAG = "PlatformViewsController"; - - private final WindowManager delegate; - FakeWindowViewGroup fakeWindowRootView; - - WindowManagerHandler(WindowManager delegate, FakeWindowViewGroup fakeWindowViewGroup) { - this.delegate = delegate; - fakeWindowRootView = fakeWindowViewGroup; - } - - public WindowManager getWindowManager() { - return (WindowManager) - Proxy.newProxyInstance( - WindowManager.class.getClassLoader(), new Class>[] {WindowManager.class}, this); - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - switch (method.getName()) { - case "addView": - addView(args); - return null; - case "removeView": - removeView(args); - return null; - case "removeViewImmediate": - removeViewImmediate(args); - return null; - case "updateViewLayout": - updateViewLayout(args); - return null; - } - try { - return method.invoke(delegate, args); - } catch (InvocationTargetException e) { - throw e.getCause(); - } - } - - private void addView(Object[] args) { - if (fakeWindowRootView == null) { - Log.w(TAG, "Embedded view called addView while detached from presentation"); - return; - } - View view = (View) args[0]; - WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) args[1]; - fakeWindowRootView.addView(view, layoutParams); - } - - private void removeView(Object[] args) { - if (fakeWindowRootView == null) { - Log.w(TAG, "Embedded view called removeView while detached from presentation"); - return; - } - View view = (View) args[0]; - fakeWindowRootView.removeView(view); - } - - private void removeViewImmediate(Object[] args) { - if (fakeWindowRootView == null) { - Log.w(TAG, "Embedded view called removeViewImmediate while detached from presentation"); - return; - } - View view = (View) args[0]; - view.clearAnimation(); - fakeWindowRootView.removeView(view); - } - - private void updateViewLayout(Object[] args) { - if (fakeWindowRootView == null) { - Log.w(TAG, "Embedded view called updateViewLayout while detached from presentation"); - return; - } - View view = (View) args[0]; - WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) args[1]; - fakeWindowRootView.updateViewLayout(view, layoutParams); - } - } - private static class AccessibilityDelegatingFrameLayout extends FrameLayout { private final AccessibilityEventsDelegate accessibilityEventsDelegate; private final View embeddedView; diff --git a/shell/platform/android/io/flutter/plugin/platform/SingleViewWindowManager.java b/shell/platform/android/io/flutter/plugin/platform/SingleViewWindowManager.java new file mode 100644 index 0000000000000..ad004acbf4cad --- /dev/null +++ b/shell/platform/android/io/flutter/plugin/platform/SingleViewWindowManager.java @@ -0,0 +1,127 @@ +// 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.plugin.platform; + +import android.os.Build; +import android.view.Display; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.WindowMetrics; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; +import io.flutter.Log; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * A static proxy handler for a WindowManager with custom overrides. + * + *
The presentation's window manager delegates all calls to the default window manager. + * WindowManager#addView calls triggered by views that are attached to the virtual display are + * crashing (see: https://github.com/flutter/flutter/issues/20714). This was triggered when + * selecting text in an embedded WebView (as the selection handles are implemented as popup + * windows). + * + *
This static proxy overrides the addView, removeView, removeViewImmediate, and updateViewLayout + * methods to prevent these crashes, and forwards all other calls to the delegate. + * + *
This is an abstract class because some clients of Flutter compile the Android embedding with
+ * the Android System SDK, which has additional abstract methods that need to be overriden.
+ */
+abstract class SingleViewWindowManager implements WindowManager {
+ private static final String TAG = "PlatformViewsController";
+
+ final WindowManager delegate;
+ SingleViewFakeWindowViewGroup fakeWindowRootView;
+
+ SingleViewWindowManager(
+ WindowManager delegate, SingleViewFakeWindowViewGroup fakeWindowViewGroup) {
+ this.delegate = delegate;
+ fakeWindowRootView = fakeWindowViewGroup;
+ }
+
+ @Override
+ @Deprecated
+ public Display getDefaultDisplay() {
+ return delegate.getDefaultDisplay();
+ }
+
+ @Override
+ public void removeViewImmediate(View view) {
+ if (fakeWindowRootView == null) {
+ Log.w(TAG, "Embedded view called removeViewImmediate while detached from presentation");
+ return;
+ }
+ view.clearAnimation();
+ fakeWindowRootView.removeView(view);
+ }
+
+ @Override
+ public void addView(View view, ViewGroup.LayoutParams params) {
+ if (fakeWindowRootView == null) {
+ Log.w(TAG, "Embedded view called addView while detached from presentation");
+ return;
+ }
+ fakeWindowRootView.addView(view, params);
+ }
+
+ @Override
+ public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
+ if (fakeWindowRootView == null) {
+ Log.w(TAG, "Embedded view called updateViewLayout while detached from presentation");
+ return;
+ }
+ fakeWindowRootView.updateViewLayout(view, params);
+ }
+
+ @Override
+ public void removeView(View view) {
+ if (fakeWindowRootView == null) {
+ Log.w(TAG, "Embedded view called removeView while detached from presentation");
+ return;
+ }
+ fakeWindowRootView.removeView(view);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.R)
+ @NonNull
+ @Override
+ public WindowMetrics getCurrentWindowMetrics() {
+ return delegate.getCurrentWindowMetrics();
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.R)
+ @NonNull
+ @Override
+ public WindowMetrics getMaximumWindowMetrics() {
+ return delegate.getMaximumWindowMetrics();
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.S)
+ @Override
+ public boolean isCrossWindowBlurEnabled() {
+ return delegate.isCrossWindowBlurEnabled();
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.S)
+ @Override
+ public void addCrossWindowBlurEnabledListener(@NonNull Consumer