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.engine.renderer; 6 7 import android.annotation.TargetApi; 8 import android.graphics.Bitmap; 9 import android.graphics.SurfaceTexture; 10 import android.os.Build; 11 import android.os.Handler; 12 import android.support.annotation.NonNull; 13 import android.support.annotation.Nullable; 14 import android.view.Surface; 15 16 import java.nio.ByteBuffer; 17 import java.util.concurrent.atomic.AtomicLong; 18 19 import io.flutter.Log; 20 import io.flutter.embedding.android.FlutterView; 21 import io.flutter.embedding.engine.FlutterJNI; 22 import io.flutter.view.TextureRegistry; 23 24 /** 25 * WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE. 26 * IF YOU USE IT, WE WILL BREAK YOU. 27 * 28 * {@code FlutterRenderer} works in tandem with a provided {@link RenderSurface} to create an 29 * interactive Flutter UI. 30 * 31 * {@code FlutterRenderer} manages textures for rendering, and forwards some Java calls to native Flutter 32 * code via JNI. The corresponding {@link RenderSurface} is used as a delegate to carry out 33 * certain actions on behalf of this {@code FlutterRenderer} within an Android view hierarchy. 34 * 35 * {@link FlutterView} is an implementation of a {@link RenderSurface}. 36 */ 37 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 38 public class FlutterRenderer implements TextureRegistry { 39 private static final String TAG = "FlutterRenderer"; 40 41 private final FlutterJNI flutterJNI; 42 private final AtomicLong nextTextureId = new AtomicLong(0L); 43 private RenderSurface renderSurface; 44 private boolean hasRenderedFirstFrame = false; 45 46 private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() { 47 @Override 48 public void onFirstFrameRendered() { 49 hasRenderedFirstFrame = true; 50 } 51 }; 52 FlutterRenderer(@onNull FlutterJNI flutterJNI)53 public FlutterRenderer(@NonNull FlutterJNI flutterJNI) { 54 this.flutterJNI = flutterJNI; 55 this.flutterJNI.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener); 56 } 57 58 /** 59 * Returns true if this {@code FlutterRenderer} is attached to the given {@link RenderSurface}, 60 * false otherwise. 61 */ isAttachedTo(@onNull RenderSurface renderSurface)62 public boolean isAttachedTo(@NonNull RenderSurface renderSurface) { 63 return this.renderSurface == renderSurface; 64 } 65 attachToRenderSurface(@onNull RenderSurface renderSurface)66 public void attachToRenderSurface(@NonNull RenderSurface renderSurface) { 67 Log.v(TAG, "Attaching to RenderSurface."); 68 // TODO(mattcarroll): determine desired behavior when attaching to an already attached renderer 69 if (this.renderSurface != null) { 70 Log.v(TAG, "Already attached to a RenderSurface. Detaching from old one and attaching to new one."); 71 detachFromRenderSurface(); 72 } 73 74 this.renderSurface = renderSurface; 75 this.renderSurface.attachToRenderer(this); 76 this.flutterJNI.setRenderSurface(renderSurface); 77 } 78 detachFromRenderSurface()79 public void detachFromRenderSurface() { 80 Log.v(TAG, "Detaching from RenderSurface."); 81 // TODO(mattcarroll): determine desired behavior if we're asked to detach without first being attached 82 if (this.renderSurface != null) { 83 this.renderSurface.detachFromRenderer(); 84 this.renderSurface = null; 85 surfaceDestroyed(); 86 this.flutterJNI.setRenderSurface(null); 87 } 88 } 89 hasRenderedFirstFrame()90 public boolean hasRenderedFirstFrame() { 91 return hasRenderedFirstFrame; 92 } 93 addOnFirstFrameRenderedListener(@onNull OnFirstFrameRenderedListener listener)94 public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { 95 flutterJNI.addOnFirstFrameRenderedListener(listener); 96 97 if (hasRenderedFirstFrame) { 98 listener.onFirstFrameRendered(); 99 } 100 } 101 removeOnFirstFrameRenderedListener(@onNull OnFirstFrameRenderedListener listener)102 public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { 103 flutterJNI.removeOnFirstFrameRenderedListener(listener); 104 } 105 106 //------ START TextureRegistry IMPLEMENTATION ----- 107 // TODO(mattcarroll): detachFromGLContext requires API 16. Create solution for earlier APIs. 108 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 109 @Override createSurfaceTexture()110 public SurfaceTextureEntry createSurfaceTexture() { 111 Log.v(TAG, "Creating a SurfaceTexture."); 112 final SurfaceTexture surfaceTexture = new SurfaceTexture(0); 113 surfaceTexture.detachFromGLContext(); 114 final SurfaceTextureRegistryEntry entry = new SurfaceTextureRegistryEntry( 115 nextTextureId.getAndIncrement(), 116 surfaceTexture 117 ); 118 Log.v(TAG, "New SurfaceTexture ID: " + entry.id()); 119 registerTexture(entry.id(), surfaceTexture); 120 return entry; 121 } 122 123 final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextureEntry { 124 private final long id; 125 @NonNull 126 private final SurfaceTexture surfaceTexture; 127 private boolean released; 128 SurfaceTextureRegistryEntry(long id, @NonNull SurfaceTexture surfaceTexture)129 SurfaceTextureRegistryEntry(long id, @NonNull SurfaceTexture surfaceTexture) { 130 this.id = id; 131 this.surfaceTexture = surfaceTexture; 132 133 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 134 // The callback relies on being executed on the UI thread (unsynchronised read of mNativeView 135 // and also the engine code check for platform thread in Shell::OnPlatformViewMarkTextureFrameAvailable), 136 // so we explicitly pass a Handler for the current thread. 137 this.surfaceTexture.setOnFrameAvailableListener(onFrameListener, new Handler()); 138 } else { 139 // Android documentation states that the listener can be called on an arbitrary thread. 140 // But in practice, versions of Android that predate the newer API will call the listener 141 // on the thread where the SurfaceTexture was constructed. 142 this.surfaceTexture.setOnFrameAvailableListener(onFrameListener); 143 } 144 } 145 146 private SurfaceTexture.OnFrameAvailableListener onFrameListener = new SurfaceTexture.OnFrameAvailableListener() { 147 @Override 148 public void onFrameAvailable(@NonNull SurfaceTexture texture) { 149 if (released) { 150 // Even though we make sure to unregister the callback before releasing, as of Android O 151 // SurfaceTexture has a data race when accessing the callback, so the callback may 152 // still be called by a stale reference after released==true and mNativeView==null. 153 return; 154 } 155 markTextureFrameAvailable(id); 156 } 157 }; 158 159 @Override 160 @NonNull surfaceTexture()161 public SurfaceTexture surfaceTexture() { 162 return surfaceTexture; 163 } 164 165 @Override id()166 public long id() { 167 return id; 168 } 169 170 @Override release()171 public void release() { 172 if (released) { 173 return; 174 } 175 Log.v(TAG, "Releasing a SurfaceTexture (" + id + ")."); 176 surfaceTexture.release(); 177 unregisterTexture(id); 178 released = true; 179 } 180 } 181 //------ END TextureRegistry IMPLEMENTATION ---- 182 183 // TODO(mattcarroll): describe the native behavior that this invokes surfaceCreated(@onNull Surface surface)184 public void surfaceCreated(@NonNull Surface surface) { 185 flutterJNI.onSurfaceCreated(surface); 186 } 187 188 // TODO(mattcarroll): describe the native behavior that this invokes surfaceChanged(int width, int height)189 public void surfaceChanged(int width, int height) { 190 flutterJNI.onSurfaceChanged(width, height); 191 } 192 193 // TODO(mattcarroll): describe the native behavior that this invokes surfaceDestroyed()194 public void surfaceDestroyed() { 195 flutterJNI.onSurfaceDestroyed(); 196 } 197 198 // TODO(mattcarroll): describe the native behavior that this invokes setViewportMetrics(@onNull ViewportMetrics viewportMetrics)199 public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) { 200 Log.v(TAG, "Setting viewport metrics\n" 201 + "Size: " + viewportMetrics.width + " x " + viewportMetrics.height + "\n" 202 + "Padding - L: " + viewportMetrics.paddingLeft + ", T: " + viewportMetrics.paddingTop 203 + ", R: " + viewportMetrics.paddingRight + ", B: " + viewportMetrics.paddingBottom + "\n" 204 + "Insets - L: " + viewportMetrics.viewInsetLeft + ", T: " + viewportMetrics.viewInsetTop 205 + ", R: " + viewportMetrics.viewInsetRight + ", B: " + viewportMetrics.viewInsetBottom + "\n" 206 + "System Gesture Insets - L: " + viewportMetrics.systemGestureInsetLeft + ", T: " + viewportMetrics.systemGestureInsetTop 207 + ", R: " + viewportMetrics.systemGestureInsetRight + ", B: " + viewportMetrics.viewInsetBottom); 208 209 flutterJNI.setViewportMetrics( 210 viewportMetrics.devicePixelRatio, 211 viewportMetrics.width, 212 viewportMetrics.height, 213 viewportMetrics.paddingTop, 214 viewportMetrics.paddingRight, 215 viewportMetrics.paddingBottom, 216 viewportMetrics.paddingLeft, 217 viewportMetrics.viewInsetTop, 218 viewportMetrics.viewInsetRight, 219 viewportMetrics.viewInsetBottom, 220 viewportMetrics.viewInsetLeft, 221 viewportMetrics.systemGestureInsetTop, 222 viewportMetrics.systemGestureInsetRight, 223 viewportMetrics.systemGestureInsetBottom, 224 viewportMetrics.systemGestureInsetLeft 225 ); 226 } 227 228 // TODO(mattcarroll): describe the native behavior that this invokes 229 // TODO(mattcarroll): determine if this is nullable or nonnull getBitmap()230 public Bitmap getBitmap() { 231 return flutterJNI.getBitmap(); 232 } 233 234 // TODO(mattcarroll): describe the native behavior that this invokes dispatchPointerDataPacket(@onNull ByteBuffer buffer, int position)235 public void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position) { 236 flutterJNI.dispatchPointerDataPacket(buffer, position); 237 } 238 239 // TODO(mattcarroll): describe the native behavior that this invokes registerTexture(long textureId, @NonNull SurfaceTexture surfaceTexture)240 private void registerTexture(long textureId, @NonNull SurfaceTexture surfaceTexture) { 241 flutterJNI.registerTexture(textureId, surfaceTexture); 242 } 243 244 // TODO(mattcarroll): describe the native behavior that this invokes markTextureFrameAvailable(long textureId)245 private void markTextureFrameAvailable(long textureId) { 246 flutterJNI.markTextureFrameAvailable(textureId); 247 } 248 249 // TODO(mattcarroll): describe the native behavior that this invokes unregisterTexture(long textureId)250 private void unregisterTexture(long textureId) { 251 flutterJNI.unregisterTexture(textureId); 252 } 253 254 // TODO(mattcarroll): describe the native behavior that this invokes isSoftwareRenderingEnabled()255 public boolean isSoftwareRenderingEnabled() { 256 return FlutterJNI.nativeGetIsSoftwareRenderingEnabled(); 257 } 258 259 // TODO(mattcarroll): describe the native behavior that this invokes setAccessibilityFeatures(int flags)260 public void setAccessibilityFeatures(int flags) { 261 flutterJNI.setAccessibilityFeatures(flags); 262 } 263 264 // TODO(mattcarroll): describe the native behavior that this invokes setSemanticsEnabled(boolean enabled)265 public void setSemanticsEnabled(boolean enabled) { 266 flutterJNI.setSemanticsEnabled(enabled); 267 } 268 269 // TODO(mattcarroll): describe the native behavior that this invokes dispatchSemanticsAction(int id, int action, @Nullable ByteBuffer args, int argsPosition)270 public void dispatchSemanticsAction(int id, 271 int action, 272 @Nullable ByteBuffer args, 273 int argsPosition) { 274 flutterJNI.dispatchSemanticsAction( 275 id, 276 action, 277 args, 278 argsPosition 279 ); 280 } 281 282 /** 283 * Delegate used in conjunction with a {@link FlutterRenderer} to create an interactive Flutter 284 * UI. 285 * 286 * A {@code RenderSurface} is responsible for carrying out behaviors that are needed by a 287 * corresponding {@link FlutterRenderer}. 288 * 289 * A {@code RenderSurface} also receives callbacks for important events, e.g., 290 * {@link #onFirstFrameRendered()}. 291 */ 292 public interface RenderSurface { 293 /** 294 * Invoked by the owner of this {@code RenderSurface} when it wants to begin rendering 295 * a Flutter UI to this {@code RenderSurface}. 296 * 297 * The details of how rendering is handled is an implementation detail. 298 */ attachToRenderer(@onNull FlutterRenderer renderer)299 void attachToRenderer(@NonNull FlutterRenderer renderer); 300 301 /** 302 * Invoked by the owner of this {@code RenderSurface} when it no longer wants to render 303 * a Flutter UI to this {@code RenderSurface}. 304 * 305 * This method will cease any on-going rendering from Flutter to this {@code RenderSurface}. 306 */ detachFromRenderer()307 void detachFromRenderer(); 308 309 // TODO(mattcarroll): convert old FlutterView to use FlutterEngine instead of individual 310 // components, then use FlutterEngine's FlutterRenderer to watch for the first frame and 311 // remove the following methods from this interface. 312 /** 313 * The {@link FlutterRenderer} corresponding to this {@code RenderSurface} has painted its 314 * first frame since being initialized. 315 * 316 * "Initialized" refers to Flutter engine initialization, not the first frame after attaching 317 * to the {@link FlutterRenderer}. Therefore, the first frame may have already rendered by 318 * the time a {@code RenderSurface} has called {@link #attachToRenderSurface(RenderSurface)} 319 * on a {@link FlutterRenderer}. In such a situation, {@code #onFirstFrameRendered()} will 320 * never be called. 321 */ onFirstFrameRendered()322 void onFirstFrameRendered(); 323 324 /** 325 * Adds the given {@code listener} to this {@code FlutterRenderer}, to be notified upon Flutter's 326 * first rendered frame. 327 */ addOnFirstFrameRenderedListener(@onNull OnFirstFrameRenderedListener listener)328 void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener); 329 330 /** 331 * Removes the given {@code listener}, which was previously added with 332 * {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}. 333 */ removeOnFirstFrameRenderedListener(@onNull OnFirstFrameRenderedListener listener)334 void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener); 335 } 336 337 /** 338 * Mutable data structure that holds all viewport metrics properties that Flutter cares about. 339 * 340 * All distance measurements, e.g., width, height, padding, viewInsets, are measured in device 341 * pixels, not logical pixels. 342 */ 343 public static final class ViewportMetrics { 344 public float devicePixelRatio = 1.0f; 345 public int width = 0; 346 public int height = 0; 347 public int paddingTop = 0; 348 public int paddingRight = 0; 349 public int paddingBottom = 0; 350 public int paddingLeft = 0; 351 public int viewInsetTop = 0; 352 public int viewInsetRight = 0; 353 public int viewInsetBottom = 0; 354 public int viewInsetLeft = 0; 355 public int systemGestureInsetTop = 0; 356 public int systemGestureInsetRight = 0; 357 public int systemGestureInsetBottom = 0; 358 public int systemGestureInsetLeft = 0; 359 } 360 } 361