Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Reverts "Remove WindowManager reflection in SingleViewPresentation.java (#49996)" #50873

Merged
merged 1 commit into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -360,7 +359,7 @@ public Object getSystemService(String name) {

private WindowManager getWindowManager() {
if (windowManager == null) {
windowManager = windowManagerHandler;
windowManager = windowManagerHandler.getWindowManager();
}
return windowManager;
}
Expand All @@ -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;
Expand All @@ -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<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);
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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<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);
}
}