From 6658792491f0395bb160aac0072a034e9031f3e1 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 28 Jul 2020 18:24:41 -0700 Subject: [PATCH 1/7] Wait until first frame is rendered back in surface --- .../embedding/android/FlutterView.java | 52 +++++++++++++++++-- .../platform/PlatformViewsController.java | 35 +++++++------ 2 files changed, 67 insertions(+), 20 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 8cf69c24cf568..cd29ff4fb4811 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -45,6 +45,7 @@ import io.flutter.view.AccessibilityBridge; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.Callable; /** * Displays a Flutter UI on an Android device. @@ -970,9 +971,12 @@ public void convertToImageView() { /** * If the surface is rendered by a {@code FlutterImageView}. Then, calling this method will stop - * rendering to a {@code FlutterImageView}, and use the previous surface instead. + * rendering to a {@code FlutterImageView}, and render on the previous surface instead. + * + * @param onDone a callback called when Flutter UI is rendered on the previous surface. Use this + * callback to perform cleanups. For example, destroy overlay surfaces. */ - public void revertImageView() { + public void revertImageView(@NonNull Callable onDone) { if (flutterImageView == null) { Log.v(TAG, "Tried to revert the image view, but no image view is used."); return; @@ -981,11 +985,49 @@ public void revertImageView() { Log.v(TAG, "Tried to revert the image view, but no previous surface was used."); return; } - flutterImageView.detachFromRenderer(); renderSurface = previousRenderSurface; previousRenderSurface = null; - if (flutterEngine != null) { - renderSurface.attachToRenderer(flutterEngine.getRenderer()); + if (flutterEngine == null) { + flutterImageView.detachFromRenderer(); + callSafely(onDone); + return; + } + // Start rendering on the previous surface. + // This surface is typically `FlutterSurfaceView` or `FlutterTextureView`. + renderSurface.attachToRenderer(flutterEngine.getRenderer()); + + FlutterRenderer render = renderSurface.getAttachedRenderer(); + if (render == null) { + flutterImageView.detachFromRenderer(); + callSafely(onDone); + return; + } + // Install a Flutter UI listener to wait until the first frame is rendered + // in the new surface to call the `onDone` callback. + render.addIsDisplayingFlutterUiListener( + new FlutterUiDisplayListener() { + @Override + public void onFlutterUiDisplayed() { + FlutterRenderer render = renderSurface.getAttachedRenderer(); + if (render != null) { + render.removeIsDisplayingFlutterUiListener(this); + } + callSafely(onDone); + flutterImageView.detachFromRenderer(); + } + + @Override + public void onFlutterUiNoLongerDisplayed() { + // no-op + } + }); + } + + private static void callSafely(@NonNull Callable onDone) { + try { + onDone.call(); + } catch (Exception exception) { + Log.e(TAG, "onDone callback threw exception: " + exception.getMessage()); } } diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index ee7d393320a2b..b7a2e6cf6bc2d 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -753,6 +753,21 @@ public void onBeginFrame() { } public void onEndFrame() { + final FlutterView view = (FlutterView) flutterView; + // If there are no platform views in the current frame, + // then revert the image view surface and use the previous surface. + // + // Otherwise, acquire the latest image. + if (flutterViewConvertedToImageView && currentFrameUsedPlatformViewIds.isEmpty()) { + flutterViewConvertedToImageView = false; + view.revertImageView( + () -> { + // Destroy overlay surfaces once the surface reversion is completed. + finishFrame(false); + return null; + }); + return; + } // Whether the current frame was rendered using ImageReaders. // // Since the image readers may not have images available at this point, @@ -762,22 +777,12 @@ public void onEndFrame() { // If one of the surfaces doesn't have an image, the frame may be incomplete and must be // dropped. // For example, a toolbar widget painted by Flutter may not be rendered. - boolean isFrameRenderedUsingImageReaders = false; - - if (flutterViewConvertedToImageView) { - FlutterView view = (FlutterView) flutterView; - // If there are no platform views in the current frame, - // then revert the image view surface and use the previous surface. - // - // Otherwise, acquire the latest image. - if (currentFrameUsedPlatformViewIds.isEmpty()) { - view.revertImageView(); - flutterViewConvertedToImageView = false; - } else { - isFrameRenderedUsingImageReaders = view.acquireLatestImageViewFrame(); - } - } + boolean isFrameRenderedUsingImageReaders = + flutterViewConvertedToImageView && view.acquireLatestImageViewFrame(); + finishFrame(isFrameRenderedUsingImageReaders); + } + private void finishFrame(boolean isFrameRenderedUsingImageReaders) { for (int i = 0; i < overlayLayerViews.size(); i++) { int overlayId = overlayLayerViews.keyAt(i); FlutterImageView overlayView = overlayLayerViews.valueAt(i); From 54fca205b3bf789caf55cf7e526704ae472359ae Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 28 Jul 2020 18:26:41 -0700 Subject: [PATCH 2/7] final --- .../android/io/flutter/embedding/android/FlutterView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index cd29ff4fb4811..4f9b6fd93ee56 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -996,7 +996,7 @@ public void revertImageView(@NonNull Callable onDone) { // This surface is typically `FlutterSurfaceView` or `FlutterTextureView`. renderSurface.attachToRenderer(flutterEngine.getRenderer()); - FlutterRenderer render = renderSurface.getAttachedRenderer(); + final FlutterRenderer render = renderSurface.getAttachedRenderer(); if (render == null) { flutterImageView.detachFromRenderer(); callSafely(onDone); From 7b768865e556b3e74d3b1434f81d13444c8ccde5 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 29 Jul 2020 19:46:03 -0700 Subject: [PATCH 3/7] Test --- .../embedding/android/FlutterView.java | 26 ++- .../flutter/embedding/engine/FlutterJNI.java | 20 +- .../engine/renderer/FlutterRenderer.java | 2 +- .../platform/PlatformViewsController.java | 21 +- .../android/io/flutter/view/FlutterView.java | 2 +- .../platform/PlatformViewsControllerTest.java | 206 +++++++++++++++++- 6 files changed, 239 insertions(+), 38 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 4f9b6fd93ee56..59df4fbe4ef20 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -950,13 +950,18 @@ public void detachFromFlutterEngine() { flutterEngine = null; } + @VisibleForTesting + @NonNull + public FlutterImageView createImageView() { + return new FlutterImageView( + getContext(), getWidth(), getHeight(), FlutterImageView.SurfaceKind.background); + } + public void convertToImageView() { renderSurface.pause(); if (flutterImageView == null) { - flutterImageView = - new FlutterImageView( - getContext(), getWidth(), getHeight(), FlutterImageView.SurfaceKind.background); + flutterImageView = createImageView(); addView(flutterImageView); } else { flutterImageView.resizeIfNeeded(getWidth(), getHeight()); @@ -992,26 +997,23 @@ public void revertImageView(@NonNull Callable onDone) { callSafely(onDone); return; } - // Start rendering on the previous surface. - // This surface is typically `FlutterSurfaceView` or `FlutterTextureView`. - renderSurface.attachToRenderer(flutterEngine.getRenderer()); - - final FlutterRenderer render = renderSurface.getAttachedRenderer(); + final FlutterRenderer render = flutterEngine.getRenderer(); if (render == null) { flutterImageView.detachFromRenderer(); callSafely(onDone); return; } + // Start rendering on the previous surface. + // This surface is typically `FlutterSurfaceView` or `FlutterTextureView`. + renderSurface.attachToRenderer(render); + // Install a Flutter UI listener to wait until the first frame is rendered // in the new surface to call the `onDone` callback. render.addIsDisplayingFlutterUiListener( new FlutterUiDisplayListener() { @Override public void onFlutterUiDisplayed() { - FlutterRenderer render = renderSurface.getAttachedRenderer(); - if (render != null) { - render.removeIsDisplayingFlutterUiListener(this); - } + render.removeIsDisplayingFlutterUiListener(this); callSafely(onDone); flutterImageView.detachFromRenderer(); } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 1291d5db05c96..76291417bea33 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -90,7 +90,7 @@ * *

To invoke a native method that is not associated with a platform view, invoke it statically: * - *

{@code bool enabled = FlutterJNI.nativeGetIsSoftwareRenderingEnabled(); } + *

{@code bool enabled = FlutterJNI.getIsSoftwareRenderingEnabled(); } */ @Keep public class FlutterJNI { @@ -120,9 +120,14 @@ public static native void nativeInit( */ public static native void nativePrefetchDefaultFontManager(); - // TODO(mattcarroll): add javadocs + private native boolean nativeGetIsSoftwareRenderingEnabled(); + + @VisibleForTesting @UiThread - public native boolean nativeGetIsSoftwareRenderingEnabled(); + // TODO(mattcarroll): add javadocs + public boolean getIsSoftwareRenderingEnabled() { + return nativeGetIsSoftwareRenderingEnabled(); + } @Nullable // TODO(mattcarroll): add javadocs @@ -212,7 +217,12 @@ public boolean isAttached() { public void attachToNative(boolean isBackgroundView) { ensureRunningOnMainThread(); ensureNotAttachedToNative(); - nativePlatformViewId = nativeAttach(this, isBackgroundView); + nativePlatformViewId = performNativeAttach(this, isBackgroundView); + } + + @VisibleForTesting + public long performNativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgroundView) { + return nativeAttach(flutterJNI, isBackgroundView); } private native long nativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgroundView); @@ -279,7 +289,7 @@ public void removeIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListene @SuppressWarnings("unused") @VisibleForTesting @UiThread - void onFirstFrame() { + public void onFirstFrame() { ensureRunningOnMainThread(); for (FlutterUiDisplayListener listener : flutterUiDisplayListeners) { 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 f0cd24adea675..0aa11c4f2aacf 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -314,7 +314,7 @@ private void unregisterTexture(long textureId) { // TODO(mattcarroll): describe the native behavior that this invokes public boolean isSoftwareRenderingEnabled() { - return flutterJNI.nativeGetIsSoftwareRenderingEnabled(); + return flutterJNI.getIsSoftwareRenderingEnabled(); } // TODO(mattcarroll): describe the native behavior that this invokes diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index b7a2e6cf6bc2d..c9df5e158d937 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -713,7 +713,7 @@ public void onDisplayPlatformView( int width, int height, int viewWidth, - int ViewHeight, + int viewHeight, FlutterMutatorsStack mutatorsStack) { initializeRootImageViewIfNeeded(); initializePlatformViewIfNeeded(viewId); @@ -723,7 +723,7 @@ public void onDisplayPlatformView( mutatorView.setVisibility(View.VISIBLE); mutatorView.bringToFront(); - FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(viewWidth, ViewHeight); + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(viewWidth, viewHeight); View platformView = platformViews.get(viewId); platformView.setLayoutParams(layoutParams); platformView.bringToFront(); @@ -823,6 +823,14 @@ private void finishFrame(boolean isFrameRenderedUsingImageReaders) { } } + @VisibleForTesting + @TargetApi(19) + public FlutterOverlaySurface createOverlaySurface(@NonNull FlutterImageView imageView) { + final int id = nextOverlayLayerId++; + overlayLayerViews.put(id, imageView); + return new FlutterOverlaySurface(id, imageView.getSurface()); + } + @TargetApi(19) public FlutterOverlaySurface createOverlaySurface() { // Overlay surfaces have the same size as the background surface. @@ -831,17 +839,12 @@ public FlutterOverlaySurface createOverlaySurface() { // if the drawings they contain have a different tight bound. // // The final view size is determined when its frame is set. - FlutterImageView imageView = + return createOverlaySurface( new FlutterImageView( flutterView.getContext(), flutterView.getWidth(), flutterView.getHeight(), - FlutterImageView.SurfaceKind.overlay); - - int id = nextOverlayLayerId++; - overlayLayerViews.put(id, imageView); - - return new FlutterOverlaySurface(id, imageView.getSurface()); + FlutterImageView.SurfaceKind.overlay)); } public void destroyOverlaySurfaces() { diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index ed1499ced9641..f472b07c0753c 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -169,7 +169,7 @@ public FlutterView(Context context, AttributeSet attrs, FlutterNativeView native dartExecutor = mNativeView.getDartExecutor(); flutterRenderer = new FlutterRenderer(mNativeView.getFlutterJNI()); - mIsSoftwareRenderingEnabled = mNativeView.getFlutterJNI().nativeGetIsSoftwareRenderingEnabled(); + mIsSoftwareRenderingEnabled = mNativeView.getFlutterJNI().getIsSoftwareRenderingEnabled(); mMetrics = new ViewportMetrics(); mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density; setFocusable(true); 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 fad8dca431591..659056afc5132 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -8,15 +8,29 @@ import android.content.Context; import android.content.res.AssetManager; import android.view.MotionEvent; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; import android.view.View; import android.view.ViewParent; +import io.flutter.embedding.android.FlutterImageView; import io.flutter.embedding.android.FlutterView; import io.flutter.embedding.android.MotionEventTracker; +import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.embedding.engine.FlutterOverlaySurface; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.mutatorsstack.FlutterMutatorView; +import io.flutter.embedding.engine.mutatorsstack.FlutterMutatorsStack; +import io.flutter.embedding.engine.renderer.FlutterRenderer; +import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; +import io.flutter.embedding.engine.systemchannels.KeyEventChannel; +import io.flutter.embedding.engine.systemchannels.MouseCursorChannel; +import io.flutter.embedding.engine.systemchannels.SettingsChannel; +import io.flutter.embedding.engine.systemchannels.TextInputChannel; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.StandardMethodCodec; +import io.flutter.plugin.localization.LocalizationPlugin; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; @@ -27,6 +41,9 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowSurfaceView; @Config(manifest = Config.NONE) @RunWith(RobolectricTestRunner.class) @@ -196,6 +213,7 @@ public void itUsesActionEventTypeFromMotionEventForHybridPlatformViews() { } @Test + @Config(shadows = {ShadowFlutterJNI.class}) public void getPlatformViewById__hybridComposition() { PlatformViewsController platformViewsController = new PlatformViewsController(); @@ -223,6 +241,7 @@ public void getPlatformViewById__hybridComposition() { } @Test + @Config(shadows = {ShadowFlutterJNI.class}) public void initializePlatformViewIfNeeded__throwsIfViewIsNull() { PlatformViewsController platformViewsController = new PlatformViewsController(); @@ -254,6 +273,7 @@ public void initializePlatformViewIfNeeded__throwsIfViewIsNull() { } @Test + @Config(shadows = {ShadowFlutterJNI.class}) public void initializePlatformViewIfNeeded__throwsIfViewHasParent() { PlatformViewsController platformViewsController = new PlatformViewsController(); @@ -286,6 +306,7 @@ public void initializePlatformViewIfNeeded__throwsIfViewHasParent() { } @Test + @Config(shadows = {ShadowFlutterJNI.class}) public void disposeAndroidView__hybridComposition() { PlatformViewsController platformViewsController = new PlatformViewsController(); @@ -324,10 +345,72 @@ public void disposeAndroidView__hybridComposition() { assertTrue(androidView.getParent() instanceof FlutterMutatorView); } + @Test + @Config(shadows = {ShadowFlutterSurfaceView.class, ShadowFlutterJNI.class}) + public void onEndFrame__destroysOverlaySurfaceAfterFrameOnFlutterSurfaceView() { + final PlatformViewsController platformViewsController = new PlatformViewsController(); + + final int platformViewId = 0; + assertNull(platformViewsController.getPlatformViewById(platformViewId)); + + final PlatformViewFactory viewFactory = mock(PlatformViewFactory.class); + final PlatformView platformView = mock(PlatformView.class); + when(platformView.getView()).thenReturn(mock(View.class)); + when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView); + + platformViewsController.getRegistry().registerViewFactory("testType", viewFactory); + + final FlutterJNI jni = new FlutterJNI(); + jni.attachToNative(false); + attach(jni, platformViewsController); + + jni.onFirstFrame(); + + // Simulate create call from the framework. + createPlatformView(jni, platformViewsController, platformViewId, "testType"); + + // Produce a frame that displays a platform view and an overlay surface. + platformViewsController.onBeginFrame(); + platformViewsController.onDisplayPlatformView( + platformViewId, + /* x=*/ 0, + /* y=*/ 0, + /* width=*/ 10, + /* height=*/ 10, + /* viewWidth=*/ 10, + /* viewHeight=*/ 10, + /* mutatorsStack=*/ new FlutterMutatorsStack()); + + final FlutterImageView overlayImageView = mock(FlutterImageView.class); + when(overlayImageView.acquireLatestImage()).thenReturn(true); + + final FlutterOverlaySurface overlaySurface = + platformViewsController.createOverlaySurface(overlayImageView); + platformViewsController.onDisplayOverlaySurface( + overlaySurface.getId(), /* x=*/ 0, /* y=*/ 0, /* width=*/ 10, /* height=*/ 10); + + platformViewsController.onEndFrame(); + + // Simulate first frame from the framework. + jni.onFirstFrame(); + + verify(overlayImageView, never()).detachFromRenderer(); + + // Produce a frame that doesn't display platform views. + platformViewsController.onBeginFrame(); + platformViewsController.onEndFrame(); + + verify(overlayImageView, never()).detachFromRenderer(); + + // Simulate first frame from the framework. + jni.onFirstFrame(); + verify(overlayImageView, times(1)).detachFromRenderer(); + } + private static byte[] encodeMethodCall(MethodCall call) { - ByteBuffer buffer = StandardMethodCodec.INSTANCE.encodeMethodCall(call); + final ByteBuffer buffer = StandardMethodCodec.INSTANCE.encodeMethodCall(call); buffer.rewind(); - byte[] dest = new byte[buffer.remaining()]; + final byte[] dest = new byte[buffer.remaining()]; buffer.get(dest); return dest; } @@ -337,12 +420,13 @@ private static void createPlatformView( PlatformViewsController platformViewsController, int platformViewId, String viewType) { - Map platformViewCreateArguments = new HashMap<>(); + final Map platformViewCreateArguments = new HashMap<>(); platformViewCreateArguments.put("hybrid", true); platformViewCreateArguments.put("id", platformViewId); platformViewCreateArguments.put("viewType", viewType); platformViewCreateArguments.put("direction", 0); - MethodCall platformCreateMethodCall = new MethodCall("create", platformViewCreateArguments); + final MethodCall platformCreateMethodCall = + new MethodCall("create", platformViewCreateArguments); jni.handlePlatformMessage( "flutter/platform_views", encodeMethodCall(platformCreateMethodCall), /*replyId=*/ 0); @@ -350,22 +434,124 @@ private static void createPlatformView( private static void disposePlatformView( FlutterJNI jni, PlatformViewsController platformViewsController, int platformViewId) { - Map platformViewDisposeArguments = new HashMap<>(); + + final Map platformViewDisposeArguments = new HashMap<>(); platformViewDisposeArguments.put("hybrid", true); platformViewDisposeArguments.put("id", platformViewId); - MethodCall platformDisposeMethodCall = new MethodCall("dispose", platformViewDisposeArguments); + + final MethodCall platformDisposeMethodCall = + new MethodCall("dispose", platformViewDisposeArguments); jni.handlePlatformMessage( "flutter/platform_views", encodeMethodCall(platformDisposeMethodCall), /*replyId=*/ 0); } - private void attach(FlutterJNI jni, PlatformViewsController platformViewsController) { - DartExecutor executor = new DartExecutor(jni, mock(AssetManager.class)); + private static void attach(FlutterJNI jni, PlatformViewsController platformViewsController) { + final DartExecutor executor = new DartExecutor(jni, mock(AssetManager.class)); executor.onAttachedToJNI(); - Context context = RuntimeEnvironment.application.getApplicationContext(); + final Context context = RuntimeEnvironment.application.getApplicationContext(); platformViewsController.attach(context, null, executor); - platformViewsController.attachToView(mock(FlutterView.class)); + final FlutterView view = + new FlutterView(context, FlutterView.RenderMode.surface) { + @Override + public FlutterImageView createImageView() { + final FlutterImageView view = mock(FlutterImageView.class); + when(view.acquireLatestImage()).thenReturn(true); + return mock(FlutterImageView.class); + } + }; + + view.layout(0, 0, 100, 100); + + final FlutterEngine engine = mock(FlutterEngine.class); + when(engine.getRenderer()).thenReturn(new FlutterRenderer(jni)); + when(engine.getMouseCursorChannel()).thenReturn(mock(MouseCursorChannel.class)); + when(engine.getTextInputChannel()).thenReturn(mock(TextInputChannel.class)); + when(engine.getSettingsChannel()).thenReturn(new SettingsChannel(executor)); + when(engine.getPlatformViewsController()).thenReturn(platformViewsController); + when(engine.getLocalizationPlugin()).thenReturn(mock(LocalizationPlugin.class)); + when(engine.getKeyEventChannel()).thenReturn(mock(KeyEventChannel.class)); + when(engine.getAccessibilityChannel()).thenReturn(mock(AccessibilityChannel.class)); + + view.attachToFlutterEngine(engine); + platformViewsController.attachToView(view); + } + + @Implements(FlutterJNI.class) + public static class ShadowFlutterJNI { + + public ShadowFlutterJNI() {} + + @Implementation + public boolean getIsSoftwareRenderingEnabled() { + return false; + } + + @Implementation + public long performNativeAttach(FlutterJNI flutterJNI, boolean isBackgroundView) { + return 1; + } + + @Implementation + public void dispatchPlatformMessage( + String channel, ByteBuffer message, int position, int responseId) {} + + @Implementation + public void onSurfaceCreated(Surface surface) {} + + @Implementation + public void onSurfaceDestroyed() {} + + @Implementation + public void onSurfaceWindowChanged(Surface surface) {} + + @Implementation + public void setViewportMetrics( + float devicePixelRatio, + int physicalWidth, + int physicalHeight, + int physicalPaddingTop, + int physicalPaddingRight, + int physicalPaddingBottom, + int physicalPaddingLeft, + int physicalViewInsetTop, + int physicalViewInsetRight, + int physicalViewInsetBottom, + int physicalViewInsetLeft, + int systemGestureInsetTop, + int systemGestureInsetRight, + int systemGestureInsetBottom, + int systemGestureInsetLeft) {} + + @Implementation + public void invokePlatformMessageResponseCallback( + int responseId, ByteBuffer message, int position) {} + } + + @Implements(SurfaceView.class) + public static class ShadowFlutterSurfaceView extends ShadowSurfaceView { + private final FakeSurfaceHolder holder = new FakeSurfaceHolder(); + + public static class FakeSurfaceHolder extends ShadowSurfaceView.FakeSurfaceHolder { + private final Surface surface = mock(Surface.class); + + public Surface getSurface() { + return surface; + } + + @Implementation + public void addCallback(SurfaceHolder.Callback callback) { + callback.surfaceCreated(this); + } + } + + public ShadowFlutterSurfaceView() {} + + @Implementation + public SurfaceHolder getHolder() { + return holder; + } } } From 3f93967ad8392b02c1b5ff3a9d83cb45e1fc4a81 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Thu, 30 Jul 2020 13:26:50 -0700 Subject: [PATCH 4/7] Feedback --- .../io/flutter/embedding/android/FlutterView.java | 12 ++++++------ .../io/flutter/embedding/engine/FlutterJNI.java | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 59df4fbe4ef20..ce51949009475 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -975,7 +975,7 @@ public void convertToImageView() { } /** - * If the surface is rendered by a {@code FlutterImageView}. Then, calling this method will stop + * If the surface is rendered by a {@code FlutterImageView}, then calling this method will stop * rendering to a {@code FlutterImageView}, and render on the previous surface instead. * * @param onDone a callback called when Flutter UI is rendered on the previous surface. Use this @@ -997,23 +997,23 @@ public void revertImageView(@NonNull Callable onDone) { callSafely(onDone); return; } - final FlutterRenderer render = flutterEngine.getRenderer(); - if (render == null) { + final FlutterRenderer renderer = flutterEngine.getRenderer(); + if (renderer == null) { flutterImageView.detachFromRenderer(); callSafely(onDone); return; } // Start rendering on the previous surface. // This surface is typically `FlutterSurfaceView` or `FlutterTextureView`. - renderSurface.attachToRenderer(render); + renderSurface.attachToRenderer(renderer); // Install a Flutter UI listener to wait until the first frame is rendered // in the new surface to call the `onDone` callback. - render.addIsDisplayingFlutterUiListener( + renderer.addIsDisplayingFlutterUiListener( new FlutterUiDisplayListener() { @Override public void onFlutterUiDisplayed() { - render.removeIsDisplayingFlutterUiListener(this); + renderer.removeIsDisplayingFlutterUiListener(this); callSafely(onDone); flutterImageView.detachFromRenderer(); } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 76291417bea33..59fa7132e3008 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -122,7 +122,6 @@ public static native void nativeInit( private native boolean nativeGetIsSoftwareRenderingEnabled(); - @VisibleForTesting @UiThread // TODO(mattcarroll): add javadocs public boolean getIsSoftwareRenderingEnabled() { From daf41be7f8ab6fd2147b8d2335a2905a92366953 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Thu, 30 Jul 2020 13:48:06 -0700 Subject: [PATCH 5/7] javadocs --- .../android/io/flutter/embedding/android/FlutterView.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index ce51949009475..4cadcd2ee22ef 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -957,6 +957,10 @@ public FlutterImageView createImageView() { getContext(), getWidth(), getHeight(), FlutterImageView.SurfaceKind.background); } + /** + * Converts the current render surface to a {@link FlutterImageView} if it's not one already. + * Otherwise, it resizes the {@link FlutterImageView} based on the current view size. + */ public void convertToImageView() { renderSurface.pause(); @@ -975,8 +979,8 @@ public void convertToImageView() { } /** - * If the surface is rendered by a {@code FlutterImageView}, then calling this method will stop - * rendering to a {@code FlutterImageView}, and render on the previous surface instead. + * If the surface is rendered by a {@link FlutterImageView}, then calling this method will stop + * rendering to a {@link FlutterImageView}, and render on the previous surface instead. * * @param onDone a callback called when Flutter UI is rendered on the previous surface. Use this * callback to perform cleanups. For example, destroy overlay surfaces. From 94503c14415529ae9b258c8ffb7519405714626f Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Thu, 30 Jul 2020 14:20:30 -0700 Subject: [PATCH 6/7] Callable->Runnable --- .../flutter/embedding/android/FlutterView.java | 17 ++++------------- .../platform/PlatformViewsController.java | 1 - 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 4cadcd2ee22ef..1cb273e32a009 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -45,7 +45,6 @@ import io.flutter.view.AccessibilityBridge; import java.util.HashSet; import java.util.Set; -import java.util.concurrent.Callable; /** * Displays a Flutter UI on an Android device. @@ -985,7 +984,7 @@ public void convertToImageView() { * @param onDone a callback called when Flutter UI is rendered on the previous surface. Use this * callback to perform cleanups. For example, destroy overlay surfaces. */ - public void revertImageView(@NonNull Callable onDone) { + public void revertImageView(@NonNull Runnable onDone) { if (flutterImageView == null) { Log.v(TAG, "Tried to revert the image view, but no image view is used."); return; @@ -998,13 +997,13 @@ public void revertImageView(@NonNull Callable onDone) { previousRenderSurface = null; if (flutterEngine == null) { flutterImageView.detachFromRenderer(); - callSafely(onDone); + onDone.run(); return; } final FlutterRenderer renderer = flutterEngine.getRenderer(); if (renderer == null) { flutterImageView.detachFromRenderer(); - callSafely(onDone); + onDone.run(); return; } // Start rendering on the previous surface. @@ -1018,7 +1017,7 @@ public void revertImageView(@NonNull Callable onDone) { @Override public void onFlutterUiDisplayed() { renderer.removeIsDisplayingFlutterUiListener(this); - callSafely(onDone); + onDone.run(); flutterImageView.detachFromRenderer(); } @@ -1029,14 +1028,6 @@ public void onFlutterUiNoLongerDisplayed() { }); } - private static void callSafely(@NonNull Callable onDone) { - try { - onDone.call(); - } catch (Exception exception) { - Log.e(TAG, "onDone callback threw exception: " + exception.getMessage()); - } - } - public void attachOverlaySurfaceToRender(FlutterImageView view) { if (flutterEngine != null) { view.attachToRenderer(flutterEngine.getRenderer()); diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index c9df5e158d937..7c064a6f90818 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -764,7 +764,6 @@ public void onEndFrame() { () -> { // Destroy overlay surfaces once the surface reversion is completed. finishFrame(false); - return null; }); return; } From ba2c8b707ffd301e82a88a41eddf2952a6d66901 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Thu, 30 Jul 2020 15:37:41 -0700 Subject: [PATCH 7/7] Retrigger LUCI checks