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 listener) { + delegate.addCrossWindowBlurEnabledListener(listener); + } + + @RequiresApi(api = Build.VERSION_CODES.S) + @Override + public void addCrossWindowBlurEnabledListener( + @NonNull Executor executor, @NonNull Consumer listener) { + delegate.addCrossWindowBlurEnabledListener(executor, listener); + } + + @RequiresApi(api = Build.VERSION_CODES.S) + @Override + public void removeCrossWindowBlurEnabledListener(@NonNull Consumer 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 0000000000000..5072420b6ba54 --- /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 0000000000000..c7fd1d353f8a8 --- /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 mockListener = (Consumer) 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); + } +}