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.embedding.android; 6 7 import android.content.Context; 8 import android.graphics.SurfaceTexture; 9 import android.support.annotation.NonNull; 10 import android.support.annotation.Nullable; 11 import android.util.AttributeSet; 12 import android.view.Surface; 13 import android.view.TextureView; 14 15 import java.util.HashSet; 16 import java.util.Set; 17 18 import io.flutter.Log; 19 import io.flutter.embedding.engine.renderer.FlutterRenderer; 20 import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; 21 22 /** 23 * Paints a Flutter UI on a {@link SurfaceTexture}. 24 * 25 * To begin rendering a Flutter UI, the owner of this {@code FlutterTextureView} must invoke 26 * {@link #attachToRenderer(FlutterRenderer)} with the desired {@link FlutterRenderer}. 27 * 28 * To stop rendering a Flutter UI, the owner of this {@code FlutterTextureView} must invoke 29 * {@link #detachFromRenderer()}. 30 * 31 * A {@code FlutterTextureView} is intended for situations where a developer needs to render 32 * a Flutter UI, but does not require any keyboard input, gesture input, accessibility 33 * integrations or any other interactivity beyond rendering. If standard interactivity is 34 * desired, consider using a {@link FlutterView} which provides all of these behaviors and 35 * utilizes a {@code FlutterTextureView} internally. 36 */ 37 public class FlutterTextureView extends TextureView implements FlutterRenderer.RenderSurface { 38 private static final String TAG = "FlutterTextureView"; 39 40 private boolean isSurfaceAvailableForRendering = false; 41 private boolean isAttachedToFlutterRenderer = false; 42 @Nullable 43 private FlutterRenderer flutterRenderer; 44 @NonNull 45 private Set<OnFirstFrameRenderedListener> onFirstFrameRenderedListeners = new HashSet<>(); 46 47 // Connects the {@code SurfaceTexture} beneath this {@code TextureView} with Flutter's native code. 48 // Callbacks are received by this Object and then those messages are forwarded to our 49 // FlutterRenderer, and then on to the JNI bridge over to native Flutter code. 50 private final SurfaceTextureListener surfaceTextureListener = new SurfaceTextureListener() { 51 @Override 52 public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { 53 Log.v(TAG, "SurfaceTextureListener.onSurfaceTextureAvailable()"); 54 isSurfaceAvailableForRendering = true; 55 56 // If we're already attached to a FlutterRenderer then we're now attached to both a renderer 57 // and the Android window, so we can begin rendering now. 58 if (isAttachedToFlutterRenderer) { 59 connectSurfaceToRenderer(); 60 } 61 } 62 63 @Override 64 public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) { 65 Log.v(TAG, "SurfaceTextureListener.onSurfaceTextureSizeChanged()"); 66 if (isAttachedToFlutterRenderer) { 67 changeSurfaceSize(width, height); 68 } 69 } 70 71 @Override 72 public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { 73 // Invoked every time a new frame is available. We don't care. 74 } 75 76 @Override 77 public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { 78 Log.v(TAG, "SurfaceTextureListener.onSurfaceTextureDestroyed()"); 79 isSurfaceAvailableForRendering = false; 80 81 // If we're attached to a FlutterRenderer then we need to notify it that our SurfaceTexture 82 // has been destroyed. 83 if (isAttachedToFlutterRenderer) { 84 disconnectSurfaceFromRenderer(); 85 } 86 87 // Return true to indicate that no further painting will take place 88 // within this SurfaceTexture. 89 return true; 90 } 91 }; 92 93 /** 94 * Constructs a {@code FlutterTextureView} programmatically, without any XML attributes. 95 */ FlutterTextureView(@onNull Context context)96 public FlutterTextureView(@NonNull Context context) { 97 this(context, null); 98 } 99 100 /** 101 * Constructs a {@code FlutterTextureView} in an XML-inflation-compliant manner. 102 */ FlutterTextureView(@onNull Context context, @Nullable AttributeSet attrs)103 public FlutterTextureView(@NonNull Context context, @Nullable AttributeSet attrs) { 104 super(context, attrs); 105 init(); 106 } 107 init()108 private void init() { 109 // Listen for when our underlying SurfaceTexture becomes available, changes size, or 110 // gets destroyed, and take the appropriate actions. 111 setSurfaceTextureListener(surfaceTextureListener); 112 } 113 114 /** 115 * Invoked by the owner of this {@code FlutterTextureView} when it wants to begin rendering 116 * a Flutter UI to this {@code FlutterTextureView}. 117 * 118 * If an Android {@link SurfaceTexture} is available, this method will give that 119 * {@link SurfaceTexture} to the given {@link FlutterRenderer} to begin rendering 120 * Flutter's UI to this {@code FlutterTextureView}. 121 * 122 * If no Android {@link SurfaceTexture} is available yet, this {@code FlutterTextureView} 123 * will wait until a {@link SurfaceTexture} becomes available and then give that 124 * {@link SurfaceTexture} to the given {@link FlutterRenderer} to begin rendering 125 * Flutter's UI to this {@code FlutterTextureView}. 126 */ attachToRenderer(@onNull FlutterRenderer flutterRenderer)127 public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) { 128 Log.v(TAG, "Attaching to FlutterRenderer."); 129 if (this.flutterRenderer != null) { 130 Log.v(TAG, "Already connected to a FlutterRenderer. Detaching from old one and attaching to new one."); 131 this.flutterRenderer.detachFromRenderSurface(); 132 } 133 134 this.flutterRenderer = flutterRenderer; 135 isAttachedToFlutterRenderer = true; 136 137 // If we're already attached to an Android window then we're now attached to both a renderer 138 // and the Android window. We can begin rendering now. 139 if (isSurfaceAvailableForRendering) { 140 Log.v(TAG, "Surface is available for rendering. Connecting FlutterRenderer to Android surface."); 141 connectSurfaceToRenderer(); 142 } 143 } 144 145 /** 146 * Invoked by the owner of this {@code FlutterTextureView} when it no longer wants to render 147 * a Flutter UI to this {@code FlutterTextureView}. 148 * 149 * This method will cease any on-going rendering from Flutter to this {@code FlutterTextureView}. 150 */ detachFromRenderer()151 public void detachFromRenderer() { 152 if (flutterRenderer != null) { 153 // If we're attached to an Android window then we were rendering a Flutter UI. Now that 154 // this FlutterTextureView is detached from the FlutterRenderer, we need to stop rendering. 155 // TODO(mattcarroll): introduce a isRendererConnectedToSurface() to wrap "getWindowToken() != null" 156 if (getWindowToken() != null) { 157 Log.v(TAG, "Disconnecting FlutterRenderer from Android surface."); 158 disconnectSurfaceFromRenderer(); 159 } 160 161 flutterRenderer = null; 162 isAttachedToFlutterRenderer = false; 163 } else { 164 Log.w(TAG, "detachFromRenderer() invoked when no FlutterRenderer was attached."); 165 } 166 } 167 168 // FlutterRenderer and getSurfaceTexture() must both be non-null. connectSurfaceToRenderer()169 private void connectSurfaceToRenderer() { 170 if (flutterRenderer == null || getSurfaceTexture() == null) { 171 throw new IllegalStateException("connectSurfaceToRenderer() should only be called when flutterRenderer and getSurfaceTexture() are non-null."); 172 } 173 174 flutterRenderer.surfaceCreated(new Surface(getSurfaceTexture())); 175 } 176 177 // FlutterRenderer must be non-null. changeSurfaceSize(int width, int height)178 private void changeSurfaceSize(int width, int height) { 179 if (flutterRenderer == null) { 180 throw new IllegalStateException("changeSurfaceSize() should only be called when flutterRenderer is non-null."); 181 } 182 183 Log.v(TAG, "Notifying FlutterRenderer that Android surface size has changed to " + width + " x " + height); 184 flutterRenderer.surfaceChanged(width, height); 185 } 186 187 // FlutterRenderer must be non-null. disconnectSurfaceFromRenderer()188 private void disconnectSurfaceFromRenderer() { 189 if (flutterRenderer == null) { 190 throw new IllegalStateException("disconnectSurfaceFromRenderer() should only be called when flutterRenderer is non-null."); 191 } 192 193 flutterRenderer.surfaceDestroyed(); 194 } 195 196 /** 197 * Adds the given {@code listener} to this {@code FlutterTextureView}, to be notified upon Flutter's 198 * first rendered frame. 199 */ 200 @Override addOnFirstFrameRenderedListener(@onNull OnFirstFrameRenderedListener listener)201 public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { 202 onFirstFrameRenderedListeners.add(listener); 203 } 204 205 /** 206 * Removes the given {@code listener}, which was previously added with 207 * {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}. 208 */ 209 @Override removeOnFirstFrameRenderedListener(@onNull OnFirstFrameRenderedListener listener)210 public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { 211 onFirstFrameRenderedListeners.remove(listener); 212 } 213 214 @Override onFirstFrameRendered()215 public void onFirstFrameRendered() { 216 // TODO(mattcarroll): decide where this method should live and what it needs to do. 217 Log.v(TAG, "onFirstFrameRendered()"); 218 219 for (OnFirstFrameRenderedListener listener : onFirstFrameRenderedListeners) { 220 listener.onFirstFrameRendered(); 221 } 222 } 223 } 224