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