From 72f2b18bb094f92f62a3113a8075240ebb59affa Mon Sep 17 00:00:00 2001 From: Gray Mackall <34871572+gmackall@users.noreply.github.com> Date: Tue, 8 Jul 2025 10:33:53 -0700 Subject: [PATCH] [stable] [android] release background image readers on <= Android 14 (#171737) CP of https://github.com/flutter/flutter/pull/171193. Template: - Impacted Users (Approximately who will hit this issue, ex. all Flutter devs, Windows developers, all end-customers, apps using X framework feature). All end users on Android 10-13 (inclusive) using an app with platform views. - Impact Description (What is the impact? ex. visual jank on Samsung phones, app crash, cannot ship an iOS app. Does it impact development? ex. flutter doctor crashes when Android Studio is installed. Or shipping a production app? ex. the app crashes on launch). Full app crash when backgrounding and then bringing the app back up. - Workaround (Is there a workaround for this issue?) No workaround. - Risk (What is the risk level of this cherry-pick?) Medium - Test Coverage (Are you confident that your fix is well-tested by automated tests?) No - Validation Steps (What are the steps to validate that this fix works?) Launch an app with platform views on these api levels and background them, and then bring the app back up. --- .../platform/PlatformViewsController.java | 4 +- .../platform/PlatformViewsControllerTest.java | 193 +++++++++++------- 2 files changed, 122 insertions(+), 75 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 c430192d1d..a8ec08ff5b 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 @@ -74,7 +74,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega private FlutterView flutterView; // 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; @@ -978,7 +978,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 d30731aba9..1a346fe7c7 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 @@ -107,7 +107,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,55 @@ 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(); + 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() { @@ -1572,95 +1618,96 @@ 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 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 boolean handlesCropAndRotation() { - return false; - } - - public void scheduleFrame() {} - }; - } - }; + }); platformViewsController.attach(context, registry, executor);