diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java b/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java index 48863f0d42766..6db0d93d0783c 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java @@ -17,6 +17,8 @@ import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import io.flutter.embedding.engine.renderer.FlutterRenderer; +import io.flutter.embedding.engine.renderer.RenderSurface; /** * Paints a Flutter UI provided by an {@link android.media.ImageReader} onto a {@link @@ -32,10 +34,12 @@ */ @SuppressLint("ViewConstructor") @TargetApi(19) -public class FlutterImageView extends View { +public class FlutterImageView extends View implements RenderSurface { private final ImageReader imageReader; @Nullable private Image nextImage; @Nullable private Image currentImage; + @Nullable private Bitmap currentBitmap; + @Nullable private FlutterRenderer flutterRenderer; /** * Constructs a {@code FlutterImageView} with an {@link android.media.ImageReader} that provides @@ -46,6 +50,37 @@ public FlutterImageView(@NonNull Context context, @NonNull ImageReader imageRead this.imageReader = imageReader; } + @Nullable + @Override + public FlutterRenderer getAttachedRenderer() { + return flutterRenderer; + } + + /** + * Invoked by the owner of this {@code FlutterImageView} when it wants to begin rendering a + * Flutter UI to this {@code FlutterImageView}. + */ + @Override + public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) { + if (this.flutterRenderer != null) { + this.flutterRenderer.stopRenderingToSurface(); + } + + this.flutterRenderer = flutterRenderer; + flutterRenderer.startRenderingToSurface(imageReader.getSurface()); + } + + /** + * Invoked by the owner of this {@code FlutterImageView} when it no longer wants to render a + * Flutter UI to this {@code FlutterImageView}. + */ + public void detachFromRenderer() { + if (flutterRenderer != null) { + flutterRenderer.stopRenderingToSurface(); + flutterRenderer = null; + } + } + /** Acquires the next image to be drawn to the {@link android.graphics.Canvas}. */ @TargetApi(19) public void acquireLatestImage() { @@ -56,51 +91,44 @@ public void acquireLatestImage() { @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - if (nextImage == null) { - return; - } - - if (currentImage != null) { - currentImage.close(); + if (nextImage != null) { + if (currentImage != null) { + currentImage.close(); + } + currentImage = nextImage; + nextImage = null; + updateCurrentBitmap(); } - currentImage = nextImage; - nextImage = null; - if (android.os.Build.VERSION.SDK_INT >= 29) { - drawImageBuffer(canvas); - return; + if (currentBitmap != null) { + canvas.drawBitmap(currentBitmap, 0, 0, null); } - - drawImagePlane(canvas); } @TargetApi(29) - private void drawImageBuffer(@NonNull Canvas canvas) { - final HardwareBuffer buffer = currentImage.getHardwareBuffer(); - - final Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB)); - canvas.drawBitmap(bitmap, 0, 0, null); - } - - private void drawImagePlane(@NonNull Canvas canvas) { - if (currentImage == null) { - return; - } - - final Plane[] imagePlanes = currentImage.getPlanes(); - if (imagePlanes.length != 1) { - return; + private void updateCurrentBitmap() { + if (android.os.Build.VERSION.SDK_INT >= 29) { + final HardwareBuffer buffer = currentImage.getHardwareBuffer(); + currentBitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB)); + } else { + final Plane[] imagePlanes = currentImage.getPlanes(); + if (imagePlanes.length != 1) { + return; + } + + final Plane imagePlane = imagePlanes[0]; + final int desiredWidth = imagePlane.getRowStride() / imagePlane.getPixelStride(); + final int desiredHeight = currentImage.getHeight(); + + if (currentBitmap == null + || currentBitmap.getWidth() != desiredWidth + || currentBitmap.getHeight() != desiredHeight) { + currentBitmap = + Bitmap.createBitmap( + desiredWidth, desiredHeight, android.graphics.Bitmap.Config.ARGB_8888); + } + + currentBitmap.copyPixelsFromBuffer(imagePlane.getBuffer()); } - - final Plane imagePlane = imagePlanes[0]; - final int desiredWidth = imagePlane.getRowStride() / imagePlane.getPixelStride(); - final int desiredHeight = currentImage.getHeight(); - - final Bitmap bitmap = - android.graphics.Bitmap.createBitmap( - desiredWidth, desiredHeight, android.graphics.Bitmap.Config.ARGB_8888); - - bitmap.copyPixelsFromBuffer(imagePlane.getBuffer()); - canvas.drawBitmap(bitmap, 0, 0, null); } } diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 7da7a5f06f0be..043097bc40a11 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -10,6 +10,7 @@ import android.content.res.Configuration; import android.graphics.Insets; import android.graphics.Rect; +import android.media.ImageReader; import android.os.Build; import android.text.format.DateFormat; import android.util.AttributeSet; @@ -303,6 +304,7 @@ private FlutterView( super(context, attrs); this.flutterImageView = flutterImageView; + this.renderSurface = flutterImageView; init(); } @@ -940,6 +942,32 @@ public void detachFromFlutterEngine() { flutterEngine = null; } + public void convertToImageView() { + renderSurface.detachFromRenderer(); + + ImageReader imageReader = PlatformViewsController.createImageReader(getWidth(), getHeight()); + flutterImageView = new FlutterImageView(getContext(), imageReader); + renderSurface = flutterImageView; + if (flutterEngine != null) { + renderSurface.attachToRenderer(flutterEngine.getRenderer()); + } + + removeAllViews(); + addView(flutterImageView); + + // TODO(jsimmons): this is a temporary hack that schedules a redraw of the FlutterImageView + // at a time when the engine has presumably posted a frame. Remove this when + // PlatformViewsController.onEndFrame callbacks have been implemented. + postDelayed( + new Runnable() { + public void run() { + flutterImageView.acquireLatestImage(); + flutterImageView.invalidate(); + } + }, + 1000); + } + /** Returns true if this {@code FlutterView} is currently attached to a {@link FlutterEngine}. */ @VisibleForTesting public boolean isAttachedToFlutterEngine() { diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 6b70ebd57ca2a..e0a409d8daa12 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -22,6 +22,7 @@ import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; import io.flutter.embedding.android.FlutterImageView; +import io.flutter.embedding.android.FlutterView; import io.flutter.embedding.engine.FlutterOverlaySurface; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.systemchannels.PlatformViewsChannel; @@ -79,9 +80,12 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega // Map of unique IDs to views that render overlay layers. private final LongSparseArray overlayLayerViews; - // Next available unique ID for use in overlayLayerViews; + // Next available unique ID for use in overlayLayerViews. private long nextOverlayLayerId = 0; + // Tracks whether the flutterView has been converted to use a FlutterImageView. + private boolean flutterViewConvertedToImageView = false; + private final PlatformViewsChannel.PlatformViewsHandler channelHandler = new PlatformViewsChannel.PlatformViewsHandler() { @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @@ -552,7 +556,10 @@ public void onDisplayPlatformView(int viewId, int x, int y, int width, int heigh } public void onDisplayOverlaySurface(int id, int x, int y, int width, int height) { - // TODO: Implement this method. https://github.com/flutter/flutter/issues/58288 + if (!flutterViewConvertedToImageView) { + ((FlutterView) flutterView).convertToImageView(); + flutterViewConvertedToImageView = true; + } } public void onBeginFrame() { @@ -564,22 +571,22 @@ public void onEndFrame() { } @TargetApi(19) - public FlutterOverlaySurface createOverlaySurface() { - ImageReader imageReader; + public static ImageReader createImageReader(int width, int height) { if (android.os.Build.VERSION.SDK_INT >= 29) { - imageReader = - ImageReader.newInstance( - flutterView.getWidth(), - flutterView.getHeight(), - PixelFormat.RGBA_8888, - 2, - HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT); + return ImageReader.newInstance( + width, + height, + PixelFormat.RGBA_8888, + 2, + HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT); } else { - imageReader = - ImageReader.newInstance( - flutterView.getWidth(), flutterView.getHeight(), PixelFormat.RGBA_8888, 2); + return ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2); } + } + @TargetApi(19) + public FlutterOverlaySurface createOverlaySurface() { + ImageReader imageReader = createImageReader(flutterView.getWidth(), flutterView.getHeight()); FlutterImageView imageView = new FlutterImageView(flutterView.getContext(), imageReader); long id = nextOverlayLayerId++; overlayLayerViews.put(id, imageView);