[Re-land] Remove WindowManager reflection in SingleViewPresentation.java (#50890)
relands https://github.com/flutter/engine/pull/49996
Context b/326363243
[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index 97d4270..b8b483f 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/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/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 8d7d5e9..97eaf28 100644
--- a/shell/platform/android/BUILD.gn
+++ b/shell/platform/android/BUILD.gn
@@ -324,10 +324,13 @@
"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 0000000..fa16b52
--- /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 c272f85..82a8013 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 @@
// 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 @@
// 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 @@
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 @@
private WindowManager getWindowManager() {
if (windowManager == null) {
- windowManager = windowManagerHandler.getWindowManager();
+ windowManager = windowManagerHandler;
}
return windowManager;
}
@@ -371,101 +311,6 @@
}
}
- /*
- * 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 0000000..ad004ac
--- /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.
+ *
+ * <p>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).
+ *
+ * <p>This static proxy overrides the addView, removeView, removeViewImmediate, and updateViewLayout
+ * methods to prevent these crashes, and forwards all other calls to the delegate.
+ *
+ * <p>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<Boolean> listener) {
+ delegate.addCrossWindowBlurEnabledListener(listener);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.S)
+ @Override
+ public void addCrossWindowBlurEnabledListener(
+ @NonNull Executor executor, @NonNull Consumer<Boolean> listener) {
+ delegate.addCrossWindowBlurEnabledListener(executor, listener);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.S)
+ @Override
+ public void removeCrossWindowBlurEnabledListener(@NonNull Consumer<Boolean> listener) {
+ delegate.removeCrossWindowBlurEnabledListener(listener);
+ }
+}
diff --git a/shell/platform/android/io/flutter/plugin/platform/WindowManagerHandler.java b/shell/platform/android/io/flutter/plugin/platform/WindowManagerHandler.java
new file mode 100644
index 0000000..5072420
--- /dev/null
+++ b/shell/platform/android/io/flutter/plugin/platform/WindowManagerHandler.java
@@ -0,0 +1,15 @@
+// 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.view.WindowManager;
+
+/** Default implementation when using the regular Android SDK. */
+final class WindowManagerHandler extends SingleViewWindowManager {
+
+ WindowManagerHandler(WindowManager delegate, SingleViewFakeWindowViewGroup fakeWindowViewGroup) {
+ super(delegate, fakeWindowViewGroup);
+ }
+}
diff --git a/shell/platform/android/test/io/flutter/plugin/platform/WindowManagerHandlerTest.java b/shell/platform/android/test/io/flutter/plugin/platform/WindowManagerHandlerTest.java
new file mode 100644
index 0000000..c7fd1d3
--- /dev/null
+++ b/shell/platform/android/test/io/flutter/plugin/platform/WindowManagerHandlerTest.java
@@ -0,0 +1,135 @@
+// 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 static android.os.Build.VERSION_CODES.P;
+import static android.os.Build.VERSION_CODES.R;
+import static android.os.Build.VERSION_CODES.S;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+
+import android.annotation.TargetApi;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@Config(manifest = Config.NONE)
+@RunWith(AndroidJUnit4.class)
+@TargetApi(P)
+public class WindowManagerHandlerTest {
+ @Test
+ @Config(minSdk = R)
+ public void windowManagerHandler_passesCorrectlyToFakeWindowViewGroup() {
+ // Mock the WindowManager and FakeWindowViewGroup that get used by the WindowManagerHandler.
+ WindowManager mockWindowManager = mock(WindowManager.class);
+ SingleViewFakeWindowViewGroup mockSingleViewFakeWindowViewGroup =
+ mock(SingleViewFakeWindowViewGroup.class);
+
+ View mockView = mock(View.class);
+ ViewGroup.LayoutParams mockLayoutParams = mock(ViewGroup.LayoutParams.class);
+
+ WindowManagerHandler windowManagerHandler =
+ new WindowManagerHandler(mockWindowManager, mockSingleViewFakeWindowViewGroup);
+
+ // removeViewImmediate
+ windowManagerHandler.removeViewImmediate(mockView);
+ verify(mockView).clearAnimation();
+ verify(mockSingleViewFakeWindowViewGroup).removeView(mockView);
+ verifyNoInteractions(mockWindowManager);
+
+ // addView
+ windowManagerHandler.addView(mockView, mockLayoutParams);
+ verify(mockSingleViewFakeWindowViewGroup).addView(mockView, mockLayoutParams);
+ verifyNoInteractions(mockWindowManager);
+
+ // updateViewLayout
+ windowManagerHandler.updateViewLayout(mockView, mockLayoutParams);
+ verify(mockSingleViewFakeWindowViewGroup).updateViewLayout(mockView, mockLayoutParams);
+ verifyNoInteractions(mockWindowManager);
+
+ // removeView
+ windowManagerHandler.updateViewLayout(mockView, mockLayoutParams);
+ verify(mockSingleViewFakeWindowViewGroup).removeView(mockView);
+ verifyNoInteractions(mockWindowManager);
+ }
+
+ @Test
+ @Config(minSdk = R)
+ public void windowManagerHandler_logAndReturnEarly_whenFakeWindowViewGroupIsNull() {
+ // Mock the WindowManager and FakeWindowViewGroup that get used by the WindowManagerHandler.
+ WindowManager mockWindowManager = mock(WindowManager.class);
+
+ View mockView = mock(View.class);
+ ViewGroup.LayoutParams mockLayoutParams = mock(ViewGroup.LayoutParams.class);
+
+ WindowManagerHandler windowManagerHandler = new WindowManagerHandler(mockWindowManager, null);
+
+ // removeViewImmediate
+ windowManagerHandler.removeViewImmediate(mockView);
+ verifyNoInteractions(mockView);
+ verifyNoInteractions(mockWindowManager);
+
+ // addView
+ windowManagerHandler.addView(mockView, mockLayoutParams);
+ verifyNoInteractions(mockWindowManager);
+
+ // updateViewLayout
+ windowManagerHandler.updateViewLayout(mockView, mockLayoutParams);
+ verifyNoInteractions(mockWindowManager);
+
+ // removeView
+ windowManagerHandler.updateViewLayout(mockView, mockLayoutParams);
+ verifyNoInteractions(mockWindowManager);
+ }
+
+ // This section tests that WindowManagerHandler forwards all of the non-special case calls to the
+ // delegate WindowManager. Because this must include some deprecated WindowManager method calls
+ // (because the proxy overrides every method), we suppress deprecation warnings here.
+ @Test
+ @Config(minSdk = S)
+ @SuppressWarnings("deprecation")
+ public void windowManagerHandler_forwardsAllOtherCallsToDelegate() {
+ // Mock the WindowManager and FakeWindowViewGroup that get used by the WindowManagerHandler.
+ WindowManager mockWindowManager = mock(WindowManager.class);
+ SingleViewFakeWindowViewGroup mockSingleViewFakeWindowViewGroup =
+ mock(SingleViewFakeWindowViewGroup.class);
+
+ WindowManagerHandler windowManagerHandler =
+ new WindowManagerHandler(mockWindowManager, mockSingleViewFakeWindowViewGroup);
+
+ // Verify that all other calls get forwarded to the delegate.
+ Executor mockExecutor = mock(Executor.class);
+ @SuppressWarnings("Unchecked cast")
+ Consumer<Boolean> mockListener = (Consumer<Boolean>) mock(Consumer.class);
+
+ windowManagerHandler.getDefaultDisplay();
+ verify(mockWindowManager).getDefaultDisplay();
+
+ windowManagerHandler.getCurrentWindowMetrics();
+ verify(mockWindowManager).getCurrentWindowMetrics();
+
+ windowManagerHandler.getMaximumWindowMetrics();
+ verify(mockWindowManager).getMaximumWindowMetrics();
+
+ windowManagerHandler.isCrossWindowBlurEnabled();
+ verify(mockWindowManager).isCrossWindowBlurEnabled();
+
+ windowManagerHandler.addCrossWindowBlurEnabledListener(mockListener);
+ verify(mockWindowManager).addCrossWindowBlurEnabledListener(mockListener);
+
+ windowManagerHandler.addCrossWindowBlurEnabledListener(mockExecutor, mockListener);
+ verify(mockWindowManager).addCrossWindowBlurEnabledListener(mockExecutor, mockListener);
+
+ windowManagerHandler.removeCrossWindowBlurEnabledListener(mockListener);
+ verify(mockWindowManager).removeCrossWindowBlurEnabledListener(mockListener);
+ }
+}