• 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.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