blob: deff5834aadfe9dd79d62bdcf4f2cf6bf173a68b [file] [log] [blame]
package io.flutter.plugin.platform;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
import android.annotation.TargetApi;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.SurfaceTexture;
import android.os.Build;
import android.view.Surface;
import io.flutter.Log;
import io.flutter.view.TextureRegistry;
import io.flutter.view.TextureRegistry.SurfaceTextureEntry;
import java.util.concurrent.atomic.AtomicLong;
@TargetApi(26)
public class SurfaceTexturePlatformViewRenderTarget implements PlatformViewRenderTarget {
private static final String TAG = "SurfaceTexturePlatformViewRenderTarget";
private final AtomicLong pendingFramesCount = new AtomicLong(0L);
private void onFrameProduced() {
if (Build.VERSION.SDK_INT == 29) {
pendingFramesCount.incrementAndGet();
}
}
private final SurfaceTextureEntry surfaceTextureEntry;
private SurfaceTexture surfaceTexture;
private Surface surface;
private int bufferWidth = 0;
private int bufferHeight = 0;
private final TextureRegistry.OnFrameConsumedListener frameConsumedListener =
new TextureRegistry.OnFrameConsumedListener() {
@Override
public void onFrameConsumed() {
if (Build.VERSION.SDK_INT == 29) {
pendingFramesCount.decrementAndGet();
}
}
};
private boolean shouldRecreateSurfaceForLowMemory = false;
private final TextureRegistry.OnTrimMemoryListener trimMemoryListener =
new TextureRegistry.OnTrimMemoryListener() {
@Override
public void onTrimMemory(int level) {
// When a memory pressure warning is received and the level equal {@code
// ComponentCallbacks2.TRIM_MEMORY_COMPLETE}, the Android system releases the
// underlying
// surface. If we continue to use the surface (e.g., call lockHardwareCanvas), a
// crash
// occurs, and we found that this crash appeared on Android10 and above.
// See https://github.com/flutter/flutter/issues/103870 for more details.
//
// Here our workaround is to recreate the surface before using it.
if (level == TRIM_MEMORY_COMPLETE && Build.VERSION.SDK_INT >= 29) {
shouldRecreateSurfaceForLowMemory = true;
}
}
};
private void recreateSurfaceIfNeeded() {
if (!shouldRecreateSurfaceForLowMemory) {
return;
}
if (surface != null) {
surface.release();
surface = null;
}
surface = createSurface();
shouldRecreateSurfaceForLowMemory = false;
}
protected Surface createSurface() {
return new Surface(surfaceTexture);
}
private void init() {
if (bufferWidth > 0 && bufferHeight > 0) {
surfaceTexture.setDefaultBufferSize(bufferWidth, bufferHeight);
}
if (surface != null) {
surface.release();
surface = null;
}
surface = createSurface();
// Fill the entire canvas with a transparent color.
// As a result, the background color of the platform view container is displayed
// to the user until the platform view draws its first frame.
final Canvas canvas = lockHardwareCanvas();
try {
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
} finally {
unlockCanvasAndPost(canvas);
}
}
/** Implementation of PlatformViewRenderTarget */
public SurfaceTexturePlatformViewRenderTarget(SurfaceTextureEntry surfaceTextureEntry) {
if (Build.VERSION.SDK_INT < 23) {
throw new UnsupportedOperationException(
"Platform views cannot be displayed below API level 23"
+ "You can prevent this issue by setting `minSdkVersion: 23` in build.gradle.");
}
this.surfaceTextureEntry = surfaceTextureEntry;
this.surfaceTexture = surfaceTextureEntry.surfaceTexture();
surfaceTextureEntry.setOnFrameConsumedListener(frameConsumedListener);
surfaceTextureEntry.setOnTrimMemoryListener(trimMemoryListener);
init();
}
public Canvas lockHardwareCanvas() {
recreateSurfaceIfNeeded();
// We've observed on Android Q that we have to wait for the consumer of {@link
// SurfaceTexture}
// to consume the last image before continuing to draw, otherwise subsequent
// calls to
// {@code dequeueBuffer} to request a free buffer from the {@link BufferQueue}
// will fail.
// See https://github.com/flutter/flutter/issues/98722
if (Build.VERSION.SDK_INT == 29 && pendingFramesCount.get() > 0L) {
return null;
}
if (surfaceTexture == null || surfaceTexture.isReleased()) {
Log.e(TAG, "Invalid RenderTarget: null or already released SurfaceTexture");
return null;
}
onFrameProduced();
return surface.lockHardwareCanvas();
}
public void unlockCanvasAndPost(Canvas canvas) {
surface.unlockCanvasAndPost(canvas);
}
public void resize(int width, int height) {
bufferWidth = width;
bufferHeight = height;
if (surfaceTexture != null) {
surfaceTexture.setDefaultBufferSize(bufferWidth, bufferHeight);
}
}
public int getWidth() {
return bufferWidth;
}
public int getHeight() {
return bufferHeight;
}
public long getId() {
return this.surfaceTextureEntry.id();
}
public boolean isReleased() {
return surfaceTexture == null;
}
public void release() {
// Don't release the texture, let the GC finalize it.
surfaceTexture = null;
if (surface != null) {
surface.release();
surface = null;
}
}
public Surface getSurface() {
recreateSurfaceIfNeeded();
return surface;
}
}