7
7
import android .annotation .TargetApi ;
8
8
import android .app .Presentation ;
9
9
import android .content .Context ;
10
+ import android .content .ContextWrapper ;
11
+ import android .graphics .Rect ;
10
12
import android .os .Build ;
11
13
import android .os .Bundle ;
12
- import android .view .Display ;
13
- import android .view .View ;
14
- import android .view .WindowManager ;
14
+ import android .util .Log ;
15
+ import android .view .*;
15
16
import android .widget .FrameLayout ;
16
17
18
+ import java .lang .reflect .*;
19
+
20
+ import static android .content .Context .WINDOW_SERVICE ;
21
+
22
+ /*
23
+ * A presentation used for hosting a single Android view in a virtual display.
24
+ *
25
+ * This presentation overrides the WindowManager's addView/removeView/updateViewLayout methods, such that views added
26
+ * directly to the WindowManager are added as part of the presentation's view hierarchy (to mFakeWindowRootView).
27
+ *
28
+ * The view hierarchy for the presentation is as following:
29
+ *
30
+ * mRootView
31
+ * / \
32
+ * / \
33
+ * / \
34
+ * mContainer mState.mFakeWindowRootView
35
+ * |
36
+ * EmbeddedView
37
+ */
17
38
@ TargetApi (Build .VERSION_CODES .JELLY_BEAN_MR1 )
18
39
class SingleViewPresentation extends Presentation {
40
+
41
+ /*
42
+ * When an embedded view is resized in Flutterverse we move the Android view to a new virtual display
43
+ * that has the new size. This class keeps the presentation state that moves with the view to the presentation of
44
+ * the new virtual display.
45
+ */
46
+ static class PresentationState {
47
+ // The Android view we are embedding in the Flutter app.
48
+ private PlatformView mView ;
49
+
50
+ // The InvocationHandler for a WindowManager proxy. This is essentially the custom window manager for the
51
+ // presentation.
52
+ private WindowManagerHandler mWindowManagerHandler ;
53
+
54
+ // Contains views that were added directly to the window manager (e.g android.widget.PopupWindow).
55
+ private FakeWindowViewGroup mFakeWindowRootView ;
56
+ }
57
+
19
58
private final PlatformViewFactory mViewFactory ;
20
59
21
- private PlatformView mView ;
60
+ // This is the view id assigned by the Flutter framework to the embedded view, we keep it here
61
+ // so when we create the platform we can tell it its view id.
22
62
private int mViewId ;
23
63
24
- // As the root view of a display cannot be detached, we use this mContainer
25
- // as the root, and attach mView to it. This allows us to detach mView.
64
+ // The root view for the presentation, it has 2 childs: mContainer which contains the embedded view, and
65
+ // mFakeWindowRootView which contains views that were added directly to the presentation's window manager.
66
+ private FrameLayout mRootView ;
67
+
68
+ // Contains the embedded platform view (mView.getView()) when it is attached to the presentation.
26
69
private FrameLayout mContainer ;
27
70
71
+ private PresentationState mState ;
72
+
28
73
/**
29
74
* Creates a presentation that will use the view factory to create a new
30
75
* platform view in the presentation's onCreate, and attach it.
@@ -33,6 +78,7 @@ public SingleViewPresentation(Context outerContext, Display display, PlatformVie
33
78
super (outerContext , display );
34
79
mViewFactory = viewFactory ;
35
80
mViewId = viewId ;
81
+ mState = new PresentationState ();
36
82
getWindow ().setFlags (
37
83
WindowManager .LayoutParams .FLAG_NOT_FOCUSABLE ,
38
84
WindowManager .LayoutParams .FLAG_NOT_FOCUSABLE
@@ -46,10 +92,10 @@ public SingleViewPresentation(Context outerContext, Display display, PlatformVie
46
92
* <p>The display's density must match the density of the context used
47
93
* when the view was created.
48
94
*/
49
- public SingleViewPresentation (Context outerContext , Display display , PlatformView view ) {
95
+ public SingleViewPresentation (Context outerContext , Display display , PresentationState state ) {
50
96
super (outerContext , display );
51
97
mViewFactory = null ;
52
- mView = view ;
98
+ mState = state ;
53
99
getWindow ().setFlags (
54
100
WindowManager .LayoutParams .FLAG_NOT_FOCUSABLE ,
55
101
WindowManager .LayoutParams .FLAG_NOT_FOCUSABLE
@@ -59,22 +105,195 @@ public SingleViewPresentation(Context outerContext, Display display, PlatformVie
59
105
@ Override
60
106
protected void onCreate (Bundle savedInstanceState ) {
61
107
super .onCreate (savedInstanceState );
62
- if (mView == null ) {
63
- mView = mViewFactory . create (getContext (), mViewId );
108
+ if (mState . mFakeWindowRootView == null ) {
109
+ mState . mFakeWindowRootView = new FakeWindowViewGroup (getContext ());
64
110
}
111
+ if (mState .mWindowManagerHandler == null ) {
112
+ WindowManager windowManagerDelegate = (WindowManager ) getContext ().getSystemService (WINDOW_SERVICE );
113
+ mState .mWindowManagerHandler = new WindowManagerHandler (windowManagerDelegate , mState .mFakeWindowRootView );
114
+ }
115
+
65
116
mContainer = new FrameLayout (getContext ());
66
- mContainer .addView (mView .getView ());
67
- setContentView (mContainer );
117
+ PresentationContext context = new PresentationContext (getContext (), mState .mWindowManagerHandler );
118
+
119
+ if (mState .mView == null ) {
120
+ mState .mView = mViewFactory .create (context , mViewId );
121
+ }
122
+
123
+ mContainer .addView (mState .mView .getView ());
124
+ mRootView = new FrameLayout (getContext ());
125
+ mRootView .addView (mContainer );
126
+ mRootView .addView (mState .mFakeWindowRootView );
127
+ setContentView (mRootView );
68
128
}
69
129
70
- public PlatformView detachView () {
71
- mContainer .removeView (mView .getView ());
72
- return mView ;
130
+ public PresentationState detachState () {
131
+ mContainer .removeAllViews ();
132
+ mRootView .removeAllViews ();
133
+ return mState ;
73
134
}
74
135
75
- public View getView () {
76
- if (mView == null )
136
+ public PlatformView getView () {
137
+ if (mState . mView == null )
77
138
return null ;
78
- return mView .getView ();
139
+ return mState .mView ;
140
+ }
141
+
142
+ /*
143
+ * A view group that implements the same layout protocol that exist between the WindowManager and its direct
144
+ * children.
145
+ *
146
+ * Currently only a subset of the protocol is supported (gravity, x, and y).
147
+ */
148
+ static class FakeWindowViewGroup extends ViewGroup {
149
+ // Used in onLayout to keep the bounds of the current view.
150
+ // We keep it as a member to avoid object allocations during onLayout which are discouraged.
151
+ private final Rect mViewBounds ;
152
+
153
+ // Used in onLayout to keep the bounds of the child views.
154
+ // We keep it as a member to avoid object allocations during onLayout which are discouraged.
155
+ private final Rect mChildRect ;
156
+
157
+ public FakeWindowViewGroup (Context context ) {
158
+ super (context );
159
+ mViewBounds = new Rect ();
160
+ mChildRect = new Rect ();
161
+ }
162
+
163
+ @ Override
164
+ protected void onLayout (boolean changed , int l , int t , int r , int b ) {
165
+ for (int i = 0 ; i < getChildCount (); i ++) {
166
+ View child = getChildAt (i );
167
+ WindowManager .LayoutParams params = (WindowManager .LayoutParams ) child .getLayoutParams ();
168
+ mViewBounds .set (l , t , r , b );
169
+ Gravity .apply (params .gravity , child .getMeasuredWidth (), child .getMeasuredHeight (), mViewBounds , params .x ,
170
+ params .y , mChildRect );
171
+ child .layout (mChildRect .left , mChildRect .top , mChildRect .right , mChildRect .bottom );
172
+ }
173
+ }
174
+
175
+ @ Override
176
+ protected void onMeasure (int widthMeasureSpec , int heightMeasureSpec ) {
177
+ for (int i = 0 ; i < getChildCount (); i ++) {
178
+ View child = getChildAt (i );
179
+ child .measure (atMost (widthMeasureSpec ), atMost (heightMeasureSpec ));
180
+ }
181
+ super .onMeasure (widthMeasureSpec , heightMeasureSpec );
182
+ }
183
+
184
+ private static int atMost (int measureSpec ) {
185
+ return MeasureSpec .makeMeasureSpec (MeasureSpec .getSize (measureSpec ), MeasureSpec .AT_MOST );
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Proxies a Context replacing the WindowManager with our custom instance.
191
+ */
192
+ static class PresentationContext extends ContextWrapper {
193
+ private WindowManager mWindowManager ;
194
+ private final WindowManagerHandler mWindowManagerHandler ;
195
+
196
+ PresentationContext (Context base , WindowManagerHandler windowManagerHandler ) {
197
+ super (base );
198
+ mWindowManagerHandler = windowManagerHandler ;
199
+ }
200
+
201
+ @ Override
202
+ public Object getSystemService (String name ) {
203
+ if (WINDOW_SERVICE .equals (name )) {
204
+ return getWindowManager ();
205
+ }
206
+ return super .getSystemService (name );
207
+ }
208
+
209
+ private WindowManager getWindowManager () {
210
+ if (mWindowManager == null ) {
211
+ mWindowManager = mWindowManagerHandler .getWindowManager ();
212
+ }
213
+ return mWindowManager ;
214
+ }
215
+ }
216
+
217
+ /*
218
+ * A dynamic proxy handler for a WindowManager with custom overrides.
219
+ *
220
+ * The presentation's window manager delegates all calls to the default window manager.
221
+ * WindowManager#addView calls triggered by views that are attached to the virtual display are crashing
222
+ * (see: https://github.com/flutter/flutter/issues/20714). This was triggered when selecting text in an embedded
223
+ * WebView (as the selection handles are implemented as popup windows).
224
+ *
225
+ * This dynamic proxy overrides the addView, removeView, and updateViewLayout methods to prevent these crashes.
226
+ *
227
+ * This will be more efficient as a static proxy that's not using reflection, but as the engine is currently
228
+ * not being built against the latest Android SDK we cannot override all relevant method.
229
+ * Tracking issue for upgrading the engine's Android sdk: https://github.com/flutter/flutter/issues/20717
230
+ */
231
+ static class WindowManagerHandler implements InvocationHandler {
232
+ private static final String TAG = "PlatformViewsController" ;
233
+
234
+ private final WindowManager mDelegate ;
235
+ FakeWindowViewGroup mFakeWindowRootView ;
236
+
237
+ WindowManagerHandler (WindowManager delegate , FakeWindowViewGroup fakeWindowViewGroup ) {
238
+ mDelegate = delegate ;
239
+ mFakeWindowRootView = fakeWindowViewGroup ;
240
+ }
241
+
242
+ public WindowManager getWindowManager () {
243
+ return (WindowManager ) Proxy .newProxyInstance (
244
+ WindowManager .class .getClassLoader (),
245
+ new Class [] { WindowManager .class },
246
+ this
247
+ );
248
+ }
249
+
250
+ @ Override
251
+ public Object invoke (Object proxy , Method method , Object [] args ) throws Throwable {
252
+ switch (method .getName ()) {
253
+ case "addView" :
254
+ addView (args );
255
+ return null ;
256
+ case "removeView" :
257
+ removeView (args );
258
+ return null ;
259
+ case "updateViewLayout" :
260
+ updateViewLayout (args );
261
+ return null ;
262
+ }
263
+ try {
264
+ return method .invoke (mDelegate , args );
265
+ } catch (InvocationTargetException e ) {
266
+ throw e .getCause ();
267
+ }
268
+ }
269
+
270
+ private void addView (Object [] args ) {
271
+ if (mFakeWindowRootView == null ) {
272
+ Log .w (TAG , "Embedded view called addView while detached from presentation" );
273
+ return ;
274
+ }
275
+ View view = (View ) args [0 ];
276
+ WindowManager .LayoutParams layoutParams = (WindowManager .LayoutParams ) args [1 ];
277
+ mFakeWindowRootView .addView (view , layoutParams );
278
+ }
279
+
280
+ private void removeView (Object [] args ) {
281
+ if (mFakeWindowRootView == null ) {
282
+ Log .w (TAG , "Embedded view called removeView while detached from presentation" );
283
+ return ;
284
+ }
285
+ View view = (View ) args [0 ];
286
+ mFakeWindowRootView .removeView (view );
287
+ }
288
+
289
+ private void updateViewLayout (Object [] args ) {
290
+ if (mFakeWindowRootView == null ) {
291
+ Log .w (TAG , "Embedded view called updateViewLayout while detached from presentation" );
292
+ return ;
293
+ }
294
+ View view = (View ) args [0 ];
295
+ WindowManager .LayoutParams layoutParams = (WindowManager .LayoutParams ) args [1 ];
296
+ mFakeWindowRootView .updateViewLayout (view , layoutParams );
297
+ }
79
298
}
80
299
}
0 commit comments