diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index eb2713c557b90..d07a4edc56d6f 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -690,6 +690,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/Flutt FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterEngineProvider.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterSplashView.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterTextureView.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index fc33b1ce4b474..7bd8b22322684 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -138,6 +138,7 @@ android_java_sources = [ "io/flutter/embedding/android/FlutterEngineProvider.java", "io/flutter/embedding/android/FlutterFragment.java", "io/flutter/embedding/android/FlutterFragmentActivity.java", + "io/flutter/embedding/android/FlutterImageView.java", "io/flutter/embedding/android/FlutterSplashView.java", "io/flutter/embedding/android/FlutterSurfaceView.java", "io/flutter/embedding/android/FlutterTextureView.java", diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java b/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java new file mode 100644 index 0000000000000..48863f0d42766 --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/android/FlutterImageView.java @@ -0,0 +1,106 @@ +// 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.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ColorSpace; +import android.hardware.HardwareBuffer; +import android.media.Image; +import android.media.Image.Plane; +import android.media.ImageReader; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Paints a Flutter UI provided by an {@link android.media.ImageReader} onto a {@link + * android.graphics.Canvas}. + * + *
A {@code FlutterImageView} is intended for situations where a developer needs to render a + * Flutter UI, but also needs to render an interactive {@link + * io.flutter.plugin.platform.PlatformView}. + * + *
This {@code View} takes an {@link android.media.ImageReader} that provides the Flutter UI in
+ * an {@link android.media.Image} and renders it to the {@link android.graphics.Canvas} in {@code
+ * onDraw}.
+ */
+@SuppressLint("ViewConstructor")
+@TargetApi(19)
+public class FlutterImageView extends View {
+ private final ImageReader imageReader;
+ @Nullable private Image nextImage;
+ @Nullable private Image currentImage;
+
+ /**
+ * Constructs a {@code FlutterImageView} with an {@link android.media.ImageReader} that provides
+ * the Flutter UI.
+ */
+ public FlutterImageView(@NonNull Context context, @NonNull ImageReader imageReader) {
+ super(context, null);
+ this.imageReader = imageReader;
+ }
+
+ /** Acquires the next image to be drawn to the {@link android.graphics.Canvas}. */
+ @TargetApi(19)
+ public void acquireLatestImage() {
+ nextImage = imageReader.acquireLatestImage();
+ invalidate();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (nextImage == null) {
+ return;
+ }
+
+ if (currentImage != null) {
+ currentImage.close();
+ }
+ currentImage = nextImage;
+ nextImage = null;
+
+ if (android.os.Build.VERSION.SDK_INT >= 29) {
+ drawImageBuffer(canvas);
+ return;
+ }
+
+ 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;
+ }
+
+ 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 c05b37bdb4e67..2fd22da61d4e1 100644
--- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java
+++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java
@@ -79,6 +79,7 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC
// Internal view hierarchy references.
@Nullable private FlutterSurfaceView flutterSurfaceView;
@Nullable private FlutterTextureView flutterTextureView;
+ @Nullable private FlutterImageView flutterImageView;
@Nullable private RenderSurface renderSurface;
private final Set {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context} to be
+ * compatible with {@link PlatformViewsController}.
+ */
+ @TargetApi(19)
+ public FlutterView(@NonNull Context context, @NonNull FlutterImageView flutterImageView) {
+ this(context, null, flutterImageView);
+ }
+
/**
* Constructs a {@code FlutterView} in an XML-inflation-compliant manner.
*
@@ -243,9 +260,12 @@ public FlutterView(
flutterSurfaceView =
new FlutterSurfaceView(context, transparencyMode == TransparencyMode.transparent);
renderSurface = flutterSurfaceView;
- } else {
+ } else if (renderMode == RenderMode.texture) {
flutterTextureView = new FlutterTextureView(context);
renderSurface = flutterTextureView;
+ } else {
+ throw new IllegalArgumentException(
+ String.format("RenderMode not supported with this constructor: ", renderMode));
}
init();
@@ -275,15 +295,30 @@ private FlutterView(
init();
}
+ @TargetApi(19)
+ private FlutterView(
+ @NonNull Context context,
+ @Nullable AttributeSet attrs,
+ @NonNull FlutterImageView flutterImageView) {
+ super(context, attrs);
+
+ this.flutterImageView = flutterImageView;
+
+ init();
+ }
+
private void init() {
Log.v(TAG, "Initializing FlutterView");
if (flutterSurfaceView != null) {
Log.v(TAG, "Internally using a FlutterSurfaceView.");
addView(flutterSurfaceView);
- } else {
+ } else if (flutterTextureView != null) {
Log.v(TAG, "Internally using a FlutterTextureView.");
addView(flutterTextureView);
+ } else {
+ Log.v(TAG, "Internally using a FlutterImageView.");
+ addView(flutterImageView);
}
// FlutterView needs to be focusable so that the InputMethodManager can interact with it.
@@ -1018,7 +1053,16 @@ public enum RenderMode {
* android.graphics.SurfaceTexture} are required, developers should strongly prefer the {@link
* RenderMode#surface} render mode.
*/
- texture
+ texture,
+ /**
+ * {@code RenderMode}, which paints Paints a Flutter UI provided by an {@link
+ * android.media.ImageReader} onto a {@link android.graphics.Canvas}. This mode is not as
+ * performant as {@link RenderMode#surface}, but a {@code FlutterView} in this mode can handle
+ * full interactivity with a {@link io.flutter.plugin.platform.PlatformView}. Unless {@link
+ * io.flutter.plugin.platform.PlatformView}s are required developers should strongly prefer the
+ * {@link RenderMode#surface} render mode.
+ */
+ image
}
/**
diff --git a/shell/platform/android/io/flutter/embedding/android/RenderMode.java b/shell/platform/android/io/flutter/embedding/android/RenderMode.java
index 3c0907bb850a3..2471396178aec 100644
--- a/shell/platform/android/io/flutter/embedding/android/RenderMode.java
+++ b/shell/platform/android/io/flutter/embedding/android/RenderMode.java
@@ -21,5 +21,14 @@ public enum RenderMode {
* Views}. Unless the special capabilities of a {@link android.graphics.SurfaceTexture} are
* required, developers should strongly prefer the {@link #surface} render mode.
*/
- texture
+ texture,
+ /**
+ * {@code RenderMode}, which paints Paints a Flutter UI provided by an {@link
+ * android.media.ImageReader} onto a {@link android.graphics.Canvas}. This mode is not as
+ * performant as {@link RenderMode#surface}, but a {@code FlutterView} in this mode can handle
+ * full interactivity with a {@link io.flutter.plugin.platform.PlatformView}. Unless {@link
+ * io.flutter.plugin.platform.PlatformView}s are required developers should strongly prefer the
+ * {@link RenderMode#surface} render mode.
+ */
+ image
}
diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java
index 7461674f243b0..ad29381ef273d 100644
--- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java
+++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java
@@ -12,6 +12,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.media.ImageReader;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -370,6 +371,17 @@ public void systemInsetHandlesFullscreenNavbarLeft() {
assertEquals(100, viewportMetricsCaptor.getValue().paddingRight);
}
+ @Test
+ public void flutterImageView_acquiresImageAndInvalidates() {
+ final ImageReader mockReader = mock(ImageReader.class);
+ final FlutterImageView imageView =
+ spy(new FlutterImageView(RuntimeEnvironment.application, mockReader));
+
+ imageView.acquireLatestImage();
+ verify(mockReader, times(1)).acquireLatestImage();
+ verify(imageView, times(1)).invalidate();
+ }
+
/*
* A custom shadow that reports fullscreen flag for system UI visibility
*/