未验证 提交 be0c3143 编写于 作者: A amirh 提交者: GitHub

Add views added to the WindowManager into the presentation view tree. (#6043)

The default WindowManager implementation in Android's Presentation is
delegating addView/removeView/updateViewLayout calls to the global
WindowManager.
This can result in a crash when an embedded view is trying to e.g show a
PopupWindow.

This change adds a custom WindowManager that overrides
addView (and removeView/updateViewLayout) and adds the view to the
presentation's view tree.

Note that views might keep a reference to the window manager which
might be an issue when we move a view from one virtual display to
another (due to a resize). For this reason when re-sizing we are not
creating a new window manager for the new presentation, but updating the
window manager's references to be relevant for the new presentation and
re-use it.
上级 5bd9620b
......@@ -7,24 +7,69 @@ package io.flutter.plugin.platform;
import android.annotation.TargetApi;
import android.app.Presentation;
import android.content.Context;
import android.content.ContextWrapper;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import android.util.Log;
import android.view.*;
import android.widget.FrameLayout;
import java.lang.reflect.*;
import static android.content.Context.WINDOW_SERVICE;
/*
* A presentation used for hosting a single Android view in a virtual display.
*
* This presentation overrides the WindowManager's addView/removeView/updateViewLayout methods, such that views added
* directly to the WindowManager are added as part of the presentation's view hierarchy (to mFakeWindowRootView).
*
* The view hierarchy for the presentation is as following:
*
* mRootView
* / \
* / \
* / \
* mContainer mState.mFakeWindowRootView
* |
* EmbeddedView
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
class SingleViewPresentation extends Presentation {
/*
* When an embedded view is resized in Flutterverse we move the Android view to a new virtual display
* that has the new size. This class keeps the presentation state that moves with the view to the presentation of
* the new virtual display.
*/
static class PresentationState {
// The Android view we are embedding in the Flutter app.
private PlatformView mView;
// The InvocationHandler for a WindowManager proxy. This is essentially the custom window manager for the
// presentation.
private WindowManagerHandler mWindowManagerHandler;
// Contains views that were added directly to the window manager (e.g android.widget.PopupWindow).
private FakeWindowViewGroup mFakeWindowRootView;
}
private final PlatformViewFactory mViewFactory;
private PlatformView mView;
// This is the view id assigned by the Flutter framework to the embedded view, we keep it here
// so when we create the platform we can tell it its view id.
private int mViewId;
// As the root view of a display cannot be detached, we use this mContainer
// as the root, and attach mView to it. This allows us to detach mView.
// The root view for the presentation, it has 2 childs: mContainer which contains the embedded view, and
// mFakeWindowRootView which contains views that were added directly to the presentation's window manager.
private FrameLayout mRootView;
// Contains the embedded platform view (mView.getView()) when it is attached to the presentation.
private FrameLayout mContainer;
private PresentationState mState;
/**
* Creates a presentation that will use the view factory to create a new
* platform view in the presentation's onCreate, and attach it.
......@@ -33,6 +78,7 @@ class SingleViewPresentation extends Presentation {
super(outerContext, display);
mViewFactory = viewFactory;
mViewId = viewId;
mState = new PresentationState();
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
......@@ -46,10 +92,10 @@ class SingleViewPresentation extends Presentation {
* <p>The display's density must match the density of the context used
* when the view was created.
*/
public SingleViewPresentation(Context outerContext, Display display, PlatformView view) {
public SingleViewPresentation(Context outerContext, Display display, PresentationState state) {
super(outerContext, display);
mViewFactory = null;
mView = view;
mState = state;
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
......@@ -59,22 +105,195 @@ class SingleViewPresentation extends Presentation {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mView == null) {
mView = mViewFactory.create(getContext(), mViewId);
if (mState.mFakeWindowRootView == null) {
mState.mFakeWindowRootView = new FakeWindowViewGroup(getContext());
}
if (mState.mWindowManagerHandler == null) {
WindowManager windowManagerDelegate = (WindowManager) getContext().getSystemService(WINDOW_SERVICE);
mState.mWindowManagerHandler = new WindowManagerHandler(windowManagerDelegate, mState.mFakeWindowRootView);
}
mContainer = new FrameLayout(getContext());
mContainer.addView(mView.getView());
setContentView(mContainer);
PresentationContext context = new PresentationContext(getContext(), mState.mWindowManagerHandler);
if (mState.mView == null) {
mState.mView = mViewFactory.create(context, mViewId);
}
mContainer.addView(mState.mView.getView());
mRootView = new FrameLayout(getContext());
mRootView.addView(mContainer);
mRootView.addView(mState.mFakeWindowRootView);
setContentView(mRootView);
}
public PlatformView detachView() {
mContainer.removeView(mView.getView());
return mView;
public PresentationState detachState() {
mContainer.removeAllViews();
mRootView.removeAllViews();
return mState;
}
public View getView() {
if (mView == null)
public PlatformView getView() {
if (mState.mView == null)
return null;
return mView.getView();
return mState.mView;
}
/*
* 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 mViewBounds;
// 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 mChildRect;
public FakeWindowViewGroup(Context context) {
super(context);
mViewBounds = new Rect();
mChildRect = 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();
mViewBounds.set(l, t, r, b);
Gravity.apply(params.gravity, child.getMeasuredWidth(), child.getMeasuredHeight(), mViewBounds, params.x,
params.y, mChildRect);
child.layout(mChildRect.left, mChildRect.top, mChildRect.right, mChildRect.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);
}
}
/**
* Proxies a Context replacing the WindowManager with our custom instance.
*/
static class PresentationContext extends ContextWrapper {
private WindowManager mWindowManager;
private final WindowManagerHandler mWindowManagerHandler;
PresentationContext(Context base, WindowManagerHandler windowManagerHandler) {
super(base);
mWindowManagerHandler = windowManagerHandler;
}
@Override
public Object getSystemService(String name) {
if (WINDOW_SERVICE.equals(name)) {
return getWindowManager();
}
return super.getSystemService(name);
}
private WindowManager getWindowManager() {
if (mWindowManager == null) {
mWindowManager = mWindowManagerHandler.getWindowManager();
}
return mWindowManager;
}
}
/*
* 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, 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 mDelegate;
FakeWindowViewGroup mFakeWindowRootView;
WindowManagerHandler(WindowManager delegate, FakeWindowViewGroup fakeWindowViewGroup) {
mDelegate = delegate;
mFakeWindowRootView = 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 "updateViewLayout":
updateViewLayout(args);
return null;
}
try {
return method.invoke(mDelegate, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
private void addView(Object[] args) {
if (mFakeWindowRootView == 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];
mFakeWindowRootView.addView(view, layoutParams);
}
private void removeView(Object[] args) {
if (mFakeWindowRootView == null) {
Log.w(TAG, "Embedded view called removeView while detached from presentation");
return;
}
View view = (View) args[0];
mFakeWindowRootView.removeView(view);
}
private void updateViewLayout(Object[] args) {
if (mFakeWindowRootView == 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];
mFakeWindowRootView.updateViewLayout(view, layoutParams);
}
}
}
......@@ -72,7 +72,7 @@ class VirtualDisplayController {
}
public void resize(final int width, final int height, final Runnable onNewSizeFrameAvailable) {
final PlatformView view = mPresentation.detachView();
final SingleViewPresentation.PresentationState presentationState = mPresentation.detachState();
// We detach the surface to prevent it being destroyed when releasing the vd.
//
// setSurface is only available starting API 20. We could support API 19 by re-creating a new
......@@ -118,18 +118,21 @@ class VirtualDisplayController {
public void onViewDetachedFromWindow(View v) {}
});
mPresentation = new SingleViewPresentation(mContext, mVirtualDisplay.getDisplay(), view);
mPresentation = new SingleViewPresentation(mContext, mVirtualDisplay.getDisplay(), presentationState);
mPresentation.show();
}
public void dispose() {
mPresentation.detachView().dispose();
PlatformView view = mPresentation.getView();
mPresentation.detachState();
view.dispose();
mVirtualDisplay.release();
}
public View getView() {
if (mPresentation == null)
return null;
return mPresentation.getView();
PlatformView platformView = mPresentation.getView();
return platformView.getView();
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册