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.PixelFormat; 9 import android.support.annotation.NonNull; 10 import android.support.annotation.Nullable; 11 import android.util.AttributeSet; 12 import android.view.SurfaceHolder; 13 import android.view.SurfaceView; 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 android.view.Surface}. 24 * 25 * To begin rendering a Flutter UI, the owner of this {@code FlutterSurfaceView} must invoke 26 * {@link #attachToRenderer(FlutterRenderer)} with the desired {@link FlutterRenderer}. 27 * 28 * To stop rendering a Flutter UI, the owner of this {@code FlutterSurfaceView} must invoke 29 * {@link #detachFromRenderer()}. 30 * 31 * A {@code FlutterSurfaceView} 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 FlutterSurfaceView} internally. 36 */ 37 public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.RenderSurface { 38 private static final String TAG = "FlutterSurfaceView"; 39 40 private final boolean renderTransparently; 41 private boolean isSurfaceAvailableForRendering = false; 42 private boolean isAttachedToFlutterRenderer = false; 43 @Nullable 44 private FlutterRenderer flutterRenderer; 45 @NonNull 46 private Set<OnFirstFrameRenderedListener> onFirstFrameRenderedListeners = new HashSet<>(); 47 48 // Connects the {@code Surface} beneath this {@code SurfaceView} with Flutter's native code. 49 // Callbacks are received by this Object and then those messages are forwarded to our 50 // FlutterRenderer, and then on to the JNI bridge over to native Flutter code. 51 private final SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() { 52 @Override 53 public void surfaceCreated(@NonNull SurfaceHolder holder) { 54 Log.v(TAG, "SurfaceHolder.Callback.surfaceCreated()"); 55 isSurfaceAvailableForRendering = true; 56 57 if (isAttachedToFlutterRenderer) { 58 connectSurfaceToRenderer(); 59 } 60 } 61 62 @Override 63 public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) { 64 Log.v(TAG, "SurfaceHolder.Callback.surfaceChanged()"); 65 if (isAttachedToFlutterRenderer) { 66 changeSurfaceSize(width, height); 67 } 68 } 69 70 @Override 71 public void surfaceDestroyed(@NonNull SurfaceHolder holder) { 72 Log.v(TAG, "SurfaceHolder.Callback.surfaceDestroyed()"); 73 isSurfaceAvailableForRendering = false; 74 75 if (isAttachedToFlutterRenderer) { 76 disconnectSurfaceFromRenderer(); 77 } 78 } 79 }; 80 81 /** 82 * Constructs a {@code FlutterSurfaceView} programmatically, without any XML attributes. 83 */ FlutterSurfaceView(@onNull Context context)84 public FlutterSurfaceView(@NonNull Context context) { 85 this(context, null, false); 86 } 87 88 /** 89 * Constructs a {@code FlutterSurfaceView} programmatically, without any XML attributes, and 90 * with control over whether or not this {@code FlutterSurfaceView} renders with transparency. 91 */ FlutterSurfaceView(@onNull Context context, boolean renderTransparently)92 public FlutterSurfaceView(@NonNull Context context, boolean renderTransparently) { 93 this(context, null, renderTransparently); 94 } 95 96 /** 97 * Constructs a {@code FlutterSurfaceView} in an XML-inflation-compliant manner. 98 */ FlutterSurfaceView(@onNull Context context, @NonNull AttributeSet attrs)99 public FlutterSurfaceView(@NonNull Context context, @NonNull AttributeSet attrs) { 100 this(context, attrs, false); 101 } 102 FlutterSurfaceView(@onNull Context context, @Nullable AttributeSet attrs, boolean renderTransparently)103 private FlutterSurfaceView(@NonNull Context context, @Nullable AttributeSet attrs, boolean renderTransparently) { 104 super(context, attrs); 105 this.renderTransparently = renderTransparently; 106 init(); 107 } 108 init()109 private void init() { 110 // If transparency is desired then we'll enable a transparent pixel format and place 111 // our Window above everything else to get transparent background rendering. 112 if (renderTransparently) { 113 getHolder().setFormat(PixelFormat.TRANSPARENT); 114 setZOrderOnTop(true); 115 } 116 117 // Grab a reference to our underlying Surface and register callbacks with that Surface so we 118 // can monitor changes and forward those changes on to native Flutter code. 119 getHolder().addCallback(surfaceCallback); 120 121 // Keep this SurfaceView transparent until Flutter has a frame ready to render. This avoids 122 // displaying a black rectangle in our place. 123 setAlpha(0.0f); 124 } 125 126 /** 127 * Invoked by the owner of this {@code FlutterSurfaceView} when it wants to begin rendering 128 * a Flutter UI to this {@code FlutterSurfaceView}. 129 * 130 * If an Android {@link android.view.Surface} is available, this method will give that 131 * {@link android.view.Surface} to the given {@link FlutterRenderer} to begin rendering 132 * Flutter's UI to this {@code FlutterSurfaceView}. 133 * 134 * If no Android {@link android.view.Surface} is available yet, this {@code FlutterSurfaceView} 135 * will wait until a {@link android.view.Surface} becomes available and then give that 136 * {@link android.view.Surface} to the given {@link FlutterRenderer} to begin rendering 137 * Flutter's UI to this {@code FlutterSurfaceView}. 138 */ attachToRenderer(@onNull FlutterRenderer flutterRenderer)139 public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) { 140 Log.v(TAG, "Attaching to FlutterRenderer."); 141 if (this.flutterRenderer != null) { 142 Log.v(TAG, "Already connected to a FlutterRenderer. Detaching from old one and attaching to new one."); 143 this.flutterRenderer.detachFromRenderSurface(); 144 } 145 146 this.flutterRenderer = flutterRenderer; 147 isAttachedToFlutterRenderer = true; 148 149 // If we're already attached to an Android window then we're now attached to both a renderer 150 // and the Android window. We can begin rendering now. 151 if (isSurfaceAvailableForRendering) { 152 Log.v(TAG, "Surface is available for rendering. Connecting FlutterRenderer to Android surface."); 153 connectSurfaceToRenderer(); 154 } 155 } 156 157 /** 158 * Invoked by the owner of this {@code FlutterSurfaceView} when it no longer wants to render 159 * a Flutter UI to this {@code FlutterSurfaceView}. 160 * 161 * This method will cease any on-going rendering from Flutter to this {@code FlutterSurfaceView}. 162 */ detachFromRenderer()163 public void detachFromRenderer() { 164 if (flutterRenderer != null) { 165 // If we're attached to an Android window then we were rendering a Flutter UI. Now that 166 // this FlutterSurfaceView is detached from the FlutterRenderer, we need to stop rendering. 167 // TODO(mattcarroll): introduce a isRendererConnectedToSurface() to wrap "getWindowToken() != null" 168 if (getWindowToken() != null) { 169 Log.v(TAG, "Disconnecting FlutterRenderer from Android surface."); 170 disconnectSurfaceFromRenderer(); 171 } 172 173 // Make the SurfaceView invisible to avoid showing a black rectangle. 174 setAlpha(0.0f); 175 176 flutterRenderer = null; 177 isAttachedToFlutterRenderer = false; 178 } else { 179 Log.w(TAG, "detachFromRenderer() invoked when no FlutterRenderer was attached."); 180 } 181 } 182 183 // FlutterRenderer and getSurfaceTexture() must both be non-null. connectSurfaceToRenderer()184 private void connectSurfaceToRenderer() { 185 if (flutterRenderer == null || getHolder() == null) { 186 throw new IllegalStateException("connectSurfaceToRenderer() should only be called when flutterRenderer and getHolder() are non-null."); 187 } 188 189 flutterRenderer.surfaceCreated(getHolder().getSurface()); 190 } 191 192 // FlutterRenderer must be non-null. changeSurfaceSize(int width, int height)193 private void changeSurfaceSize(int width, int height) { 194 if (flutterRenderer == null) { 195 throw new IllegalStateException("changeSurfaceSize() should only be called when flutterRenderer is non-null."); 196 } 197 198 Log.v(TAG, "Notifying FlutterRenderer that Android surface size has changed to " + width + " x " + height); 199 flutterRenderer.surfaceChanged(width, height); 200 } 201 202 // FlutterRenderer must be non-null. disconnectSurfaceFromRenderer()203 private void disconnectSurfaceFromRenderer() { 204 if (flutterRenderer == null) { 205 throw new IllegalStateException("disconnectSurfaceFromRenderer() should only be called when flutterRenderer is non-null."); 206 } 207 208 flutterRenderer.surfaceDestroyed(); 209 } 210 211 /** 212 * Adds the given {@code listener} to this {@code FlutterSurfaceView}, to be notified upon Flutter's 213 * first rendered frame. 214 */ 215 @Override addOnFirstFrameRenderedListener(@onNull OnFirstFrameRenderedListener listener)216 public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { 217 onFirstFrameRenderedListeners.add(listener); 218 } 219 220 /** 221 * Removes the given {@code listener}, which was previously added with 222 * {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}. 223 */ 224 @Override removeOnFirstFrameRenderedListener(@onNull OnFirstFrameRenderedListener listener)225 public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { 226 onFirstFrameRenderedListeners.remove(listener); 227 } 228 229 @Override onFirstFrameRendered()230 public void onFirstFrameRendered() { 231 // TODO(mattcarroll): decide where this method should live and what it needs to do. 232 Log.v(TAG, "onFirstFrameRendered()"); 233 // Now that a frame is ready to display, take this SurfaceView from transparent to opaque. 234 setAlpha(1.0f); 235 236 for (OnFirstFrameRenderedListener listener : onFirstFrameRenderedListeners) { 237 listener.onFirstFrameRendered(); 238 } 239 } 240 } 241