diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 781328b38771c..862c63d9de9db 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -59,7 +59,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega private View flutterView; // The texture registry maintaining the textures into which the embedded views will be rendered. - private TextureRegistry textureRegistry; + @Nullable private TextureRegistry textureRegistry; @Nullable private TextInputPlugin textInputPlugin; @@ -79,7 +79,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega // Since each virtual display has it's unique context this allows associating any view with the // platform view that // it is associated with(e.g if a platform view creates other views in the same virtual display. - private final HashMap contextToPlatformView; + @VisibleForTesting /* package */ final HashMap contextToPlatformView; // The views returned by `PlatformView#getView()`. // @@ -711,6 +711,10 @@ private void flushAllViews() { while (platformViews.size() > 0) { channelHandler.disposeAndroidViewForPlatformView(platformViews.keyAt(0)); } + + if (contextToPlatformView.size() > 0) { + contextToPlatformView.clear(); + } } private void initializeRootImageViewIfNeeded() { 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 9f3b93fc882aa..18ffe1638444a 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -7,6 +7,7 @@ import android.content.Context; import android.content.res.AssetManager; +import android.graphics.SurfaceTexture; import android.util.SparseArray; import android.view.MotionEvent; import android.view.Surface; @@ -14,6 +15,7 @@ import android.view.SurfaceView; import android.view.View; import android.view.ViewParent; +import android.widget.FrameLayout.LayoutParams; import io.flutter.embedding.android.FlutterImageView; import io.flutter.embedding.android.FlutterView; import io.flutter.embedding.android.MotionEventTracker; @@ -32,6 +34,7 @@ import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.StandardMethodCodec; import io.flutter.plugin.localization.LocalizationPlugin; +import io.flutter.view.TextureRegistry; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; @@ -232,7 +235,7 @@ public void getPlatformViewById__hybridComposition() { attach(jni, platformViewsController); // Simulate create call from the framework. - createPlatformView(jni, platformViewsController, platformViewId, "testType"); + createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); platformViewsController.initializePlatformViewIfNeeded(platformViewId); @@ -259,7 +262,7 @@ public void createPlatformViewMessage__initializesAndroidView() { attach(jni, platformViewsController); // Simulate create call from the framework. - createPlatformView(jni, platformViewsController, platformViewId, "testType"); + createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); verify(viewFactory, times(1)).create(any(), eq(platformViewId), any()); } @@ -281,7 +284,7 @@ public void createPlatformViewMessage__throwsIfViewIsNull() { attach(jni, platformViewsController); // Simulate create call from the framework. - createPlatformView(jni, platformViewsController, platformViewId, "testType"); + createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); assertEquals(ShadowFlutterJNI.getResponses().size(), 1); assertThrows( @@ -291,6 +294,66 @@ public void createPlatformViewMessage__throwsIfViewIsNull() { }); } + @Test + @Config(shadows = {ShadowFlutterJNI.class}) + public void onDetachedFromJNI_clearsPlatformViewContext() { + PlatformViewsController platformViewsController = new PlatformViewsController(); + + int platformViewId = 0; + assertNull(platformViewsController.getPlatformViewById(platformViewId)); + + PlatformViewFactory viewFactory = mock(PlatformViewFactory.class); + PlatformView platformView = mock(PlatformView.class); + + View pv = mock(View.class); + when(pv.getLayoutParams()).thenReturn(new LayoutParams(1, 1)); + + when(platformView.getView()).thenReturn(pv); + when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView); + platformViewsController.getRegistry().registerViewFactory("testType", viewFactory); + + FlutterJNI jni = new FlutterJNI(); + attach(jni, platformViewsController); + + // Simulate create call from the framework. + createPlatformView( + jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ false); + + assertFalse(platformViewsController.contextToPlatformView.isEmpty()); + platformViewsController.onDetachedFromJNI(); + assertTrue(platformViewsController.contextToPlatformView.isEmpty()); + } + + @Test + @Config(shadows = {ShadowFlutterJNI.class}) + public void onPreEngineRestart_clearsPlatformViewContext() { + PlatformViewsController platformViewsController = new PlatformViewsController(); + + int platformViewId = 0; + assertNull(platformViewsController.getPlatformViewById(platformViewId)); + + PlatformViewFactory viewFactory = mock(PlatformViewFactory.class); + PlatformView platformView = mock(PlatformView.class); + + View pv = mock(View.class); + when(pv.getLayoutParams()).thenReturn(new LayoutParams(1, 1)); + + when(platformView.getView()).thenReturn(pv); + when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView); + platformViewsController.getRegistry().registerViewFactory("testType", viewFactory); + + FlutterJNI jni = new FlutterJNI(); + attach(jni, platformViewsController); + + // Simulate create call from the framework. + createPlatformView( + jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ false); + + assertFalse(platformViewsController.contextToPlatformView.isEmpty()); + platformViewsController.onDetachedFromJNI(); + assertTrue(platformViewsController.contextToPlatformView.isEmpty()); + } + @Test @Config(shadows = {ShadowFlutterJNI.class}) public void createPlatformViewMessage__throwsIfViewHasParent() { @@ -311,7 +374,7 @@ public void createPlatformViewMessage__throwsIfViewHasParent() { attach(jni, platformViewsController); // Simulate create call from the framework. - createPlatformView(jni, platformViewsController, platformViewId, "testType"); + createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); assertEquals(ShadowFlutterJNI.getResponses().size(), 1); assertThrows( @@ -343,7 +406,7 @@ public void disposeAndroidView__hybridComposition() { attach(jni, platformViewsController); // Simulate create call from the framework. - createPlatformView(jni, platformViewsController, platformViewId, "testType"); + createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); platformViewsController.initializePlatformViewIfNeeded(platformViewId); assertNotNull(androidView.getParent()); @@ -354,7 +417,7 @@ public void disposeAndroidView__hybridComposition() { assertNull(androidView.getParent()); // Simulate create call from the framework. - createPlatformView(jni, platformViewsController, platformViewId, "testType"); + createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); platformViewsController.initializePlatformViewIfNeeded(platformViewId); assertNotNull(androidView.getParent()); @@ -384,7 +447,7 @@ public void onEndFrame__destroysOverlaySurfaceAfterFrameOnFlutterSurfaceView() { jni.onFirstFrame(); // Simulate create call from the framework. - createPlatformView(jni, platformViewsController, platformViewId, "testType"); + createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); // Produce a frame that displays a platform view and an overlay surface. platformViewsController.onBeginFrame(); @@ -447,7 +510,7 @@ public void onEndFrame__removesPlatformView() { jni.onFirstFrame(); // Simulate create call from the framework. - createPlatformView(jni, platformViewsController, platformViewId, "testType"); + createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); // Simulate first frame from the framework. jni.onFirstFrame(); @@ -484,7 +547,7 @@ public void onEndFrame__removesPlatformViewParent() { jni.onFirstFrame(); // Simulate create call from the framework. - createPlatformView(jni, platformViewsController, platformViewId, "testType"); + createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); platformViewsController.initializePlatformViewIfNeeded(platformViewId); assertEquals(flutterView.getChildCount(), 2); @@ -520,7 +583,7 @@ public void detach__destroysOverlaySurfaces() { jni.onFirstFrame(); // Simulate create call from the framework. - createPlatformView(jni, platformViewsController, platformViewId, "testType"); + createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); // Produce a frame that displays a platform view and an overlay surface. platformViewsController.onBeginFrame(); @@ -653,7 +716,7 @@ public void convertPlatformViewRenderSurfaceAsDefault() { jni.onFirstFrame(); // Simulate create call from the framework. - createPlatformView(jni, platformViewsController, platformViewId, "testType"); + createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); // Produce a frame that displays a platform view and an overlay surface. platformViewsController.onBeginFrame(); @@ -702,7 +765,7 @@ public void dontConverRenderSurfaceWhenFlagIsTrue() { synchronizeToNativeViewHierarchy(jni, platformViewsController, false); // Simulate create call from the framework. - createPlatformView(jni, platformViewsController, platformViewId, "testType"); + createPlatformView(jni, platformViewsController, platformViewId, "testType", /* hybrid=*/ true); // Produce a frame that displays a platform view and an overlay surface. platformViewsController.onBeginFrame(); @@ -734,12 +797,16 @@ private static void createPlatformView( FlutterJNI jni, PlatformViewsController platformViewsController, int platformViewId, - String viewType) { + String viewType, + boolean hybrid) { final Map platformViewCreateArguments = new HashMap<>(); - platformViewCreateArguments.put("hybrid", true); + platformViewCreateArguments.put("hybrid", hybrid); platformViewCreateArguments.put("id", platformViewId); platformViewCreateArguments.put("viewType", viewType); platformViewCreateArguments.put("direction", 0); + platformViewCreateArguments.put("width", 1.0); + platformViewCreateArguments.put("height", 1.0); + final MethodCall platformCreateMethodCall = new MethodCall("create", platformViewCreateArguments); @@ -776,7 +843,30 @@ private static FlutterView attach( executor.onAttachedToJNI(); final Context context = RuntimeEnvironment.application.getApplicationContext(); - platformViewsController.attach(context, null, executor); + final TextureRegistry registry = + new TextureRegistry() { + public void TextureRegistry() {} + + @Override + public SurfaceTextureEntry createSurfaceTexture() { + return new SurfaceTextureEntry() { + @Override + public SurfaceTexture surfaceTexture() { + return mock(SurfaceTexture.class); + } + + @Override + public long id() { + return 0; + } + + @Override + public void release() {} + }; + } + }; + + platformViewsController.attach(context, registry, executor); final FlutterView view = new FlutterView(context, FlutterView.RenderMode.surface) {