Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
FlutterSurfaceView.java
Go to the documentation of this file.
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
5package io.flutter.embedding.android;
6
7import android.content.Context;
8import android.graphics.PixelFormat;
9import android.graphics.Region;
10import android.util.AttributeSet;
11import android.view.SurfaceHolder;
12import android.view.SurfaceView;
13import androidx.annotation.NonNull;
14import androidx.annotation.Nullable;
15import androidx.annotation.VisibleForTesting;
16import io.flutter.Log;
17import io.flutter.embedding.engine.renderer.FlutterRenderer;
18import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
19import io.flutter.embedding.engine.renderer.RenderSurface;
20
21/**
22 * Paints a Flutter UI on a {@link android.view.Surface}.
23 *
24 * <p>To begin rendering a Flutter UI, the owner of this {@code FlutterSurfaceView} must invoke
25 * {@link #attachToRenderer(FlutterRenderer)} with the desired {@link FlutterRenderer}.
26 *
27 * <p>To stop rendering a Flutter UI, the owner of this {@code FlutterSurfaceView} must invoke
28 * {@link #detachFromRenderer()}.
29 *
30 * <p>A {@code FlutterSurfaceView} is intended for situations where a developer needs to render a
31 * Flutter UI, but does not require any keyboard input, gesture input, accessibility integrations or
32 * any other interactivity beyond rendering. If standard interactivity is desired, consider using a
33 * {@link FlutterView} which provides all of these behaviors and utilizes a {@code
34 * FlutterSurfaceView} internally.
35 */
36public class FlutterSurfaceView extends SurfaceView implements RenderSurface {
37 private static final String TAG = "FlutterSurfaceView";
38
39 private final boolean renderTransparently;
40 private boolean isSurfaceAvailableForRendering = false;
41 private boolean isPaused = false;
42 @Nullable private FlutterRenderer flutterRenderer;
43
44 private boolean shouldNotify() {
45 return flutterRenderer != null && !isPaused;
46 }
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 =
52 new SurfaceHolder.Callback() {
53 @Override
54 public void surfaceCreated(@NonNull SurfaceHolder holder) {
55 Log.v(TAG, "SurfaceHolder.Callback.startRenderingToSurface()");
56 isSurfaceAvailableForRendering = true;
57
58 if (shouldNotify()) {
59 connectSurfaceToRenderer();
60 }
61 }
62
63 @Override
64 public void surfaceChanged(
65 @NonNull SurfaceHolder holder, int format, int width, int height) {
66 Log.v(TAG, "SurfaceHolder.Callback.surfaceChanged()");
67 if (shouldNotify()) {
68 changeSurfaceSize(width, height);
69 }
70 }
71
72 @Override
73 public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
74 Log.v(TAG, "SurfaceHolder.Callback.stopRenderingToSurface()");
75 isSurfaceAvailableForRendering = false;
76
77 if (shouldNotify()) {
78 disconnectSurfaceFromRenderer();
79 }
80 }
81 };
82
83 private final FlutterUiDisplayListener flutterUiDisplayListener =
84 new FlutterUiDisplayListener() {
85 @Override
86 public void onFlutterUiDisplayed() {
87 Log.v(TAG, "onFlutterUiDisplayed()");
88 // Now that a frame is ready to display, take this SurfaceView from transparent to opaque.
89 setAlpha(1.0f);
90
91 if (flutterRenderer != null) {
92 flutterRenderer.removeIsDisplayingFlutterUiListener(this);
93 }
94 }
95
96 @Override
97 public void onFlutterUiNoLongerDisplayed() {
98 // no-op
99 }
100 };
101
102 /** Constructs a {@code FlutterSurfaceView} programmatically, without any XML attributes. */
103 public FlutterSurfaceView(@NonNull Context context) {
104 this(context, null, false);
105 }
106
107 /**
108 * Constructs a {@code FlutterSurfaceView} programmatically, without any XML attributes, and with
109 * control over whether or not this {@code FlutterSurfaceView} renders with transparency.
110 */
111 public FlutterSurfaceView(@NonNull Context context, boolean renderTransparently) {
112 this(context, null, renderTransparently);
113 }
114
115 /** Constructs a {@code FlutterSurfaceView} in an XML-inflation-compliant manner. */
116 public FlutterSurfaceView(@NonNull Context context, @NonNull AttributeSet attrs) {
117 this(context, attrs, false);
118 }
119
120 private FlutterSurfaceView(
121 @NonNull Context context, @Nullable AttributeSet attrs, boolean renderTransparently) {
122 super(context, attrs);
123 this.renderTransparently = renderTransparently;
124 init();
125 }
126
127 private void init() {
128 // If transparency is desired then we'll enable a transparent pixel format and place
129 // our Window above everything else to get transparent background rendering.
130 if (renderTransparently) {
131 getHolder().setFormat(PixelFormat.TRANSPARENT);
132 setZOrderOnTop(true);
133 }
134
135 // Grab a reference to our underlying Surface and register callbacks with that Surface so we
136 // can monitor changes and forward those changes on to native Flutter code.
137 getHolder().addCallback(surfaceCallback);
138
139 // Keep this SurfaceView transparent until Flutter has a frame ready to render. This avoids
140 // displaying a black rectangle in our place.
141 setAlpha(0.0f);
142 }
143
144 // This is a work around for TalkBack.
145 // If Android decides that our layer is transparent because, e.g. the status-
146 // bar is transparent, TalkBack highlighting stops working.
147 // Explicitly telling Android this part of the region is not actually
148 // transparent makes TalkBack work again.
149 // See https://github.com/flutter/flutter/issues/73413 for context.
150 @Override
151 public boolean gatherTransparentRegion(Region region) {
152 if (getAlpha() < 1.0f) {
153 return false;
154 }
155 final int[] location = new int[2];
156 getLocationInWindow(location);
157 region.op(
158 location[0],
159 location[1],
160 location[0] + getRight() - getLeft(),
161 location[1] + getBottom() - getTop(),
162 Region.Op.DIFFERENCE);
163 return true;
164 }
165
166 @Nullable
167 @Override
168 public FlutterRenderer getAttachedRenderer() {
169 return flutterRenderer;
170 }
171
172 @VisibleForTesting
173 /* package */ boolean isSurfaceAvailableForRendering() {
174 return isSurfaceAvailableForRendering;
175 }
176 /**
177 * Invoked by the owner of this {@code FlutterSurfaceView} when it wants to begin rendering a
178 * Flutter UI to this {@code FlutterSurfaceView}.
179 *
180 * <p>If an Android {@link android.view.Surface} is available, this method will give that {@link
181 * android.view.Surface} to the given {@link FlutterRenderer} to begin rendering Flutter's UI to
182 * this {@code FlutterSurfaceView}.
183 *
184 * <p>If no Android {@link android.view.Surface} is available yet, this {@code FlutterSurfaceView}
185 * will wait until a {@link android.view.Surface} becomes available and then give that {@link
186 * android.view.Surface} to the given {@link FlutterRenderer} to begin rendering Flutter's UI to
187 * this {@code FlutterSurfaceView}.
188 */
189 public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
190 Log.v(TAG, "Attaching to FlutterRenderer.");
191 if (this.flutterRenderer != null) {
192 Log.v(
193 TAG,
194 "Already connected to a FlutterRenderer. Detaching from old one and attaching to new"
195 + " one.");
196 this.flutterRenderer.stopRenderingToSurface();
197 this.flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener);
198 }
199
200 this.flutterRenderer = flutterRenderer;
201
202 resume();
203 }
204
205 /**
206 * Invoked by the owner of this {@code FlutterSurfaceView} when it no longer wants to render a
207 * Flutter UI to this {@code FlutterSurfaceView}.
208 *
209 * <p>This method will cease any on-going rendering from Flutter to this {@code
210 * FlutterSurfaceView}.
211 */
212 public void detachFromRenderer() {
213 if (flutterRenderer != null) {
214 // If we're attached to an Android window then we were rendering a Flutter UI. Now that
215 // this FlutterSurfaceView is detached from the FlutterRenderer, we need to stop rendering.
216 // TODO(mattcarroll): introduce a isRendererConnectedToSurface() to wrap "getWindowToken() !=
217 // null"
218 if (getWindowToken() != null) {
219 Log.v(TAG, "Disconnecting FlutterRenderer from Android surface.");
220 disconnectSurfaceFromRenderer();
221 }
222
223 // Make the SurfaceView invisible to avoid showing a black rectangle.
224 setAlpha(0.0f);
225 flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener);
226 flutterRenderer = null;
227
228 } else {
229 Log.w(TAG, "detachFromRenderer() invoked when no FlutterRenderer was attached.");
230 }
231 }
232
233 /**
234 * Invoked by the owner of this {@code FlutterSurfaceView} when it should pause rendering Flutter
235 * UI to this {@code FlutterSurfaceView}.
236 */
237 public void pause() {
238 if (flutterRenderer == null) {
239 Log.w(TAG, "pause() invoked when no FlutterRenderer was attached.");
240 return;
241 }
242 isPaused = true;
243 }
244
245 public void resume() {
246 if (flutterRenderer == null) {
247 Log.w(TAG, "resume() invoked when no FlutterRenderer was attached.");
248 return;
249 }
250 this.flutterRenderer.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
251
252 // If we're already attached to an Android window then we're now attached to both a renderer
253 // and the Android window. We can begin rendering now.
254 if (isSurfaceAvailableForRendering()) {
255 Log.v(
256 TAG,
257 "Surface is available for rendering. Connecting FlutterRenderer to Android surface.");
258 connectSurfaceToRenderer();
259 }
260 isPaused = false;
261 }
262
263 // FlutterRenderer and getSurfaceTexture() must both be non-null.
264 private void connectSurfaceToRenderer() {
265 if (flutterRenderer == null || getHolder() == null) {
266 throw new IllegalStateException(
267 "connectSurfaceToRenderer() should only be called when flutterRenderer and getHolder()"
268 + " are non-null.");
269 }
270 // When connecting the surface to the renderer, it's possible that the surface is currently
271 // paused. For instance, when a platform view is displayed, the current FlutterSurfaceView
272 // is paused, and rendering continues in a FlutterImageView buffer while the platform view
273 // is displayed.
274 //
275 // startRenderingToSurface stops rendering to an active surface if it isn't paused.
276 flutterRenderer.startRenderingToSurface(getHolder().getSurface(), isPaused);
277 }
278
279 // FlutterRenderer must be non-null.
280 private void changeSurfaceSize(int width, int height) {
281 if (flutterRenderer == null) {
282 throw new IllegalStateException(
283 "changeSurfaceSize() should only be called when flutterRenderer is non-null.");
284 }
285
286 Log.v(
287 TAG,
288 "Notifying FlutterRenderer that Android surface size has changed to "
289 + width
290 + " x "
291 + height);
292 flutterRenderer.surfaceChanged(width, height);
293 }
294
295 // FlutterRenderer must be non-null.
296 private void disconnectSurfaceFromRenderer() {
297 if (flutterRenderer == null) {
298 throw new IllegalStateException(
299 "disconnectSurfaceFromRenderer() should only be called when flutterRenderer is"
300 + " non-null.");
301 }
302
303 flutterRenderer.stopRenderingToSurface();
304 }
305}
bool op(const SkIRect &rect, Op op)
Definition SkRegion.h:384
static void v(@NonNull String tag, @NonNull String message)
Definition Log.java:40
static void w(@NonNull String tag, @NonNull String message)
Definition Log.java:76
uint32_t uint32_t * format
#define TAG()
int32_t height
int32_t width