blob: 9416301b8afd47be084ba1154a5752616ee15932 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.embedding.android;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.util.AttributeSet;
import android.view.Surface;
import android.view.TextureView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.Log;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.RenderSurface;
/**
* Paints a Flutter UI on a {@link SurfaceTexture}.
*
* <p>To begin rendering a Flutter UI, the owner of this {@code FlutterTextureView} must invoke
* {@link #attachToRenderer(FlutterRenderer)} with the desired {@link FlutterRenderer}.
*
* <p>To stop rendering a Flutter UI, the owner of this {@code FlutterTextureView} must invoke
* {@link #detachFromRenderer()}.
*
* <p>A {@code FlutterTextureView} is intended for situations where a developer needs to render a
* Flutter UI, but does not require any keyboard input, gesture input, accessibility integrations or
* any other interactivity beyond rendering. If standard interactivity is desired, consider using a
* {@link FlutterView} which provides all of these behaviors and utilizes a {@code
* FlutterTextureView} internally.
*/
public class FlutterTextureView extends TextureView implements RenderSurface {
private static final String TAG = "FlutterTextureView";
private boolean isSurfaceAvailableForRendering = false;
private boolean isAttachedToFlutterRenderer = false;
@Nullable private FlutterRenderer flutterRenderer;
@Nullable private Surface renderSurface;
// Connects the {@code SurfaceTexture} beneath this {@code TextureView} with Flutter's native
// code.
// Callbacks are received by this Object and then those messages are forwarded to our
// FlutterRenderer, and then on to the JNI bridge over to native Flutter code.
private final SurfaceTextureListener surfaceTextureListener =
new SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(
SurfaceTexture surfaceTexture, int width, int height) {
Log.v(TAG, "SurfaceTextureListener.onSurfaceTextureAvailable()");
isSurfaceAvailableForRendering = true;
// If we're already attached to a FlutterRenderer then we're now attached to both a
// renderer
// and the Android window, so we can begin rendering now.
if (isAttachedToFlutterRenderer) {
connectSurfaceToRenderer();
}
}
@Override
public void onSurfaceTextureSizeChanged(
@NonNull SurfaceTexture surface, int width, int height) {
Log.v(TAG, "SurfaceTextureListener.onSurfaceTextureSizeChanged()");
if (isAttachedToFlutterRenderer) {
changeSurfaceSize(width, height);
}
}
@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
// Invoked every time a new frame is available. We don't care.
}
@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
Log.v(TAG, "SurfaceTextureListener.onSurfaceTextureDestroyed()");
isSurfaceAvailableForRendering = false;
// If we're attached to a FlutterRenderer then we need to notify it that our
// SurfaceTexture
// has been destroyed.
if (isAttachedToFlutterRenderer) {
disconnectSurfaceFromRenderer();
}
// Return true to indicate that no further painting will take place
// within this SurfaceTexture.
return true;
}
};
/** Constructs a {@code FlutterTextureView} programmatically, without any XML attributes. */
public FlutterTextureView(@NonNull Context context) {
this(context, null);
}
/** Constructs a {@code FlutterTextureView} in an XML-inflation-compliant manner. */
public FlutterTextureView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// Listen for when our underlying SurfaceTexture becomes available, changes size, or
// gets destroyed, and take the appropriate actions.
setSurfaceTextureListener(surfaceTextureListener);
}
@Nullable
@Override
public FlutterRenderer getAttachedRenderer() {
return flutterRenderer;
}
/**
* Invoked by the owner of this {@code FlutterTextureView} when it wants to begin rendering a
* Flutter UI to this {@code FlutterTextureView}.
*
* <p>If an Android {@link SurfaceTexture} is available, this method will give that {@link
* SurfaceTexture} to the given {@link FlutterRenderer} to begin rendering Flutter's UI to this
* {@code FlutterTextureView}.
*
* <p>If no Android {@link SurfaceTexture} is available yet, this {@code FlutterTextureView} will
* wait until a {@link SurfaceTexture} becomes available and then give that {@link SurfaceTexture}
* to the given {@link FlutterRenderer} to begin rendering Flutter's UI to this {@code
* FlutterTextureView}.
*/
public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
Log.v(TAG, "Attaching to FlutterRenderer.");
if (this.flutterRenderer != null) {
Log.v(
TAG,
"Already connected to a FlutterRenderer. Detaching from old one and attaching to new one.");
this.flutterRenderer.stopRenderingToSurface();
}
this.flutterRenderer = flutterRenderer;
isAttachedToFlutterRenderer = true;
// If we're already attached to an Android window then we're now attached to both a renderer
// and the Android window. We can begin rendering now.
if (isSurfaceAvailableForRendering) {
Log.v(
TAG,
"Surface is available for rendering. Connecting FlutterRenderer to Android surface.");
connectSurfaceToRenderer();
}
}
/**
* Invoked by the owner of this {@code FlutterTextureView} when it no longer wants to render a
* Flutter UI to this {@code FlutterTextureView}.
*
* <p>This method will cease any on-going rendering from Flutter to this {@code
* FlutterTextureView}.
*/
public void detachFromRenderer() {
if (flutterRenderer != null) {
// If we're attached to an Android window then we were rendering a Flutter UI. Now that
// this FlutterTextureView is detached from the FlutterRenderer, we need to stop rendering.
// TODO(mattcarroll): introduce a isRendererConnectedToSurface() to wrap "getWindowToken() !=
// null"
if (getWindowToken() != null) {
Log.v(TAG, "Disconnecting FlutterRenderer from Android surface.");
disconnectSurfaceFromRenderer();
}
flutterRenderer = null;
isAttachedToFlutterRenderer = false;
} else {
Log.w(TAG, "detachFromRenderer() invoked when no FlutterRenderer was attached.");
}
}
// FlutterRenderer and getSurfaceTexture() must both be non-null.
private void connectSurfaceToRenderer() {
if (flutterRenderer == null || getSurfaceTexture() == null) {
throw new IllegalStateException(
"connectSurfaceToRenderer() should only be called when flutterRenderer and getSurfaceTexture() are non-null.");
}
renderSurface = new Surface(getSurfaceTexture());
flutterRenderer.startRenderingToSurface(renderSurface);
}
// FlutterRenderer must be non-null.
private void changeSurfaceSize(int width, int height) {
if (flutterRenderer == null) {
throw new IllegalStateException(
"changeSurfaceSize() should only be called when flutterRenderer is non-null.");
}
Log.v(
TAG,
"Notifying FlutterRenderer that Android surface size has changed to "
+ width
+ " x "
+ height);
flutterRenderer.surfaceChanged(width, height);
}
// FlutterRenderer must be non-null.
private void disconnectSurfaceFromRenderer() {
if (flutterRenderer == null) {
throw new IllegalStateException(
"disconnectSurfaceFromRenderer() should only be called when flutterRenderer is non-null.");
}
flutterRenderer.stopRenderingToSurface();
renderSurface.release();
renderSurface = null;
}
}