From df21e0b0ab88bf64de51ceca8e8b6fd9f08678b3 Mon Sep 17 00:00:00 2001 From: Gray Mackall <34871572+gmackall@users.noreply.github.com> Date: Mon, 7 Jul 2025 14:05:12 -0700 Subject: [PATCH] [android] release background image readers on <= Android 14 (#171193) Fixes the version of https://github.com/flutter/flutter/issues/162147 that repros on 3.32 (but not the version that the original submitter experiences on 3.27 - I've not been able to reproduce that issue). See https://github.com/flutter/flutter/issues/162147#issuecomment-3006117342 Result of bisecting https://github.com/flutter/flutter/issues/162147 to https://github.com/flutter/flutter/pull/165942 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Gray Mackall --- .../platform/PlatformViewsController.java | 4 +- .../platform/PlatformViewsControllerTest.java | 204 +++++++++++------- 2 files changed, 128 insertions(+), 80 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index c60dbe6051b..de15f569772 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -78,7 +78,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega private FlutterJNI flutterJNI = null; // The texture registry maintaining the textures into which the embedded views will be rendered. - @Nullable private TextureRegistry textureRegistry; + @VisibleForTesting @Nullable TextureRegistry textureRegistry; @Nullable private TextInputPlugin textInputPlugin; @@ -999,7 +999,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega TextureRegistry textureRegistry) { if (enableSurfaceProducerRenderTarget && Build.VERSION.SDK_INT >= API_LEVELS.API_29) { TextureRegistry.SurfaceLifecycle lifecycle = - Build.VERSION.SDK_INT == API_LEVELS.API_34 + Build.VERSION.SDK_INT <= API_LEVELS.API_34 ? TextureRegistry.SurfaceLifecycle.resetInBackground : TextureRegistry.SurfaceLifecycle.manual; final TextureRegistry.SurfaceProducer textureEntry = diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java index 6ab5e056310..3de266c00cd 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -106,7 +106,9 @@ public class PlatformViewsControllerTest { } @Test - @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class}) + @Config( + shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class}, + minSdk = 35) public void itRemovesPlatformViewBeforeDiposeIsCalled() { PlatformViewsController platformViewsController = new PlatformViewsController(); FlutterJNI jni = new FlutterJNI(); @@ -141,11 +143,56 @@ public class PlatformViewsControllerTest { assertTrue(pView instanceof CountingPlatformView); CountingPlatformView cpv = (CountingPlatformView) pView; platformViewsController.configureForTextureLayerComposition(pView, request); + verify(platformViewsController.textureRegistry, times(1)) + .createSurfaceProducer(TextureRegistry.SurfaceLifecycle.manual); assertEquals(0, cpv.disposeCalls); platformViewsController.disposePlatformView(viewId); assertEquals(1, cpv.disposeCalls); } + @Test + @Config( + shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class}, + minSdk = 29, + maxSdk = 34) + public void itPassesSurfaceLifecyleResetInBackgroundLeqApi34() { + PlatformViewsController platformViewsController = new PlatformViewsController(); + FlutterJNI jni = new FlutterJNI(); + platformViewsController.setFlutterJNI(jni); + attach(jni, platformViewsController); + // Get the platform view registry. + PlatformViewRegistry registry = platformViewsController.getRegistry(); + + // Register a factory for our platform view. + registry.registerViewFactory( + CountingPlatformView.VIEW_TYPE_ID, + new PlatformViewFactory(StandardMessageCodec.INSTANCE) { + @Override + public PlatformView create(Context context, int viewId, Object args) { + return new CountingPlatformView(context); + } + }); + + // Create the platform view. + int viewId = 0; + final PlatformViewsChannel.PlatformViewCreationRequest request = + new PlatformViewsChannel.PlatformViewCreationRequest( + viewId, + CountingPlatformView.VIEW_TYPE_ID, + 0, + 0, + 128, + 128, + View.LAYOUT_DIRECTION_LTR, + null); + PlatformView pView = platformViewsController.createPlatformView(request, true); + assertTrue(pView instanceof CountingPlatformView); + CountingPlatformView cpv = (CountingPlatformView) pView; + platformViewsController.configureForTextureLayerComposition(pView, request); + verify(platformViewsController.textureRegistry, times(1)) + .createSurfaceProducer(TextureRegistry.SurfaceLifecycle.resetInBackground); + } + @Test @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class}) public void itNotifiesPlatformViewsOfEngineAttachmentAndDetachment() { @@ -1609,100 +1656,101 @@ public class PlatformViewsControllerTest { final Context context = ApplicationProvider.getApplicationContext(); final TextureRegistry registry = - new TextureRegistry() { - public void TextureRegistry() {} + spy( + new TextureRegistry() { + public void TextureRegistry() {} - @NonNull - @Override - public SurfaceTextureEntry createSurfaceTexture() { - return registerSurfaceTexture(mock(SurfaceTexture.class)); - } - - @NonNull - @Override - public SurfaceTextureEntry registerSurfaceTexture( - @NonNull SurfaceTexture surfaceTexture) { - return new SurfaceTextureEntry() { @NonNull @Override - public SurfaceTexture surfaceTexture() { - return mock(SurfaceTexture.class); + public SurfaceTextureEntry createSurfaceTexture() { + return registerSurfaceTexture(mock(SurfaceTexture.class)); } + @NonNull @Override - public long id() { - return 0; + public SurfaceTextureEntry registerSurfaceTexture( + @NonNull SurfaceTexture surfaceTexture) { + return new SurfaceTextureEntry() { + @NonNull + @Override + public SurfaceTexture surfaceTexture() { + return mock(SurfaceTexture.class); + } + + @Override + public long id() { + return 0; + } + + @Override + public void release() {} + }; } + @NonNull @Override - public void release() {} - }; - } + public ImageTextureEntry createImageTexture() { + return new ImageTextureEntry() { + @Override + public long id() { + return 0; + } - @NonNull - @Override - public ImageTextureEntry createImageTexture() { - return new ImageTextureEntry() { - @Override - public long id() { - return 0; + @Override + public void release() {} + + @Override + public void pushImage(Image image) {} + }; } + @NonNull @Override - public void release() {} + public SurfaceProducer createSurfaceProducer(SurfaceLifecycle lifecycle) { + return new SurfaceProducer() { + @Override + public void setCallback(SurfaceProducer.Callback cb) {} - @Override - public void pushImage(Image image) {} - }; - } + @Override + public long id() { + return 0; + } - @NonNull - @Override - public SurfaceProducer createSurfaceProducer(SurfaceLifecycle lifecycle) { - return new SurfaceProducer() { - @Override - public void setCallback(SurfaceProducer.Callback cb) {} + @Override + public void release() {} - @Override - public long id() { - return 0; + @Override + public int getWidth() { + return 0; + } + + @Override + public int getHeight() { + return 0; + } + + @Override + public void setSize(int width, int height) {} + + @Override + public Surface getSurface() { + return null; + } + + @Override + public Surface getForcedNewSurface() { + return null; + } + + @Override + public boolean handlesCropAndRotation() { + return false; + } + + public void scheduleFrame() {} + }; } - - @Override - public void release() {} - - @Override - public int getWidth() { - return 0; - } - - @Override - public int getHeight() { - return 0; - } - - @Override - public void setSize(int width, int height) {} - - @Override - public Surface getSurface() { - return null; - } - - @Override - public Surface getForcedNewSurface() { - return null; - } - - @Override - public boolean handlesCropAndRotation() { - return false; - } - - public void scheduleFrame() {} - }; - } - }; + }); platformViewsController.attach(context, registry, executor);