• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
5 package io.flutter.plugin.platform;
6 
7 import android.annotation.TargetApi;
8 import android.content.Context;
9 import android.hardware.display.DisplayManager;
10 import android.hardware.display.VirtualDisplay;
11 import android.os.Build;
12 import android.util.Log;
13 import android.view.Surface;
14 import android.view.View;
15 import android.view.ViewTreeObserver;
16 import io.flutter.view.TextureRegistry;
17 
18 import static android.view.View.OnFocusChangeListener;
19 
20 @TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
21 class VirtualDisplayController {
22 
create( Context context, AccessibilityEventsDelegate accessibilityEventsDelegate, PlatformViewFactory viewFactory, TextureRegistry.SurfaceTextureEntry textureEntry, int width, int height, int viewId, Object createParams, OnFocusChangeListener focusChangeListener )23     public static VirtualDisplayController create(
24             Context context,
25             AccessibilityEventsDelegate accessibilityEventsDelegate,
26             PlatformViewFactory viewFactory,
27             TextureRegistry.SurfaceTextureEntry textureEntry,
28             int width,
29             int height,
30             int viewId,
31             Object createParams,
32             OnFocusChangeListener focusChangeListener
33     ) {
34         textureEntry.surfaceTexture().setDefaultBufferSize(width, height);
35         Surface surface = new Surface(textureEntry.surfaceTexture());
36         DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
37 
38         int densityDpi = context.getResources().getDisplayMetrics().densityDpi;
39         VirtualDisplay virtualDisplay = displayManager.createVirtualDisplay(
40                 "flutter-vd",
41                 width,
42                 height,
43                 densityDpi,
44                 surface,
45                 0
46         );
47 
48         if (virtualDisplay == null) {
49             return null;
50         }
51 
52         return new VirtualDisplayController(
53                 context, accessibilityEventsDelegate, virtualDisplay, viewFactory, surface, textureEntry, focusChangeListener, viewId, createParams);
54     }
55 
56     private final Context context;
57     private final AccessibilityEventsDelegate accessibilityEventsDelegate;
58     private final int densityDpi;
59     private final TextureRegistry.SurfaceTextureEntry textureEntry;
60     private final OnFocusChangeListener focusChangeListener;
61     private VirtualDisplay virtualDisplay;
62     private SingleViewPresentation presentation;
63     private Surface surface;
64 
65 
VirtualDisplayController( Context context, AccessibilityEventsDelegate accessibilityEventsDelegate, VirtualDisplay virtualDisplay, PlatformViewFactory viewFactory, Surface surface, TextureRegistry.SurfaceTextureEntry textureEntry, OnFocusChangeListener focusChangeListener, int viewId, Object createParams )66     private VirtualDisplayController(
67             Context context,
68             AccessibilityEventsDelegate accessibilityEventsDelegate,
69             VirtualDisplay virtualDisplay,
70             PlatformViewFactory viewFactory,
71             Surface surface,
72             TextureRegistry.SurfaceTextureEntry textureEntry,
73             OnFocusChangeListener focusChangeListener,
74             int viewId,
75             Object createParams
76     ) {
77         this.context = context;
78         this.accessibilityEventsDelegate = accessibilityEventsDelegate;
79         this.textureEntry = textureEntry;
80         this.focusChangeListener = focusChangeListener;
81         this.surface = surface;
82         this.virtualDisplay = virtualDisplay;
83         densityDpi = context.getResources().getDisplayMetrics().densityDpi;
84         presentation = new SingleViewPresentation(
85                 context,
86                 this.virtualDisplay.getDisplay(),
87                 viewFactory,
88                 accessibilityEventsDelegate,
89                 viewId,
90                 createParams,
91                 focusChangeListener);
92         presentation.show();
93     }
94 
resize(final int width, final int height, final Runnable onNewSizeFrameAvailable)95     public void resize(final int width, final int height, final Runnable onNewSizeFrameAvailable) {
96         boolean isFocused = getView().isFocused();
97         final SingleViewPresentation.PresentationState presentationState = presentation.detachState();
98         // We detach the surface to prevent it being destroyed when releasing the vd.
99         //
100         // setSurface is only available starting API 20. We could support API 19 by re-creating a new
101         // SurfaceTexture here. This will require refactoring the TextureRegistry to allow recycling texture
102         // entry IDs.
103         virtualDisplay.setSurface(null);
104         virtualDisplay.release();
105 
106         textureEntry.surfaceTexture().setDefaultBufferSize(width, height);
107         DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
108         virtualDisplay = displayManager.createVirtualDisplay(
109                 "flutter-vd",
110                 width,
111                 height,
112                 densityDpi,
113                 surface,
114                 0
115         );
116 
117         final View embeddedView = getView();
118         // There's a bug in Android version older than O where view tree observer onDrawListeners don't get properly
119         // merged when attaching to window, as a workaround we register the on draw listener after the view is attached.
120         embeddedView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
121             @Override
122             public void onViewAttachedToWindow(View v) {
123                 OneTimeOnDrawListener.schedule(embeddedView, new Runnable() {
124                     @Override
125                     public void run() {
126                         // We need some delay here until the frame propagates through the vd surface to to the texture,
127                         // 128ms was picked pretty arbitrarily based on trial and error.
128                         // As long as we invoke the runnable after a new frame is available we avoid the scaling jank
129                         // described in: https://github.com/flutter/flutter/issues/19572
130                         // We should ideally run onNewSizeFrameAvailable ASAP to make the embedded view more responsive
131                         // following a resize.
132                         embeddedView.postDelayed(onNewSizeFrameAvailable, 128);
133                     }
134                 });
135                 embeddedView.removeOnAttachStateChangeListener(this);
136             }
137 
138             @Override
139             public void onViewDetachedFromWindow(View v) {}
140         });
141 
142         presentation = new SingleViewPresentation(
143                 context,
144                 virtualDisplay.getDisplay(),
145                 accessibilityEventsDelegate,
146                 presentationState,
147                 focusChangeListener,
148                 isFocused);
149         presentation.show();
150     }
151 
dispose()152     public void dispose() {
153         PlatformView view = presentation.getView();
154         // Fix rare crash on HuaWei device described in: https://github.com/flutter/engine/pull/9192
155         presentation.cancel();
156         presentation.detachState();
157         view.dispose();
158         virtualDisplay.release();
159         textureEntry.release();
160     }
161 
onInputConnectionLocked()162     /*package*/ void onInputConnectionLocked() {
163         if (presentation == null || presentation.getView() == null) {
164             return;
165         }
166         presentation.getView().onInputConnectionLocked();
167     }
168 
onInputConnectionUnlocked()169     /*package*/ void onInputConnectionUnlocked() {
170         if (presentation == null || presentation.getView() == null) {
171             return;
172         }
173         presentation.getView().onInputConnectionUnlocked();
174     }
175 
getView()176     public View getView() {
177         if (presentation == null)
178             return null;
179         PlatformView platformView = presentation.getView();
180         return platformView.getView();
181     }
182 
183     @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
184     static class OneTimeOnDrawListener implements ViewTreeObserver.OnDrawListener {
schedule(View view, Runnable runnable)185         static void schedule(View view, Runnable runnable) {
186             OneTimeOnDrawListener listener = new OneTimeOnDrawListener(view, runnable);
187             view.getViewTreeObserver().addOnDrawListener(listener);
188         }
189 
190         final View mView;
191         Runnable mOnDrawRunnable;
192 
OneTimeOnDrawListener(View view, Runnable onDrawRunnable)193         OneTimeOnDrawListener(View view, Runnable onDrawRunnable) {
194             this.mView = view;
195             this.mOnDrawRunnable = onDrawRunnable;
196         }
197 
198         @Override
onDraw()199         public void onDraw() {
200             if (mOnDrawRunnable == null) {
201                 return;
202             }
203             mOnDrawRunnable.run();
204             mOnDrawRunnable = null;
205             mView.post(new Runnable() {
206                 @Override
207                 public void run() {
208                     mView.getViewTreeObserver().removeOnDrawListener(OneTimeOnDrawListener.this);
209                 }
210             });
211         }
212     }
213 }
214 
215