Flutter Engine
The Flutter Engine
VirtualDisplayController.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.view.View.OnFocusChangeListener;
8import static io.flutter.Build.API_LEVELS;
9
10import android.annotation.TargetApi;
11import android.content.Context;
12import android.hardware.display.DisplayManager;
13import android.hardware.display.VirtualDisplay;
14import android.os.Build;
15import android.util.DisplayMetrics;
16import android.view.MotionEvent;
17import android.view.View;
18import android.view.View.OnFocusChangeListener;
19import android.view.ViewTreeObserver;
20import androidx.annotation.NonNull;
21import androidx.annotation.VisibleForTesting;
22
24 private static String TAG = "VirtualDisplayController";
25
26 private static VirtualDisplay.Callback callback =
27 new VirtualDisplay.Callback() {
28 @Override
29 public void onPaused() {}
30
31 @Override
32 public void onResumed() {}
33 };
34
36 Context context,
37 AccessibilityEventsDelegate accessibilityEventsDelegate,
38 PlatformView view,
39 PlatformViewRenderTarget renderTarget,
40 int width,
41 int height,
42 int viewId,
43 Object createParams,
44 OnFocusChangeListener focusChangeListener) {
45 if (width == 0 || height == 0) {
46 return null;
47 }
48
49 DisplayManager displayManager =
50 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
51 final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
52
53 // Virtual Display crashes for some PlatformViews if the width or height is bigger
54 // than the physical screen size. We have tried to clamp or scale down the size to prevent
55 // the crash, but both solutions lead to unwanted behavior because the
56 // AndroidPlatformView(https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/platform_view.dart#L677) widget doesn't
57 // scale or clamp, which leads to a mismatch between the size of the widget and the size of
58 // virtual display.
59 // This mismatch leads to some test failures: https://github.com/flutter/flutter/issues/106750
60 // TODO(cyanglaz): find a way to prevent the crash without introducing size mistach betewen
61 // virtual display and AndroidPlatformView widget.
62 // https://github.com/flutter/flutter/issues/93115
63 renderTarget.resize(width, height);
64 int flags = 0;
65 VirtualDisplay virtualDisplay =
66 displayManager.createVirtualDisplay(
67 "flutter-vd#" + viewId,
68 width,
69 height,
70 metrics.densityDpi,
71 renderTarget.getSurface(),
72 flags,
74 null /* handler */);
75
76 if (virtualDisplay == null) {
77 return null;
78 }
79 VirtualDisplayController controller =
81 context,
82 accessibilityEventsDelegate,
83 virtualDisplay,
84 view,
85 renderTarget,
86 focusChangeListener,
87 viewId,
88 createParams);
89 return controller;
90 }
91
93
94 private final Context context;
95 private final AccessibilityEventsDelegate accessibilityEventsDelegate;
96 private final int densityDpi;
97 private final int viewId;
98 private final PlatformViewRenderTarget renderTarget;
99 private final OnFocusChangeListener focusChangeListener;
100
101 private VirtualDisplay virtualDisplay;
102
104 Context context,
105 AccessibilityEventsDelegate accessibilityEventsDelegate,
106 VirtualDisplay virtualDisplay,
107 PlatformView view,
108 PlatformViewRenderTarget renderTarget,
109 OnFocusChangeListener focusChangeListener,
110 int viewId,
111 Object createParams) {
112 this.context = context;
113 this.accessibilityEventsDelegate = accessibilityEventsDelegate;
114 this.renderTarget = renderTarget;
115 this.focusChangeListener = focusChangeListener;
116 this.viewId = viewId;
117 this.virtualDisplay = virtualDisplay;
118 this.densityDpi = context.getResources().getDisplayMetrics().densityDpi;
121 context,
122 this.virtualDisplay.getDisplay(),
123 view,
124 accessibilityEventsDelegate,
125 viewId,
126 focusChangeListener);
127 presentation.show();
128 }
129
130 public int getRenderTargetWidth() {
131 if (renderTarget != null) {
132 return renderTarget.getWidth();
133 }
134 return 0;
135 }
136
138 if (renderTarget != null) {
139 return renderTarget.getHeight();
140 }
141 return 0;
142 }
143
144 public void resize(final int width, final int height, final Runnable onNewSizeFrameAvailable) {
145 // When 'hot reload', although the resize method is triggered, the size of the native View has
146 // not changed.
148 getView().postDelayed(onNewSizeFrameAvailable, 0);
149 return;
150 }
151 if (Build.VERSION.SDK_INT >= API_LEVELS.API_31) {
152 resize31(getView(), width, height, onNewSizeFrameAvailable);
153 return;
154 }
155 boolean isFocused = getView().isFocused();
156 final SingleViewPresentation.PresentationState presentationState = presentation.detachState();
157 // We detach the surface to prevent it being destroyed when releasing the vd.
158 virtualDisplay.setSurface(null);
159 virtualDisplay.release();
160
161 final DisplayManager displayManager =
162 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
163 renderTarget.resize(width, height);
164 int flags = 0;
165 virtualDisplay =
166 displayManager.createVirtualDisplay(
167 "flutter-vd#" + viewId,
168 width,
169 height,
170 densityDpi,
171 renderTarget.getSurface(),
172 flags,
173 callback,
174 null /* handler */);
175
176 final View embeddedView = getView();
177 // There's a bug in Android version older than O where view tree observer onDrawListeners don't
178 // get properly
179 // merged when attaching to window, as a workaround we register the on draw listener after the
180 // view is attached.
181 embeddedView.addOnAttachStateChangeListener(
182 new View.OnAttachStateChangeListener() {
183 @Override
184 public void onViewAttachedToWindow(View v) {
185 OneTimeOnDrawListener.schedule(
186 embeddedView,
187 new Runnable() {
188 @Override
189 public void run() {
190 // We need some delay here until the frame propagates through the vd surface to
191 // the texture,
192 // 128ms was picked pretty arbitrarily based on trial and error.
193 // As long as we invoke the runnable after a new frame is available we avoid the
194 // scaling jank
195 // described in: https://github.com/flutter/flutter/issues/19572
196 // We should ideally run onNewSizeFrameAvailable ASAP to make the embedded view
197 // more responsive
198 // following a resize.
199 embeddedView.postDelayed(onNewSizeFrameAvailable, 128);
200 }
201 });
202 embeddedView.removeOnAttachStateChangeListener(this);
203 }
204
205 @Override
206 public void onViewDetachedFromWindow(View v) {}
207 });
208
209 // Create a new SingleViewPresentation and show() it before we cancel() the existing
210 // presentation. Calling show() and cancel() in this order fixes
211 // https://github.com/flutter/flutter/issues/26345 and maintains seamless transition
212 // of the contents of the presentation.
213 SingleViewPresentation newPresentation =
215 context,
216 virtualDisplay.getDisplay(),
217 accessibilityEventsDelegate,
218 presentationState,
219 focusChangeListener,
220 isFocused);
221 newPresentation.show();
222 presentation.cancel();
223 presentation = newPresentation;
224 }
225
226 public void dispose() {
227 // Fix rare crash on HuaWei device described in: https://github.com/flutter/engine/pull/9192
228 presentation.cancel();
229 presentation.detachState();
230 virtualDisplay.release();
231 renderTarget.release();
232 }
233
234 // On Android versions 31+ resizing of a Virtual Display's Presentation is natively supported.
235 @TargetApi(API_LEVELS.API_31)
236 private void resize31(
237 View embeddedView, int width, int height, final Runnable onNewSizeFrameAvailable) {
238 renderTarget.resize(width, height);
239 virtualDisplay.resize(width, height, densityDpi);
240 // Must update the surface to match the renderTarget's current surface.
241 virtualDisplay.setSurface(renderTarget.getSurface());
242 embeddedView.postDelayed(onNewSizeFrameAvailable, 0);
243 }
244
245 /** See {@link PlatformView#onFlutterViewAttached(View)} */
246 /*package*/ void onFlutterViewAttached(@NonNull View flutterView) {
247 if (presentation == null || presentation.getView() == null) {
248 return;
249 }
250 presentation.getView().onFlutterViewAttached(flutterView);
251 }
252
253 /** See {@link PlatformView#onFlutterViewDetached()} */
254 /*package*/ void onFlutterViewDetached() {
255 if (presentation == null || presentation.getView() == null) {
256 return;
257 }
258 presentation.getView().onFlutterViewDetached();
259 }
260
261 /*package*/ void onInputConnectionLocked() {
262 if (presentation == null || presentation.getView() == null) {
263 return;
264 }
265 presentation.getView().onInputConnectionLocked();
266 }
267
268 /*package*/ void onInputConnectionUnlocked() {
269 if (presentation == null || presentation.getView() == null) {
270 return;
271 }
272 presentation.getView().onInputConnectionUnlocked();
273 }
274
275 public View getView() {
276 if (presentation == null) return null;
277 PlatformView platformView = presentation.getView();
278 return platformView.getView();
279 }
280
281 /** Dispatches a motion event to the presentation for this controller. */
282 public void dispatchTouchEvent(MotionEvent event) {
283 if (presentation == null) return;
284 presentation.dispatchTouchEvent(event);
285 }
286
287 public void clearSurface() {
288 virtualDisplay.setSurface(null);
289 }
290
291 public void resetSurface() {
292 final int width = getRenderTargetWidth();
293 final int height = getRenderTargetHeight();
294 final boolean isFocused = getView().isFocused();
295 final SingleViewPresentation.PresentationState presentationState = presentation.detachState();
296
297 // We detach the surface to prevent it being destroyed when releasing the vd.
298 virtualDisplay.setSurface(null);
299 virtualDisplay.release();
300 final DisplayManager displayManager =
301 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
302 int flags = 0;
303 virtualDisplay =
304 displayManager.createVirtualDisplay(
305 "flutter-vd#" + viewId,
306 width,
307 height,
308 densityDpi,
309 renderTarget.getSurface(),
310 flags,
311 callback,
312 null /* handler */);
313 // Create a new SingleViewPresentation and show() it before we cancel() the existing
314 // presentation. Calling show() and cancel() in this order fixes
315 // https://github.com/flutter/flutter/issues/26345 and maintains seamless transition
316 // of the contents of the presentation.
317 SingleViewPresentation newPresentation =
319 context,
320 virtualDisplay.getDisplay(),
321 accessibilityEventsDelegate,
322 presentationState,
323 focusChangeListener,
324 isFocused);
325 newPresentation.show();
326 presentation.cancel();
327 presentation = newPresentation;
328 }
329
330 static class OneTimeOnDrawListener implements ViewTreeObserver.OnDrawListener {
331 static void schedule(View view, Runnable runnable) {
332 OneTimeOnDrawListener listener = new OneTimeOnDrawListener(view, runnable);
333 view.getViewTreeObserver().addOnDrawListener(listener);
334 }
335
336 final View mView;
338
339 OneTimeOnDrawListener(View view, Runnable onDrawRunnable) {
340 this.mView = view;
341 this.mOnDrawRunnable = onDrawRunnable;
342 }
343
344 @Override
345 public void onDraw() {
346 if (mOnDrawRunnable == null) {
347 return;
348 }
349 mOnDrawRunnable.run();
350 mOnDrawRunnable = null;
351 mView.post(
352 new Runnable() {
353 @Override
354 public void run() {
355 mView.getViewTreeObserver().removeOnDrawListener(OneTimeOnDrawListener.this);
356 }
357 });
358 }
359 }
360}
static final int API_31
Definition: Build.java:21
static VirtualDisplayController create(Context context, AccessibilityEventsDelegate accessibilityEventsDelegate, PlatformView view, PlatformViewRenderTarget renderTarget, int width, int height, int viewId, Object createParams, OnFocusChangeListener focusChangeListener)
void resize(final int width, final int height, final Runnable onNewSizeFrameAvailable)
FlutterSemanticsFlag flags
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
FlKeyEvent * event
def Build(configs, env, options)
Definition: build.py:232
def run(cmd)
Definition: run.py:14
int32_t height
int32_t width