From ca4882894930adc1fb8e35aba1e976e7192d9854 Mon Sep 17 00:00:00 2001 From: "auto-submit[bot]" Date: Thu, 22 Feb 2024 18:06:19 +0000 Subject: [PATCH] Revert "Remove WindowManager reflection in SingleViewPresentation.java (#49996)" This reverts commit a023d98dd2127f9e8f3e1b510f6063d79431a6c6. --- .../platform/SingleViewPresentation.java | 124 ++++++++---------- .../platform/SingleViewPresentationTest.java | 116 ---------------- 2 files changed, 56 insertions(+), 184 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java index d8dc53049a5be..b7ca559ac7c64 100644 --- a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java +++ b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java @@ -22,18 +22,17 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; -import android.view.WindowMetrics; import android.view.accessibility.AccessibilityEvent; import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; import androidx.annotation.Keep; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.annotation.VisibleForTesting; import io.flutter.Log; -import java.util.concurrent.Executor; -import java.util.function.Consumer; +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. @@ -360,7 +359,7 @@ public Object getSystemService(String name) { private WindowManager getWindowManager() { if (windowManager == null) { - windowManager = windowManagerHandler; + windowManager = windowManagerHandler.getWindowManager(); } return windowManager; } @@ -378,18 +377,21 @@ private boolean isCalledFromAlertDialog() { } /* - * A static proxy handler for a WindowManager with custom overrides. + * 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 static proxy overrides the addView, removeView, removeViewImmediate, and updateViewLayout methods - * to prevent these crashes, and forwards all other calls to the delegate. + * 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 */ - @VisibleForTesting - static class WindowManagerHandler implements WindowManager { + static class WindowManagerHandler implements InvocationHandler { private static final String TAG = "PlatformViewsController"; private final WindowManager delegate; @@ -400,86 +402,72 @@ static class WindowManagerHandler implements WindowManager { fakeWindowRootView = fakeWindowViewGroup; } - @Override - @Deprecated - public Display getDefaultDisplay() { - return delegate.getDefaultDisplay(); + public WindowManager getWindowManager() { + return (WindowManager) + Proxy.newProxyInstance( + WindowManager.class.getClassLoader(), new Class[] {WindowManager.class}, this); } @Override - public void removeViewImmediate(View view) { - if (fakeWindowRootView == null) { - Log.w(TAG, "Embedded view called removeViewImmediate while detached from presentation"); - return; + 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(); } - view.clearAnimation(); - fakeWindowRootView.removeView(view); } - @Override - public void addView(View view, ViewGroup.LayoutParams params) { + private void addView(Object[] args) { if (fakeWindowRootView == null) { Log.w(TAG, "Embedded view called addView while detached from presentation"); return; } - fakeWindowRootView.addView(view, params); + View view = (View) args[0]; + WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) args[1]; + fakeWindowRootView.addView(view, layoutParams); } - @Override - public void updateViewLayout(View view, ViewGroup.LayoutParams params) { + private void removeView(Object[] args) { if (fakeWindowRootView == null) { - Log.w(TAG, "Embedded view called updateViewLayout while detached from presentation"); + Log.w(TAG, "Embedded view called removeView while detached from presentation"); return; } - fakeWindowRootView.updateViewLayout(view, params); + View view = (View) args[0]; + fakeWindowRootView.removeView(view); } - @Override - public void removeView(View view) { + private void removeViewImmediate(Object[] args) { if (fakeWindowRootView == null) { - Log.w(TAG, "Embedded view called removeView while detached from presentation"); + Log.w(TAG, "Embedded view called removeViewImmediate while detached from presentation"); return; } + View view = (View) args[0]; + view.clearAnimation(); 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); + 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); } } diff --git a/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java b/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java index d99d344568d25..d27e08bbbdc97 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java @@ -7,26 +7,18 @@ import static android.os.Build.VERSION_CODES.KITKAT; 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.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import android.annotation.TargetApi; import android.content.Context; import android.hardware.display.DisplayManager; import android.view.Display; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import androidx.test.core.app.ApplicationProvider; 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; @@ -91,112 +83,4 @@ public void returnsOuterContextInputMethodManager_createDisplayContext() { // Android OS (or Robolectric's shadow, in this case). assertEquals(expected, actual); } - - @Test - @Config(minSdk = R) - public void windowManagerHandler_passesCorrectlyToFakeWindowViewGroup() { - // Mock the WindowManager and FakeWindowViewGroup that get used by the WindowManagerHandler. - WindowManager mockWindowManager = mock(WindowManager.class); - SingleViewPresentation.FakeWindowViewGroup mockFakeWindowViewGroup = - mock(SingleViewPresentation.FakeWindowViewGroup.class); - - View mockView = mock(View.class); - ViewGroup.LayoutParams mockLayoutParams = mock(ViewGroup.LayoutParams.class); - - SingleViewPresentation.WindowManagerHandler windowManagerHandler = - new SingleViewPresentation.WindowManagerHandler(mockWindowManager, mockFakeWindowViewGroup); - - // removeViewImmediate - windowManagerHandler.removeViewImmediate(mockView); - verify(mockView).clearAnimation(); - verify(mockFakeWindowViewGroup).removeView(mockView); - verifyNoInteractions(mockWindowManager); - - // addView - windowManagerHandler.addView(mockView, mockLayoutParams); - verify(mockFakeWindowViewGroup).addView(mockView, mockLayoutParams); - verifyNoInteractions(mockWindowManager); - - // updateViewLayout - windowManagerHandler.updateViewLayout(mockView, mockLayoutParams); - verify(mockFakeWindowViewGroup).updateViewLayout(mockView, mockLayoutParams); - verifyNoInteractions(mockWindowManager); - - // removeView - windowManagerHandler.updateViewLayout(mockView, mockLayoutParams); - verify(mockFakeWindowViewGroup).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); - - SingleViewPresentation.WindowManagerHandler windowManagerHandler = - new SingleViewPresentation.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); - SingleViewPresentation.FakeWindowViewGroup mockFakeWindowViewGroup = - mock(SingleViewPresentation.FakeWindowViewGroup.class); - - SingleViewPresentation.WindowManagerHandler windowManagerHandler = - new SingleViewPresentation.WindowManagerHandler(mockWindowManager, mockFakeWindowViewGroup); - - // 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); - } }