From 3835eb1a54a514e7179517222400efc2205c8fbe Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 26 Jan 2022 11:01:37 -0800 Subject: [PATCH 01/29] test --- .../engine/renderer/FlutterRenderer.java | 16 +- .../platform/PlatformViewsController.java | 150 ++++++++++++++---- .../android/io/flutter/view/FlutterView.java | 9 +- .../io/flutter/view/TextureRegistry.java | 5 +- .../engine/renderer/FlutterRendererTest.java | 2 +- .../platform/PlatformViewsControllerTest.java | 5 +- 6 files changed, 144 insertions(+), 43 deletions(-) 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 d58e1b791870e..3c2325c79c75c 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -100,7 +100,7 @@ public void removeIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListene public SurfaceTextureEntry createSurfaceTexture() { Log.v(TAG, "Creating a SurfaceTexture."); final SurfaceTexture surfaceTexture = new SurfaceTexture(0); - return registerSurfaceTexture(surfaceTexture); + return registerSurfaceTexture(surfaceTexture, null); } /** @@ -108,10 +108,10 @@ public SurfaceTextureEntry createSurfaceTexture() { * available to Flutter code. */ @Override - public SurfaceTextureEntry registerSurfaceTexture(@NonNull SurfaceTexture surfaceTexture) { + public SurfaceTextureEntry registerSurfaceTexture(@NonNull SurfaceTexture surfaceTexture, @NonNull Bitmap bitmap) { surfaceTexture.detachFromGLContext(); final SurfaceTextureRegistryEntry entry = - new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(), surfaceTexture); + new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(), surfaceTexture, bitmap); Log.v(TAG, "New SurfaceTexture ID: " + entry.id()); registerTexture(entry.id(), entry.textureWrapper()); return entry; @@ -121,10 +121,12 @@ final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextur private final long id; @NonNull private final SurfaceTextureWrapper textureWrapper; private boolean released; + private Bitmap bitmap; - SurfaceTextureRegistryEntry(long id, @NonNull SurfaceTexture surfaceTexture) { + SurfaceTextureRegistryEntry(long id, @NonNull SurfaceTexture surfaceTexture, @NonNull Bitmap bitmap) { this.id = id; this.textureWrapper = new SurfaceTextureWrapper(surfaceTexture); + this.bitmap = bitmap; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // The callback relies on being executed on the UI thread (unsynchronised read of @@ -167,6 +169,12 @@ public SurfaceTexture surfaceTexture() { return textureWrapper.surfaceTexture(); } + @Override + @NonNull + public Bitmap bitmap() { + return bitmap; + } + @Override public long id() { return id; diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 54916f31e9784..215645472841e 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -15,11 +15,14 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.TextureView; +import android.graphics.Color; import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; +import android.view.MotionEvent; import io.flutter.Log; import io.flutter.embedding.android.AndroidTouchProcessor; import io.flutter.embedding.android.FlutterImageView; @@ -37,6 +40,14 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.SurfaceTexture; +import android.view.Surface; +import android.view.TextureView.SurfaceTextureListener; +import android.graphics.HardwareRenderer; +import android.graphics.RenderNode; /** * Manages platform views. @@ -207,43 +218,116 @@ public long createVirtualDisplayForPlatformView( int physicalHeight = toPhysicalPixels(request.logicalHeight); validateVirtualDisplayDimensions(physicalWidth, physicalHeight); + + // final SurfaceTexture surfaceTexture = new SurfaceTexture(0); + + + + // final HardwareRenderer hr = new HardwareRenderer(); + // hr.setSurface(surface); + + // RenderNode rn = new RenderNode("myRenderNode"); + // rn.setPosition(0, 0, physicalWidth, physicalHeight); + // RecordingCanvas canvas = rn.beginRecording(); + // hr.setContentRoot(content); + + + + // TextureView textureView = new TextureView(context); + // textureView.setSurfaceTexture(surfaceTexture); + TextureRegistry.SurfaceTextureEntry textureEntry = textureRegistry.createSurfaceTexture(); - VirtualDisplayController vdController = - VirtualDisplayController.create( - context, - accessibilityEventsDelegate, - viewFactory, - textureEntry, - physicalWidth, - physicalHeight, - request.viewId, - createParams, - (view, hasFocus) -> { - if (hasFocus) { - platformViewsChannel.invokeViewFocused(request.viewId); - } - }); + textureEntry.surfaceTexture().setDefaultBufferSize(physicalWidth, physicalHeight); - if (vdController == null) { - throw new IllegalStateException( - "Failed creating virtual display for a " - + request.viewType - + " with id: " - + request.viewId); - } - // If our FlutterEngine is already attached to a Flutter UI, provide that Android - // View to this new platform view. - if (flutterView != null) { - vdController.onFlutterViewAttached(flutterView); - } - vdControllers.put(request.viewId, vdController); - View platformView = vdController.getView(); - platformView.setLayoutDirection(request.direction); - contextToPlatformView.put(platformView.getContext(), platformView); + final PlatformView platformView = viewFactory.create(context, request.viewId, createParams); + final FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(physicalWidth, physicalHeight); - // TODO(amirh): copy accessibility nodes to the FlutterView's accessibility tree. + + + Surface surface = new Surface(textureEntry.surfaceTexture()); + + FrameLayout parent = new FrameLayout(context) { + @Override + public void draw(Canvas canvas) { + if (textureEntry.surfaceTexture().isReleased()) { + super.draw(canvas); + return; + } + System.out.println("draw"); + + Canvas c = surface.lockHardwareCanvas(); + if (c != null) { + try { + c.drawColor(Color.BLACK); + super.draw(c); + } finally { + surface.unlockCanvasAndPost(c); + } + } + } + + @Override + public void onDescendantInvalidated(@NonNull View child, @NonNull View target) { + //not called per every frame. e.g. playing video on youtube.com doesn't result in calls. + // super.onDescendantInvalidated(child, target); + System.out.println("onDescendantInvalidated"); + invalidate(); + } + }; + + parent.setLayoutParams(layoutParams); + parent.setWillNotDraw(false); + parent.addView(platformView.getView()); + parent.bringToFront(); + flutterView.addView(parent); + + + System.out.println("isHardwareAccelerated=" + parent.isHardwareAccelerated()); + + + + + + + + // VirtualDisplayController vdController = + // VirtualDisplayController.create( + // context, + // accessibilityEventsDelegate, + // viewFactory, + // textureEntry, + // physicalWidth, + // physicalHeight, + // request.viewId, + // createParams, + // (view, hasFocus) -> { + // if (hasFocus) { + // platformViewsChannel.invokeViewFocused(request.viewId); + // } + // }); + + // if (vdController == null) { + // throw new IllegalStateException( + // "Failed creating virtual display for a " + // + request.viewType + // + " with id: " + // + request.viewId); + // } + + // // If our FlutterEngine is already attached to a Flutter UI, provide that Android + // // View to this new platform view. + // if (flutterView != null) { + // vdController.onFlutterViewAttached(flutterView); + // } + + // vdControllers.put(request.viewId, vdController); + // View platformView = vdController.getView(); + // platformView.setLayoutDirection(request.direction); + // contextToPlatformView.put(platformView.getContext(), platformView); + + // // TODO(amirh): copy accessibility nodes to the FlutterView's accessibility tree. return textureEntry.id(); } @@ -311,7 +395,7 @@ public void onTouch(@NonNull PlatformViewsChannel.PlatformViewTouch touch) { view.dispatchTouchEvent(event); } } else { - throw new IllegalStateException("Sending touch to an unknown view with id: " + viewId); + // throw new IllegalStateException("Sending touch to an unknown view with id: " + viewId); } } diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 9b86ed08193fe..dccd40a0cd484 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -873,12 +873,12 @@ public interface FirstFrameListener { @Override public TextureRegistry.SurfaceTextureEntry createSurfaceTexture() { final SurfaceTexture surfaceTexture = new SurfaceTexture(0); - return registerSurfaceTexture(surfaceTexture); + return registerSurfaceTexture(surfaceTexture, null); } @Override public TextureRegistry.SurfaceTextureEntry registerSurfaceTexture( - @NonNull SurfaceTexture surfaceTexture) { + @NonNull SurfaceTexture surfaceTexture, @NonNull Bitmap bitmap) { surfaceTexture.detachFromGLContext(); final SurfaceTextureRegistryEntry entry = new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(), surfaceTexture); @@ -941,6 +941,11 @@ public long id() { return id; } + @Override + public Bitmap bitmap() { + return null; + } + @Override public void release() { if (released) { diff --git a/shell/platform/android/io/flutter/view/TextureRegistry.java b/shell/platform/android/io/flutter/view/TextureRegistry.java index 6af82ce915fcc..f45b3b784b8bc 100644 --- a/shell/platform/android/io/flutter/view/TextureRegistry.java +++ b/shell/platform/android/io/flutter/view/TextureRegistry.java @@ -4,6 +4,7 @@ package io.flutter.view; +import android.graphics.Bitmap; import android.graphics.SurfaceTexture; import androidx.annotation.NonNull; @@ -26,13 +27,15 @@ public interface TextureRegistry { * * @return A SurfaceTextureEntry. */ - SurfaceTextureEntry registerSurfaceTexture(@NonNull SurfaceTexture surfaceTexture); + SurfaceTextureEntry registerSurfaceTexture(@NonNull SurfaceTexture surfaceTexture, @NonNull Bitmap bitmap); /** A registry entry for a managed SurfaceTexture. */ interface SurfaceTextureEntry { /** @return The managed SurfaceTexture. */ SurfaceTexture surfaceTexture(); + Bitmap bitmap(); + /** @return The identity of this SurfaceTexture. */ long id(); 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 2b9f007d1ed2d..5ddb624ede400 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 @@ -168,7 +168,7 @@ public void itRegistersExistingSurfaceTexture() { // Execute the behavior under test. FlutterRenderer.SurfaceTextureRegistryEntry entry = (FlutterRenderer.SurfaceTextureRegistryEntry) - flutterRenderer.registerSurfaceTexture(surfaceTexture); + flutterRenderer.registerSurfaceTexture(surfaceTexture, null); flutterRenderer.startRenderingToSurface(fakeSurface, /*keepCurrentSurface=*/ false); 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 f05f73cab3a75..82319c8107dff 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -10,6 +10,7 @@ import android.content.Context; import android.content.res.AssetManager; import android.graphics.SurfaceTexture; +import android.graphics.Bitmap; import android.util.SparseArray; import android.view.MotionEvent; import android.view.Surface; @@ -875,11 +876,11 @@ public void TextureRegistry() {} @Override public SurfaceTextureEntry createSurfaceTexture() { - return registerSurfaceTexture(mock(SurfaceTexture.class)); + return registerSurfaceTexture(mock(SurfaceTexture.class), mock(Bitmap.class)); } @Override - public SurfaceTextureEntry registerSurfaceTexture(SurfaceTexture surfaceTexture) { + public SurfaceTextureEntry registerSurfaceTexture(SurfaceTexture surfaceTexture, Bitmap bitmap) { return new SurfaceTextureEntry() { @Override public SurfaceTexture surfaceTexture() { From 630af99687e33e5c3c437f22ebf7c5acf5ab9325 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Thu, 27 Jan 2022 11:24:26 -0800 Subject: [PATCH 02/29] offset --- .../systemchannels/PlatformViewsChannel.java | 22 +++++++++++++++++++ .../platform/PlatformViewsController.java | 18 ++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java index 87891710bd784..80adac5d05897 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java @@ -64,6 +64,9 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result case "resize": resize(call, result); break; + case "offset": + offset(call, result); + break; case "touch": touch(call, result); break; @@ -151,6 +154,19 @@ public void run() { } } + private void offset(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + Map offsetArgs = call.arguments(); + try { + handler.setOffset( + (int) offsetArgs.get("id"), + (double) offsetArgs.get("top"), + (double) offsetArgs.get("left")); + result.success(null); + } catch (IllegalStateException exception) { + result.error("error", detailedExceptionString(exception), null); + } + } + private void touch(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { List args = call.arguments(); PlatformViewTouch touch = @@ -280,6 +296,12 @@ public interface PlatformViewsHandler { void resizePlatformView( @NonNull PlatformViewResizeRequest request, @NonNull Runnable onComplete); + /** + * The Flutter application would like to change the offset an existing Android {@code View}, i.e., platform + * view. + */ + void setOffset(int viewId, double top, double left); + /** * The user touched a platform view within Flutter. * diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 215645472841e..f3a5dcd5de76c 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -248,7 +248,7 @@ public long createVirtualDisplayForPlatformView( Surface surface = new Surface(textureEntry.surfaceTexture()); - FrameLayout parent = new FrameLayout(context) { + FrameLayout parent = new FrameLayout(context) { @Override public void draw(Canvas canvas) { if (textureEntry.surfaceTexture().isReleased()) { @@ -282,6 +282,9 @@ public void onDescendantInvalidated(@NonNull View child, @NonNull View target) { parent.addView(platformView.getView()); parent.bringToFront(); flutterView.addView(parent); + + pv = parent; + viewLayout = layoutParams; System.out.println("isHardwareAccelerated=" + parent.isHardwareAccelerated()); @@ -332,6 +335,9 @@ public void onDescendantInvalidated(@NonNull View child, @NonNull View target) { return textureEntry.id(); } + private FrameLayout.LayoutParams viewLayout; + private FrameLayout pv; + @Override public void disposeVirtualDisplayForPlatformView(int viewId) { ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH); @@ -350,6 +356,16 @@ public void disposeVirtualDisplayForPlatformView(int viewId) { vdControllers.remove(viewId); } + @Override + public void setOffset(int viewId, double top, double left) { + int physicalTop = toPhysicalPixels(top); + int physicalLeft = toPhysicalPixels(left); + + viewLayout.topMargin = physicalTop; + viewLayout.leftMargin = physicalLeft; + pv.setLayoutParams(viewLayout); + } + @Override public void resizePlatformView( @NonNull PlatformViewsChannel.PlatformViewResizeRequest request, From 8db507c79f351d8f7c2ccd6020ec8293c0cfab44 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 1 Feb 2022 17:00:00 -0800 Subject: [PATCH 03/29] Improve performance of platform views --- ci/licenses_golden/licenses_flutter | 3 +- shell/platform/android/BUILD.gn | 3 +- .../embedding/android/FlutterView.java | 34 +- .../mutatorsstack/FlutterMutatorView.java | 29 +- .../engine/renderer/FlutterRenderer.java | 6 +- .../systemchannels/PlatformViewsChannel.java | 86 ++- .../systemchannels/TextInputChannel.java | 8 +- .../plugin/editing/TextInputPlugin.java | 21 +- .../plugin/platform/PlatformViewWrapper.java | 201 +++++++ .../PlatformViewsAccessibilityDelegate.java | 7 +- .../platform/PlatformViewsController.java | 496 +++++------------- .../platform/SingleViewPresentation.java | 478 ----------------- .../platform/VirtualDisplayController.java | 249 --------- .../android/io/flutter/util/ViewUtils.java | 26 + .../io/flutter/view/AccessibilityBridge.java | 37 +- .../android/io/flutter/view/FlutterView.java | 9 - .../io/flutter/view/TextureRegistry.java | 3 +- .../test/io/flutter/FlutterTestSuite.java | 2 - .../platform/PlatformViewsControllerTest.java | 78 +-- .../platform/SingleViewPresentationTest.java | 82 --- .../flutter/view/AccessibilityBridgeTest.java | 4 - tools/android_lint/baseline.xml | 11 - tools/android_lint/project.xml | 8 +- 23 files changed, 443 insertions(+), 1438 deletions(-) create mode 100644 shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java delete mode 100644 shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java delete mode 100644 shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java delete mode 100644 shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 536c859585413..bd2617ff450cc 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -950,10 +950,9 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/Platfor FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewFactory.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewRegistry.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewRegistryImpl.java +FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java -FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java -FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java FILE: ../../../flutter/shell/platform/android/io/flutter/util/PathUtils.java FILE: ../../../flutter/shell/platform/android/io/flutter/util/Preconditions.java FILE: ../../../flutter/shell/platform/android/io/flutter/util/Predicate.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index ac44391e0d85a..41f9e3aed0ec7 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -268,10 +268,9 @@ android_java_sources = [ "io/flutter/plugin/platform/PlatformViewFactory.java", "io/flutter/plugin/platform/PlatformViewRegistry.java", "io/flutter/plugin/platform/PlatformViewRegistryImpl.java", + "io/flutter/plugin/platform/PlatformViewWrapper.java", "io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java", "io/flutter/plugin/platform/PlatformViewsController.java", - "io/flutter/plugin/platform/SingleViewPresentation.java", - "io/flutter/plugin/platform/VirtualDisplayController.java", "io/flutter/util/PathUtils.java", "io/flutter/util/Preconditions.java", "io/flutter/util/Predicate.java", diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index b172d2aa11f41..5f18f4108c61e 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -50,6 +50,7 @@ import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.FlutterRenderer.DisplayFeatureState; import io.flutter.embedding.engine.renderer.FlutterRenderer.DisplayFeatureType; +import io.flutter.embedding.engine.renderer.FlutterRenderer.ViewportMetrics; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.embedding.engine.renderer.RenderSurface; import io.flutter.embedding.engine.systemchannels.SettingsChannel; @@ -769,9 +770,27 @@ navigationBarVisible && guessBottomKeyboardInset(insets) == 0 sendViewportMetricsToFlutter(); + for (ViewportMetricsListener listener : sViewportMetricsListeners) { + listener.onMetricsChanged(viewportMetrics); + } return newInsets; } + public interface ViewportMetricsListener { + public void onMetricsChanged(ViewportMetrics metrics); + } + + private Set sViewportMetricsListeners = + new HashSet(); + + public void addViewportMetricsListener(ViewportMetricsListener l) { + sViewportMetricsListeners.add(l); + } + + public void removeViewportMetricsListener(ViewportMetricsListener l) { + sViewportMetricsListeners.remove(l); + } + /** * Invoked when Android's desired window insets change, i.e., padding. * @@ -844,21 +863,6 @@ public InputConnection onCreateInputConnection(@NonNull EditorInfo outAttrs) { return textInputPlugin.createInputConnection(this, keyboardManager, outAttrs); } - /** - * Allows a {@code View} that is not currently the input connection target to invoke commands on - * the {@link android.view.inputmethod.InputMethodManager}, which is otherwise disallowed. - * - *

Returns true to allow non-input-connection-targets to invoke methods on {@code - * InputMethodManager}, or false to exclusively allow the input connection target to invoke such - * methods. - */ - @Override - public boolean checkInputConnectionProxy(View view) { - return flutterEngine != null - ? flutterEngine.getPlatformViewsController().checkInputConnectionProxy(view) - : super.checkInputConnectionProxy(view); - } - /** * Invoked when a hardware key is pressed or released. * diff --git a/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java b/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java index d46f5346d7ab3..9be312cc53baf 100644 --- a/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java +++ b/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java @@ -9,13 +9,13 @@ import android.graphics.Path; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import io.flutter.embedding.android.AndroidTouchProcessor; +import io.flutter.util.ViewUtils; /** * A view that applies the {@link io.flutter.embedding.engine.mutatorsstack.FlutterMutatorsStack} to @@ -49,31 +49,6 @@ public FlutterMutatorView(@NonNull Context context) { this(context, 1, /* androidTouchProcessor=*/ null); } - /** - * Determines if the current view or any descendant view has focus. - * - * @param root The root view. - * @return True if the current view or any descendant view has focus. - */ - @VisibleForTesting - public static boolean childHasFocus(@Nullable View root) { - if (root == null) { - return false; - } - if (root.hasFocus()) { - return true; - } - if (root instanceof ViewGroup) { - final ViewGroup viewGroup = (ViewGroup) root; - for (int idx = 0; idx < viewGroup.getChildCount(); idx++) { - if (childHasFocus(viewGroup.getChildAt(idx))) { - return true; - } - } - } - return false; - } - @Nullable @VisibleForTesting ViewTreeObserver.OnGlobalFocusChangeListener activeFocusListener; /** @@ -95,7 +70,7 @@ public void setOnDescendantFocusChangeListener(@NonNull OnFocusChangeListener us new ViewTreeObserver.OnGlobalFocusChangeListener() { @Override public void onGlobalFocusChanged(View oldFocus, View newFocus) { - userFocusListener.onFocusChange(mutatorView, childHasFocus(mutatorView)); + userFocusListener.onFocusChange(mutatorView, ViewUtils.childHasFocus(mutatorView)); } }; observer.addOnGlobalFocusChangeListener(activeFocusListener); 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 3c2325c79c75c..03d61d4d8fe3f 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -108,7 +108,8 @@ public SurfaceTextureEntry createSurfaceTexture() { * available to Flutter code. */ @Override - public SurfaceTextureEntry registerSurfaceTexture(@NonNull SurfaceTexture surfaceTexture, @NonNull Bitmap bitmap) { + public SurfaceTextureEntry registerSurfaceTexture( + @NonNull SurfaceTexture surfaceTexture, @NonNull Bitmap bitmap) { surfaceTexture.detachFromGLContext(); final SurfaceTextureRegistryEntry entry = new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(), surfaceTexture, bitmap); @@ -123,7 +124,8 @@ final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextur private boolean released; private Bitmap bitmap; - SurfaceTextureRegistryEntry(long id, @NonNull SurfaceTexture surfaceTexture, @NonNull Bitmap bitmap) { + SurfaceTextureRegistryEntry( + long id, @NonNull SurfaceTexture surfaceTexture, @NonNull Bitmap bitmap) { this.id = id; this.textureWrapper = new SurfaceTextureWrapper(surfaceTexture); this.bitmap = bitmap; diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java index 80adac5d05897..549d10cf64063 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java @@ -86,11 +86,12 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result private void create(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { Map createArgs = call.arguments(); - boolean usesHybridComposition = + // TODO(egarciad): The word "hybrid" is misleading. + boolean usesPlatformViewLayer = createArgs.containsKey("hybrid") && (boolean) createArgs.get("hybrid"); // In hybrid mode, the size of the view is determined by the size of the Flow layer. - double width = (usesHybridComposition) ? 0 : (double) createArgs.get("width"); - double height = (usesHybridComposition) ? 0 : (double) createArgs.get("height"); + double width = (usesPlatformViewLayer) ? 0 : (double) createArgs.get("width"); + double height = (usesPlatformViewLayer) ? 0 : (double) createArgs.get("height"); PlatformViewCreationRequest request = new PlatformViewCreationRequest( @@ -103,11 +104,11 @@ private void create(@NonNull MethodCall call, @NonNull MethodChannel.Result resu ? ByteBuffer.wrap((byte[]) createArgs.get("params")) : null); try { - if (usesHybridComposition) { - handler.createAndroidViewForPlatformView(request); + if (usesPlatformViewLayer) { + handler.createForPlatformViewLayer(request); result.success(null); } else { - long textureId = handler.createVirtualDisplayForPlatformView(request); + long textureId = handler.createForTextureLayer(request); result.success(textureId); } } catch (IllegalStateException exception) { @@ -118,15 +119,9 @@ private void create(@NonNull MethodCall call, @NonNull MethodChannel.Result resu private void dispose(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { Map disposeArgs = call.arguments(); int viewId = (int) disposeArgs.get("id"); - boolean usesHybridComposition = - disposeArgs.containsKey("hybrid") && (boolean) disposeArgs.get("hybrid"); try { - if (usesHybridComposition) { - handler.disposeAndroidViewForPlatformView(viewId); - } else { - handler.disposeVirtualDisplayForPlatformView(viewId); - } + handler.dispose(viewId); result.success(null); } catch (IllegalStateException exception) { result.error("error", detailedExceptionString(exception), null); @@ -141,14 +136,8 @@ private void resize(@NonNull MethodCall call, @NonNull MethodChannel.Result resu (double) resizeArgs.get("width"), (double) resizeArgs.get("height")); try { - handler.resizePlatformView( - resizeRequest, - new Runnable() { - @Override - public void run() { - result.success(null); - } - }); + handler.resize(resizeRequest); + result.success(null); } catch (IllegalStateException exception) { result.error("error", detailedExceptionString(exception), null); } @@ -157,10 +146,10 @@ public void run() { private void offset(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { Map offsetArgs = call.arguments(); try { - handler.setOffset( - (int) offsetArgs.get("id"), - (double) offsetArgs.get("top"), - (double) offsetArgs.get("left")); + handler.offset( + (int) offsetArgs.get("id"), + (double) offsetArgs.get("top"), + (double) offsetArgs.get("left")); result.success(null); } catch (IllegalStateException exception) { result.error("error", detailedExceptionString(exception), null); @@ -265,42 +254,33 @@ public interface PlatformViewsHandler { * The Flutter application would like to display a new Android {@code View}, i.e., platform * view. * - *

The Android {@code View} is added to the view hierarchy. - */ - void createAndroidViewForPlatformView(@NonNull PlatformViewCreationRequest request); - - /** - * The Flutter application would like to dispose of an existing Android {@code View} rendered in - * the view hierarchy. + *

The Android View is added to the view hierarchy. This view is rendered in the Flutter + * framework by a PlatformViewLayer. + * + * @param request The metadata sent from the framework. */ - void disposeAndroidViewForPlatformView(int viewId); + void createForPlatformViewLayer(@NonNull PlatformViewCreationRequest request); /** - * The Flutter application would like to display a new Android {@code View}. + * The Flutter application would like to display a new Android {@code View}, i.e., platform + * view. * - *

{@code View} is added to a {@code VirtualDisplay}. The framework uses id returned by this - * method to lookup the texture in the engine. + *

The Android View is added to the view hierarchy. This view is rendered in the Flutter + * framework by a TextureLayer. + * + * @param request The metadata sent from the framework. + * @return The texture ID. */ - long createVirtualDisplayForPlatformView(@NonNull PlatformViewCreationRequest request); + long createForTextureLayer(@NonNull PlatformViewCreationRequest request); - /** - * The Flutter application would like to dispose of an existing Android {@code View} rendered in - * a virtual display. - */ - void disposeVirtualDisplayForPlatformView(int viewId); + /** The Flutter application would like to dispose of an existing Android {@code View}. */ + void dispose(int viewId); - /** - * The Flutter application would like to resize an existing Android {@code View}, i.e., platform - * view. - */ - void resizePlatformView( - @NonNull PlatformViewResizeRequest request, @NonNull Runnable onComplete); + /** The Flutter application would like to resize an existing Android {@code View}. */ + void resize(@NonNull PlatformViewResizeRequest request); - /** - * The Flutter application would like to change the offset an existing Android {@code View}, i.e., platform - * view. - */ - void setOffset(int viewId, double top, double left); + /** The Flutter application would like to change the offset an existing Android {@code View}. */ + void offset(int viewId, double top, double left); /** * The user touched a platform view within Flutter. diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java index ecdf86c71820e..4c497e05d092b 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java @@ -89,9 +89,7 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result try { final JSONObject arguments = (JSONObject) args; final int platformViewId = arguments.getInt("platformViewId"); - final boolean usesVirtualDisplay = - arguments.optBoolean("usesVirtualDisplay", false); - textInputMethodHandler.setPlatformViewClient(platformViewId, usesVirtualDisplay); + textInputMethodHandler.setPlatformViewClient(platformViewId); result.success(null); } catch (JSONException exception) { result.error("error", exception.getMessage(), null); @@ -402,10 +400,8 @@ public interface TextInputMethodHandler { * different client is set. * * @param id the ID of the platform view to be set as a text input client. - * @param usesVirtualDisplay True if the platform view uses a virtual display, false if it uses - * hybrid composition. */ - void setPlatformViewClient(int id, boolean usesVirtualDisplay); + void setPlatformViewClient(int id); /** * Sets the size and the transform matrix of the current text input client. diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 7ca0febb19c39..b12a06b2a6fa6 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -136,8 +136,8 @@ public void setClient( } @Override - public void setPlatformViewClient(int platformViewId, boolean usesVirtualDisplay) { - setPlatformViewTextInputClient(platformViewId, usesVirtualDisplay); + public void setPlatformViewClient(int platformViewId) { + setPlatformViewTextInputClient(platformViewId); } @Override @@ -429,20 +429,9 @@ void setTextInputClient(int client, TextInputChannel.Configuration configuration mEditable.addEditingStateListener(this); } - private void setPlatformViewTextInputClient(int platformViewId, boolean usesVirtualDisplay) { - if (usesVirtualDisplay) { - // We need to make sure that the Flutter view is focused so that no imm operations get short - // circuited. - // Not asking for focus here specifically manifested in a but on API 28 devices where the - // platform view's request to show a keyboard was ignored. - mView.requestFocus(); - inputTarget = new InputTarget(InputTarget.Type.VD_PLATFORM_VIEW, platformViewId); - mImm.restartInput(mView); - mRestartInputPending = false; - } else { - inputTarget = new InputTarget(InputTarget.Type.HC_PLATFORM_VIEW, platformViewId); - lastInputConnection = null; - } + private void setPlatformViewTextInputClient(int platformViewId) { + inputTarget = new InputTarget(InputTarget.Type.HC_PLATFORM_VIEW, platformViewId); + lastInputConnection = null; } private static boolean composingChanged( diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java new file mode 100644 index 0000000000000..1433620dd0945 --- /dev/null +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -0,0 +1,201 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugin.platform; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.SurfaceTexture; +import android.os.Build; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.View; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import io.flutter.Log; +import io.flutter.embedding.android.AndroidTouchProcessor; +import io.flutter.util.ViewUtils; + +@TargetApi(Build.VERSION_CODES.M) +class PlatformViewWrapper extends FrameLayout { + private static final String TAG = "PlatformViewWrapper"; + + private int sPrevLeft; + private int sPrevTop; + private int sLeft; + private int sTop; + private int sWidth; + private int sHeight; + private SurfaceTexture sTx; + private Surface sSurface; + private AndroidTouchProcessor sTouchProcessor; + + @Nullable @VisibleForTesting ViewTreeObserver.OnGlobalFocusChangeListener activeFocusListener; + + public PlatformViewWrapper(@NonNull Context context) { + super(context); + setWillNotDraw(false); + } + + public void setTouchProcessor(@Nullable AndroidTouchProcessor touchProcessor) { + sTouchProcessor = touchProcessor; + } + + public void setTexture(@Nullable SurfaceTexture tx) { + if (sTx != null) { + sTx.release(); + } + sTx = tx; + + if (sSurface != null) { + sSurface.release(); + } + sSurface = new Surface(sTx); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + // Fill the entire canvas with a transparent color. + // As a result, the background color of the platform view container is displayed + // to the user + // until the platform view draws its first frame. + final Canvas canvas = sSurface.lockHardwareCanvas(); + canvas.drawColor(Color.TRANSPARENT); + sSurface.unlockCanvasAndPost(canvas); + } else { + Log.e(TAG, "Platform views cannot be displayed below API level 23."); + } + } + + public void setLayoutParams(@NonNull FrameLayout.LayoutParams params) { + super.setLayoutParams(params); + + sLeft = params.leftMargin; + sTop = params.topMargin; + } + + public void setDefaultBufferSize(int width, int height) { + sWidth = width; + sHeight = height; + if (sTx != null) { + sTx.setDefaultBufferSize(sWidth, sHeight); + } + } + + public int getBufferWidth() { + return sWidth; + } + + public int getBufferHeight() { + return sHeight; + } + + @Nullable + public SurfaceTexture getTexture() { + return sTx; + } + + public void release() { + if (sTx != null) { + sTx.release(); + sTx = null; + } + if (sSurface != null) { + sSurface.release(); + sSurface = null; + } + } + + @Override + public boolean onInterceptTouchEvent(@NonNull MotionEvent event) { + return true; + } + + @Override + public void onDescendantInvalidated(@NonNull View child, @NonNull View target) { + invalidate(); + } + + @Override + public void draw(Canvas canvas) { + if (sSurface == null || !sSurface.isValid()) { + Log.e(TAG, "Invalid surface. The platform view cannot be displayed."); + return; + } + if (sTx == null || sTx.isReleased()) { + Log.e(TAG, "Invalid texture. The platform view cannot be displayed."); + return; + } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + Log.e(TAG, "Platform views cannot be displayed below API level 23."); + return; + } + // Override the canvas that this subtree of views will use to draw. + final Canvas surfaceCanvas = sSurface.lockHardwareCanvas(); + try { + super.draw(surfaceCanvas); + } finally { + sSurface.unlockCanvasAndPost(surfaceCanvas); + } + } + + @Override + @SuppressLint("ClickableViewAccessibility") + public boolean onTouchEvent(@NonNull MotionEvent event) { + if (sTouchProcessor == null) { + return super.onTouchEvent(event); + } + final Matrix screenMatrix = new Matrix(); + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + sPrevLeft = sLeft; + sPrevTop = sTop; + screenMatrix.postTranslate(sPrevLeft, sPrevTop); + break; + case MotionEvent.ACTION_MOVE: + // While the view is dragged, use the left and top positions as + // they were at the moment the touch event fired. + screenMatrix.postTranslate(sPrevTop, sPrevTop); + sPrevLeft = sLeft; + sPrevTop = sTop; + break; + case MotionEvent.ACTION_UP: + default: + screenMatrix.postTranslate(sLeft, sTop); + break; + } + return sTouchProcessor.onTouchEvent(event, screenMatrix); + } + + public void setOnDescendantFocusChangeListener(@NonNull OnFocusChangeListener userFocusListener) { + unsetOnDescendantFocusChangeListener(); + + final ViewTreeObserver observer = getViewTreeObserver(); + if (observer.isAlive() && activeFocusListener == null) { + activeFocusListener = + new ViewTreeObserver.OnGlobalFocusChangeListener() { + @Override + public void onGlobalFocusChanged(View oldFocus, View newFocus) { + userFocusListener.onFocusChange( + PlatformViewWrapper.this, ViewUtils.childHasFocus(PlatformViewWrapper.this)); + } + }; + observer.addOnGlobalFocusChangeListener(activeFocusListener); + } + } + + public void unsetOnDescendantFocusChangeListener() { + final ViewTreeObserver observer = getViewTreeObserver(); + if (observer.isAlive() && activeFocusListener != null) { + final ViewTreeObserver.OnGlobalFocusChangeListener currFocusListener = activeFocusListener; + activeFocusListener = null; + observer.removeOnGlobalFocusChangeListener(currFocusListener); + } + } +} diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java index ad693a8724880..0ab99bfe1fc66 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java @@ -5,6 +5,7 @@ package io.flutter.plugin.platform; import android.view.View; +import androidx.annotation.Nullable; import io.flutter.view.AccessibilityBridge; /** Facilitates interaction between the accessibility bridge and embedded platform views. */ @@ -13,10 +14,8 @@ public interface PlatformViewsAccessibilityDelegate { * Returns the root of the view hierarchy for the platform view with the requested id, or null if * there is no corresponding view. */ - View getPlatformViewById(Integer id); - - /** Returns true if the platform view uses virtual displays. */ - boolean usesVirtualDisplay(Integer id); + @Nullable + View getPlatformViewById(int viewId); /** * Attaches an accessibility bridge for this platform views accessibility delegate. diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 38a6225a12f12..4925128f4bcdc 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -10,20 +10,14 @@ import android.annotation.TargetApi; import android.content.Context; import android.os.Build; -import android.util.DisplayMetrics; import android.util.SparseArray; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; -import android.view.TextureView; -import android.graphics.Color; import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; -import android.view.MotionEvent; -import io.flutter.Log; import io.flutter.embedding.android.AndroidTouchProcessor; import io.flutter.embedding.android.FlutterImageView; import io.flutter.embedding.android.FlutterView; @@ -37,17 +31,8 @@ import io.flutter.view.AccessibilityBridge; import io.flutter.view.TextureRegistry; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.SurfaceTexture; -import android.view.Surface; -import android.view.TextureView.SurfaceTextureListener; -import android.graphics.HardwareRenderer; -import android.graphics.RenderNode; /** * Manages platform views. @@ -81,20 +66,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega // dispatched. private final AccessibilityEventsDelegate accessibilityEventsDelegate; - // TODO(mattcarroll): Refactor overall platform views to facilitate testing and then make - // this private. This is visible as a hack to facilitate testing. This was deemed the least - // bad option at the time of writing. - @VisibleForTesting /* package */ final HashMap vdControllers; - - // Maps a virtual display's context to the platform view hosted in this virtual display. - // 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. - @VisibleForTesting /* package */ final HashMap contextToPlatformView; - - // The views returned by `PlatformView#getView()`. - // - // This only applies to hybrid composition. + // The platform views. private final SparseArray platformViews; // The platform view parents that are appended to `FlutterView`. @@ -104,12 +76,18 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega // This view provides a wrapper that applies scene builder operations to the platform view. // For example, a transform matrix, or setting opacity on the platform view layer. // - // This is only applies to hybrid composition. + // This is only applies to hybrid composition (PlatformViewLayer render). + // TODO(egarciad): Eliminate this. private final SparseArray platformViewParent; // Map of unique IDs to views that render overlay layers. private final SparseArray overlayLayerViews; + // View wrappers are FrameLayouts that contain a single child view. + // This child view is the platform view. + // This only applies to hybrid composition (TextureLayer render). + private final SparseArray viewWrappers; + // Next available unique ID for use in overlayLayerViews. private int nextOverlayLayerId = 0; @@ -135,7 +113,8 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega @TargetApi(Build.VERSION_CODES.KITKAT) @Override - public void createAndroidViewForPlatformView( + // TODO(egarciad): Remove the need for this. + public void createForPlatformViewLayer( @NonNull PlatformViewsChannel.PlatformViewCreationRequest request) { // API level 19 is required for `android.graphics.ImageReader`. ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT); @@ -165,255 +144,145 @@ public void createAndroidViewForPlatformView( platformViews.put(request.viewId, platformView); } + @TargetApi(Build.VERSION_CODES.M) @Override - public void disposeAndroidViewForPlatformView(int viewId) { - // Hybrid view. - final PlatformView platformView = platformViews.get(viewId); - final FlutterMutatorView parentView = platformViewParent.get(viewId); - if (platformView != null) { - if (parentView != null) { - parentView.removeView(platformView.getView()); - } - platformViews.remove(viewId); - platformView.dispose(); - } - if (parentView != null) { - parentView.unsetOnDescendantFocusChangeListener(); - ((ViewGroup) parentView.getParent()).removeView(parentView); - platformViewParent.remove(viewId); - } - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - @Override - public long createVirtualDisplayForPlatformView( + public long createForTextureLayer( @NonNull PlatformViewsChannel.PlatformViewCreationRequest request) { - // API level 20 is required for VirtualDisplay#setSurface which we use when resizing a - // platform view. - ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH); + final int viewId = request.viewId; + if (viewWrappers.contains(viewId)) { + throw new IllegalStateException( + "Trying to create an already created platform view, view id: " + viewId); + } if (!validateDirection(request.direction)) { throw new IllegalStateException( "Trying to create a view with unknown direction value: " + request.direction + "(view id: " - + request.viewId + + viewId + ")"); } - - if (vdControllers.containsKey(request.viewId)) { - throw new IllegalStateException( - "Trying to create an already created platform view, view id: " + request.viewId); - } - - PlatformViewFactory viewFactory = registry.getFactory(request.viewType); + final PlatformViewFactory viewFactory = registry.getFactory(request.viewType); if (viewFactory == null) { throw new IllegalStateException( "Trying to create a platform view of unregistered type: " + request.viewType); } - Object createParams = null; if (request.params != null) { createParams = viewFactory.getCreateArgsCodec().decodeMessage(request.params); } - int physicalWidth = toPhysicalPixels(request.logicalWidth); - int physicalHeight = toPhysicalPixels(request.logicalHeight); - validateVirtualDisplayDimensions(physicalWidth, physicalHeight); - - - // final SurfaceTexture surfaceTexture = new SurfaceTexture(0); - - - - // final HardwareRenderer hr = new HardwareRenderer(); - // hr.setSurface(surface); - - // RenderNode rn = new RenderNode("myRenderNode"); - // rn.setPosition(0, 0, physicalWidth, physicalHeight); - // RecordingCanvas canvas = rn.beginRecording(); - // hr.setContentRoot(content); - - - - // TextureView textureView = new TextureView(context); - // textureView.setSurfaceTexture(surfaceTexture); - - TextureRegistry.SurfaceTextureEntry textureEntry = textureRegistry.createSurfaceTexture(); - textureEntry.surfaceTexture().setDefaultBufferSize(physicalWidth, physicalHeight); - - - - final PlatformView platformView = viewFactory.create(context, request.viewId, createParams); - final FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(physicalWidth, physicalHeight); - - - - Surface surface = new Surface(textureEntry.surfaceTexture()); - - FrameLayout parent = new FrameLayout(context) { - @Override - public void draw(Canvas canvas) { - if (textureEntry.surfaceTexture().isReleased()) { - super.draw(canvas); - return; - } - System.out.println("draw"); - - Canvas c = surface.lockHardwareCanvas(); - if (c != null) { - try { - c.drawColor(Color.BLACK); - super.draw(c); - } finally { - surface.unlockCanvasAndPost(c); - } - } + final PlatformView platformView = viewFactory.create(context, viewId, createParams); + platformViews.put(viewId, platformView); + + final int physicalWidth = toPhysicalPixels(request.logicalWidth); + final int physicalHeight = toPhysicalPixels(request.logicalHeight); + final TextureRegistry.SurfaceTextureEntry textureEntry = + textureRegistry.createSurfaceTexture(); + + final PlatformViewWrapper wrapperView = new PlatformViewWrapper(context); + wrapperView.setTexture(textureEntry.surfaceTexture()); + wrapperView.setDefaultBufferSize(physicalWidth, physicalHeight); + wrapperView.setTouchProcessor(androidTouchProcessor); + + final FrameLayout.LayoutParams layoutParams = + new FrameLayout.LayoutParams(physicalWidth, physicalHeight); + wrapperView.setLayoutParams(layoutParams); + + wrapperView.setLayoutDirection(request.direction); + wrapperView.addView(platformView.getView()); + wrapperView.setOnDescendantFocusChangeListener( + (view, hasFocus) -> { + if (hasFocus) { + platformViewsChannel.invokeViewFocused(viewId); + } else if (textInputPlugin != null) { + textInputPlugin.clearPlatformViewClient(viewId); } + }); - @Override - public void onDescendantInvalidated(@NonNull View child, @NonNull View target) { - //not called per every frame. e.g. playing video on youtube.com doesn't result in calls. - // super.onDescendantInvalidated(child, target); - System.out.println("onDescendantInvalidated"); - invalidate(); - } - }; - - parent.setLayoutParams(layoutParams); - parent.setWillNotDraw(false); - parent.addView(platformView.getView()); - parent.bringToFront(); - flutterView.addView(parent); - - pv = parent; - viewLayout = layoutParams; - - - System.out.println("isHardwareAccelerated=" + parent.isHardwareAccelerated()); - - - - - - - - // VirtualDisplayController vdController = - // VirtualDisplayController.create( - // context, - // accessibilityEventsDelegate, - // viewFactory, - // textureEntry, - // physicalWidth, - // physicalHeight, - // request.viewId, - // createParams, - // (view, hasFocus) -> { - // if (hasFocus) { - // platformViewsChannel.invokeViewFocused(request.viewId); - // } - // }); - - // if (vdController == null) { - // throw new IllegalStateException( - // "Failed creating virtual display for a " - // + request.viewType - // + " with id: " - // + request.viewId); - // } - - // // If our FlutterEngine is already attached to a Flutter UI, provide that Android - // // View to this new platform view. - // if (flutterView != null) { - // vdController.onFlutterViewAttached(flutterView); - // } - - // vdControllers.put(request.viewId, vdController); - // View platformView = vdController.getView(); - // platformView.setLayoutDirection(request.direction); - // contextToPlatformView.put(platformView.getContext(), platformView); - - // // TODO(amirh): copy accessibility nodes to the FlutterView's accessibility tree. - + flutterView.addView(wrapperView); + viewWrappers.append(viewId, wrapperView); return textureEntry.id(); } - private FrameLayout.LayoutParams viewLayout; - private FrameLayout pv; - @Override - public void disposeVirtualDisplayForPlatformView(int viewId) { - ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH); - VirtualDisplayController vdController = vdControllers.get(viewId); - if (vdController == null) { - throw new IllegalStateException( - "Trying to dispose a platform view with unknown id: " + viewId); + public void dispose(int viewId) { + final PlatformView platformView = platformViews.get(viewId); + if (platformView != null) { + platformViews.remove(viewId); + platformView.dispose(); } - - if (textInputPlugin != null) { - textInputPlugin.clearPlatformViewClient(viewId); + // The platform view is displayed using a TextureLayer. + if (viewWrappers.contains(viewId)) { + final PlatformViewWrapper viewWrapper = viewWrappers.get(viewId); + viewWrapper.unsetOnDescendantFocusChangeListener(); + flutterView.removeView(viewWrapper); + viewWrappers.remove(viewId); + return; + } + // The platform view is displayed using a PlatformViewLayer. + if (platformViewParent.contains(viewId)) { + final FlutterMutatorView parentView = platformViewParent.get(viewId); + parentView.unsetOnDescendantFocusChangeListener(); + flutterView.removeView(parentView); + platformViewParent.remove(viewId); } - - contextToPlatformView.remove(vdController.getView().getContext()); - vdController.dispose(); - vdControllers.remove(viewId); } @Override - public void setOffset(int viewId, double top, double left) { - int physicalTop = toPhysicalPixels(top); - int physicalLeft = toPhysicalPixels(left); - - viewLayout.topMargin = physicalTop; - viewLayout.leftMargin = physicalLeft; - pv.setLayoutParams(viewLayout); + public void offset(int viewId, double top, double left) { + if (!viewWrappers.contains(viewId)) { + return; + } + final int physicalTop = toPhysicalPixels(top); + final int physicalLeft = toPhysicalPixels(left); + final PlatformViewWrapper view = viewWrappers.get(viewId); + final FrameLayout.LayoutParams layoutParams = + (FrameLayout.LayoutParams) view.getLayoutParams(); + layoutParams.topMargin = physicalTop; + layoutParams.leftMargin = physicalLeft; + view.requestLayout(); } @Override - public void resizePlatformView( - @NonNull PlatformViewsChannel.PlatformViewResizeRequest request, - @NonNull Runnable onComplete) { - ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH); - - final VirtualDisplayController vdController = vdControllers.get(request.viewId); - if (vdController == null) { - throw new IllegalStateException( - "Trying to resize a platform view with unknown id: " + request.viewId); + public void resize(@NonNull PlatformViewsChannel.PlatformViewResizeRequest request) { + final int viewId = request.viewId; + if (!viewWrappers.contains(viewId)) { + return; } - - int physicalWidth = toPhysicalPixels(request.newLogicalWidth); - int physicalHeight = toPhysicalPixels(request.newLogicalHeight); - validateVirtualDisplayDimensions(physicalWidth, physicalHeight); - - // Resizing involved moving the platform view to a new virtual display. Doing so - // potentially results in losing an active input connection. To make sure we preserve - // the input connection when resizing we lock it here and unlock after the resize is - // complete. - lockInputConnection(vdController); - vdController.resize( - physicalWidth, - physicalHeight, - () -> { - unlockInputConnection(vdController); - onComplete.run(); - }); + int newWidth = toPhysicalPixels(request.newLogicalWidth); + int newHeight = toPhysicalPixels(request.newLogicalHeight); + + final PlatformViewWrapper view = viewWrappers.get(viewId); + // Resize the buffer only when the current buffer size is smaller than the new size. + // This is required to prevent a situation when smooth keyboard animation + // resizes the texture too often, such that the GPU and the platform thread don't agree on + // the + // timing of the new size. + // Resizing the texture causes pixel stretching since the size of the GL texture used in + // the engine + // is set by the framework, but the texture buffer size is set by the platform down below. + if (newWidth > view.getBufferWidth() || newHeight > view.getBufferHeight()) { + view.setDefaultBufferSize(newWidth, newHeight); + } + final FrameLayout.LayoutParams layoutParams = + (FrameLayout.LayoutParams) view.getLayoutParams(); + layoutParams.width = newWidth; + layoutParams.height = newHeight; + view.requestLayout(); } @Override public void onTouch(@NonNull PlatformViewsChannel.PlatformViewTouch touch) { final int viewId = touch.viewId; - float density = context.getResources().getDisplayMetrics().density; + if (!platformViews.contains(viewId)) { + throw new IllegalStateException("Sending touch to an unknown view with id: " + viewId); + } ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH); - if (vdControllers.containsKey(viewId)) { - final MotionEvent event = toMotionEvent(density, touch, /*usingVirtualDiplays=*/ true); - vdControllers.get(touch.viewId).dispatchTouchEvent(event); - } else if (platformViews.get(viewId) != null) { - final MotionEvent event = toMotionEvent(density, touch, /*usingVirtualDiplays=*/ false); - View view = platformViews.get(touch.viewId).getView(); - if (view != null) { - view.dispatchTouchEvent(event); - } - } else { - // throw new IllegalStateException("Sending touch to an unknown view with id: " + viewId); + final float density = context.getResources().getDisplayMetrics().density; + final MotionEvent event = toMotionEvent(density, touch); + final View view = platformViews.get(viewId).getView(); + if (view != null) { + view.dispatchTouchEvent(event); } } @@ -428,34 +297,21 @@ public void setDirection(int viewId, int direction) { + viewId + ")"); } - - ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH); - final PlatformView platformView = platformViews.get(viewId); - if (platformView != null) { - platformView.getView().setLayoutDirection(direction); - return; - } - VirtualDisplayController controller = vdControllers.get(viewId); - if (controller == null) { + if (!platformViews.contains(viewId)) { throw new IllegalStateException( - "Trying to set direction: " - + direction - + " to an unknown platform view with id: " - + viewId); + "Setting direction to an unknown view with id: " + viewId); } - controller.getView().setLayoutDirection(direction); + ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH); + platformViews.get(viewId).getView().setLayoutDirection(direction); } @Override public void clearFocus(int viewId) { - final PlatformView platformView = platformViews.get(viewId); - if (platformView != null) { - platformView.getView().clearFocus(); - return; + if (!platformViews.contains(viewId)) { + throw new IllegalStateException("Clearing focus on an unknown view with id: " + viewId); } - ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH); - View view = vdControllers.get(viewId).getView(); - view.clearFocus(); + final PlatformView platformView = platformViews.get(viewId); + platformView.getView().clearFocus(); } private void ensureValidAndroidVersion(int minSdkVersion) { @@ -475,8 +331,7 @@ public void synchronizeToNativeViewHierarchy(boolean yes) { }; @VisibleForTesting - public MotionEvent toMotionEvent( - float density, PlatformViewsChannel.PlatformViewTouch touch, boolean usingVirtualDiplays) { + public MotionEvent toMotionEvent(float density, PlatformViewsChannel.PlatformViewTouch touch) { MotionEventTracker.MotionEventId motionEventId = MotionEventTracker.MotionEventId.from(touch.motionEventId); MotionEvent trackedEvent = motionEventTracker.pop(motionEventId); @@ -492,7 +347,7 @@ public MotionEvent toMotionEvent( parsePointerCoordsList(touch.rawPointerCoords, density) .toArray(new PointerCoords[touch.pointerCount]); - if (!usingVirtualDiplays && trackedEvent != null) { + if (trackedEvent != null) { return MotionEvent.obtain( trackedEvent.getDownTime(), trackedEvent.getEventTime(), @@ -531,13 +386,11 @@ public MotionEvent toMotionEvent( public PlatformViewsController() { registry = new PlatformViewRegistryImpl(); - vdControllers = new HashMap<>(); accessibilityEventsDelegate = new AccessibilityEventsDelegate(); - contextToPlatformView = new HashMap<>(); overlayLayerViews = new SparseArray<>(); currentFrameUsedOverlayLayerIds = new HashSet<>(); currentFrameUsedPlatformViewIds = new HashSet<>(); - + viewWrappers = new SparseArray<>(); platformViews = new SparseArray<>(); platformViewParent = new SparseArray<>(); @@ -594,8 +447,9 @@ public void attachToView(@NonNull FlutterView flutterView) { // Inform all existing platform views that they are now associated with // a Flutter View. - for (VirtualDisplayController controller : vdControllers.values()) { - controller.onFlutterViewAttached(flutterView); + for (int i = 0; i < platformViews.size(); i++) { + final PlatformView view = platformViews.valueAt(i); + view.onFlutterViewAttached(flutterView); } } @@ -607,15 +461,14 @@ public void attachToView(@NonNull FlutterView flutterView) { * the previously attached {@code View}. */ public void detachFromView() { + for (int i = 0; i < platformViews.size(); i++) { + final PlatformView view = platformViews.valueAt(i); + view.onFlutterViewDetached(); + } + // TODO(egarciad): Remove this. destroyOverlaySurfaces(); this.flutterView = null; flutterViewConvertedToImageView = false; - - // Inform all existing platform views that they are no longer associated with - // a Flutter View. - for (VirtualDisplayController controller : vdControllers.values()) { - controller.onFlutterViewDetached(); - } } @Override @@ -646,29 +499,6 @@ public void detachTextInputPlugin() { textInputPlugin = null; } - /** - * Returns true if Flutter should perform input connection proxying for the view. - * - *

If the view is a platform view managed by this platform views controller returns true. Else - * if the view was created in a platform view's VD, delegates the decision to the platform view's - * {@link View#checkInputConnectionProxy(View)} method. Else returns false. - */ - public boolean checkInputConnectionProxy(@Nullable View view) { - // View can be null on some devices - // See: https://github.com/flutter/flutter/issues/36517 - if (view == null) { - return false; - } - if (!contextToPlatformView.containsKey(view.getContext())) { - return false; - } - View platformView = contextToPlatformView.get(view.getContext()); - if (platformView == view) { - return true; - } - return platformView.checkInputConnectionProxy(view); - } - public PlatformViewRegistry getRegistry() { return registry; } @@ -686,8 +516,6 @@ public void onAttachedToJNI() { * PlatformViewsController} detaches from JNI. */ public void onDetachedFromJNI() { - // Dispose all virtual displays so that any future updates to textures will not be - // propagated to the native peer. flushAllViews(); } @@ -696,37 +524,11 @@ public void onPreEngineRestart() { } @Override - public View getPlatformViewById(Integer id) { - // Hybrid composition. - if (platformViews.get(id) != null) { - return platformViews.get(id).getView(); - } - VirtualDisplayController controller = vdControllers.get(id); - if (controller == null) { + public View getPlatformViewById(int viewId) { + if (!platformViews.contains(viewId)) { return null; } - return controller.getView(); - } - - @Override - public boolean usesVirtualDisplay(Integer id) { - return vdControllers.containsKey(id); - } - - private void lockInputConnection(@NonNull VirtualDisplayController controller) { - if (textInputPlugin == null) { - return; - } - textInputPlugin.lockPlatformViewInputConnection(); - controller.onInputConnectionLocked(); - } - - private void unlockInputConnection(@NonNull VirtualDisplayController controller) { - if (textInputPlugin == null) { - return; - } - textInputPlugin.unlockPlatformViewInputConnection(); - controller.onInputConnectionUnlocked(); + return platformViews.get(viewId).getView(); } private static boolean validateDirection(int direction) { @@ -778,29 +580,6 @@ private static PointerCoords parsePointerCoords(Object rawCoords, float density) return coords; } - // Creating a VirtualDisplay larger than the size of the device screen size - // could cause the device to restart: https://github.com/flutter/flutter/issues/28978 - private void validateVirtualDisplayDimensions(int width, int height) { - DisplayMetrics metrics = context.getResources().getDisplayMetrics(); - if (height > metrics.heightPixels || width > metrics.widthPixels) { - String message = - "Creating a virtual display of size: " - + "[" - + width - + ", " - + height - + "] may result in problems" - + "(https://github.com/flutter/flutter/issues/2897)." - + "It is larger than the device screen size: " - + "[" - + metrics.widthPixels - + ", " - + metrics.heightPixels - + "]."; - Log.w(TAG, message); - } - } - private float getDisplayDensity() { return context.getResources().getDisplayMetrics().density; } @@ -810,17 +589,8 @@ private int toPhysicalPixels(double logicalPixels) { } private void flushAllViews() { - for (VirtualDisplayController controller : vdControllers.values()) { - controller.dispose(); - } - vdControllers.clear(); - while (platformViews.size() > 0) { - channelHandler.disposeAndroidViewForPlatformView(platformViews.keyAt(0)); - } - - if (contextToPlatformView.size() > 0) { - contextToPlatformView.clear(); + channelHandler.dispose(platformViews.keyAt(0)); } } diff --git a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java deleted file mode 100644 index a561f73868d86..0000000000000 --- a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java +++ /dev/null @@ -1,478 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugin.platform; - -import static android.content.Context.WINDOW_SERVICE; -import static android.view.View.OnFocusChangeListener; - -import android.annotation.TargetApi; -import android.app.AlertDialog; -import android.app.Presentation; -import android.content.Context; -import android.content.ContextWrapper; -import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; -import android.os.Build; -import android.os.Bundle; -import android.view.Display; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityEvent; -import android.view.inputmethod.InputMethodManager; -import android.widget.FrameLayout; -import androidx.annotation.Keep; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import io.flutter.Log; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; - -/* - * A presentation used for hosting a single Android view in a virtual display. - * - * This presentation overrides the WindowManager's addView/removeView/updateViewLayout methods, such that views added - * directly to the WindowManager are added as part of the presentation's view hierarchy (to fakeWindowViewGroup). - * - * The view hierarchy for the presentation is as following: - * - * rootView - * / \ - * / \ - * / \ - * container state.fakeWindowViewGroup - * | - * EmbeddedView - */ -@Keep -@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) -class SingleViewPresentation extends Presentation { - - /* - * When an embedded view is resized in Flutterverse we move the Android view to a new virtual display - * that has the new size. This class keeps the presentation state that moves with the view to the presentation of - * the new virtual display. - */ - static class PresentationState { - // The Android view we are embedding in the Flutter app. - private PlatformView platformView; - - // The InvocationHandler for a WindowManager proxy. This is essentially the custom window - // manager for the - // presentation. - private WindowManagerHandler windowManagerHandler; - - // Contains views that were added directly to the window manager (e.g - // android.widget.PopupWindow). - private FakeWindowViewGroup fakeWindowViewGroup; - } - - private final PlatformViewFactory viewFactory; - - // A reference to the current accessibility bridge to which accessibility events will be - // delegated. - private final AccessibilityEventsDelegate accessibilityEventsDelegate; - - private final OnFocusChangeListener focusChangeListener; - - // This is the view id assigned by the Flutter framework to the embedded view, we keep it here - // so when we create the platform view we can tell it its view id. - private int viewId; - - // This is the creation parameters for the platform view, we keep it here - // so when we create the platform view we can tell it its view id. - private Object createParams; - - // The root view for the presentation, it has 2 childs: container which contains the embedded - // view, and - // fakeWindowViewGroup which contains views that were added directly to the presentation's window - // manager. - private AccessibilityDelegatingFrameLayout rootView; - - // Contains the embedded platform view (platformView.getView()) when it is attached to the - // presentation. - private FrameLayout container; - - private final PresentationState state; - - private boolean startFocused = false; - - // The context for the application window that hosts FlutterView. - private final Context outerContext; - - /** - * Creates a presentation that will use the view factory to create a new platform view in the - * presentation's onCreate, and attach it. - */ - public SingleViewPresentation( - Context outerContext, - Display display, - PlatformViewFactory viewFactory, - AccessibilityEventsDelegate accessibilityEventsDelegate, - int viewId, - Object createParams, - OnFocusChangeListener focusChangeListener) { - super(new ImmContext(outerContext), display); - this.viewFactory = viewFactory; - this.accessibilityEventsDelegate = accessibilityEventsDelegate; - this.viewId = viewId; - this.createParams = createParams; - this.focusChangeListener = focusChangeListener; - this.outerContext = outerContext; - state = new PresentationState(); - getWindow() - .setFlags( - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - getWindow().setType(WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION); - } - } - - /** - * Creates a presentation that will attach an already existing view as its root view. - * - *

The display's density must match the density of the context used when the view was created. - */ - public SingleViewPresentation( - Context outerContext, - Display display, - AccessibilityEventsDelegate accessibilityEventsDelegate, - PresentationState state, - OnFocusChangeListener focusChangeListener, - boolean startFocused) { - super(new ImmContext(outerContext), display); - this.accessibilityEventsDelegate = accessibilityEventsDelegate; - viewFactory = null; - this.state = state; - this.focusChangeListener = focusChangeListener; - this.outerContext = outerContext; - getWindow() - .setFlags( - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); - this.startFocused = startFocused; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // This makes sure we preserve alpha for the VD's content. - getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT)); - if (state.fakeWindowViewGroup == null) { - state.fakeWindowViewGroup = new FakeWindowViewGroup(getContext()); - } - if (state.windowManagerHandler == null) { - WindowManager windowManagerDelegate = - (WindowManager) getContext().getSystemService(WINDOW_SERVICE); - state.windowManagerHandler = - new WindowManagerHandler(windowManagerDelegate, state.fakeWindowViewGroup); - } - - container = new FrameLayout(getContext()); - - // Our base mContext has already been wrapped with an IMM cache at instantiation time, but - // we want to wrap it again here to also return state.windowManagerHandler. - Context context = - new PresentationContext(getContext(), state.windowManagerHandler, outerContext); - - if (state.platformView == null) { - state.platformView = viewFactory.create(context, viewId, createParams); - } - - View embeddedView = state.platformView.getView(); - container.addView(embeddedView); - rootView = - new AccessibilityDelegatingFrameLayout( - getContext(), accessibilityEventsDelegate, embeddedView); - rootView.addView(container); - rootView.addView(state.fakeWindowViewGroup); - - embeddedView.setOnFocusChangeListener(focusChangeListener); - rootView.setFocusableInTouchMode(true); - if (startFocused) { - embeddedView.requestFocus(); - } else { - rootView.requestFocus(); - } - setContentView(rootView); - } - - public PresentationState detachState() { - container.removeAllViews(); - rootView.removeAllViews(); - return state; - } - - public PlatformView getView() { - if (state.platformView == null) return null; - return state.platformView; - } - - /* - * A view group that implements the same layout protocol that exist between the WindowManager and its direct - * children. - * - * Currently only a subset of the protocol is supported (gravity, x, and y). - */ - static class FakeWindowViewGroup extends ViewGroup { - // Used in onLayout to keep the bounds of the current view. - // We keep it as a member to avoid object allocations during onLayout which are discouraged. - private final Rect viewBounds; - - // Used in onLayout to keep the bounds of the child views. - // We keep it as a member to avoid object allocations during onLayout which are discouraged. - private final Rect childRect; - - public FakeWindowViewGroup(Context context) { - super(context); - viewBounds = new Rect(); - childRect = new Rect(); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - WindowManager.LayoutParams params = (WindowManager.LayoutParams) child.getLayoutParams(); - viewBounds.set(l, t, r, b); - Gravity.apply( - params.gravity, - child.getMeasuredWidth(), - child.getMeasuredHeight(), - viewBounds, - params.x, - params.y, - childRect); - child.layout(childRect.left, childRect.top, childRect.right, childRect.bottom); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - child.measure(atMost(widthMeasureSpec), atMost(heightMeasureSpec)); - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - private static int atMost(int measureSpec) { - return MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(measureSpec), MeasureSpec.AT_MOST); - } - } - - /** Answers calls for {@link InputMethodManager} with an instance cached at creation time. */ - // TODO(mklim): This caches the IMM at construction time and won't pick up any changes. In rare - // cases where the FlutterView changes windows this will return an outdated instance. This - // should be fixed to instead defer returning the IMM to something that know's FlutterView's - // true Context. - private static class ImmContext extends ContextWrapper { - private @NonNull final InputMethodManager inputMethodManager; - - ImmContext(Context base) { - this(base, /*inputMethodManager=*/ null); - } - - private ImmContext(Context base, @Nullable InputMethodManager inputMethodManager) { - super(base); - this.inputMethodManager = - inputMethodManager != null - ? inputMethodManager - : (InputMethodManager) base.getSystemService(INPUT_METHOD_SERVICE); - } - - @Override - public Object getSystemService(String name) { - if (INPUT_METHOD_SERVICE.equals(name)) { - return inputMethodManager; - } - return super.getSystemService(name); - } - - @Override - public Context createDisplayContext(Display display) { - Context displayContext = super.createDisplayContext(display); - return new ImmContext(displayContext, inputMethodManager); - } - } - - /** Proxies a Context replacing the WindowManager with our custom instance. */ - // TODO(mklim): This caches the IMM at construction time and won't pick up any changes. In rare - // cases where the FlutterView changes windows this will return an outdated instance. This - // should be fixed to instead defer returning the IMM to something that know's FlutterView's - // true Context. - private static class PresentationContext extends ContextWrapper { - private @NonNull final WindowManagerHandler windowManagerHandler; - private @Nullable WindowManager windowManager; - private final Context flutterAppWindowContext; - - PresentationContext( - Context base, - @NonNull WindowManagerHandler windowManagerHandler, - Context flutterAppWindowContext) { - super(base); - this.windowManagerHandler = windowManagerHandler; - this.flutterAppWindowContext = flutterAppWindowContext; - } - - @Override - public Object getSystemService(String name) { - if (WINDOW_SERVICE.equals(name)) { - if (isCalledFromAlertDialog()) { - // Alert dialogs are showing on top of the entire application and should not be limited to - // the virtual - // display. If we detect that an android.app.AlertDialog constructor is what's fetching - // the window manager - // we return the one for the application's window. - // - // Note that if we don't do this AlertDialog will throw a ClassCastException as down the - // line it tries - // to case this instance to a WindowManagerImpl which the object returned by - // getWindowManager is not - // a subclass of. - return flutterAppWindowContext.getSystemService(name); - } - return getWindowManager(); - } - return super.getSystemService(name); - } - - private WindowManager getWindowManager() { - if (windowManager == null) { - windowManager = windowManagerHandler.getWindowManager(); - } - return windowManager; - } - - private boolean isCalledFromAlertDialog() { - StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); - for (int i = 0; i < stackTraceElements.length && i < 11; i++) { - if (stackTraceElements[i].getClassName().equals(AlertDialog.class.getCanonicalName()) - && stackTraceElements[i].getMethodName().equals("")) { - return true; - } - } - return false; - } - } - - /* - * A dynamic proxy handler for a WindowManager with custom overrides. - * - * The presentation's window manager delegates all calls to the default window manager. - * WindowManager#addView calls triggered by views that are attached to the virtual display are crashing - * (see: https://github.com/flutter/flutter/issues/20714). This was triggered when selecting text in an embedded - * WebView (as the selection handles are implemented as popup windows). - * - * This dynamic proxy overrides the addView, removeView, removeViewImmediate, and updateViewLayout methods - * to prevent these crashes. - * - * This will be more efficient as a static proxy that's not using reflection, but as the engine is currently - * not being built against the latest Android SDK we cannot override all relevant method. - * Tracking issue for upgrading the engine's Android sdk: https://github.com/flutter/flutter/issues/20717 - */ - static class WindowManagerHandler implements InvocationHandler { - private static final String TAG = "PlatformViewsController"; - - private final WindowManager delegate; - FakeWindowViewGroup fakeWindowRootView; - - WindowManagerHandler(WindowManager delegate, FakeWindowViewGroup fakeWindowViewGroup) { - this.delegate = delegate; - fakeWindowRootView = fakeWindowViewGroup; - } - - public WindowManager getWindowManager() { - return (WindowManager) - Proxy.newProxyInstance( - WindowManager.class.getClassLoader(), new Class[] {WindowManager.class}, this); - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - switch (method.getName()) { - case "addView": - addView(args); - return null; - case "removeView": - removeView(args); - return null; - case "removeViewImmediate": - removeViewImmediate(args); - return null; - case "updateViewLayout": - updateViewLayout(args); - return null; - } - try { - return method.invoke(delegate, args); - } catch (InvocationTargetException e) { - throw e.getCause(); - } - } - - private void addView(Object[] args) { - if (fakeWindowRootView == null) { - Log.w(TAG, "Embedded view called addView while detached from presentation"); - return; - } - View view = (View) args[0]; - WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) args[1]; - fakeWindowRootView.addView(view, layoutParams); - } - - private void removeView(Object[] args) { - if (fakeWindowRootView == null) { - Log.w(TAG, "Embedded view called removeView while detached from presentation"); - return; - } - View view = (View) args[0]; - fakeWindowRootView.removeView(view); - } - - private void removeViewImmediate(Object[] args) { - if (fakeWindowRootView == null) { - Log.w(TAG, "Embedded view called removeViewImmediate while detached from presentation"); - return; - } - View view = (View) args[0]; - view.clearAnimation(); - fakeWindowRootView.removeView(view); - } - - private void updateViewLayout(Object[] args) { - if (fakeWindowRootView == null) { - Log.w(TAG, "Embedded view called updateViewLayout while detached from presentation"); - return; - } - View view = (View) args[0]; - WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) args[1]; - fakeWindowRootView.updateViewLayout(view, layoutParams); - } - } - - private static class AccessibilityDelegatingFrameLayout extends FrameLayout { - private final AccessibilityEventsDelegate accessibilityEventsDelegate; - private final View embeddedView; - - public AccessibilityDelegatingFrameLayout( - Context context, - AccessibilityEventsDelegate accessibilityEventsDelegate, - View embeddedView) { - super(context); - this.accessibilityEventsDelegate = accessibilityEventsDelegate; - this.embeddedView = embeddedView; - } - - @Override - public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) { - return accessibilityEventsDelegate.requestSendAccessibilityEvent(embeddedView, child, event); - } - } -} diff --git a/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java b/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java deleted file mode 100644 index fec53e89a6d9b..0000000000000 --- a/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugin.platform; - -import static android.view.View.OnFocusChangeListener; - -import android.annotation.TargetApi; -import android.content.Context; -import android.hardware.display.DisplayManager; -import android.hardware.display.VirtualDisplay; -import android.os.Build; -import android.view.MotionEvent; -import android.view.Surface; -import android.view.View; -import android.view.ViewTreeObserver; -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; -import io.flutter.view.TextureRegistry; - -@TargetApi(Build.VERSION_CODES.KITKAT_WATCH) -class VirtualDisplayController { - - public static VirtualDisplayController create( - Context context, - AccessibilityEventsDelegate accessibilityEventsDelegate, - PlatformViewFactory viewFactory, - TextureRegistry.SurfaceTextureEntry textureEntry, - int width, - int height, - int viewId, - Object createParams, - OnFocusChangeListener focusChangeListener) { - textureEntry.surfaceTexture().setDefaultBufferSize(width, height); - Surface surface = new Surface(textureEntry.surfaceTexture()); - DisplayManager displayManager = - (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); - - int densityDpi = context.getResources().getDisplayMetrics().densityDpi; - VirtualDisplay virtualDisplay = - displayManager.createVirtualDisplay("flutter-vd", width, height, densityDpi, surface, 0); - - if (virtualDisplay == null) { - return null; - } - - return new VirtualDisplayController( - context, - accessibilityEventsDelegate, - virtualDisplay, - viewFactory, - surface, - textureEntry, - focusChangeListener, - viewId, - createParams); - } - - private final Context context; - private final AccessibilityEventsDelegate accessibilityEventsDelegate; - private final int densityDpi; - private final TextureRegistry.SurfaceTextureEntry textureEntry; - private final OnFocusChangeListener focusChangeListener; - private VirtualDisplay virtualDisplay; - @VisibleForTesting SingleViewPresentation presentation; - private final Surface surface; - - private VirtualDisplayController( - Context context, - AccessibilityEventsDelegate accessibilityEventsDelegate, - VirtualDisplay virtualDisplay, - PlatformViewFactory viewFactory, - Surface surface, - TextureRegistry.SurfaceTextureEntry textureEntry, - OnFocusChangeListener focusChangeListener, - int viewId, - Object createParams) { - this.context = context; - this.accessibilityEventsDelegate = accessibilityEventsDelegate; - this.textureEntry = textureEntry; - this.focusChangeListener = focusChangeListener; - this.surface = surface; - this.virtualDisplay = virtualDisplay; - densityDpi = context.getResources().getDisplayMetrics().densityDpi; - presentation = - new SingleViewPresentation( - context, - this.virtualDisplay.getDisplay(), - viewFactory, - accessibilityEventsDelegate, - viewId, - createParams, - focusChangeListener); - presentation.show(); - } - - public void resize(final int width, final int height, final Runnable onNewSizeFrameAvailable) { - boolean isFocused = getView().isFocused(); - final SingleViewPresentation.PresentationState presentationState = presentation.detachState(); - // We detach the surface to prevent it being destroyed when releasing the vd. - // - // setSurface is only available starting API 20. We could support API 19 by re-creating a new - // SurfaceTexture here. This will require refactoring the TextureRegistry to allow recycling - // texture - // entry IDs. - virtualDisplay.setSurface(null); - virtualDisplay.release(); - - textureEntry.surfaceTexture().setDefaultBufferSize(width, height); - DisplayManager displayManager = - (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); - virtualDisplay = - displayManager.createVirtualDisplay("flutter-vd", width, height, densityDpi, surface, 0); - - final View embeddedView = getView(); - // There's a bug in Android version older than O where view tree observer onDrawListeners don't - // get properly - // merged when attaching to window, as a workaround we register the on draw listener after the - // view is attached. - embeddedView.addOnAttachStateChangeListener( - new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - OneTimeOnDrawListener.schedule( - embeddedView, - new Runnable() { - @Override - public void run() { - // We need some delay here until the frame propagates through the vd surface to - // to the texture, - // 128ms was picked pretty arbitrarily based on trial and error. - // As long as we invoke the runnable after a new frame is available we avoid the - // scaling jank - // described in: https://github.com/flutter/flutter/issues/19572 - // We should ideally run onNewSizeFrameAvailable ASAP to make the embedded view - // more responsive - // following a resize. - embeddedView.postDelayed(onNewSizeFrameAvailable, 128); - } - }); - embeddedView.removeOnAttachStateChangeListener(this); - } - - @Override - public void onViewDetachedFromWindow(View v) {} - }); - - // Create a new SingleViewPresentation and show() it before we cancel() the existing - // presentation. Calling show() and cancel() in this order fixes - // https://github.com/flutter/flutter/issues/26345 and maintains seamless transition - // of the contents of the presentation. - SingleViewPresentation newPresentation = - new SingleViewPresentation( - context, - virtualDisplay.getDisplay(), - accessibilityEventsDelegate, - presentationState, - focusChangeListener, - isFocused); - newPresentation.show(); - presentation.cancel(); - presentation = newPresentation; - } - - public void dispose() { - PlatformView view = presentation.getView(); - // Fix rare crash on HuaWei device described in: https://github.com/flutter/engine/pull/9192 - presentation.cancel(); - presentation.detachState(); - view.dispose(); - virtualDisplay.release(); - textureEntry.release(); - } - - /** See {@link PlatformView#onFlutterViewAttached(View)} */ - /*package*/ void onFlutterViewAttached(@NonNull View flutterView) { - if (presentation == null || presentation.getView() == null) { - return; - } - presentation.getView().onFlutterViewAttached(flutterView); - } - - /** See {@link PlatformView#onFlutterViewDetached()} */ - /*package*/ void onFlutterViewDetached() { - if (presentation == null || presentation.getView() == null) { - return; - } - presentation.getView().onFlutterViewDetached(); - } - - /*package*/ void onInputConnectionLocked() { - if (presentation == null || presentation.getView() == null) { - return; - } - presentation.getView().onInputConnectionLocked(); - } - - /*package*/ void onInputConnectionUnlocked() { - if (presentation == null || presentation.getView() == null) { - return; - } - presentation.getView().onInputConnectionUnlocked(); - } - - public View getView() { - if (presentation == null) return null; - PlatformView platformView = presentation.getView(); - return platformView.getView(); - } - - /** Dispatches a motion event to the presentation for this controller. */ - public void dispatchTouchEvent(MotionEvent event) { - if (presentation == null) return; - presentation.dispatchTouchEvent(event); - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - static class OneTimeOnDrawListener implements ViewTreeObserver.OnDrawListener { - static void schedule(View view, Runnable runnable) { - OneTimeOnDrawListener listener = new OneTimeOnDrawListener(view, runnable); - view.getViewTreeObserver().addOnDrawListener(listener); - } - - final View mView; - Runnable mOnDrawRunnable; - - OneTimeOnDrawListener(View view, Runnable onDrawRunnable) { - this.mView = view; - this.mOnDrawRunnable = onDrawRunnable; - } - - @Override - public void onDraw() { - if (mOnDrawRunnable == null) { - return; - } - mOnDrawRunnable.run(); - mOnDrawRunnable = null; - mView.post( - new Runnable() { - @Override - public void run() { - mView.getViewTreeObserver().removeOnDrawListener(OneTimeOnDrawListener.this); - } - }); - } - } -} diff --git a/shell/platform/android/io/flutter/util/ViewUtils.java b/shell/platform/android/io/flutter/util/ViewUtils.java index 6ebac49a00f89..bac41ac7c06ca 100644 --- a/shell/platform/android/io/flutter/util/ViewUtils.java +++ b/shell/platform/android/io/flutter/util/ViewUtils.java @@ -9,6 +9,8 @@ import android.content.ContextWrapper; import android.os.Build; import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.Nullable; public final class ViewUtils { /** @@ -45,4 +47,28 @@ public static int generateViewId(int fallbackId) { } return fallbackId; } + + /** + * Determines if the current view or any descendant view has focus. + * + * @param root The root view. + * @return True if the current view or any descendant view has focus. + */ + public static boolean childHasFocus(@Nullable View root) { + if (root == null) { + return false; + } + if (root.hasFocus()) { + return true; + } + if (root instanceof ViewGroup) { + final ViewGroup viewGroup = (ViewGroup) root; + for (int idx = 0; idx < viewGroup.getChildCount(); idx++) { + if (childHasFocus(viewGroup.getChildAt(idx))) { + return true; + } + } + } + return false; + } } diff --git a/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/shell/platform/android/io/flutter/view/AccessibilityBridge.java index 2381d879898e5..b79191bd09970 100644 --- a/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -573,23 +573,6 @@ public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { return null; } - // Generate accessibility node for platform views using a virtual display. - // - // In this case, register the accessibility node in the view embedder, - // so the accessibility tree can be mirrored as a subtree of the Flutter accessibility tree. - // This is in constrast to hybrid composition where the embedded view is in the view hiearchy, - // so it doesn't need to be mirrored. - // - // See the case down below for how hybrid composition is handled. - if (semanticsNode.platformViewId != -1) { - View embeddedView = - platformViewsAccessibilityDelegate.getPlatformViewById(semanticsNode.platformViewId); - if (platformViewsAccessibilityDelegate.usesVirtualDisplay(semanticsNode.platformViewId)) { - Rect bounds = semanticsNode.getGlobalRect(); - return accessibilityViewEmbedder.getRootNode(embeddedView, semanticsNode.id, bounds); - } - } - AccessibilityNodeInfo result = obtainAccessibilityNodeInfo(rootAccessibilityView, virtualViewId); // Work around for https://github.com/flutter/flutter/issues/2101 @@ -904,17 +887,10 @@ && shouldSetCollectionInfo(semanticsNode)) { // Add the embedded view as a child of the current accessibility node if it's using // hybrid composition. - // - // In this case, the view is in the Activity's view hierarchy, so it doesn't need to be - // mirrored. - // - // See the case above for how virtual displays are handled. - if (!platformViewsAccessibilityDelegate.usesVirtualDisplay(child.platformViewId)) { - result.addChild(embeddedView); - continue; - } + result.addChild(embeddedView); + } else { + result.addChild(rootAccessibilityView, child.id); } - result.addChild(rootAccessibilityView, child.id); } return result; } @@ -1545,8 +1521,7 @@ void updateSemantics( if (semanticsNode.hadPreviousConfig) { updated.add(semanticsNode); } - if (semanticsNode.platformViewId != -1 - && !platformViewsAccessibilityDelegate.usesVirtualDisplay(semanticsNode.platformViewId)) { + if (semanticsNode.platformViewId != -1) { View embeddedView = platformViewsAccessibilityDelegate.getPlatformViewById(semanticsNode.platformViewId); if (embeddedView != null) { @@ -1958,9 +1933,7 @@ private void willRemoveSemanticsNode(SemanticsNode semanticsNodeToBeRemoved) { embeddedAccessibilityFocusedNodeId = null; } - if (semanticsNodeToBeRemoved.platformViewId != -1 - && !platformViewsAccessibilityDelegate.usesVirtualDisplay( - semanticsNodeToBeRemoved.platformViewId)) { + if (semanticsNodeToBeRemoved.platformViewId != -1) { View embeddedView = platformViewsAccessibilityDelegate.getPlatformViewById( semanticsNodeToBeRemoved.platformViewId); diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index dccd40a0cd484..ab58c16104652 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -26,7 +26,6 @@ import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; -import android.view.View; import android.view.ViewConfiguration; import android.view.ViewStructure; import android.view.WindowInsets; @@ -427,14 +426,6 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) { return mTextInputPlugin.createInputConnection(this, mKeyboardManager, outAttrs); } - @Override - public boolean checkInputConnectionProxy(View view) { - return mNativeView - .getPluginRegistry() - .getPlatformViewsController() - .checkInputConnectionProxy(view); - } - @Override public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) { super.onProvideAutofillVirtualStructure(structure, flags); diff --git a/shell/platform/android/io/flutter/view/TextureRegistry.java b/shell/platform/android/io/flutter/view/TextureRegistry.java index f45b3b784b8bc..5d30d3ddc00ea 100644 --- a/shell/platform/android/io/flutter/view/TextureRegistry.java +++ b/shell/platform/android/io/flutter/view/TextureRegistry.java @@ -27,7 +27,8 @@ public interface TextureRegistry { * * @return A SurfaceTextureEntry. */ - SurfaceTextureEntry registerSurfaceTexture(@NonNull SurfaceTexture surfaceTexture, @NonNull Bitmap bitmap); + SurfaceTextureEntry registerSurfaceTexture( + @NonNull SurfaceTexture surfaceTexture, @NonNull Bitmap bitmap); /** A registry entry for a managed SurfaceTexture. */ interface SurfaceTextureEntry { diff --git a/shell/platform/android/test/io/flutter/FlutterTestSuite.java b/shell/platform/android/test/io/flutter/FlutterTestSuite.java index 7f42093a2f657..86e501c33d5f3 100644 --- a/shell/platform/android/test/io/flutter/FlutterTestSuite.java +++ b/shell/platform/android/test/io/flutter/FlutterTestSuite.java @@ -43,7 +43,6 @@ import io.flutter.plugin.mouse.MouseCursorPluginTest; import io.flutter.plugin.platform.PlatformPluginTest; import io.flutter.plugin.platform.PlatformViewsControllerTest; -import io.flutter.plugin.platform.SingleViewPresentationTest; import io.flutter.util.PreconditionsTest; import io.flutter.view.AccessibilityBridgeTest; import org.junit.runner.RunWith; @@ -96,7 +95,6 @@ RenderingComponentTest.class, RestorationChannelTest.class, ShimPluginRegistryTest.class, - SingleViewPresentationTest.class, SmokeTest.class, StandardMessageCodecTest.class, StandardMethodCodecTest.class, 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 c9a4b178eb5cd..d8cc771f023c5 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -9,8 +9,8 @@ import android.content.Context; import android.content.res.AssetManager; -import android.graphics.SurfaceTexture; import android.graphics.Bitmap; +import android.graphics.SurfaceTexture; import android.util.SparseArray; import android.view.MotionEvent; import android.view.Surface; @@ -57,72 +57,6 @@ @RunWith(RobolectricTestRunner.class) public class PlatformViewsControllerTest { - @Ignore - @Test - public void itNotifiesVirtualDisplayControllersOfViewAttachmentAndDetachment() { - // Setup test structure. - // Create a fake View that represents the View that renders a Flutter UI. - FlutterView fakeFlutterView = new FlutterView(RuntimeEnvironment.systemContext); - - // Create fake VirtualDisplayControllers. This requires internal knowledge of - // PlatformViewsController. We know that all PlatformViewsController does is - // forward view attachment/detachment calls to it's VirtualDisplayControllers. - // - // TODO(mattcarroll): once PlatformViewsController is refactored into testable - // pieces, remove this test and avoid verifying private behavior. - VirtualDisplayController fakeVdController1 = mock(VirtualDisplayController.class); - VirtualDisplayController fakeVdController2 = mock(VirtualDisplayController.class); - - // Create the PlatformViewsController that is under test. - PlatformViewsController platformViewsController = new PlatformViewsController(); - - // Manually inject fake VirtualDisplayControllers into the PlatformViewsController. - platformViewsController.vdControllers.put(0, fakeVdController1); - platformViewsController.vdControllers.put(1, fakeVdController1); - - // Execute test & verify results. - // Attach PlatformViewsController to the fake Flutter View. - platformViewsController.attachToView(fakeFlutterView); - - // Verify that all virtual display controllers were notified of View attachment. - verify(fakeVdController1, times(1)).onFlutterViewAttached(eq(fakeFlutterView)); - verify(fakeVdController1, never()).onFlutterViewDetached(); - verify(fakeVdController2, times(1)).onFlutterViewAttached(eq(fakeFlutterView)); - verify(fakeVdController2, never()).onFlutterViewDetached(); - - // Detach PlatformViewsController from the fake Flutter View. - platformViewsController.detachFromView(); - - // Verify that all virtual display controllers were notified of the View detachment. - verify(fakeVdController1, times(1)).onFlutterViewAttached(eq(fakeFlutterView)); - verify(fakeVdController1, times(1)).onFlutterViewDetached(); - verify(fakeVdController2, times(1)).onFlutterViewAttached(eq(fakeFlutterView)); - verify(fakeVdController2, times(1)).onFlutterViewDetached(); - } - - @Ignore - @Test - public void itCancelsOldPresentationOnResize() { - // Setup test structure. - // Create a fake View that represents the View that renders a Flutter UI. - View fakeFlutterView = new View(RuntimeEnvironment.systemContext); - - // Create fake VirtualDisplayControllers. This requires internal knowledge of - // PlatformViewsController. We know that all PlatformViewsController does is - // forward view attachment/detachment calls to it's VirtualDisplayControllers. - // - // TODO(mattcarroll): once PlatformViewsController is refactored into testable - // pieces, remove this test and avoid verifying private behavior. - VirtualDisplayController fakeVdController1 = mock(VirtualDisplayController.class); - - SingleViewPresentation presentation = fakeVdController1.presentation; - - fakeVdController1.resize(10, 10, null); - - assertEquals(fakeVdController1.presentation != presentation, true); - assertEquals(presentation.isShowing(), false); - } - @Test public void itUsesActionEventTypeFromFrameworkEventForVirtualDisplays() { MotionEventTracker motionEventTracker = MotionEventTracker.getInstance(); @@ -738,13 +672,6 @@ public void destroyOverlaySurfaces__doesNotThrowIfControllerIsDetached() { verify(overlayImageView, times(1)).closeImageReader(); } - @Test - public void checkInputConnectionProxy__falseIfViewIsNull() { - final PlatformViewsController platformViewsController = new PlatformViewsController(); - boolean shouldProxying = platformViewsController.checkInputConnectionProxy(null); - assertFalse(shouldProxying); - } - @Test @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class}) public void convertPlatformViewRenderSurfaceAsDefault() { @@ -933,7 +860,8 @@ public SurfaceTextureEntry createSurfaceTexture() { } @Override - public SurfaceTextureEntry registerSurfaceTexture(SurfaceTexture surfaceTexture, Bitmap bitmap) { + public SurfaceTextureEntry registerSurfaceTexture( + SurfaceTexture surfaceTexture, Bitmap bitmap) { return new SurfaceTextureEntry() { @Override public SurfaceTexture surfaceTexture() { diff --git a/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java b/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java deleted file mode 100644 index 1aa4953c387ab..0000000000000 --- a/shell/platform/android/test/io/flutter/plugin/platform/SingleViewPresentationTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package io.flutter.plugin.platform; - -import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; -import static android.os.Build.VERSION_CODES.P; -import static android.os.Build.VERSION_CODES.R; -import static junit.framework.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -import android.annotation.TargetApi; -import android.content.Context; -import android.hardware.display.DisplayManager; -import android.view.Display; -import android.view.inputmethod.InputMethodManager; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; - -@Config(manifest = Config.NONE) -@RunWith(RobolectricTestRunner.class) -@TargetApi(P) -public class SingleViewPresentationTest { - @Test - @Config(minSdk = JELLY_BEAN_MR1, maxSdk = R) - public void returnsOuterContextInputMethodManager() { - // There's a bug in Android Q caused by the IMM being instanced per display. - // https://github.com/flutter/flutter/issues/38375. We need the context returned by - // SingleViewPresentation to be consistent from its instantiation instead of defaulting to - // what the system would have returned at call time. - - // It's not possible to set up the exact same conditions as the unit test in the bug here, - // but we can make sure that we're wrapping the Context passed in at instantiation time and - // returning the same InputMethodManager from it. This test passes in a Spy context instance - // that initially returns a mock. Without the bugfix this test falls back to Robolectric's - // system service instead of the spy's and fails. - - // Create an SVP under test with a Context that returns a local IMM mock. - Context context = spy(RuntimeEnvironment.application); - InputMethodManager expected = mock(InputMethodManager.class); - when(context.getSystemService(Context.INPUT_METHOD_SERVICE)).thenReturn(expected); - DisplayManager dm = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); - SingleViewPresentation svp = - new SingleViewPresentation(context, dm.getDisplay(0), null, null, null, false); - - // Get the IMM from the SVP's context. - InputMethodManager actual = - (InputMethodManager) svp.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - - // This should be the mocked instance from construction, not the IMM from the greater - // Android OS (or Robolectric's shadow, in this case). - assertEquals(expected, actual); - } - - @Test - @Config(minSdk = JELLY_BEAN_MR1, maxSdk = R) - public void returnsOuterContextInputMethodManager_createDisplayContext() { - // The IMM should also persist across display contexts created from the base context. - - // Create an SVP under test with a Context that returns a local IMM mock. - Context context = spy(RuntimeEnvironment.application); - InputMethodManager expected = mock(InputMethodManager.class); - when(context.getSystemService(Context.INPUT_METHOD_SERVICE)).thenReturn(expected); - Display display = - ((DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE)).getDisplay(0); - SingleViewPresentation svp = - new SingleViewPresentation(context, display, null, null, null, false); - - // Get the IMM from the SVP's context. - InputMethodManager actual = - (InputMethodManager) - svp.getContext() - .createDisplayContext(display) - .getSystemService(Context.INPUT_METHOD_SERVICE); - - // This should be the mocked instance from construction, not the IMM from the greater - // Android OS (or Robolectric's shadow, in this case). - assertEquals(expected, actual); - } -} diff --git a/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java b/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java index db0890b7fb812..189a59a65379a 100644 --- a/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java +++ b/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java @@ -1467,7 +1467,6 @@ public void itProducesPlatformViewNodeForHybridComposition() { View embeddedView = mock(View.class); when(accessibilityDelegate.getPlatformViewById(1)).thenReturn(embeddedView); - when(accessibilityDelegate.usesVirtualDisplay(1)).thenReturn(false); AccessibilityNodeInfo nodeInfo = mock(AccessibilityNodeInfo.class); when(embeddedView.createAccessibilityNodeInfo()).thenReturn(nodeInfo); @@ -1505,7 +1504,6 @@ public void itMakesPlatformViewImportantForAccessibility() { View embeddedView = mock(View.class); when(accessibilityDelegate.getPlatformViewById(1)).thenReturn(embeddedView); - when(accessibilityDelegate.usesVirtualDisplay(1)).thenReturn(false); TestSemanticsUpdate testSemanticsRootUpdate = root.toUpdate(); testSemanticsRootUpdate.sendUpdateToBridge(accessibilityBridge); @@ -1540,7 +1538,6 @@ public void itMakesPlatformViewNoImportantForAccessibility() { View embeddedView = mock(View.class); when(accessibilityDelegate.getPlatformViewById(1)).thenReturn(embeddedView); - when(accessibilityDelegate.usesVirtualDisplay(1)).thenReturn(false); TestSemanticsUpdate testSemanticsRootWithPlatformViewUpdate = rootWithPlatformView.toUpdate(); testSemanticsRootWithPlatformViewUpdate.sendUpdateToBridge(accessibilityBridge); @@ -1577,7 +1574,6 @@ public void itProducesPlatformViewNodeForVirtualDisplay() { View embeddedView = mock(View.class); when(accessibilityDelegate.getPlatformViewById(1)).thenReturn(embeddedView); - when(accessibilityDelegate.usesVirtualDisplay(1)).thenReturn(true); accessibilityBridge.createAccessibilityNodeInfo(0); verify(accessibilityViewEmbedder).getRootNode(eq(embeddedView), eq(0), any(Rect.class)); diff --git a/tools/android_lint/baseline.xml b/tools/android_lint/baseline.xml index 0e47fcf9f88ea..00aad19954db8 100644 --- a/tools/android_lint/baseline.xml +++ b/tools/android_lint/baseline.xml @@ -133,17 +133,6 @@ column="27"/> - - - - - - + @@ -151,13 +150,12 @@ - - + @@ -178,8 +176,8 @@ - + From 525539d761ff405d1be7d7ef3624b355fa5e731a Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 1 Feb 2022 18:17:46 -0800 Subject: [PATCH 04/29] Fix bug --- .../io/flutter/embedding/android/AndroidTouchProcessor.java | 2 +- .../io/flutter/plugin/platform/PlatformViewWrapper.java | 4 ++-- .../io/flutter/plugin/platform/PlatformViewsController.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java index 3a614b61963b3..5a8fe5c4c8b1c 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java @@ -101,7 +101,7 @@ public boolean onTouchEvent(@NonNull MotionEvent event) { * the gesture pointers into screen coordinates. * @return True if the event was handled. */ - public boolean onTouchEvent(@NonNull MotionEvent event, Matrix transformMatrix) { + public boolean onTouchEvent(@NonNull MotionEvent event, @NonNull Matrix transformMatrix) { int pointerCount = event.getPointerCount(); // Prepare a data packet of the appropriate size and order. diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index 1433620dd0945..d82f4f9b7b83b 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -156,12 +156,12 @@ public boolean onTouchEvent(@NonNull MotionEvent event) { case MotionEvent.ACTION_DOWN: sPrevLeft = sLeft; sPrevTop = sTop; - screenMatrix.postTranslate(sPrevLeft, sPrevTop); + screenMatrix.postTranslate(sLeft, sTop); break; case MotionEvent.ACTION_MOVE: // While the view is dragged, use the left and top positions as // they were at the moment the touch event fired. - screenMatrix.postTranslate(sPrevTop, sPrevTop); + screenMatrix.postTranslate(sPrevLeft, sPrevTop); sPrevLeft = sLeft; sPrevTop = sTop; break; diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 4925128f4bcdc..38fa89b848998 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -240,7 +240,7 @@ public void offset(int viewId, double top, double left) { (FrameLayout.LayoutParams) view.getLayoutParams(); layoutParams.topMargin = physicalTop; layoutParams.leftMargin = physicalLeft; - view.requestLayout(); + view.setLayoutParams(layoutParams); } @Override @@ -268,7 +268,7 @@ public void resize(@NonNull PlatformViewsChannel.PlatformViewResizeRequest reque (FrameLayout.LayoutParams) view.getLayoutParams(); layoutParams.width = newWidth; layoutParams.height = newHeight; - view.requestLayout(); + view.setLayoutParams(layoutParams); } @Override From 9c4e95ee1c3be4242b06bd5717a7ed2932915fde Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 1 Feb 2022 21:13:22 -0800 Subject: [PATCH 05/29] Clear canvas --- .../io/flutter/plugin/platform/PlatformViewWrapper.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index d82f4f9b7b83b..6023d8f1f03fd 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -7,6 +7,7 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; +import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; @@ -63,8 +64,7 @@ public void setTexture(@Nullable SurfaceTexture tx) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // Fill the entire canvas with a transparent color. // As a result, the background color of the platform view container is displayed - // to the user - // until the platform view draws its first frame. + // to the user until the platform view draws its first frame. final Canvas canvas = sSurface.lockHardwareCanvas(); canvas.drawColor(Color.TRANSPARENT); sSurface.unlockCanvasAndPost(canvas); @@ -139,6 +139,9 @@ public void draw(Canvas canvas) { // Override the canvas that this subtree of views will use to draw. final Canvas surfaceCanvas = sSurface.lockHardwareCanvas(); try { + // Clear the current pixels in the canvas. + // This helps when a WebView renders an HTML document with transparent background. + surfaceCanvas.drawColor(Color.TRANSPARENT, BlendMode.CLEAR); super.draw(surfaceCanvas); } finally { sSurface.unlockCanvasAndPost(surfaceCanvas); @@ -175,7 +178,6 @@ public boolean onTouchEvent(@NonNull MotionEvent event) { public void setOnDescendantFocusChangeListener(@NonNull OnFocusChangeListener userFocusListener) { unsetOnDescendantFocusChangeListener(); - final ViewTreeObserver observer = getViewTreeObserver(); if (observer.isAlive() && activeFocusListener == null) { activeFocusListener = From 8b4ec6d5a0bc10edcea7b9b59111eca04ef0011e Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 1 Feb 2022 21:13:38 -0800 Subject: [PATCH 06/29] Use view parent instead of flutter view --- .../io/flutter/plugin/platform/PlatformViewsController.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 38fa89b848998..85dc6e746485c 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -13,6 +13,7 @@ import android.util.SparseArray; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -215,7 +216,8 @@ public void dispose(int viewId) { if (viewWrappers.contains(viewId)) { final PlatformViewWrapper viewWrapper = viewWrappers.get(viewId); viewWrapper.unsetOnDescendantFocusChangeListener(); - flutterView.removeView(viewWrapper); + + ((ViewGroup) viewWrapper.getParent()).removeView(viewWrapper); viewWrappers.remove(viewId); return; } @@ -223,7 +225,7 @@ public void dispose(int viewId) { if (platformViewParent.contains(viewId)) { final FlutterMutatorView parentView = platformViewParent.get(viewId); parentView.unsetOnDescendantFocusChangeListener(); - flutterView.removeView(parentView); + ((ViewGroup) parentView.getParent()).removeView(parentView); platformViewParent.remove(viewId); } } From a49a98d23573ea8fd129ac3a377f148cb052486e Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 1 Feb 2022 21:18:09 -0800 Subject: [PATCH 07/29] Clean up --- .../engine/renderer/FlutterRenderer.java | 18 ++++-------------- .../io/flutter/view/TextureRegistry.java | 6 +----- .../platform/PlatformViewsControllerTest.java | 6 ++---- 3 files changed, 7 insertions(+), 23 deletions(-) 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 03d61d4d8fe3f..d58e1b791870e 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -100,7 +100,7 @@ public void removeIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListene public SurfaceTextureEntry createSurfaceTexture() { Log.v(TAG, "Creating a SurfaceTexture."); final SurfaceTexture surfaceTexture = new SurfaceTexture(0); - return registerSurfaceTexture(surfaceTexture, null); + return registerSurfaceTexture(surfaceTexture); } /** @@ -108,11 +108,10 @@ public SurfaceTextureEntry createSurfaceTexture() { * available to Flutter code. */ @Override - public SurfaceTextureEntry registerSurfaceTexture( - @NonNull SurfaceTexture surfaceTexture, @NonNull Bitmap bitmap) { + public SurfaceTextureEntry registerSurfaceTexture(@NonNull SurfaceTexture surfaceTexture) { surfaceTexture.detachFromGLContext(); final SurfaceTextureRegistryEntry entry = - new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(), surfaceTexture, bitmap); + new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(), surfaceTexture); Log.v(TAG, "New SurfaceTexture ID: " + entry.id()); registerTexture(entry.id(), entry.textureWrapper()); return entry; @@ -122,13 +121,10 @@ final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextur private final long id; @NonNull private final SurfaceTextureWrapper textureWrapper; private boolean released; - private Bitmap bitmap; - SurfaceTextureRegistryEntry( - long id, @NonNull SurfaceTexture surfaceTexture, @NonNull Bitmap bitmap) { + SurfaceTextureRegistryEntry(long id, @NonNull SurfaceTexture surfaceTexture) { this.id = id; this.textureWrapper = new SurfaceTextureWrapper(surfaceTexture); - this.bitmap = bitmap; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // The callback relies on being executed on the UI thread (unsynchronised read of @@ -171,12 +167,6 @@ public SurfaceTexture surfaceTexture() { return textureWrapper.surfaceTexture(); } - @Override - @NonNull - public Bitmap bitmap() { - return bitmap; - } - @Override public long id() { return id; diff --git a/shell/platform/android/io/flutter/view/TextureRegistry.java b/shell/platform/android/io/flutter/view/TextureRegistry.java index 5d30d3ddc00ea..6af82ce915fcc 100644 --- a/shell/platform/android/io/flutter/view/TextureRegistry.java +++ b/shell/platform/android/io/flutter/view/TextureRegistry.java @@ -4,7 +4,6 @@ package io.flutter.view; -import android.graphics.Bitmap; import android.graphics.SurfaceTexture; import androidx.annotation.NonNull; @@ -27,16 +26,13 @@ public interface TextureRegistry { * * @return A SurfaceTextureEntry. */ - SurfaceTextureEntry registerSurfaceTexture( - @NonNull SurfaceTexture surfaceTexture, @NonNull Bitmap bitmap); + SurfaceTextureEntry registerSurfaceTexture(@NonNull SurfaceTexture surfaceTexture); /** A registry entry for a managed SurfaceTexture. */ interface SurfaceTextureEntry { /** @return The managed SurfaceTexture. */ SurfaceTexture surfaceTexture(); - Bitmap bitmap(); - /** @return The identity of this SurfaceTexture. */ long id(); 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 d8cc771f023c5..3586156b0ed4f 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -9,7 +9,6 @@ import android.content.Context; import android.content.res.AssetManager; -import android.graphics.Bitmap; import android.graphics.SurfaceTexture; import android.util.SparseArray; import android.view.MotionEvent; @@ -856,12 +855,11 @@ public void TextureRegistry() {} @Override public SurfaceTextureEntry createSurfaceTexture() { - return registerSurfaceTexture(mock(SurfaceTexture.class), mock(Bitmap.class)); + return registerSurfaceTexture(mock(SurfaceTexture.class)); } @Override - public SurfaceTextureEntry registerSurfaceTexture( - SurfaceTexture surfaceTexture, Bitmap bitmap) { + public SurfaceTextureEntry registerSurfaceTexture(SurfaceTexture surfaceTexture) { return new SurfaceTextureEntry() { @Override public SurfaceTexture surfaceTexture() { From 0be4e1033f211aebf73e2eeda0118b47d5f5b245 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 1 Feb 2022 21:24:20 -0800 Subject: [PATCH 08/29] Move tests --- .../embedding/android/FlutterView.java | 20 --------- .../android/io/flutter/view/FlutterView.java | 9 +--- .../mutatorsstack/FlutterMutatorViewTest.java | 45 ------------------- .../test/io/flutter/util/ViewUtilsTest.java | 45 +++++++++++++++++++ 4 files changed, 47 insertions(+), 72 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 5f18f4108c61e..90e7098244009 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -50,7 +50,6 @@ import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.FlutterRenderer.DisplayFeatureState; import io.flutter.embedding.engine.renderer.FlutterRenderer.DisplayFeatureType; -import io.flutter.embedding.engine.renderer.FlutterRenderer.ViewportMetrics; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.embedding.engine.renderer.RenderSurface; import io.flutter.embedding.engine.systemchannels.SettingsChannel; @@ -769,28 +768,9 @@ navigationBarVisible && guessBottomKeyboardInset(insets) == 0 + viewportMetrics.viewInsetBottom); sendViewportMetricsToFlutter(); - - for (ViewportMetricsListener listener : sViewportMetricsListeners) { - listener.onMetricsChanged(viewportMetrics); - } return newInsets; } - public interface ViewportMetricsListener { - public void onMetricsChanged(ViewportMetrics metrics); - } - - private Set sViewportMetricsListeners = - new HashSet(); - - public void addViewportMetricsListener(ViewportMetricsListener l) { - sViewportMetricsListeners.add(l); - } - - public void removeViewportMetricsListener(ViewportMetricsListener l) { - sViewportMetricsListeners.remove(l); - } - /** * Invoked when Android's desired window insets change, i.e., padding. * diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index ab58c16104652..beb15c38c0a8f 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -864,12 +864,12 @@ public interface FirstFrameListener { @Override public TextureRegistry.SurfaceTextureEntry createSurfaceTexture() { final SurfaceTexture surfaceTexture = new SurfaceTexture(0); - return registerSurfaceTexture(surfaceTexture, null); + return registerSurfaceTexture(surfaceTexture); } @Override public TextureRegistry.SurfaceTextureEntry registerSurfaceTexture( - @NonNull SurfaceTexture surfaceTexture, @NonNull Bitmap bitmap) { + @NonNull SurfaceTexture surfaceTexture) { surfaceTexture.detachFromGLContext(); final SurfaceTextureRegistryEntry entry = new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(), surfaceTexture); @@ -932,11 +932,6 @@ public long id() { return id; } - @Override - public Bitmap bitmap() { - return null; - } - @Override public void release() { if (released) { diff --git a/shell/platform/android/test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java b/shell/platform/android/test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java index 9a5e46b030971..5933d66e94d1e 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java @@ -6,8 +6,6 @@ import android.graphics.Matrix; import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; import android.view.ViewTreeObserver; import io.flutter.embedding.android.AndroidTouchProcessor; import org.junit.Test; @@ -81,49 +79,6 @@ public void canDragViews() { } } - @Test - public void childHasFocus_rootHasFocus() { - final View rootView = mock(View.class); - when(rootView.hasFocus()).thenReturn(true); - assertTrue(FlutterMutatorView.childHasFocus(rootView)); - } - - @Test - public void childHasFocus_rootDoesNotHaveFocus() { - final View rootView = mock(View.class); - when(rootView.hasFocus()).thenReturn(false); - assertFalse(FlutterMutatorView.childHasFocus(rootView)); - } - - @Test - public void childHasFocus_rootIsNull() { - assertFalse(FlutterMutatorView.childHasFocus(null)); - } - - @Test - public void childHasFocus_childHasFocus() { - final View childView = mock(View.class); - when(childView.hasFocus()).thenReturn(true); - - final ViewGroup rootView = mock(ViewGroup.class); - when(rootView.getChildCount()).thenReturn(1); - when(rootView.getChildAt(0)).thenReturn(childView); - - assertTrue(FlutterMutatorView.childHasFocus(rootView)); - } - - @Test - public void childHasFocus_childDoesNotHaveFocus() { - final View childView = mock(View.class); - when(childView.hasFocus()).thenReturn(false); - - final ViewGroup rootView = mock(ViewGroup.class); - when(rootView.getChildCount()).thenReturn(1); - when(rootView.getChildAt(0)).thenReturn(childView); - - assertFalse(FlutterMutatorView.childHasFocus(rootView)); - } - @Test public void focusChangeListener_hasFocus() { final ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class); diff --git a/shell/platform/android/test/io/flutter/util/ViewUtilsTest.java b/shell/platform/android/test/io/flutter/util/ViewUtilsTest.java index 7c4bad1f95818..a476d0df9a427 100644 --- a/shell/platform/android/test/io/flutter/util/ViewUtilsTest.java +++ b/shell/platform/android/test/io/flutter/util/ViewUtilsTest.java @@ -10,6 +10,8 @@ import android.app.Activity; import android.content.Context; import android.content.ContextWrapper; +import android.view.View; +import android.view.ViewGroup; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -30,4 +32,47 @@ public void canGetActivity() { ContextWrapper wrapper = new ContextWrapper(new ContextWrapper(activity)); assertEquals(activity, ViewUtils.getActivity(wrapper)); } + + @Test + public void childHasFocus_rootHasFocus() { + final View rootView = mock(View.class); + when(rootView.hasFocus()).thenReturn(true); + assertTrue(ViewUtils.childHasFocus(rootView)); + } + + @Test + public void childHasFocus_rootDoesNotHaveFocus() { + final View rootView = mock(View.class); + when(rootView.hasFocus()).thenReturn(false); + assertFalse(ViewUtils.childHasFocus(rootView)); + } + + @Test + public void childHasFocus_rootIsNull() { + assertFalse(ViewUtils.childHasFocus(null)); + } + + @Test + public void childHasFocus_childHasFocus() { + final View childView = mock(View.class); + when(childView.hasFocus()).thenReturn(true); + + final ViewGroup rootView = mock(ViewGroup.class); + when(rootView.getChildCount()).thenReturn(1); + when(rootView.getChildAt(0)).thenReturn(childView); + + assertTrue(ViewUtils.childHasFocus(rootView)); + } + + @Test + public void childHasFocus_childDoesNotHaveFocus() { + final View childView = mock(View.class); + when(childView.hasFocus()).thenReturn(false); + + final ViewGroup rootView = mock(ViewGroup.class); + when(rootView.getChildCount()).thenReturn(1); + when(rootView.getChildAt(0)).thenReturn(childView); + + assertFalse(ViewUtils.childHasFocus(rootView)); + } } From cb7a1ab895a49e6ce30adabc208bafdb220283f9 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 7 Feb 2022 15:18:48 -0800 Subject: [PATCH 09/29] Send buffer size --- .../systemchannels/PlatformViewsChannel.java | 31 +++++- .../plugin/platform/PlatformViewWrapper.java | 100 +++++++++--------- .../platform/PlatformViewsController.java | 27 +++-- 3 files changed, 92 insertions(+), 66 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java index 549d10cf64063..233856c51b89a 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java @@ -14,6 +14,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.nio.ByteBuffer; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -136,8 +137,11 @@ private void resize(@NonNull MethodCall call, @NonNull MethodChannel.Result resu (double) resizeArgs.get("width"), (double) resizeArgs.get("height")); try { - handler.resize(resizeRequest); - result.success(null); + final PlatformViewBufferSize sz = handler.resize(resizeRequest); + final Map response = new HashMap<>(); + response.put("width", sz.width); + response.put("height", sz.height); + result.success(response); } catch (IllegalStateException exception) { result.error("error", detailedExceptionString(exception), null); } @@ -276,8 +280,13 @@ public interface PlatformViewsHandler { /** The Flutter application would like to dispose of an existing Android {@code View}. */ void dispose(int viewId); - /** The Flutter application would like to resize an existing Android {@code View}. */ - void resize(@NonNull PlatformViewResizeRequest request); + /** + * The Flutter application would like to resize an existing Android {@code View}. + * + * @param request The request to resize the platform view. + * @return The buffer size where the platform view pixels are written to. + */ + PlatformViewBufferSize resize(@NonNull PlatformViewResizeRequest request); /** The Flutter application would like to change the offset an existing Android {@code View}. */ void offset(int viewId, double top, double left); @@ -373,6 +382,20 @@ public PlatformViewResizeRequest(int viewId, double newLogicalWidth, double newL } } + /** The platform view buffer size. */ + public static class PlatformViewBufferSize { + /** The width of the buffer size. */ + public final int width; + + /** The height of the buffer size. */ + public final int height; + + public PlatformViewBufferSize(int width, int height) { + this.width = width; + this.height = height; + } + } + /** The state of a touch event in Flutter within a platform view. */ public static class PlatformViewTouch { /** The ID of the platform view as seen by the Flutter side. */ diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index 6023d8f1f03fd..c386cee8eec63 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -4,6 +4,8 @@ package io.flutter.plugin.platform; +import static io.flutter.embedding.engine.systemchannels.PlatformViewsChannel.PlatformViewBufferSize; + import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; @@ -29,16 +31,15 @@ class PlatformViewWrapper extends FrameLayout { private static final String TAG = "PlatformViewWrapper"; - private int sPrevLeft; - private int sPrevTop; - private int sLeft; - private int sTop; - private int sWidth; - private int sHeight; - private SurfaceTexture sTx; - private Surface sSurface; - private AndroidTouchProcessor sTouchProcessor; + private int prevLeft; + private int prevTop; + private int left; + private int top; + private SurfaceTexture tx; + private Surface surface; + private AndroidTouchProcessor touchProcessor; + private @Nullable PlatformViewBufferSize bufferSize; @Nullable @VisibleForTesting ViewTreeObserver.OnGlobalFocusChangeListener activeFocusListener; public PlatformViewWrapper(@NonNull Context context) { @@ -47,27 +48,27 @@ public PlatformViewWrapper(@NonNull Context context) { } public void setTouchProcessor(@Nullable AndroidTouchProcessor touchProcessor) { - sTouchProcessor = touchProcessor; + this.touchProcessor = touchProcessor; } public void setTexture(@Nullable SurfaceTexture tx) { - if (sTx != null) { - sTx.release(); + if (tx != null) { + tx.release(); } - sTx = tx; + this.tx = tx; - if (sSurface != null) { - sSurface.release(); + if (surface != null) { + surface.release(); } - sSurface = new Surface(sTx); + surface = new Surface(tx); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // Fill the entire canvas with a transparent color. // As a result, the background color of the platform view container is displayed // to the user until the platform view draws its first frame. - final Canvas canvas = sSurface.lockHardwareCanvas(); + final Canvas canvas = surface.lockHardwareCanvas(); canvas.drawColor(Color.TRANSPARENT); - sSurface.unlockCanvasAndPost(canvas); + surface.unlockCanvasAndPost(canvas); } else { Log.e(TAG, "Platform views cannot be displayed below API level 23."); } @@ -76,39 +77,36 @@ public void setTexture(@Nullable SurfaceTexture tx) { public void setLayoutParams(@NonNull FrameLayout.LayoutParams params) { super.setLayoutParams(params); - sLeft = params.leftMargin; - sTop = params.topMargin; + left = params.leftMargin; + top = params.topMargin; } - public void setDefaultBufferSize(int width, int height) { - sWidth = width; - sHeight = height; - if (sTx != null) { - sTx.setDefaultBufferSize(sWidth, sHeight); + public void setBufferSize(int width, int height) { + bufferSize = new PlatformViewBufferSize(width, height); + if (tx != null) { + tx.setDefaultBufferSize(width, height); } } - public int getBufferWidth() { - return sWidth; - } - - public int getBufferHeight() { - return sHeight; + /** Returns the size of the buffer where the platform view pixels are written to. */ + @Nullable + public PlatformViewBufferSize getBufferSize() { + return bufferSize; } @Nullable public SurfaceTexture getTexture() { - return sTx; + return tx; } public void release() { - if (sTx != null) { - sTx.release(); - sTx = null; + if (tx != null) { + tx.release(); + tx = null; } - if (sSurface != null) { - sSurface.release(); - sSurface = null; + if (surface != null) { + surface.release(); + surface = null; } } @@ -124,11 +122,11 @@ public void onDescendantInvalidated(@NonNull View child, @NonNull View target) { @Override public void draw(Canvas canvas) { - if (sSurface == null || !sSurface.isValid()) { + if (surface == null || !surface.isValid()) { Log.e(TAG, "Invalid surface. The platform view cannot be displayed."); return; } - if (sTx == null || sTx.isReleased()) { + if (tx == null || tx.isReleased()) { Log.e(TAG, "Invalid texture. The platform view cannot be displayed."); return; } @@ -137,43 +135,43 @@ public void draw(Canvas canvas) { return; } // Override the canvas that this subtree of views will use to draw. - final Canvas surfaceCanvas = sSurface.lockHardwareCanvas(); + final Canvas surfaceCanvas = surface.lockHardwareCanvas(); try { // Clear the current pixels in the canvas. // This helps when a WebView renders an HTML document with transparent background. surfaceCanvas.drawColor(Color.TRANSPARENT, BlendMode.CLEAR); super.draw(surfaceCanvas); } finally { - sSurface.unlockCanvasAndPost(surfaceCanvas); + surface.unlockCanvasAndPost(surfaceCanvas); } } @Override @SuppressLint("ClickableViewAccessibility") public boolean onTouchEvent(@NonNull MotionEvent event) { - if (sTouchProcessor == null) { + if (touchProcessor == null) { return super.onTouchEvent(event); } final Matrix screenMatrix = new Matrix(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: - sPrevLeft = sLeft; - sPrevTop = sTop; - screenMatrix.postTranslate(sLeft, sTop); + prevLeft = left; + prevTop = top; + screenMatrix.postTranslate(left, top); break; case MotionEvent.ACTION_MOVE: // While the view is dragged, use the left and top positions as // they were at the moment the touch event fired. - screenMatrix.postTranslate(sPrevLeft, sPrevTop); - sPrevLeft = sLeft; - sPrevTop = sTop; + screenMatrix.postTranslate(prevLeft, prevTop); + prevLeft = left; + prevTop = top; break; case MotionEvent.ACTION_UP: default: - screenMatrix.postTranslate(sLeft, sTop); + screenMatrix.postTranslate(left, top); break; } - return sTouchProcessor.onTouchEvent(event, screenMatrix); + return touchProcessor.onTouchEvent(event, screenMatrix); } public void setOnDescendantFocusChangeListener(@NonNull OnFocusChangeListener userFocusListener) { diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 5e51d537e7237..47efbc755a443 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -19,6 +19,7 @@ import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; +import io.flutter.Log; import io.flutter.embedding.android.AndroidTouchProcessor; import io.flutter.embedding.android.FlutterImageView; import io.flutter.embedding.android.FlutterView; @@ -175,14 +176,14 @@ public long createForTextureLayer( final PlatformView platformView = viewFactory.create(context, viewId, createParams); platformViews.put(viewId, platformView); - final int physicalWidth = toPhysicalPixels(request.logicalWidth); - final int physicalHeight = toPhysicalPixels(request.logicalHeight); + final PlatformViewWrapper wrapperView = new PlatformViewWrapper(context); final TextureRegistry.SurfaceTextureEntry textureEntry = textureRegistry.createSurfaceTexture(); - - final PlatformViewWrapper wrapperView = new PlatformViewWrapper(context); wrapperView.setTexture(textureEntry.surfaceTexture()); - wrapperView.setDefaultBufferSize(physicalWidth, physicalHeight); + + final int physicalWidth = toPhysicalPixels(request.logicalWidth); + final int physicalHeight = toPhysicalPixels(request.logicalHeight); + wrapperView.setBufferSize(physicalWidth, physicalHeight); wrapperView.setTouchProcessor(androidTouchProcessor); final FrameLayout.LayoutParams layoutParams = @@ -233,6 +234,7 @@ public void dispose(int viewId) { @Override public void offset(int viewId, double top, double left) { if (!viewWrappers.contains(viewId)) { + Log.e(TAG, "Setting offset for unknown platform view with id: " + viewId); return; } final int physicalTop = toPhysicalPixels(top); @@ -246,13 +248,15 @@ public void offset(int viewId, double top, double left) { } @Override - public void resize(@NonNull PlatformViewsChannel.PlatformViewResizeRequest request) { + public PlatformViewsChannel.PlatformViewBufferSize resize( + @NonNull PlatformViewsChannel.PlatformViewResizeRequest request) { final int viewId = request.viewId; if (!viewWrappers.contains(viewId)) { - return; + Log.e(TAG, "Resizing unknown platform view with id: " + viewId); + return null; } - int newWidth = toPhysicalPixels(request.newLogicalWidth); - int newHeight = toPhysicalPixels(request.newLogicalHeight); + final int newWidth = toPhysicalPixels(request.newLogicalWidth); + final int newHeight = toPhysicalPixels(request.newLogicalHeight); final PlatformViewWrapper view = viewWrappers.get(viewId); // Resize the buffer only when the current buffer size is smaller than the new size. @@ -263,14 +267,15 @@ public void resize(@NonNull PlatformViewsChannel.PlatformViewResizeRequest reque // Resizing the texture causes pixel stretching since the size of the GL texture used in // the engine // is set by the framework, but the texture buffer size is set by the platform down below. - if (newWidth > view.getBufferWidth() || newHeight > view.getBufferHeight()) { - view.setDefaultBufferSize(newWidth, newHeight); + if (newWidth > view.getBufferSize().width || newHeight > view.getBufferSize().height) { + view.setBufferSize(newWidth, newHeight); } final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) view.getLayoutParams(); layoutParams.width = newWidth; layoutParams.height = newHeight; view.setLayoutParams(layoutParams); + return view.getBufferSize(); } @Override From a45bbe7f1df70a6401c8fd5b6a3dcf7245319c36 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 7 Feb 2022 15:21:09 -0800 Subject: [PATCH 10/29] Null check --- .../engine/systemchannels/PlatformViewsChannel.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java index 233856c51b89a..1a389c472f93b 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java @@ -138,10 +138,14 @@ private void resize(@NonNull MethodCall call, @NonNull MethodChannel.Result resu (double) resizeArgs.get("height")); try { final PlatformViewBufferSize sz = handler.resize(resizeRequest); - final Map response = new HashMap<>(); - response.put("width", sz.width); - response.put("height", sz.height); - result.success(response); + if (sz == null) { + result.error("error", "Failed to resize the platform view", null); + } else { + final Map response = new HashMap<>(); + response.put("width", sz.width); + response.put("height", sz.height); + result.success(response); + } } catch (IllegalStateException exception) { result.error("error", detailedExceptionString(exception), null); } From 50a23f276d913d00fcf99829fe8bcd8cc348e440 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 7 Feb 2022 15:27:31 -0800 Subject: [PATCH 11/29] Comment --- .../embedding/engine/systemchannels/PlatformViewsChannel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java index 1a389c472f93b..ce1447ba38b0a 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java @@ -388,10 +388,10 @@ public PlatformViewResizeRequest(int viewId, double newLogicalWidth, double newL /** The platform view buffer size. */ public static class PlatformViewBufferSize { - /** The width of the buffer size. */ + /** The width of the screen buffer. */ public final int width; - /** The height of the buffer size. */ + /** The height of the screen buffer. */ public final int height; public PlatformViewBufferSize(int width, int height) { From 87a9a311ecf8e3e7eb8682f98e77c73ad023dcf7 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 7 Feb 2022 17:01:39 -0800 Subject: [PATCH 12/29] Fixes --- .../systemchannels/PlatformViewsChannel.java | 4 +-- .../plugin/platform/PlatformViewWrapper.java | 34 ++++++++++++------- .../platform/PlatformViewsController.java | 14 ++++++-- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java index ce1447ba38b0a..009cec8699083 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java @@ -142,8 +142,8 @@ private void resize(@NonNull MethodCall call, @NonNull MethodChannel.Result resu result.error("error", "Failed to resize the platform view", null); } else { final Map response = new HashMap<>(); - response.put("width", sz.width); - response.put("height", sz.height); + response.put("width", (double) sz.width); + response.put("height", (double) sz.height); result.success(response); } } catch (IllegalStateException exception) { diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index c386cee8eec63..b4a8dcca6b22c 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -4,8 +4,6 @@ package io.flutter.plugin.platform; -import static io.flutter.embedding.engine.systemchannels.PlatformViewsChannel.PlatformViewBufferSize; - import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; @@ -35,11 +33,12 @@ class PlatformViewWrapper extends FrameLayout { private int prevTop; private int left; private int top; + private int bufferWidth; + private int bufferHeight; private SurfaceTexture tx; private Surface surface; private AndroidTouchProcessor touchProcessor; - private @Nullable PlatformViewBufferSize bufferSize; @Nullable @VisibleForTesting ViewTreeObserver.OnGlobalFocusChangeListener activeFocusListener; public PlatformViewWrapper(@NonNull Context context) { @@ -47,20 +46,24 @@ public PlatformViewWrapper(@NonNull Context context) { setWillNotDraw(false); } - public void setTouchProcessor(@Nullable AndroidTouchProcessor touchProcessor) { - this.touchProcessor = touchProcessor; + public void setTouchProcessor(@Nullable AndroidTouchProcessor newTouchProcessor) { + touchProcessor = newTouchProcessor; } - public void setTexture(@Nullable SurfaceTexture tx) { + public void setTexture(@Nullable SurfaceTexture newTx) { if (tx != null) { tx.release(); } - this.tx = tx; + tx = newTx; + + if (bufferWidth > 0 && bufferHeight > 0) { + tx.setDefaultBufferSize(bufferWidth, bufferHeight); + } if (surface != null) { surface.release(); } - surface = new Surface(tx); + surface = new Surface(newTx); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // Fill the entire canvas with a transparent color. @@ -82,16 +85,21 @@ public void setLayoutParams(@NonNull FrameLayout.LayoutParams params) { } public void setBufferSize(int width, int height) { - bufferSize = new PlatformViewBufferSize(width, height); + bufferWidth = width; + bufferHeight = height; if (tx != null) { tx.setDefaultBufferSize(width, height); } } - /** Returns the size of the buffer where the platform view pixels are written to. */ - @Nullable - public PlatformViewBufferSize getBufferSize() { - return bufferSize; + /** Returns the screen buffer width where the platform view pixels are written to. */ + public int getBufferWidth() { + return bufferWidth; + } + + /** Returns the screen buffer height where the platform view pixels are written to. */ + public int getBufferHeight() { + return bufferHeight; } @Nullable diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 47efbc755a443..78b7b3b8efe09 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -180,11 +180,11 @@ public long createForTextureLayer( final TextureRegistry.SurfaceTextureEntry textureEntry = textureRegistry.createSurfaceTexture(); wrapperView.setTexture(textureEntry.surfaceTexture()); + wrapperView.setTouchProcessor(androidTouchProcessor); final int physicalWidth = toPhysicalPixels(request.logicalWidth); final int physicalHeight = toPhysicalPixels(request.logicalHeight); wrapperView.setBufferSize(physicalWidth, physicalHeight); - wrapperView.setTouchProcessor(androidTouchProcessor); final FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(physicalWidth, physicalHeight); @@ -216,6 +216,7 @@ public void dispose(int viewId) { // The platform view is displayed using a TextureLayer. if (viewWrappers.contains(viewId)) { final PlatformViewWrapper viewWrapper = viewWrappers.get(viewId); + viewWrapper.release(); viewWrapper.unsetOnDescendantFocusChangeListener(); ((ViewGroup) viewWrapper.getParent()).removeView(viewWrapper); @@ -267,15 +268,18 @@ public PlatformViewsChannel.PlatformViewBufferSize resize( // Resizing the texture causes pixel stretching since the size of the GL texture used in // the engine // is set by the framework, but the texture buffer size is set by the platform down below. - if (newWidth > view.getBufferSize().width || newHeight > view.getBufferSize().height) { + if (newWidth > view.getBufferWidth() || newHeight > view.getBufferHeight()) { view.setBufferSize(newWidth, newHeight); } + final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) view.getLayoutParams(); layoutParams.width = newWidth; layoutParams.height = newHeight; view.setLayoutParams(layoutParams); - return view.getBufferSize(); + + return new PlatformViewsChannel.PlatformViewBufferSize( + toLogicalPixels(view.getBufferWidth()), toLogicalPixels(view.getBufferHeight())); } @Override @@ -596,6 +600,10 @@ private int toPhysicalPixels(double logicalPixels) { return (int) Math.round(logicalPixels * getDisplayDensity()); } + private int toLogicalPixels(double physicalPixels) { + return (int) Math.round(physicalPixels / getDisplayDensity()); + } + private void flushAllViews() { while (platformViews.size() > 0) { channelHandler.dispose(platformViews.keyAt(0)); From d739a4f1b57e144baedceee39916791ad37cb411 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 7 Feb 2022 17:03:26 -0800 Subject: [PATCH 13/29] Add blend mode --- .../android/io/flutter/plugin/platform/PlatformViewWrapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index b4a8dcca6b22c..c75dca45e3280 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -70,7 +70,7 @@ public void setTexture(@Nullable SurfaceTexture newTx) { // As a result, the background color of the platform view container is displayed // to the user until the platform view draws its first frame. final Canvas canvas = surface.lockHardwareCanvas(); - canvas.drawColor(Color.TRANSPARENT); + canvas.drawColor(Color.TRANSPARENT, BlendMode.CLEAR); surface.unlockCanvasAndPost(canvas); } else { Log.e(TAG, "Platform views cannot be displayed below API level 23."); From 16cdc4849768fd587935a126a885ce607c90d5fa Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 7 Feb 2022 17:04:44 -0800 Subject: [PATCH 14/29] comemnt --- .../io/flutter/plugin/platform/PlatformViewWrapper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index c75dca45e3280..387c9c19f9f0e 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -92,12 +92,12 @@ public void setBufferSize(int width, int height) { } } - /** Returns the screen buffer width where the platform view pixels are written to. */ + /** Returns the screen buffer width. */ public int getBufferWidth() { return bufferWidth; } - /** Returns the screen buffer height where the platform view pixels are written to. */ + /** Returns the screen buffer height. */ public int getBufferHeight() { return bufferHeight; } From 89d015e5c0036ddf706591062a7f1954da33ef8f Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 7 Feb 2022 17:19:05 -0800 Subject: [PATCH 15/29] Docs --- .../plugin/platform/PlatformViewWrapper.java | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index 387c9c19f9f0e..9b093487ec49e 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -25,6 +25,16 @@ import io.flutter.embedding.android.AndroidTouchProcessor; import io.flutter.util.ViewUtils; +/** + * Wraps a platform view to intercept gestures and project this view onto a {@link SurfaceTexture}. + * + *

An Android platform view is composed by the Flutter Engine using a TextureLayer. The view is + * embeded to the Android view hierarchy like a normal view, but it's projected onto a {@link + * SurfaceTexture}, so it can be efficiently composed by the Flutter Engine. + * + *

Since the view is in the Android view hierarchy, keyboard and accessibility interactions + * behave normally. + */ @TargetApi(Build.VERSION_CODES.M) class PlatformViewWrapper extends FrameLayout { private static final String TAG = "PlatformViewWrapper"; @@ -46,10 +56,20 @@ public PlatformViewWrapper(@NonNull Context context) { setWillNotDraw(false); } + /** + * Sets the touch processor that allows to intercept gestures. + * + * @param newTouchProcessor The touch processor. + */ public void setTouchProcessor(@Nullable AndroidTouchProcessor newTouchProcessor) { touchProcessor = newTouchProcessor; } + /** + * Sets the texture where the view is projected onto. + * + * @param newTx The texture where the view is projected onto. + */ public void setTexture(@Nullable SurfaceTexture newTx) { if (tx != null) { tx.release(); @@ -77,6 +97,11 @@ public void setTexture(@Nullable SurfaceTexture newTx) { } } + /** + * Sets the layout parameters for this view. + * + * @param params The new parameters. + */ public void setLayoutParams(@NonNull FrameLayout.LayoutParams params) { super.setLayoutParams(params); @@ -84,6 +109,12 @@ public void setLayoutParams(@NonNull FrameLayout.LayoutParams params) { top = params.topMargin; } + /** + * Sets the size of the image buffer. + * + * @param width The width of the screen buffer. + * @param height The height of the screen buffer. + */ public void setBufferSize(int width, int height) { bufferWidth = width; bufferHeight = height; @@ -92,12 +123,12 @@ public void setBufferSize(int width, int height) { } } - /** Returns the screen buffer width. */ + /** Returns the image buffer width. */ public int getBufferWidth() { return bufferWidth; } - /** Returns the screen buffer height. */ + /** Returns the image buffer height. */ public int getBufferHeight() { return bufferHeight; } @@ -107,6 +138,7 @@ public SurfaceTexture getTexture() { return tx; } + /** Releases the texture and surface. */ public void release() { if (tx != null) { tx.release(); From 50e70eb45d9ac04c997d50d04a27b048943d0bfa Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 7 Feb 2022 17:25:07 -0800 Subject: [PATCH 16/29] Better error message --- .../io/flutter/plugin/platform/PlatformViewWrapper.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index 9b093487ec49e..2eaa9c956fa1a 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -171,7 +171,10 @@ public void draw(Canvas canvas) { return; } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - Log.e(TAG, "Platform views cannot be displayed below API level 23."); + Log.e( + TAG, + "Platform views cannot be displayed below API level 23. " + + "You can prevent this issue by setting minCompileSdk: 23 in build.gradle."); return; } // Override the canvas that this subtree of views will use to draw. From 4bba8bc2f6d1807ed131882217991ec2890f5ed5 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 7 Feb 2022 17:26:49 -0800 Subject: [PATCH 17/29] Second error message --- .../io/flutter/plugin/platform/PlatformViewWrapper.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index 2eaa9c956fa1a..012654c239884 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -93,7 +93,10 @@ public void setTexture(@Nullable SurfaceTexture newTx) { canvas.drawColor(Color.TRANSPARENT, BlendMode.CLEAR); surface.unlockCanvasAndPost(canvas); } else { - Log.e(TAG, "Platform views cannot be displayed below API level 23."); + Log.e( + TAG, + "Platform views cannot be displayed below API level 23. " + + "You can prevent this issue by setting minCompileSdk: 23 in build.gradle."); } } From 2c223de4291da8f99e82c2a8eac3eb010aab1963 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 7 Feb 2022 17:28:41 -0800 Subject: [PATCH 18/29] Organize --- .../flutter/plugin/platform/PlatformViewWrapper.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index 012654c239884..a0c6702b0f417 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -100,6 +100,12 @@ public void setTexture(@Nullable SurfaceTexture newTx) { } } + /** Returns the texture where the view is projected. */ + @Nullable + public SurfaceTexture getTexture() { + return tx; + } + /** * Sets the layout parameters for this view. * @@ -136,11 +142,6 @@ public int getBufferHeight() { return bufferHeight; } - @Nullable - public SurfaceTexture getTexture() { - return tx; - } - /** Releases the texture and surface. */ public void release() { if (tx != null) { From 2c723d0245fadd29ebe8a5270ff87fe7c5e70514 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 7 Feb 2022 18:12:13 -0800 Subject: [PATCH 19/29] Remove obsolete tests --- .../platform/PlatformViewsController.java | 16 ++- .../engine/renderer/FlutterRendererTest.java | 2 +- .../platform/PlatformViewsControllerTest.java | 115 +----------------- .../test/io/flutter/util/ViewUtilsTest.java | 3 + .../flutter/view/AccessibilityBridgeTest.java | 27 ---- 5 files changed, 19 insertions(+), 144 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 78b7b3b8efe09..bac0d775eac73 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -210,6 +210,10 @@ public long createForTextureLayer( public void dispose(int viewId) { final PlatformView platformView = platformViews.get(viewId); if (platformView != null) { + final ViewGroup pvParent = (ViewGroup) platformView.getView().getParent(); + if (pvParent != null) { + pvParent.removeView(platformView.getView()); + } platformViews.remove(viewId); platformView.dispose(); } @@ -219,15 +223,23 @@ public void dispose(int viewId) { viewWrapper.release(); viewWrapper.unsetOnDescendantFocusChangeListener(); - ((ViewGroup) viewWrapper.getParent()).removeView(viewWrapper); + final ViewGroup wrapperParent = (ViewGroup) viewWrapper.getParent(); + if (wrapperParent != null) { + wrapperParent.removeView(viewWrapper); + } viewWrappers.remove(viewId); return; } // The platform view is displayed using a PlatformViewLayer. + // TODO(egarciad): Eliminate this case. if (platformViewParent.contains(viewId)) { final FlutterMutatorView parentView = platformViewParent.get(viewId); parentView.unsetOnDescendantFocusChangeListener(); - ((ViewGroup) parentView.getParent()).removeView(parentView); + + final ViewGroup mutatorViewParent = (ViewGroup) parentView.getParent(); + if (mutatorViewParent != null) { + mutatorViewParent.removeView(parentView); + } platformViewParent.remove(viewId); } } 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 91649f8efcba1..e38001a7231c2 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 @@ -168,7 +168,7 @@ public void itRegistersExistingSurfaceTexture() { // Execute the behavior under test. FlutterRenderer.SurfaceTextureRegistryEntry entry = (FlutterRenderer.SurfaceTextureRegistryEntry) - flutterRenderer.registerSurfaceTexture(surfaceTexture, null); + flutterRenderer.registerSurfaceTexture(surfaceTexture); flutterRenderer.startRenderingToSurface(fakeSurface, /*keepCurrentSurface=*/ false); 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 1240d3007a632..0398f88003630 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -17,7 +17,6 @@ import android.view.SurfaceView; import android.view.View; import android.view.ViewParent; -import android.widget.FrameLayout.LayoutParams; import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.android.FlutterImageView; import io.flutter.embedding.android.FlutterView; @@ -56,54 +55,6 @@ @RunWith(AndroidJUnit4.class) public class PlatformViewsControllerTest { - @Test - public void itUsesActionEventTypeFromFrameworkEventForVirtualDisplays() { - MotionEventTracker motionEventTracker = MotionEventTracker.getInstance(); - PlatformViewsController platformViewsController = new PlatformViewsController(); - - MotionEvent original = - MotionEvent.obtain( - 100, // downTime - 100, // eventTime - 1, // action - 0, // x - 0, // y - 0 // metaState - ); - - // track an event that will later get passed to us from framework - MotionEventTracker.MotionEventId motionEventId = motionEventTracker.track(original); - - PlatformViewTouch frameWorkTouch = - new PlatformViewTouch( - 0, // viewId - original.getDownTime(), - original.getEventTime(), - 2, // action - 1, // pointerCount - Arrays.asList(Arrays.asList(0, 0)), // pointer properties - Arrays.asList(Arrays.asList(0., 1., 2., 3., 4., 5., 6., 7., 8.)), // pointer coords - original.getMetaState(), - original.getButtonState(), - original.getXPrecision(), - original.getYPrecision(), - original.getDeviceId(), - original.getEdgeFlags(), - original.getSource(), - original.getFlags(), - motionEventId.getId()); - - MotionEvent resolvedEvent = - platformViewsController.toMotionEvent( - 1, // density - frameWorkTouch, - true // usingVirtualDisplays - ); - - assertEquals(resolvedEvent.getAction(), frameWorkTouch.action); - assertNotEquals(resolvedEvent.getAction(), original.getAction()); - } - @Ignore @Test public void itUsesActionEventTypeFromMotionEventForHybridPlatformViews() { @@ -143,11 +94,7 @@ public void itUsesActionEventTypeFromMotionEventForHybridPlatformViews() { motionEventId.getId()); MotionEvent resolvedEvent = - platformViewsController.toMotionEvent( - 1, // density - frameWorkTouch, - false // usingVirtualDisplays - ); + platformViewsController.toMotionEvent(/*density=*/ 1, frameWorkTouch); assertNotEquals(resolvedEvent.getAction(), frameWorkTouch.action); assertEquals(resolvedEvent.getAction(), original.getAction()); @@ -231,66 +178,6 @@ public void createPlatformViewMessage__throwsIfViewIsNull() { }); } - @Test - @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.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, ShadowPlatformTaskQueue.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, ShadowPlatformTaskQueue.class}) public void createPlatformViewMessage__throwsIfViewHasParent() { diff --git a/shell/platform/android/test/io/flutter/util/ViewUtilsTest.java b/shell/platform/android/test/io/flutter/util/ViewUtilsTest.java index f6e4277c5b831..2adbc6c2afaba 100644 --- a/shell/platform/android/test/io/flutter/util/ViewUtilsTest.java +++ b/shell/platform/android/test/io/flutter/util/ViewUtilsTest.java @@ -5,7 +5,10 @@ package io.flutter.util; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import android.app.Activity; import android.content.Context; diff --git a/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java b/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java index 8bab76dc2643f..194465e9a5570 100644 --- a/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java +++ b/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java @@ -1552,33 +1552,6 @@ public void itMakesPlatformViewNoImportantForAccessibility() { .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); } - @Test - public void itProducesPlatformViewNodeForVirtualDisplay() { - PlatformViewsAccessibilityDelegate accessibilityDelegate = - mock(PlatformViewsAccessibilityDelegate.class); - AccessibilityViewEmbedder accessibilityViewEmbedder = mock(AccessibilityViewEmbedder.class); - AccessibilityBridge accessibilityBridge = - setUpBridge( - /*rootAccessibilityView=*/ null, - /*accessibilityChannel=*/ null, - /*accessibilityManager=*/ null, - /*contentResolver=*/ null, - accessibilityViewEmbedder, - accessibilityDelegate); - - TestSemanticsNode platformView = new TestSemanticsNode(); - platformView.platformViewId = 1; - - TestSemanticsUpdate testSemanticsUpdate = platformView.toUpdate(); - testSemanticsUpdate.sendUpdateToBridge(accessibilityBridge); - - View embeddedView = mock(View.class); - when(accessibilityDelegate.getPlatformViewById(1)).thenReturn(embeddedView); - - accessibilityBridge.createAccessibilityNodeInfo(0); - verify(accessibilityViewEmbedder).getRootNode(eq(embeddedView), eq(0), any(Rect.class)); - } - @Test public void releaseDropsChannelMessageHandler() { AccessibilityChannel mockChannel = mock(AccessibilityChannel.class); From f8b4caf812962e75e87417c756986fbf201f61c8 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 7 Feb 2022 19:22:07 -0800 Subject: [PATCH 20/29] Linter checks --- .../plugin/platform/PlatformViewWrapper.java | 48 ++++++++++-------- .../platform/PlatformViewsController.java | 49 ++++++++++--------- 2 files changed, 55 insertions(+), 42 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index a0c6702b0f417..eb6d7002b6a91 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -35,7 +35,7 @@ *

Since the view is in the Android view hierarchy, keyboard and accessibility interactions * behave normally. */ -@TargetApi(Build.VERSION_CODES.M) +@TargetApi(23) class PlatformViewWrapper extends FrameLayout { private static final String TAG = "PlatformViewWrapper"; @@ -70,7 +70,16 @@ public void setTouchProcessor(@Nullable AndroidTouchProcessor newTouchProcessor) * * @param newTx The texture where the view is projected onto. */ + @SuppressLint("NewApi") public void setTexture(@Nullable SurfaceTexture newTx) { + if (Build.VERSION.SDK_INT < 23) { + Log.e( + TAG, + "Platform views cannot be displayed below API level 23. " + + "You can prevent this issue by setting `minSdkVersion: 23` in build.gradle."); + return; + } + if (tx != null) { tx.release(); } @@ -85,18 +94,18 @@ public void setTexture(@Nullable SurfaceTexture newTx) { } surface = new Surface(newTx); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - // Fill the entire canvas with a transparent color. - // As a result, the background color of the platform view container is displayed - // to the user until the platform view draws its first frame. - final Canvas canvas = surface.lockHardwareCanvas(); - canvas.drawColor(Color.TRANSPARENT, BlendMode.CLEAR); + // Fill the entire canvas with a transparent color. + // As a result, the background color of the platform view container is displayed + // to the user until the platform view draws its first frame. + final Canvas canvas = surface.lockHardwareCanvas(); + try { + if (Build.VERSION.SDK_INT >= 29) { + canvas.drawColor(Color.TRANSPARENT, BlendMode.CLEAR); + } else { + canvas.drawColor(Color.TRANSPARENT); + } + } finally { surface.unlockCanvasAndPost(canvas); - } else { - Log.e( - TAG, - "Platform views cannot be displayed below API level 23. " - + "You can prevent this issue by setting minCompileSdk: 23 in build.gradle."); } } @@ -161,10 +170,12 @@ public boolean onInterceptTouchEvent(@NonNull MotionEvent event) { @Override public void onDescendantInvalidated(@NonNull View child, @NonNull View target) { + super.onDescendantInvalidated(child, target); invalidate(); } @Override + @SuppressLint("NewApi") public void draw(Canvas canvas) { if (surface == null || !surface.isValid()) { Log.e(TAG, "Invalid surface. The platform view cannot be displayed."); @@ -174,19 +185,16 @@ public void draw(Canvas canvas) { Log.e(TAG, "Invalid texture. The platform view cannot be displayed."); return; } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - Log.e( - TAG, - "Platform views cannot be displayed below API level 23. " - + "You can prevent this issue by setting minCompileSdk: 23 in build.gradle."); - return; - } // Override the canvas that this subtree of views will use to draw. final Canvas surfaceCanvas = surface.lockHardwareCanvas(); try { // Clear the current pixels in the canvas. // This helps when a WebView renders an HTML document with transparent background. - surfaceCanvas.drawColor(Color.TRANSPARENT, BlendMode.CLEAR); + if (Build.VERSION.SDK_INT >= 29) { + surfaceCanvas.drawColor(Color.TRANSPARENT, BlendMode.CLEAR); + } else { + surfaceCanvas.drawColor(Color.TRANSPARENT); + } super.draw(surfaceCanvas); } finally { surface.unlockCanvasAndPost(surfaceCanvas); diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index bac0d775eac73..cdadd85846b1b 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -151,7 +151,7 @@ public void createForPlatformViewLayer( public long createForTextureLayer( @NonNull PlatformViewsChannel.PlatformViewCreationRequest request) { final int viewId = request.viewId; - if (viewWrappers.contains(viewId)) { + if (viewWrappers.get(viewId) != null) { throw new IllegalStateException( "Trying to create an already created platform view, view id: " + viewId); } @@ -218,8 +218,8 @@ public void dispose(int viewId) { platformView.dispose(); } // The platform view is displayed using a TextureLayer. - if (viewWrappers.contains(viewId)) { - final PlatformViewWrapper viewWrapper = viewWrappers.get(viewId); + final PlatformViewWrapper viewWrapper = viewWrappers.get(viewId); + if (viewWrapper != null) { viewWrapper.release(); viewWrapper.unsetOnDescendantFocusChangeListener(); @@ -232,8 +232,8 @@ public void dispose(int viewId) { } // The platform view is displayed using a PlatformViewLayer. // TODO(egarciad): Eliminate this case. - if (platformViewParent.contains(viewId)) { - final FlutterMutatorView parentView = platformViewParent.get(viewId); + final FlutterMutatorView parentView = platformViewParent.get(viewId); + if (parentView != null) { parentView.unsetOnDescendantFocusChangeListener(); final ViewGroup mutatorViewParent = (ViewGroup) parentView.getParent(); @@ -246,32 +246,32 @@ public void dispose(int viewId) { @Override public void offset(int viewId, double top, double left) { - if (!viewWrappers.contains(viewId)) { + final PlatformViewWrapper wrapper = viewWrappers.get(viewId); + if (wrapper == null) { Log.e(TAG, "Setting offset for unknown platform view with id: " + viewId); return; } final int physicalTop = toPhysicalPixels(top); final int physicalLeft = toPhysicalPixels(left); - final PlatformViewWrapper view = viewWrappers.get(viewId); final FrameLayout.LayoutParams layoutParams = - (FrameLayout.LayoutParams) view.getLayoutParams(); + (FrameLayout.LayoutParams) wrapper.getLayoutParams(); layoutParams.topMargin = physicalTop; layoutParams.leftMargin = physicalLeft; - view.setLayoutParams(layoutParams); + wrapper.setLayoutParams(layoutParams); } @Override public PlatformViewsChannel.PlatformViewBufferSize resize( @NonNull PlatformViewsChannel.PlatformViewResizeRequest request) { final int viewId = request.viewId; - if (!viewWrappers.contains(viewId)) { + final PlatformViewWrapper view = viewWrappers.get(viewId); + if (view == null) { Log.e(TAG, "Resizing unknown platform view with id: " + viewId); return null; } final int newWidth = toPhysicalPixels(request.newLogicalWidth); final int newHeight = toPhysicalPixels(request.newLogicalHeight); - final PlatformViewWrapper view = viewWrappers.get(viewId); // Resize the buffer only when the current buffer size is smaller than the new size. // This is required to prevent a situation when smooth keyboard animation // resizes the texture too often, such that the GPU and the platform thread don't agree on @@ -297,13 +297,15 @@ public PlatformViewsChannel.PlatformViewBufferSize resize( @Override public void onTouch(@NonNull PlatformViewsChannel.PlatformViewTouch touch) { final int viewId = touch.viewId; - if (!platformViews.contains(viewId)) { - throw new IllegalStateException("Sending touch to an unknown view with id: " + viewId); + final PlatformView platformView = platformViews.get(viewId); + if (platformView == null) { + Log.e(TAG, "Sending touch to an unknown view with id: " + viewId); + return; } ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH); final float density = context.getResources().getDisplayMetrics().density; final MotionEvent event = toMotionEvent(density, touch); - final View view = platformViews.get(viewId).getView(); + final View view = platformView.getView(); if (view != null) { view.dispatchTouchEvent(event); } @@ -320,9 +322,10 @@ public void setDirection(int viewId, int direction) { + viewId + ")"); } - if (!platformViews.contains(viewId)) { - throw new IllegalStateException( - "Setting direction to an unknown view with id: " + viewId); + final PlatformView platformView = platformViews.get(viewId); + if (platformView == null) { + Log.e(TAG, "Setting direction to an unknown view with id: " + viewId); + return; } ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH); platformViews.get(viewId).getView().setLayoutDirection(direction); @@ -330,10 +333,11 @@ public void setDirection(int viewId, int direction) { @Override public void clearFocus(int viewId) { - if (!platformViews.contains(viewId)) { - throw new IllegalStateException("Clearing focus on an unknown view with id: " + viewId); - } final PlatformView platformView = platformViews.get(viewId); + if (platformView == null) { + Log.e(TAG, "Clearing focus on an unknown view with id: " + viewId); + return; + } platformView.getView().clearFocus(); } @@ -549,10 +553,11 @@ public void onPreEngineRestart() { @Override public View getPlatformViewById(int viewId) { - if (!platformViews.contains(viewId)) { + final PlatformView platformView = platformViews.get(viewId); + if (platformView == null) { return null; } - return platformViews.get(viewId).getView(); + return platformView.getView(); } private static boolean validateDirection(int direction) { From 8a139cfe7589ff9d6dfac5ec2f93ebe8d019bb90 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 7 Feb 2022 19:42:41 -0800 Subject: [PATCH 21/29] Remove dead code --- .../systemchannels/PlatformViewsChannel.java | 8 +- .../plugin/editing/TextInputPlugin.java | 88 ++----------------- 2 files changed, 9 insertions(+), 87 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java index 009cec8699083..f25335a2a6001 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java @@ -347,7 +347,7 @@ public static class PlatformViewCreationRequest { /** Custom parameters that are unique to the desired platform view. */ @Nullable public final ByteBuffer params; - /** Creates a request to construct a platform view that uses a virtual display. */ + /** Creates a request to construct a platform view. */ public PlatformViewCreationRequest( int viewId, @NonNull String viewType, @@ -364,11 +364,7 @@ public PlatformViewCreationRequest( } } - /** - * Request sent from Flutter to resize a platform view. - * - *

This only applies to platform views that use virtual displays. - */ + /** Request sent from Flutter to resize a platform view. */ public static class PlatformViewResizeRequest { /** The ID of the platform view as seen by the Flutter side. */ public final int viewId; diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index b12a06b2a6fa6..b400e9ab9935b 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -54,12 +54,6 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch // Initialize the "last seen" text editing values to a non-null value. private TextEditState mLastKnownFrameworkTextEditingState; - // When true following calls to createInputConnection will return the cached lastInputConnection - // if the input - // target is a platform view. See the comments on lockPlatformViewInputConnection for more - // details. - private boolean isInputConnectionLocked; - @SuppressLint("NewApi") public TextInputPlugin( View view, @@ -105,7 +99,7 @@ public void show() { @Override public void hide() { - if (inputTarget.type == InputTarget.Type.HC_PLATFORM_VIEW) { + if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) { notifyViewExited(); } else { hideTextInput(mView); @@ -182,34 +176,6 @@ ImeSyncDeferringInsetsCallback getImeSyncCallback() { return imeSyncCallback; } - /** - * Use the current platform view input connection until unlockPlatformViewInputConnection is - * called. - * - *

The current input connection instance is cached and any following call to @{link - * createInputConnection} returns the cached connection until unlockPlatformViewInputConnection is - * called. - * - *

This is a no-op if the current input target isn't a platform view. - * - *

This is used to preserve an input connection when moving a platform view from one virtual - * display to another. - */ - public void lockPlatformViewInputConnection() { - if (inputTarget.type == InputTarget.Type.VD_PLATFORM_VIEW) { - isInputConnectionLocked = true; - } - } - - /** - * Unlocks the input connection. - * - *

See also: @{link lockPlatformViewInputConnection}. - */ - public void unlockPlatformViewInputConnection() { - isInputConnectionLocked = false; - } - /** * Detaches the text input plugin from the platform views controller. * @@ -292,21 +258,10 @@ public InputConnection createInputConnection( return null; } - if (inputTarget.type == InputTarget.Type.HC_PLATFORM_VIEW) { + if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) { return null; } - if (inputTarget.type == InputTarget.Type.VD_PLATFORM_VIEW) { - if (isInputConnectionLocked) { - return lastInputConnection; - } - lastInputConnection = - platformViewsController - .getPlatformViewById(inputTarget.id) - .onCreateInputConnection(outAttrs); - return lastInputConnection; - } - outAttrs.inputType = inputTypeFromTextInputType( configuration.inputType, @@ -361,9 +316,7 @@ public InputConnection getLastInputConnection() { * input connection. */ public void clearPlatformViewClient(int platformViewId) { - if ((inputTarget.type == InputTarget.Type.VD_PLATFORM_VIEW - || inputTarget.type == InputTarget.Type.HC_PLATFORM_VIEW) - && inputTarget.id == platformViewId) { + if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW && inputTarget.id == platformViewId) { inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); notifyViewExited(); mImm.hideSoftInputFromWindow(mView.getApplicationWindowToken(), 0); @@ -430,7 +383,7 @@ void setTextInputClient(int client, TextInputChannel.Configuration configuration } private void setPlatformViewTextInputClient(int platformViewId) { - inputTarget = new InputTarget(InputTarget.Type.HC_PLATFORM_VIEW, platformViewId); + inputTarget = new InputTarget(InputTarget.Type.PLATFORM_VIEW, platformViewId); lastInputConnection = null; } @@ -522,30 +475,6 @@ public void inspect(double x, double y) { @VisibleForTesting void clearTextInputClient() { - if (inputTarget.type == InputTarget.Type.VD_PLATFORM_VIEW) { - // This only applies to platform views that use a virtual display. - // Focus changes in the framework tree have no guarantees on the order focus nodes are - // notified. A node - // that lost focus may be notified before or after a node that gained focus. - // When moving the focus from a Flutter text field to an AndroidView, it is possible that the - // Flutter text - // field's focus node will be notified that it lost focus after the AndroidView was notified - // that it gained - // focus. When this happens the text field will send a clearTextInput command which we ignore. - // By doing this we prevent the framework from clearing a platform view input client (the only - // way to do so - // is to set a new framework text client). I don't see an obvious use case for "clearing" a - // platform view's - // text input client, and it may be error prone as we don't know how the platform view manages - // the input - // connection and we probably shouldn't interfere. - // If we ever want to allow the framework to clear a platform view text client we should - // probably consider - // changing the focus manager such that focus nodes that lost focus are notified before focus - // nodes that - // gained focus as part of the same focus event. - return; - } mEditable.removeEditingStateListener(this); notifyViewExited(); updateAutofillConfigurationIfNeeded(null); @@ -560,12 +489,9 @@ enum Type { // InputConnection is managed by the TextInputPlugin, and events are forwarded to the Flutter // framework. FRAMEWORK_CLIENT, - // InputConnection is managed by an embedded platform view that is backed by a virtual - // display (VD). - VD_PLATFORM_VIEW, - // InputConnection is managed by an embedded platform view that is embeded in the Android view - // hierarchy, and uses hybrid composition (HC). - HC_PLATFORM_VIEW, + // InputConnection is managed by a platform view that is embeded in the Android view + // hierarchy. + PLATFORM_VIEW, } public InputTarget(@NonNull Type type, int id) { From 7d8c512132c2272ac066c433f5abcaff9b8aefd1 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 7 Feb 2022 20:13:36 -0800 Subject: [PATCH 22/29] Get offset from create request as well --- .../systemchannels/PlatformViewsChannel.java | 16 +++++++++++++++- .../flutter/plugin/editing/TextInputPlugin.java | 2 -- .../io/flutter/plugin/platform/PlatformView.java | 14 ++++++++------ .../plugin/platform/PlatformViewsController.java | 10 ++++++---- 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java index f25335a2a6001..3684a11f0a6a5 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java @@ -87,17 +87,21 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result private void create(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { Map createArgs = call.arguments(); - // TODO(egarciad): The word "hybrid" is misleading. + // TODO(egarciad): Remove the "hybrid" case. boolean usesPlatformViewLayer = createArgs.containsKey("hybrid") && (boolean) createArgs.get("hybrid"); // In hybrid mode, the size of the view is determined by the size of the Flow layer. double width = (usesPlatformViewLayer) ? 0 : (double) createArgs.get("width"); double height = (usesPlatformViewLayer) ? 0 : (double) createArgs.get("height"); + double top = (usesPlatformViewLayer) ? 0 : (double) createArgs.get("top"); + double left = (usesPlatformViewLayer) ? 0 : (double) createArgs.get("left"); PlatformViewCreationRequest request = new PlatformViewCreationRequest( (int) createArgs.get("id"), (String) createArgs.get("viewType"), + top, + left, width, height, (int) createArgs.get("direction"), @@ -336,6 +340,12 @@ public static class PlatformViewCreationRequest { /** The density independent height to display the platform view. */ public final double logicalHeight; + /** The density independent top position to display the platform view. */ + public final double logicalTop; + + /** The density independent left position to display the platform view. */ + public final double logicalLeft; + /** * The layout direction of the new platform view. * @@ -351,12 +361,16 @@ public static class PlatformViewCreationRequest { public PlatformViewCreationRequest( int viewId, @NonNull String viewType, + double logicalTop, + double logicalLeft, double logicalWidth, double logicalHeight, int direction, @Nullable ByteBuffer params) { this.viewId = viewId; this.viewType = viewType; + this.logicalTop = logicalTop; + this.logicalLeft = logicalLeft; this.logicalWidth = logicalWidth; this.logicalHeight = logicalHeight; this.direction = direction; diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index b400e9ab9935b..cfd57ef7a26ea 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -377,7 +377,6 @@ void setTextInputClient(int client, TextInputChannel.Configuration configuration // setTextInputClient will be followed by a call to setTextInputEditingState. // Do a restartInput at that time. mRestartInputPending = true; - unlockPlatformViewInputConnection(); lastClientRect = null; mEditable.addEditingStateListener(this); } @@ -479,7 +478,6 @@ void clearTextInputClient() { notifyViewExited(); updateAutofillConfigurationIfNeeded(null); inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); - unlockPlatformViewInputConnection(); lastClientRect = null; } diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformView.java b/shell/platform/android/io/flutter/plugin/platform/PlatformView.java index 92f034d840d11..85ed3c40e8c2e 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformView.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformView.java @@ -60,24 +60,26 @@ default void onFlutterViewDetached() {} void dispose(); /** - * Callback fired when the platform's input connection is locked, or should be used. See also - * {@link io.flutter.plugin.editing.TextInputPlugin#lockPlatformViewInputConnection}. + * Callback fired when the platform's input connection is locked, or should be used. * *

This hook only exists for rare cases where the plugin relies on the state of the input * connection. This probably doesn't need to be implemented. + * + *

This method is deprecated, and will be removed in a future release. */ - // Default interface methods are supported on all min SDK versions of Android. @SuppressLint("NewApi") + @Deprecated default void onInputConnectionLocked() {} /** - * Callback fired when the platform input connection has been unlocked. See also {@link - * io.flutter.plugin.editing.TextInputPlugin#lockPlatformViewInputConnection}. + * Callback fired when the platform input connection has been unlocked. * *

This hook only exists for rare cases where the plugin relies on the state of the input * connection. This probably doesn't need to be implemented. + * + *

This method is deprecated, and will be removed in a future release. */ - // Default interface methods are supported on all min SDK versions of Android. @SuppressLint("NewApi") + @Deprecated default void onInputConnectionUnlocked() {} } diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index cdadd85846b1b..f3dce89f2a93f 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -188,6 +188,11 @@ public long createForTextureLayer( final FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(physicalWidth, physicalHeight); + + final int physicalTop = toPhysicalPixels(request.logicalTop); + final int physicalLeft = toPhysicalPixels(request.logicalLeft); + layoutParams.topMargin = physicalTop; + layoutParams.leftMargin = physicalLeft; wrapperView.setLayoutParams(layoutParams); wrapperView.setLayoutDirection(request.direction); @@ -305,10 +310,7 @@ public void onTouch(@NonNull PlatformViewsChannel.PlatformViewTouch touch) { ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH); final float density = context.getResources().getDisplayMetrics().density; final MotionEvent event = toMotionEvent(density, touch); - final View view = platformView.getView(); - if (view != null) { - view.dispatchTouchEvent(event); - } + platformView.getView().dispatchTouchEvent(event); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) From eec8544a2a71ee2e5df5a6eb0d1ef91c8131f08f Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 7 Feb 2022 20:19:45 -0800 Subject: [PATCH 23/29] Organize --- .../systemchannels/PlatformViewsChannel.java | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java index 3684a11f0a6a5..3fc29f0d2a481 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java @@ -86,33 +86,39 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result } private void create(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - Map createArgs = call.arguments(); + final Map createArgs = call.arguments(); // TODO(egarciad): Remove the "hybrid" case. - boolean usesPlatformViewLayer = + final boolean usesPlatformViewLayer = createArgs.containsKey("hybrid") && (boolean) createArgs.get("hybrid"); - // In hybrid mode, the size of the view is determined by the size of the Flow layer. - double width = (usesPlatformViewLayer) ? 0 : (double) createArgs.get("width"); - double height = (usesPlatformViewLayer) ? 0 : (double) createArgs.get("height"); - double top = (usesPlatformViewLayer) ? 0 : (double) createArgs.get("top"); - double left = (usesPlatformViewLayer) ? 0 : (double) createArgs.get("left"); - - PlatformViewCreationRequest request = - new PlatformViewCreationRequest( - (int) createArgs.get("id"), - (String) createArgs.get("viewType"), - top, - left, - width, - height, - (int) createArgs.get("direction"), - createArgs.containsKey("params") - ? ByteBuffer.wrap((byte[]) createArgs.get("params")) - : null); + final ByteBuffer additionalParams = + createArgs.containsKey("params") + ? ByteBuffer.wrap((byte[]) createArgs.get("params")) + : null; try { if (usesPlatformViewLayer) { + final PlatformViewCreationRequest request = + new PlatformViewCreationRequest( + (int) createArgs.get("id"), + (String) createArgs.get("viewType"), + 0, + 0, + 0, + 0, + (int) createArgs.get("direction"), + additionalParams); handler.createForPlatformViewLayer(request); result.success(null); } else { + final PlatformViewCreationRequest request = + new PlatformViewCreationRequest( + (int) createArgs.get("id"), + (String) createArgs.get("viewType"), + (double) createArgs.get("top"), + (double) createArgs.get("left"), + (double) createArgs.get("width"), + (double) createArgs.get("height"), + (int) createArgs.get("direction"), + additionalParams); long textureId = handler.createForTextureLayer(request); result.success(textureId); } From 33e62193bc224d2896feb2af9c4759dd04cabbfb Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 7 Feb 2022 22:40:18 -0800 Subject: [PATCH 24/29] Test PlatformViewWrapper --- .../plugin/platform/PlatformViewWrapper.java | 8 +- .../platform/PlatformViewWrapperTest.java | 235 ++++++++++++++++++ 2 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index eb6d7002b6a91..525520029368f 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -92,7 +92,7 @@ public void setTexture(@Nullable SurfaceTexture newTx) { if (surface != null) { surface.release(); } - surface = new Surface(newTx); + surface = createSurface(newTx); // Fill the entire canvas with a transparent color. // As a result, the background color of the platform view container is displayed @@ -109,6 +109,12 @@ public void setTexture(@Nullable SurfaceTexture newTx) { } } + @NonNull + @VisibleForTesting + protected Surface createSurface(@NonNull SurfaceTexture tx) { + return new Surface(tx); + } + /** Returns the texture where the view is projected. */ @Nullable public SurfaceTexture getTexture() { diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java new file mode 100644 index 0000000000000..5a490d4239324 --- /dev/null +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java @@ -0,0 +1,235 @@ +package io.flutter.plugin.platform; + +import static android.view.View.OnFocusChangeListener; +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import android.graphics.BlendMode; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.SurfaceTexture; +import android.view.Surface; +import android.view.View; +import android.view.ViewTreeObserver; +import androidx.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@Config(manifest = Config.NONE) +@RunWith(AndroidJUnit4.class) +public class PlatformViewWrapperTest { + @Test + public void setTexture_writesToBuffer() { + final Surface surface = mock(Surface.class); + final PlatformViewWrapper wrapper = + new PlatformViewWrapper(RuntimeEnvironment.application) { + @Override + protected Surface createSurface(@NonNull SurfaceTexture tx) { + return surface; + } + }; + + final SurfaceTexture tx = mock(SurfaceTexture.class); + when(tx.isReleased()).thenReturn(false); + + final Canvas canvas = mock(Canvas.class); + when(surface.lockHardwareCanvas()).thenReturn(canvas); + + // Test. + wrapper.setTexture(tx); + + // Verify. + verify(surface, times(1)).lockHardwareCanvas(); + verify(surface, times(1)).unlockCanvasAndPost(canvas); + verify(canvas, times(1)).drawColor(Color.TRANSPARENT, BlendMode.CLEAR); + verifyNoMoreInteractions(surface); + verifyNoMoreInteractions(canvas); + } + + @Test + public void draw_writesToBuffer() { + final Surface surface = mock(Surface.class); + final PlatformViewWrapper wrapper = + new PlatformViewWrapper(RuntimeEnvironment.application) { + @Override + protected Surface createSurface(@NonNull SurfaceTexture tx) { + return surface; + } + + @Override + protected void onDraw(Canvas canvas) { + System.out.println("CANVAS DRAWN"); + } + }; + + wrapper.addView( + new View(RuntimeEnvironment.application) { + @Override + public void draw(Canvas canvas) { + canvas.drawColor(Color.RED); + } + }); + + + final int size = 100; + wrapper.measure(size, size); + wrapper.layout(0, 0, size, size); + + + final SurfaceTexture tx = mock(SurfaceTexture.class); + when(tx.isReleased()).thenReturn(false); + + when(surface.lockHardwareCanvas()).thenReturn(mock(Canvas.class)); + + wrapper.setTexture(tx); + + reset(surface); + + final Canvas canvas = mock(Canvas.class); + when(surface.lockHardwareCanvas()).thenReturn(canvas); + when(surface.isValid()).thenReturn(true); + + // Test. + wrapper.invalidate(); + wrapper.draw(null); + + // Verify. + verify(surface, times(1)).unlockCanvasAndPost(canvas); + verify(canvas, times(1)).drawColor(Color.TRANSPARENT, BlendMode.CLEAR); + verify(canvas, times(1)).drawColor(Color.RED); + verifyNoMoreInteractions(surface); + verifyNoMoreInteractions(canvas); + } + + @Test + public void focusChangeListener_hasFocus() { + final ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class); + when(viewTreeObserver.isAlive()).thenReturn(true); + + final PlatformViewWrapper view = + new PlatformViewWrapper(RuntimeEnvironment.application) { + @Override + public ViewTreeObserver getViewTreeObserver() { + return viewTreeObserver; + } + + @Override + public boolean hasFocus() { + return true; + } + }; + + final OnFocusChangeListener focusListener = mock(OnFocusChangeListener.class); + view.setOnDescendantFocusChangeListener(focusListener); + + final ArgumentCaptor focusListenerCaptor = + ArgumentCaptor.forClass(ViewTreeObserver.OnGlobalFocusChangeListener.class); + verify(viewTreeObserver).addOnGlobalFocusChangeListener(focusListenerCaptor.capture()); + + focusListenerCaptor.getValue().onGlobalFocusChanged(null, null); + verify(focusListener).onFocusChange(view, true); + } + + @Test + public void focusChangeListener_doesNotHaveFocus() { + final ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class); + when(viewTreeObserver.isAlive()).thenReturn(true); + + final PlatformViewWrapper view = + new PlatformViewWrapper(RuntimeEnvironment.application) { + @Override + public ViewTreeObserver getViewTreeObserver() { + return viewTreeObserver; + } + + @Override + public boolean hasFocus() { + return false; + } + }; + + final OnFocusChangeListener focusListener = mock(OnFocusChangeListener.class); + view.setOnDescendantFocusChangeListener(focusListener); + + final ArgumentCaptor focusListenerCaptor = + ArgumentCaptor.forClass(ViewTreeObserver.OnGlobalFocusChangeListener.class); + verify(viewTreeObserver).addOnGlobalFocusChangeListener(focusListenerCaptor.capture()); + + focusListenerCaptor.getValue().onGlobalFocusChanged(null, null); + verify(focusListener).onFocusChange(view, false); + } + + @Test + public void focusChangeListener_viewTreeObserverIsAliveFalseDoesNotThrow() { + final PlatformViewWrapper view = + new PlatformViewWrapper(RuntimeEnvironment.application) { + @Override + public ViewTreeObserver getViewTreeObserver() { + final ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class); + when(viewTreeObserver.isAlive()).thenReturn(false); + return viewTreeObserver; + } + }; + view.setOnDescendantFocusChangeListener(mock(OnFocusChangeListener.class)); + } + + @Test + public void setOnDescendantFocusChangeListener_keepsSingleListener() { + final ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class); + when(viewTreeObserver.isAlive()).thenReturn(true); + + final PlatformViewWrapper view = + new PlatformViewWrapper(RuntimeEnvironment.application) { + @Override + public ViewTreeObserver getViewTreeObserver() { + return viewTreeObserver; + } + }; + + assertNull(view.activeFocusListener); + + view.setOnDescendantFocusChangeListener(mock(OnFocusChangeListener.class)); + assertNotNull(view.activeFocusListener); + + final ViewTreeObserver.OnGlobalFocusChangeListener activeFocusListener = + view.activeFocusListener; + + view.setOnDescendantFocusChangeListener(mock(OnFocusChangeListener.class)); + assertNotNull(view.activeFocusListener); + + verify(viewTreeObserver, times(1)).removeOnGlobalFocusChangeListener(activeFocusListener); + } + + @Test + public void unsetOnDescendantFocusChangeListener_removesActiveListener() { + final ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class); + when(viewTreeObserver.isAlive()).thenReturn(true); + + final PlatformViewWrapper view = + new PlatformViewWrapper(RuntimeEnvironment.application) { + @Override + public ViewTreeObserver getViewTreeObserver() { + return viewTreeObserver; + } + }; + + assertNull(view.activeFocusListener); + + view.setOnDescendantFocusChangeListener(mock(OnFocusChangeListener.class)); + assertNotNull(view.activeFocusListener); + + final ViewTreeObserver.OnGlobalFocusChangeListener activeFocusListener = + view.activeFocusListener; + + view.unsetOnDescendantFocusChangeListener(); + assertNull(view.activeFocusListener); + + view.unsetOnDescendantFocusChangeListener(); + verify(viewTreeObserver, times(1)).removeOnGlobalFocusChangeListener(activeFocusListener); + } +} From 6930f2fd94abf4ff28fe732b02782110ce7838bf Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 9 Feb 2022 14:54:12 -0800 Subject: [PATCH 25/29] Fix test --- .../renderer/SurfaceTextureWrapper.java | 6 +- .../plugin/platform/PlatformViewWrapper.java | 21 +++---- .../platform/PlatformViewsController.java | 16 ++++- .../platform/PlatformViewWrapperTest.java | 58 ++++++++++++++----- 4 files changed, 73 insertions(+), 28 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java b/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java index b9a9c57d95de4..908e83a2c5e93 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java @@ -62,7 +62,11 @@ public void attachToGLContext(int texName) { // Called by native. @SuppressWarnings("unused") public void detachFromGLContext() { - surfaceTexture.detachFromGLContext(); + synchronized (this) { + if (!released) { + surfaceTexture.detachFromGLContext(); + } + } } // Called by native. diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java index 525520029368f..d7ebe184a656b 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewWrapper.java @@ -28,9 +28,9 @@ /** * Wraps a platform view to intercept gestures and project this view onto a {@link SurfaceTexture}. * - *

An Android platform view is composed by the Flutter Engine using a TextureLayer. The view is + *

An Android platform view is composed by the engine using a {@code TextureLayer}. The view is * embeded to the Android view hierarchy like a normal view, but it's projected onto a {@link - * SurfaceTexture}, so it can be efficiently composed by the Flutter Engine. + * SurfaceTexture}, so it can be efficiently composed by the engine. * *

Since the view is in the Android view hierarchy, keyboard and accessibility interactions * behave normally. @@ -68,6 +68,12 @@ public void setTouchProcessor(@Nullable AndroidTouchProcessor newTouchProcessor) /** * Sets the texture where the view is projected onto. * + *

{@link PlatformViewWrapper} doesn't take ownership of the {@link SurfaceTexture}. As a + * result, the caller is responsible for releasing the texture. + * + *

{@link io.flutter.view.TextureRegistry} is responsible for creating and registering textures + * in the engine. Therefore, the engine is responsible for also releasing the texture. + * * @param newTx The texture where the view is projected onto. */ @SuppressLint("NewApi") @@ -80,9 +86,6 @@ public void setTexture(@Nullable SurfaceTexture newTx) { return; } - if (tx != null) { - tx.release(); - } tx = newTx; if (bufferWidth > 0 && bufferHeight > 0) { @@ -157,12 +160,10 @@ public int getBufferHeight() { return bufferHeight; } - /** Releases the texture and surface. */ + /** Releases the surface. */ public void release() { - if (tx != null) { - tx.release(); - tx = null; - } + // Don't release the texture. + tx = null; if (surface != null) { surface.release(); surface = null; diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index f3dce89f2a93f..fbd2a4c86bfe6 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -163,6 +163,16 @@ public long createForTextureLayer( + viewId + ")"); } + if (textureRegistry == null) { + throw new IllegalStateException( + "Texture registry is null. This means that platform views controller was detached, view id: " + + viewId); + } + if (flutterView == null) { + throw new IllegalStateException( + "Flutter view is null. This means the platform views controller doesn't have an attached view, view id: " + + viewId); + } final PlatformViewFactory viewFactory = registry.getFactory(request.viewType); if (viewFactory == null) { throw new IllegalStateException( @@ -471,8 +481,8 @@ public void detach() { * This {@code PlatformViewsController} and its {@code FlutterEngine} is now attached to an * Android {@code View} that renders a Flutter UI. */ - public void attachToView(@NonNull FlutterView flutterView) { - this.flutterView = flutterView; + public void attachToView(@NonNull FlutterView newFlutterView) { + flutterView = newFlutterView; // Inform all existing platform views that they are now associated with // a Flutter View. @@ -497,7 +507,7 @@ public void detachFromView() { // TODO(egarciad): Remove this. destroyOverlaySurfaces(); removeOverlaySurfaces(); - this.flutterView = null; + flutterView = null; flutterViewConvertedToImageView = false; } diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java index 5a490d4239324..51353d915c28c 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java @@ -5,6 +5,7 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import android.content.Context; import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.Color; @@ -13,6 +14,7 @@ import android.view.View; import android.view.ViewTreeObserver; import androidx.annotation.NonNull; +import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @@ -26,8 +28,9 @@ public class PlatformViewWrapperTest { @Test public void setTexture_writesToBuffer() { final Surface surface = mock(Surface.class); + final Context ctx = ApplicationProvider.getApplicationContext(); final PlatformViewWrapper wrapper = - new PlatformViewWrapper(RuntimeEnvironment.application) { + new PlatformViewWrapper(ctx) { @Override protected Surface createSurface(@NonNull SurfaceTexture tx) { return surface; @@ -54,33 +57,28 @@ protected Surface createSurface(@NonNull SurfaceTexture tx) { @Test public void draw_writesToBuffer() { final Surface surface = mock(Surface.class); + final Context ctx = ApplicationProvider.getApplicationContext(); final PlatformViewWrapper wrapper = - new PlatformViewWrapper(RuntimeEnvironment.application) { + new PlatformViewWrapper(ctx) { @Override protected Surface createSurface(@NonNull SurfaceTexture tx) { return surface; } - - @Override - protected void onDraw(Canvas canvas) { - System.out.println("CANVAS DRAWN"); - } }; - + wrapper.addView( - new View(RuntimeEnvironment.application) { + new View(ctx) { @Override public void draw(Canvas canvas) { + super.draw(canvas); canvas.drawColor(Color.RED); } }); - final int size = 100; wrapper.measure(size, size); wrapper.layout(0, 0, size, size); - final SurfaceTexture tx = mock(SurfaceTexture.class); when(tx.isReleased()).thenReturn(false); @@ -96,16 +94,48 @@ public void draw(Canvas canvas) { // Test. wrapper.invalidate(); - wrapper.draw(null); + wrapper.draw(new Canvas()); // Verify. - verify(surface, times(1)).unlockCanvasAndPost(canvas); verify(canvas, times(1)).drawColor(Color.TRANSPARENT, BlendMode.CLEAR); - verify(canvas, times(1)).drawColor(Color.RED); + verify(surface, times(1)).isValid(); + verify(surface, times(1)).lockHardwareCanvas(); + verify(surface, times(1)).unlockCanvasAndPost(canvas); verifyNoMoreInteractions(surface); verifyNoMoreInteractions(canvas); } + @Test + public void release() { + final Surface surface = mock(Surface.class); + final Context ctx = ApplicationProvider.getApplicationContext(); + final PlatformViewWrapper wrapper = + new PlatformViewWrapper(ctx) { + @Override + protected Surface createSurface(@NonNull SurfaceTexture tx) { + return surface; + } + }; + + final SurfaceTexture tx = mock(SurfaceTexture.class); + when(tx.isReleased()).thenReturn(false); + + final Canvas canvas = mock(Canvas.class); + when(surface.lockHardwareCanvas()).thenReturn(canvas); + + wrapper.setTexture(tx); + reset(surface); + reset(tx); + + // Test. + wrapper.release(); + + // Verify. + verify(surface, times(1)).release(); + verifyNoMoreInteractions(surface); + verifyNoMoreInteractions(tx); + } + @Test public void focusChangeListener_hasFocus() { final ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class); From b252ebaf8d6852cb47a8cdca260c2bb22e6399da Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 9 Feb 2022 15:29:54 -0800 Subject: [PATCH 26/29] Linter --- .../io/flutter/plugin/platform/PlatformViewWrapperTest.java | 2 ++ tools/android_lint/project.xml | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java index 51353d915c28c..4630826be5fca 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewWrapperTest.java @@ -5,6 +5,7 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; +import android.annotation.TargetApi; import android.content.Context; import android.graphics.BlendMode; import android.graphics.Canvas; @@ -23,6 +24,7 @@ import org.robolectric.annotation.Config; @Config(manifest = Config.NONE) +@TargetApi(31) @RunWith(AndroidJUnit4.class) public class PlatformViewWrapperTest { @Test diff --git a/tools/android_lint/project.xml b/tools/android_lint/project.xml index 86ad280f8d5b6..75b87536362aa 100644 --- a/tools/android_lint/project.xml +++ b/tools/android_lint/project.xml @@ -11,7 +11,6 @@ - @@ -40,12 +39,14 @@ + + From 535fc8bd77608b2750f357724c14afb9676a95ff Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Fri, 11 Feb 2022 17:33:10 -0800 Subject: [PATCH 27/29] nits --- .../embedding/engine/systemchannels/PlatformViewsChannel.java | 4 +++- .../io/flutter/plugin/platform/PlatformViewsController.java | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java index 3fc29f0d2a481..80cdfbda956af 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java @@ -302,7 +302,9 @@ public interface PlatformViewsHandler { */ PlatformViewBufferSize resize(@NonNull PlatformViewResizeRequest request); - /** The Flutter application would like to change the offset an existing Android {@code View}. */ + /** + * The Flutter application would like to change the offset of an existing Android {@code View}. + */ void offset(int viewId, double top, double left); /** diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index fbd2a4c86bfe6..063882fced75f 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -80,6 +80,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega // // This is only applies to hybrid composition (PlatformViewLayer render). // TODO(egarciad): Eliminate this. + // https://github.com/flutter/flutter/issues/96679 private final SparseArray platformViewParent; // Map of unique IDs to views that render overlay layers. @@ -116,6 +117,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega @TargetApi(Build.VERSION_CODES.KITKAT) @Override // TODO(egarciad): Remove the need for this. + // https://github.com/flutter/flutter/issues/96679 public void createForPlatformViewLayer( @NonNull PlatformViewsChannel.PlatformViewCreationRequest request) { // API level 19 is required for `android.graphics.ImageReader`. @@ -247,6 +249,7 @@ public void dispose(int viewId) { } // The platform view is displayed using a PlatformViewLayer. // TODO(egarciad): Eliminate this case. + // https://github.com/flutter/flutter/issues/96679 final FlutterMutatorView parentView = platformViewParent.get(viewId); if (parentView != null) { parentView.unsetOnDescendantFocusChangeListener(); @@ -505,6 +508,7 @@ public void detachFromView() { view.onFlutterViewDetached(); } // TODO(egarciad): Remove this. + // https://github.com/flutter/flutter/issues/96679 destroyOverlaySurfaces(); removeOverlaySurfaces(); flutterView = null; From d0cdfa358d305f51aec090b9200f45be2ead8262 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Fri, 11 Feb 2022 17:49:49 -0800 Subject: [PATCH 28/29] Default to 0.0 --- .../embedding/engine/systemchannels/PlatformViewsChannel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java index 80cdfbda956af..290c3ac2f129a 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java @@ -113,8 +113,8 @@ private void create(@NonNull MethodCall call, @NonNull MethodChannel.Result resu new PlatformViewCreationRequest( (int) createArgs.get("id"), (String) createArgs.get("viewType"), - (double) createArgs.get("top"), - (double) createArgs.get("left"), + (double) createArgs.getOrDefault("top", 0.0), + (double) createArgs.getOrDefault("left", 0.0), (double) createArgs.get("width"), (double) createArgs.get("height"), (int) createArgs.get("direction"), From ad032f6239cd74c83a3bc66ac751429d74a6b4ed Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Fri, 11 Feb 2022 19:11:49 -0800 Subject: [PATCH 29/29] Don't use getOrDefault --- .../embedding/engine/systemchannels/PlatformViewsChannel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java index 290c3ac2f129a..4b50d01e856d9 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java @@ -113,8 +113,8 @@ private void create(@NonNull MethodCall call, @NonNull MethodChannel.Result resu new PlatformViewCreationRequest( (int) createArgs.get("id"), (String) createArgs.get("viewType"), - (double) createArgs.getOrDefault("top", 0.0), - (double) createArgs.getOrDefault("left", 0.0), + createArgs.containsKey("top") ? (double) createArgs.get("top") : 0.0, + createArgs.containsKey("left") ? (double) createArgs.get("left") : 0.0, (double) createArgs.get("width"), (double) createArgs.get("height"), (int) createArgs.get("direction"),