blob: 29443aeb63aae1fa0ac70a5f263f0cd03898524a [file] [log] [blame]
package io.flutter.plugin.platform;
import android.annotation.TargetApi;
import android.graphics.Canvas;
import android.graphics.ImageFormat;
import android.hardware.HardwareBuffer;
import android.media.Image;
import android.media.ImageReader;
import android.os.Build;
import android.os.Handler;
import android.view.Surface;
import io.flutter.Log;
import io.flutter.view.TextureRegistry.ImageTextureEntry;
@TargetApi(29)
public class ImageReaderPlatformViewRenderTarget implements PlatformViewRenderTarget {
private ImageTextureEntry textureEntry;
private ImageReader reader;
private int bufferWidth = 0;
private int bufferHeight = 0;
private static final String TAG = "ImageReaderPlatformViewRenderTarget";
private static final int MAX_IMAGES = 4;
private void closeReader() {
if (this.reader != null) {
// Push a null image, forcing the texture entry to close any cached images.
textureEntry.pushImage(null);
// Close the reader, which also closes any produced images.
this.reader.close();
this.reader = null;
}
}
private final Handler onImageAvailableHandler = new Handler();
private final ImageReader.OnImageAvailableListener onImageAvailableListener =
new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = null;
try {
image = reader.acquireLatestImage();
} catch (IllegalStateException e) {
Log.e(TAG, "onImageAvailable acquireLatestImage failed: " + e.toString());
}
if (image == null) {
return;
}
textureEntry.pushImage(image);
}
};
@TargetApi(33)
protected ImageReader createImageReader33() {
final ImageReader.Builder builder = new ImageReader.Builder(bufferWidth, bufferHeight);
// Allow for double buffering.
builder.setMaxImages(MAX_IMAGES);
// Use PRIVATE image format so that we can support video decoding.
// TODO(johnmccutchan): Should we always use PRIVATE here? It may impact our
// ability to read back texture data. If we don't always want to use it, how do
// we
// decide when to use it or not? Perhaps PlatformViews can indicate if they may
// contain
// DRM'd content.
// I need to investigate how PRIVATE impacts our ability to take screenshots or
// capture
// the output of Flutter application.
builder.setImageFormat(ImageFormat.PRIVATE);
// Hint that consumed images will only be read by GPU.
builder.setUsage(HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
final ImageReader reader = builder.build();
reader.setOnImageAvailableListener(this.onImageAvailableListener, onImageAvailableHandler);
return reader;
}
@TargetApi(29)
protected ImageReader createImageReader29() {
final ImageReader reader =
ImageReader.newInstance(
bufferWidth,
bufferHeight,
ImageFormat.PRIVATE,
MAX_IMAGES,
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
reader.setOnImageAvailableListener(this.onImageAvailableListener, onImageAvailableHandler);
return reader;
}
protected ImageReader createImageReader() {
if (Build.VERSION.SDK_INT >= 33) {
return createImageReader33();
} else if (Build.VERSION.SDK_INT >= 29) {
return createImageReader29();
}
throw new UnsupportedOperationException(
"ImageReaderPlatformViewRenderTarget requires API version 29+");
}
public ImageReaderPlatformViewRenderTarget(ImageTextureEntry textureEntry) {
if (Build.VERSION.SDK_INT < 29) {
throw new UnsupportedOperationException(
"ImageReaderPlatformViewRenderTarget requires API version 29+");
}
this.textureEntry = textureEntry;
}
public void resize(int width, int height) {
if (this.reader != null && bufferWidth == width && bufferHeight == height) {
// No size change.
return;
}
closeReader();
bufferWidth = width;
bufferHeight = height;
this.reader = createImageReader();
}
public int getWidth() {
return this.bufferWidth;
}
public int getHeight() {
return this.bufferHeight;
}
public Canvas lockHardwareCanvas() {
return getSurface().lockHardwareCanvas();
}
public void unlockCanvasAndPost(Canvas canvas) {
getSurface().unlockCanvasAndPost(canvas);
}
public long getId() {
return this.textureEntry.id();
}
public void release() {
closeReader();
// textureEntry has a finalizer attached.
textureEntry = null;
}
public boolean isReleased() {
return this.textureEntry == null;
}
public Surface getSurface() {
return this.reader.getSurface();
}
}