Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SingleViewPresentation.java
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package io.flutter.plugin.platform;
6
7import static android.content.Context.WINDOW_SERVICE;
8import static android.view.View.OnFocusChangeListener;
9
10import android.app.AlertDialog;
11import android.app.Presentation;
12import android.content.Context;
13import android.content.ContextWrapper;
14import android.content.MutableContextWrapper;
15import android.graphics.drawable.ColorDrawable;
16import android.os.Bundle;
17import android.view.Display;
18import android.view.View;
19import android.view.WindowManager;
20import android.view.accessibility.AccessibilityEvent;
21import android.view.inputmethod.InputMethodManager;
22import android.widget.FrameLayout;
23import androidx.annotation.Keep;
24import androidx.annotation.NonNull;
25import androidx.annotation.Nullable;
26import io.flutter.Log;
27
28/*
29 * A presentation used for hosting a single Android view in a virtual display.
30 *
31 * This presentation overrides the WindowManager's addView/removeView/updateViewLayout methods, such that views added
32 * directly to the WindowManager are added as part of the presentation's view hierarchy (to fakeWindowViewGroup).
33 *
34 * The view hierarchy for the presentation is as following:
35 *
36 * rootView
37 * / \
38 * / \
39 * / \
40 * container state.fakeWindowViewGroup
41 * |
42 * EmbeddedView
43 */
44@Keep
45class SingleViewPresentation extends Presentation {
46 private static final String TAG = "PlatformViewsController";
47
48 /*
49 * When an embedded view is resized in Flutterverse we move the Android view to a new virtual display
50 * that has the new size. This class keeps the presentation state that moves with the view to the presentation of
51 * the new virtual display.
52 */
53 static class PresentationState {
54 // The Android view we are embedding in the Flutter app.
55 private PlatformView platformView;
56
57 // The InvocationHandler for a WindowManager proxy. This is essentially the custom window
58 // manager for the
59 // presentation.
60 private WindowManagerHandler windowManagerHandler;
61
62 // Contains views that were added directly to the window manager (e.g
63 // android.widget.PopupWindow).
64 private SingleViewFakeWindowViewGroup fakeWindowViewGroup;
65 }
66
67 // A reference to the current accessibility bridge to which accessibility events will be
68 // delegated.
69 private final AccessibilityEventsDelegate accessibilityEventsDelegate;
70
71 private final OnFocusChangeListener focusChangeListener;
72
73 // This is the view id assigned by the Flutter framework to the embedded view, we keep it here
74 // so when we create the platform view we can tell it its view id.
75 private int viewId;
76
77 // The root view for the presentation, it has 2 childs: container which contains the embedded
78 // view, and
79 // fakeWindowViewGroup which contains views that were added directly to the presentation's window
80 // manager.
81 private AccessibilityDelegatingFrameLayout rootView;
82
83 // Contains the embedded platform view (platformView.getView()) when it is attached to the
84 // presentation.
85 private FrameLayout container;
86
87 private final PresentationState state;
88
89 private boolean startFocused = false;
90
91 // The context for the application window that hosts FlutterView.
92 private final Context outerContext;
93
94 /**
95 * Creates a presentation that will use the view factory to create a new platform view in the
96 * presentation's onCreate, and attach it.
97 */
99 Context outerContext,
100 Display display,
101 PlatformView view,
102 AccessibilityEventsDelegate accessibilityEventsDelegate,
103 int viewId,
104 OnFocusChangeListener focusChangeListener) {
105 super(new ImmContext(outerContext), display);
106 this.accessibilityEventsDelegate = accessibilityEventsDelegate;
107 this.viewId = viewId;
108 this.focusChangeListener = focusChangeListener;
109 this.outerContext = outerContext;
110 state = new PresentationState();
111 state.platformView = view;
112 getWindow()
113 .setFlags(
114 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
115 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
116 getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
117 }
118
119 /**
120 * Creates a presentation that will attach an already existing view as its root view.
121 *
122 * <p>The display's density must match the density of the context used when the view was created.
123 */
125 Context outerContext,
126 Display display,
127 AccessibilityEventsDelegate accessibilityEventsDelegate,
128 PresentationState state,
129 OnFocusChangeListener focusChangeListener,
130 boolean startFocused) {
131 super(new ImmContext(outerContext), display);
132 this.accessibilityEventsDelegate = accessibilityEventsDelegate;
133 this.state = state;
134 this.focusChangeListener = focusChangeListener;
135 this.outerContext = outerContext;
136 getWindow()
137 .setFlags(
138 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
139 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
140 this.startFocused = startFocused;
141 }
142
143 @Override
144 protected void onCreate(Bundle savedInstanceState) {
145 super.onCreate(savedInstanceState);
146 // This makes sure we preserve alpha for the VD's content.
147 getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
148 if (state.fakeWindowViewGroup == null) {
149 state.fakeWindowViewGroup = new SingleViewFakeWindowViewGroup(getContext());
150 }
151 if (state.windowManagerHandler == null) {
152 WindowManager windowManagerDelegate =
153 (WindowManager) getContext().getSystemService(WINDOW_SERVICE);
154 state.windowManagerHandler =
155 new WindowManagerHandler(windowManagerDelegate, state.fakeWindowViewGroup);
156 }
157
158 container = new FrameLayout(getContext());
159
160 // Our base mContext has already been wrapped with an IMM cache at instantiation time, but
161 // we want to wrap it again here to also return state.windowManagerHandler.
162 Context baseContext =
163 new PresentationContext(getContext(), state.windowManagerHandler, outerContext);
164
165 View embeddedView = state.platformView.getView();
166 if (embeddedView.getContext() instanceof MutableContextWrapper) {
167 MutableContextWrapper currentContext = (MutableContextWrapper) embeddedView.getContext();
168 currentContext.setBaseContext(baseContext);
169 } else {
170 // In some cases, such as when using LayoutInflator, the original context
171 // may not be preserved. For backward compatibility with previous
172 // implementations of Virtual Display, which didn't validate the context,
173 // continue, but log a warning indicating that some functionality may not
174 // work as expected.
175 // See https://github.com/flutter/flutter/issues/110146 for context.
176 Log.w(
177 TAG,
178 "Unexpected platform view context for view ID "
179 + viewId
180 + "; some functionality may not work correctly. When constructing a platform view "
181 + "in the factory, ensure that the view returned from PlatformViewFactory#create "
182 + "returns the provided context from getContext(). If you are unable to associate "
183 + "the view with that context, consider using Hybrid Composition instead.");
184 }
185
186 container.addView(embeddedView);
187 rootView =
188 new AccessibilityDelegatingFrameLayout(
189 getContext(), accessibilityEventsDelegate, embeddedView);
190 rootView.addView(container);
191 rootView.addView(state.fakeWindowViewGroup);
192
193 embeddedView.setOnFocusChangeListener(focusChangeListener);
194 rootView.setFocusableInTouchMode(true);
195 if (startFocused) {
196 embeddedView.requestFocus();
197 } else {
198 rootView.requestFocus();
199 }
200 setContentView(rootView);
201 }
202
204 // These views can be null before onCreate() is called
205 if (container != null) {
206 container.removeAllViews();
207 }
208 if (rootView != null) {
209 rootView.removeAllViews();
210 }
211 return state;
212 }
213
214 @Nullable
216 return state.platformView;
217 }
218
219 /** Answers calls for {@link InputMethodManager} with an instance cached at creation time. */
220 // TODO(mklim): This caches the IMM at construction time and won't pick up any changes. In rare
221 // cases where the FlutterView changes windows this will return an outdated instance. This
222 // should be fixed to instead defer returning the IMM to something that know's FlutterView's
223 // true Context.
224 private static class ImmContext extends ContextWrapper {
225 private @NonNull final InputMethodManager inputMethodManager;
226
227 ImmContext(Context base) {
228 this(base, /*inputMethodManager=*/ null);
229 }
230
231 private ImmContext(Context base, @Nullable InputMethodManager inputMethodManager) {
232 super(base);
233 this.inputMethodManager =
234 inputMethodManager != null
235 ? inputMethodManager
236 : (InputMethodManager) base.getSystemService(INPUT_METHOD_SERVICE);
237 }
238
239 @Override
240 public Object getSystemService(String name) {
241 if (INPUT_METHOD_SERVICE.equals(name)) {
242 return inputMethodManager;
243 }
244 return super.getSystemService(name);
245 }
246
247 @Override
248 public Context createDisplayContext(Display display) {
249 Context displayContext = super.createDisplayContext(display);
250 return new ImmContext(displayContext, inputMethodManager);
251 }
252 }
253
254 /** Proxies a Context replacing the WindowManager with our custom instance. */
255 // TODO(mklim): This caches the IMM at construction time and won't pick up any changes. In rare
256 // cases where the FlutterView changes windows this will return an outdated instance. This
257 // should be fixed to instead defer returning the IMM to something that know's FlutterView's
258 // true Context.
259 private static class PresentationContext extends ContextWrapper {
260 private @NonNull final WindowManagerHandler windowManagerHandler;
261 private @Nullable WindowManager windowManager;
262 private final Context flutterAppWindowContext;
263
264 PresentationContext(
266 @NonNull WindowManagerHandler windowManagerHandler,
267 Context flutterAppWindowContext) {
268 super(base);
269 this.windowManagerHandler = windowManagerHandler;
270 this.flutterAppWindowContext = flutterAppWindowContext;
271 }
272
273 @Override
274 public Object getSystemService(String name) {
275 if (WINDOW_SERVICE.equals(name)) {
276 if (isCalledFromAlertDialog()) {
277 // Alert dialogs are showing on top of the entire application and should not be limited to
278 // the virtual
279 // display. If we detect that an android.app.AlertDialog constructor is what's fetching
280 // the window manager
281 // we return the one for the application's window.
282 //
283 // Note that if we don't do this AlertDialog will throw a ClassCastException as down the
284 // line it tries
285 // to case this instance to a WindowManagerImpl which the object returned by
286 // getWindowManager is not
287 // a subclass of.
288 return flutterAppWindowContext.getSystemService(name);
289 }
290 return getWindowManager();
291 }
292 return super.getSystemService(name);
293 }
294
295 private WindowManager getWindowManager() {
296 if (windowManager == null) {
297 windowManager = windowManagerHandler;
298 }
299 return windowManager;
300 }
301
302 private boolean isCalledFromAlertDialog() {
303 StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
304 for (int i = 0; i < stackTraceElements.length && i < 11; i++) {
305 if (stackTraceElements[i].getClassName().equals(AlertDialog.class.getCanonicalName())
306 && stackTraceElements[i].getMethodName().equals("<init>")) {
307 return true;
308 }
309 }
310 return false;
311 }
312 }
313
314 private static class AccessibilityDelegatingFrameLayout extends FrameLayout {
315 private final AccessibilityEventsDelegate accessibilityEventsDelegate;
316 private final View embeddedView;
317
318 public AccessibilityDelegatingFrameLayout(
319 Context context,
320 AccessibilityEventsDelegate accessibilityEventsDelegate,
321 View embeddedView) {
322 super(context);
323 this.accessibilityEventsDelegate = accessibilityEventsDelegate;
324 this.embeddedView = embeddedView;
325 }
326
327 @Override
328 public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
329 return accessibilityEventsDelegate.requestSendAccessibilityEvent(embeddedView, child, event);
330 }
331 }
332}
static bool equals(T *a, T *b)
static void w(@NonNull String tag, @NonNull String message)
Definition Log.java:76
SingleViewPresentation(Context outerContext, Display display, AccessibilityEventsDelegate accessibilityEventsDelegate, PresentationState state, OnFocusChangeListener focusChangeListener, boolean startFocused)
SingleViewPresentation(Context outerContext, Display display, PlatformView view, AccessibilityEventsDelegate accessibilityEventsDelegate, int viewId, OnFocusChangeListener focusChangeListener)
AtkStateType state
FlKeyEvent * event
const char * name
Definition fuchsia.cc:50
#define TAG()