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