diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 532e692358981..7edaf4932fbb4 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -756,6 +756,11 @@ public void setCallback(Callback callback) { this.callback = callback; } + @Override + public boolean handlesCropAndRotation() { + return false; + } + @Override public long id() { return id; diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducer.java index cefd5774a117e..6f522a65c375e 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducer.java @@ -60,6 +60,11 @@ public void setCallback(Callback callback) { // Intentionally blank: SurfaceTextures don't get platform notifications or cleanup. } + @Override + public boolean handlesCropAndRotation() { + return true; + } + @Override @NonNull public SurfaceTexture getSurfaceTexture() { diff --git a/shell/platform/android/io/flutter/view/TextureRegistry.java b/shell/platform/android/io/flutter/view/TextureRegistry.java index caeb799d7598a..c9c9836bf20d6 100644 --- a/shell/platform/android/io/flutter/view/TextureRegistry.java +++ b/shell/platform/android/io/flutter/view/TextureRegistry.java @@ -124,6 +124,28 @@ interface Callback { /** This method is not officially part of the public API surface and will be deprecated. */ void scheduleFrame(); + + /** + * Returns whether the current rendering path handles crop and rotation metadata. + * + *

On most newer Android devices (API 29+), a {@link android.media.ImageReader} backend is + * used, which has more features, works in new graphic backends directly (such as Impeller's + * Vulkan backend), and is the Android recommended solution. However, crop and rotation metadata + * are not handled automatically, and require plugin authors to make + * appropriate changes ({@see https://github.com/flutter/flutter/issues/144407}). + * + *

{@code
+     * void example(SurfaceProducer producer) {
+     *   bool supported = producer.handlesCropAndRotation();
+     *   if (!supported) {
+     *       // Manually rotate/crop, either in the Android plugin or in the Dart framework layer.
+     *   }
+     * }
+     * }
+ * + * @return {@code true} if crop and rotation is handled automatically, {@code false} otherwise. + */ + boolean handlesCropAndRotation(); } /** A registry entry for a managed SurfaceTexture. */ diff --git a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java index 0ed6a4754a589..7ab861ab40854 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java @@ -7,8 +7,10 @@ import static android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; @@ -735,6 +737,27 @@ public void SurfaceTextureSurfaceProducerCreatesAConnectedTexture() { } } + @Test + public void SurfaceTextureSurfaceProducerDoesNotCropOrRotate() { + try { + FlutterRenderer.debugForceSurfaceProducerGlTextures = true; + FlutterRenderer flutterRenderer = engineRule.getFlutterEngine().getRenderer(); + TextureRegistry.SurfaceProducer producer = flutterRenderer.createSurfaceProducer(); + + assertTrue(producer.handlesCropAndRotation()); + } finally { + FlutterRenderer.debugForceSurfaceProducerGlTextures = false; + } + } + + @Test + public void ImageReaderSurfaceProducerDoesNotCropOrRotate() { + FlutterRenderer flutterRenderer = engineRule.getFlutterEngine().getRenderer(); + TextureRegistry.SurfaceProducer producer = flutterRenderer.createSurfaceProducer(); + + assertFalse(producer.handlesCropAndRotation()); + } + @Test public void ImageReaderSurfaceProducerIsDestroyedOnTrimMemory() { FlutterRenderer flutterRenderer = engineRule.getFlutterEngine().getRenderer(); diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java index 310f5fe051f6f..f9ebf55087fd4 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -1651,6 +1651,11 @@ public Surface getSurface() { return null; } + @Override + public boolean handlesCropAndRotation() { + return false; + } + public void scheduleFrame() {} }; }