diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index e02a6409acf08..827e54166a9b9 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1481,8 +1481,6 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/Platfor 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 84827f4610e77..7444efa71a0eb 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -286,8 +286,6 @@ android_java_sources = [ "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 f63e899b4e8c9..1e2eac8e61db1 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -873,21 +873,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/systemchannels/PlatformViewsChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java index d338b7cfcbce1..4b50d01e856d9 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java @@ -147,18 +147,15 @@ private void resize(@NonNull MethodCall call, @NonNull MethodChannel.Result resu (double) resizeArgs.get("width"), (double) resizeArgs.get("height")); try { - handler.resize( - resizeRequest, - (PlatformViewBufferSize bufferSize) -> { - if (bufferSize == null) { - result.error("error", "Failed to resize the platform view", null); - } else { - final Map response = new HashMap<>(); - response.put("width", (double) bufferSize.width); - response.put("height", (double) bufferSize.height); - result.success(response); - } - }); + final PlatformViewBufferSize sz = handler.resize(resizeRequest); + if (sz == null) { + result.error("error", "Failed to resize the platform view", null); + } else { + final Map response = new HashMap<>(); + response.put("width", (double) sz.width); + response.put("height", (double) sz.height); + result.success(response); + } } catch (IllegalStateException exception) { result.error("error", detailedExceptionString(exception), null); } @@ -301,11 +298,9 @@ public interface PlatformViewsHandler { * The Flutter application would like to resize an existing Android {@code View}. * * @param request The request to resize the platform view. - * @param onComplete Once the resize is completed, this is the handler to notify the size of the - * platform view buffer. + * @return The buffer size where the platform view pixels are written to. */ - void resize( - @NonNull PlatformViewResizeRequest request, @NonNull PlatformViewBufferResized onComplete); + PlatformViewBufferSize resize(@NonNull PlatformViewResizeRequest request); /** * The Flutter application would like to change the offset of an existing Android {@code View}. @@ -423,11 +418,6 @@ public PlatformViewBufferSize(int width, int height) { } } - /** Allows to notify when a platform view buffer has been resized. */ - public interface PlatformViewBufferResized { - void run(@Nullable PlatformViewBufferSize bufferSize); - } - /** 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/embedding/engine/systemchannels/TextInputChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java index 094786f6d825e..a48e073dc5f60 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); @@ -403,10 +401,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 7b1ca47ffa0e8..c35412eb2b9e5 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( @NonNull View view, @@ -105,7 +99,7 @@ public void show() { @Override public void hide() { - if (inputTarget.type == InputTarget.Type.PHYSICAL_DISPLAY_PLATFORM_VIEW) { + if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) { notifyViewExited(); } else { hideTextInput(mView); @@ -136,8 +130,8 @@ public void setClient( } @Override - public void setPlatformViewClient(int platformViewId, boolean usesVirtualDisplay) { - setPlatformViewTextInputClient(platformViewId, usesVirtualDisplay); + public void setPlatformViewClient(int platformViewId) { + setPlatformViewTextInputClient(platformViewId); } @Override @@ -182,36 +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.VIRTUAL_DISPLAY_PLATFORM_VIEW) { - isInputConnectionLocked = true; - } - } - - /** - * Unlocks the input connection. - * - *

See also: @{link lockPlatformViewInputConnection}. - */ - public void unlockPlatformViewInputConnection() { - if (inputTarget.type == InputTarget.Type.VIRTUAL_DISPLAY_PLATFORM_VIEW) { - isInputConnectionLocked = false; - } - } - /** * Detaches the text input plugin from the platform views controller. * @@ -295,21 +259,10 @@ public InputConnection createInputConnection( return null; } - if (inputTarget.type == InputTarget.Type.PHYSICAL_DISPLAY_PLATFORM_VIEW) { + if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) { return null; } - if (inputTarget.type == InputTarget.Type.VIRTUAL_DISPLAY_PLATFORM_VIEW) { - if (isInputConnectionLocked) { - return lastInputConnection; - } - lastInputConnection = - platformViewsController - .getPlatformViewById(inputTarget.id) - .onCreateInputConnection(outAttrs); - return lastInputConnection; - } - outAttrs.inputType = inputTypeFromTextInputType( configuration.inputType, @@ -364,9 +317,7 @@ public InputConnection getLastInputConnection() { * input connection. */ public void clearPlatformViewClient(int platformViewId) { - if ((inputTarget.type == InputTarget.Type.VIRTUAL_DISPLAY_PLATFORM_VIEW - || inputTarget.type == InputTarget.Type.PHYSICAL_DISPLAY_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); @@ -427,26 +378,13 @@ 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); } - 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 bug on API 28 devices where the - // platform view's request to show a keyboard was ignored. - mView.requestFocus(); - inputTarget = new InputTarget(InputTarget.Type.VIRTUAL_DISPLAY_PLATFORM_VIEW, platformViewId); - mImm.restartInput(mView); - mRestartInputPending = false; - } else { - inputTarget = - new InputTarget(InputTarget.Type.PHYSICAL_DISPLAY_PLATFORM_VIEW, platformViewId); - lastInputConnection = null; - } + private void setPlatformViewTextInputClient(int platformViewId) { + inputTarget = new InputTarget(InputTarget.Type.PLATFORM_VIEW, platformViewId); + lastInputConnection = null; } private static boolean composingChanged( @@ -537,29 +475,11 @@ public void inspect(double x, double y) { @VisibleForTesting void clearTextInputClient() { - if (inputTarget.type == InputTarget.Type.VIRTUAL_DISPLAY_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(); configuration = null; updateAutofillConfigurationIfNeeded(null); inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); - unlockPlatformViewInputConnection(); lastClientRect = null; } @@ -569,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 a platform view that is presented on a virtual display. - VIRTUAL_DISPLAY_PLATFORM_VIEW, - // InputConnection is managed by a platform view that is embedded in the activity's view - // hierarchy. This view hierarchy is displayed in a physical display within the aplication - // display area. - PHYSICAL_DISPLAY_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) { diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformView.java b/shell/platform/android/io/flutter/plugin/platform/PlatformView.java index d7b3cf52aba91..2c563ccac937b 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformView.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformView.java @@ -66,8 +66,11 @@ default void onFlutterViewDetached() {} * *

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. */ @SuppressLint("NewApi") + @Deprecated default void onInputConnectionLocked() {} /** @@ -75,7 +78,10 @@ default void onInputConnectionLocked() {} * *

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. */ @SuppressLint("NewApi") + @Deprecated default void onInputConnectionUnlocked() {} } diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewFactory.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewFactory.java index e21ee3897f555..936f9fabeca5b 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewFactory.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewFactory.java @@ -28,7 +28,7 @@ public PlatformViewFactory(@Nullable MessageCodec createArgsCodec) { * null, or no arguments were sent from the Flutter app. */ @NonNull - public abstract PlatformView create(Context context, int viewId, @Nullable Object args); + public abstract PlatformView create(@Nullable Context context, int viewId, @Nullable Object args); /** Returns the codec to be used for decoding the args parameter of {@link #create}. */ @Nullable diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java index f1f91ddaa3055..968dda33f1ee8 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java @@ -18,9 +18,6 @@ public interface PlatformViewsAccessibilityDelegate { @Nullable View getPlatformViewById(int viewId); - /** Returns true if the platform view uses virtual displays. */ - boolean usesVirtualDisplay(int id); - /** * 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 4f78a732d1fe1..8008da1a77b28 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -9,11 +9,9 @@ import android.annotation.TargetApi; import android.content.Context; -import android.content.MutableContextWrapper; import android.os.Build; import android.util.SparseArray; import android.view.MotionEvent; -import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -32,11 +30,9 @@ import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.systemchannels.PlatformViewsChannel; import io.flutter.plugin.editing.TextInputPlugin; -import io.flutter.util.ViewUtils; 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; @@ -50,19 +46,6 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelegate { private static final String TAG = "PlatformViewsController"; - // These view types allow out-of-band drawing commands that don't notify the Android view - // hierarchy. - // To support these cases, Flutter hosts the embedded view in a VirtualDisplay, - // and binds the VirtualDisplay to a GL texture that is then composed by the engine. - // However, there are a few issues with Virtual Displays. For example, they don't fully support - // accessibility due to https://github.com/flutter/flutter/issues/29717, - // and keyboard interactions may have non-deterministic behavior. - // Views that issue out-of-band drawing commands that aren't included in this array are - // required to call `View#invalidate()` to notify Flutter about the update. - // This isn't ideal, but given all the other limitations it's a reasonable tradeoff. - // Related issue: https://github.com/flutter/flutter/issues/103630 - private static Class[] VIEW_TYPES_REQUIRE_VIRTUAL_DISPLAY = {SurfaceView.class}; - private final PlatformViewRegistryImpl registry; private AndroidTouchProcessor androidTouchProcessor; @@ -85,17 +68,6 @@ 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 embedded 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 contextToEmbeddedView; - // The platform views. private final SparseArray platformViews; @@ -151,22 +123,21 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega private final PlatformViewsChannel.PlatformViewsHandler channelHandler = new PlatformViewsChannel.PlatformViewsHandler() { - @TargetApi(19) + @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`. - ensureValidAndroidVersion(19); + ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT); - final int viewId = request.viewId; if (!validateDirection(request.direction)) { throw new IllegalStateException( "Trying to create a view with unknown direction value: " + request.direction + "(view id: " - + viewId + + request.viewId + ")"); } @@ -181,13 +152,12 @@ public void createForPlatformViewLayer( createParams = factory.getCreateArgsCodec().decodeMessage(request.params); } - final PlatformView platformView = factory.create(context, viewId, createParams); + final PlatformView platformView = factory.create(context, request.viewId, createParams); platformView.getView().setLayoutDirection(request.direction); - platformViews.put(viewId, platformView); - Log.i(TAG, "Using hybrid composition for platform view: " + viewId); + platformViews.put(request.viewId, platformView); } - @TargetApi(20) + @TargetApi(Build.VERSION_CODES.M) @Override public long createForTextureLayer( @NonNull PlatformViewsChannel.PlatformViewCreationRequest request) { @@ -224,116 +194,45 @@ public long createForTextureLayer( createParams = viewFactory.getCreateArgsCodec().decodeMessage(request.params); } - // The virtual display controller will change the embedded view context. - final Context embeddedViewContext = new MutableContextWrapper(context); - final PlatformView platformView = - viewFactory.create(embeddedViewContext, viewId, createParams); + final PlatformView platformView = viewFactory.create(context, viewId, createParams); platformViews.put(viewId, platformView); - final View embeddedView = platformView.getView(); - if (embeddedView == null) { - throw new IllegalStateException( - "PlatformView#getView() returned null, but an Android view reference was expected."); - } else if (embeddedView.getParent() != null) { - throw new IllegalStateException( - "The Android view returned from PlatformView#getView() was already added to a parent view."); - } - - embeddedView.setLayoutDirection(request.direction); - - final int physicalWidth = toPhysicalPixels(request.logicalWidth); - final int physicalHeight = toPhysicalPixels(request.logicalHeight); - - // Case 1. Add the view to a virtual display if the embedded view contains any of the - // VIEW_TYPES_REQUIRE_VIRTUAL_DISPLAY view types. - // These views allow out-of-band graphics operations that aren't notified to the Android - // view hierarchy via callbacks such as ViewParent#onDescendantInvalidated(). - // The virtual display is wired up to a GL texture that is composed by the Flutter engine. - // Also, use virtual display if the API level is 20, 21 or 22 since the Case 2. requires - // at least API level 23. - final boolean shouldUseVirtualDisplay = - ViewUtils.hasChildViewOfType(embeddedView, VIEW_TYPES_REQUIRE_VIRTUAL_DISPLAY) - || Build.VERSION.SDK_INT < 23; - - if (!usesSoftwareRendering && shouldUseVirtualDisplay) { - Log.i(TAG, "Hosting view in a virtual display for platform view: " + viewId); - // API level 20 is required to use VirtualDisplay#setSurface. - ensureValidAndroidVersion(20); - - final TextureRegistry.SurfaceTextureEntry textureEntry = - textureRegistry.createSurfaceTexture(); - - final VirtualDisplayController vdController = - VirtualDisplayController.create( - context, - accessibilityEventsDelegate, - platformView, - 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); - contextToEmbeddedView.put(embeddedView.getContext(), embeddedView); - return textureEntry.id(); - } - - // Case 2. Attach the view to the Android view hierarchy and record their drawing - // operations, so they can be forwarded to a GL texture that is composed by the - // Flutter engine. - - // API level 23 is required to use Surface#lockHardwareCanvas(). - ensureValidAndroidVersion(23); - Log.i(TAG, "Hosting view in view hierarchy for platform view: " + viewId); - - PlatformViewWrapper viewWrapper; + PlatformViewWrapper wrapperView; long txId; if (usesSoftwareRendering) { - viewWrapper = new PlatformViewWrapper(context); + wrapperView = new PlatformViewWrapper(context); txId = -1; } else { final TextureRegistry.SurfaceTextureEntry textureEntry = textureRegistry.createSurfaceTexture(); - viewWrapper = new PlatformViewWrapper(context, textureEntry); + wrapperView = new PlatformViewWrapper(context, textureEntry); txId = textureEntry.id(); } - viewWrapper.setTouchProcessor(androidTouchProcessor); - viewWrapper.setBufferSize(physicalWidth, physicalHeight); + wrapperView.setTouchProcessor(androidTouchProcessor); - final FrameLayout.LayoutParams viewWrapperLayoutParams = + final int physicalWidth = toPhysicalPixels(request.logicalWidth); + final int physicalHeight = toPhysicalPixels(request.logicalHeight); + wrapperView.setBufferSize(physicalWidth, physicalHeight); + + final FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(physicalWidth, physicalHeight); - // Size and position the view wrapper. final int physicalTop = toPhysicalPixels(request.logicalTop); final int physicalLeft = toPhysicalPixels(request.logicalLeft); - viewWrapperLayoutParams.topMargin = physicalTop; - viewWrapperLayoutParams.leftMargin = physicalLeft; - viewWrapper.setLayoutParams(viewWrapperLayoutParams); + layoutParams.topMargin = physicalTop; + layoutParams.leftMargin = physicalLeft; + wrapperView.setLayoutParams(layoutParams); - // Size the embedded view. - // This isn't needed when the virtual display is used because the virtual display itself - // is sized. + final View embeddedView = platformView.getView(); + if (embeddedView == null) { + throw new IllegalStateException( + "PlatformView#getView() returned null, but an Android view reference was expected."); + } else if (embeddedView.getParent() != null) { + throw new IllegalStateException( + "The Android view returned from PlatformView#getView() was already added to a parent view."); + } embeddedView.setLayoutParams(new FrameLayout.LayoutParams(physicalWidth, physicalHeight)); + embeddedView.setLayoutDirection(request.direction); // Accessibility in the embedded view is initially disabled because if a Flutter app // disabled accessibility in the first frame, the embedding won't receive an update to @@ -345,12 +244,8 @@ public long createForTextureLayer( embeddedView.setImportantForAccessibility( View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - // Add the embedded view to the wrapper. - viewWrapper.addView(embeddedView); - - // Listen for focus changed in any subview, so the framework is notified when the platform - // view is focused. - viewWrapper.setOnDescendantFocusChangeListener( + wrapperView.addView(embeddedView); + wrapperView.setOnDescendantFocusChangeListener( (v, hasFocus) -> { if (hasFocus) { platformViewsChannel.invokeViewFocused(viewId); @@ -358,37 +253,19 @@ public long createForTextureLayer( textInputPlugin.clearPlatformViewClient(viewId); } }); - flutterView.addView(viewWrapper); - viewWrappers.append(viewId, viewWrapper); + flutterView.addView(wrapperView); + viewWrappers.append(viewId, wrapperView); return txId; } @Override public void dispose(int viewId) { final PlatformView platformView = platformViews.get(viewId); - if (platformView == null) { - Log.e(TAG, "Disposing unknown platform view with id: " + viewId); - return; - } - platformViews.remove(viewId); - - try { + if (platformView != null) { + platformViews.remove(viewId); platformView.dispose(); - } catch (RuntimeException exception) { - Log.e(TAG, "Disposing platform view threw an exception", exception); } - - if (usesVirtualDisplay(viewId)) { - final VirtualDisplayController vdController = vdControllers.get(viewId); - final View embeddedView = vdController.getView(); - if (embeddedView != null) { - contextToEmbeddedView.remove(embeddedView.getContext()); - } - vdControllers.remove(viewId); - return; - } - // The platform view is displayed using a TextureLayer and is inserted in the view - // hierarchy. + // The platform view is displayed using a TextureLayer. final PlatformViewWrapper viewWrapper = viewWrappers.get(viewId); if (viewWrapper != null) { viewWrapper.removeAllViews(); @@ -418,65 +295,33 @@ public void dispose(int viewId) { @Override public void offset(int viewId, double top, double left) { - if (usesVirtualDisplay(viewId)) { - // Virtual displays don't need an accessibility offset. - return; - } - // For platform views that use TextureView and are in the view hierarchy, set - // an offset to the wrapper view. - // This ensures that the accessibility highlights are drawn in the expected position on - // screen. - // This offset doesn't affect the position of the embeded view by itself since the GL - // texture is positioned by the Flutter engine, which knows where to position different - // types of layers. - final PlatformViewWrapper viewWrapper = viewWrappers.get(viewId); - if (viewWrapper == null) { + 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 FrameLayout.LayoutParams layoutParams = - (FrameLayout.LayoutParams) viewWrapper.getLayoutParams(); + (FrameLayout.LayoutParams) wrapper.getLayoutParams(); layoutParams.topMargin = physicalTop; layoutParams.leftMargin = physicalLeft; - viewWrapper.setLayoutParams(layoutParams); + wrapper.setLayoutParams(layoutParams); } @Override - public void resize( - @NonNull PlatformViewsChannel.PlatformViewResizeRequest request, - @NonNull PlatformViewsChannel.PlatformViewBufferResized onComplete) { - final int physicalWidth = toPhysicalPixels(request.newLogicalWidth); - final int physicalHeight = toPhysicalPixels(request.newLogicalHeight); + public PlatformViewsChannel.PlatformViewBufferSize resize( + @NonNull PlatformViewsChannel.PlatformViewResizeRequest request) { final int viewId = request.viewId; - - if (usesVirtualDisplay(viewId)) { - final VirtualDisplayController vdController = vdControllers.get(viewId); - // 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( - new PlatformViewsChannel.PlatformViewBufferSize( - toLogicalPixels(vdController.getBufferWidth()), - toLogicalPixels(vdController.getBufferHeight()))); - }); - return; - } - final PlatformView platformView = platformViews.get(viewId); - final PlatformViewWrapper viewWrapper = viewWrappers.get(viewId); - if (platformView == null || viewWrapper == null) { + final PlatformViewWrapper view = viewWrappers.get(viewId); + if (platformView == null || view == null) { Log.e(TAG, "Resizing unknown platform view with id: " + viewId); - return; + return null; } + final int newWidth = toPhysicalPixels(request.newLogicalWidth); + final int newHeight = toPhysicalPixels(request.newLogicalHeight); + // 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 @@ -485,56 +330,46 @@ public void 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 (physicalWidth > viewWrapper.getBufferWidth() - || physicalHeight > viewWrapper.getBufferHeight()) { - viewWrapper.setBufferSize(physicalWidth, physicalHeight); + if (newWidth > view.getBufferWidth() || newHeight > view.getBufferHeight()) { + view.setBufferSize(newWidth, newHeight); } - final ViewGroup.LayoutParams viewWrapperLayoutParams = viewWrapper.getLayoutParams(); - viewWrapperLayoutParams.width = physicalWidth; - viewWrapperLayoutParams.height = physicalHeight; - viewWrapper.setLayoutParams(viewWrapperLayoutParams); + final ViewGroup.LayoutParams viewWrapperLayoutParams = view.getLayoutParams(); + viewWrapperLayoutParams.width = newWidth; + viewWrapperLayoutParams.height = newHeight; + view.setLayoutParams(viewWrapperLayoutParams); final View embeddedView = platformView.getView(); if (embeddedView != null) { final ViewGroup.LayoutParams embeddedViewLayoutParams = embeddedView.getLayoutParams(); - embeddedViewLayoutParams.width = physicalWidth; - embeddedViewLayoutParams.height = physicalHeight; + embeddedViewLayoutParams.width = newWidth; + embeddedViewLayoutParams.height = newHeight; embeddedView.setLayoutParams(embeddedViewLayoutParams); } - onComplete.run( - new PlatformViewsChannel.PlatformViewBufferSize( - toLogicalPixels(viewWrapper.getBufferWidth()), - toLogicalPixels(viewWrapper.getBufferHeight()))); + return new PlatformViewsChannel.PlatformViewBufferSize( + toLogicalPixels(view.getBufferWidth()), toLogicalPixels(view.getBufferHeight())); } @Override public void onTouch(@NonNull PlatformViewsChannel.PlatformViewTouch touch) { final int viewId = touch.viewId; - final float density = context.getResources().getDisplayMetrics().density; - - if (usesVirtualDisplay(viewId)) { - final VirtualDisplayController vdController = vdControllers.get(viewId); - final MotionEvent event = toMotionEvent(density, touch, true); - vdController.dispatchTouchEvent(event); - return; - } - 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 = platformView.getView(); if (view == null) { Log.e(TAG, "Sending touch to a null view with id: " + viewId); return; } - final MotionEvent event = toMotionEvent(density, touch, false); view.dispatchTouchEvent(event); } - @TargetApi(17) + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override public void setDirection(int viewId, int direction) { if (!validateDirection(direction)) { @@ -545,47 +380,33 @@ public void setDirection(int viewId, int direction) { + viewId + ")"); } - - View embeddedView; - - if (usesVirtualDisplay(viewId)) { - final VirtualDisplayController controller = vdControllers.get(viewId); - embeddedView = controller.getView(); - } else { - final PlatformView platformView = platformViews.get(viewId); - if (platformView == null) { - Log.e(TAG, "Setting direction to an unknown view with id: " + viewId); - return; - } - embeddedView = platformView.getView(); + final PlatformView platformView = platformViews.get(viewId); + if (platformView == null) { + Log.e(TAG, "Setting direction to an unknown view with id: " + viewId); + return; } - if (embeddedView == null) { + ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT_WATCH); + final View view = platformView.getView(); + if (view == null) { Log.e(TAG, "Setting direction to a null view with id: " + viewId); return; } - embeddedView.setLayoutDirection(direction); + view.setLayoutDirection(direction); } @Override public void clearFocus(int viewId) { - View embeddedView; - - if (usesVirtualDisplay(viewId)) { - final VirtualDisplayController controller = vdControllers.get(viewId); - embeddedView = controller.getView(); - } else { - final PlatformView platformView = platformViews.get(viewId); - if (platformView == null) { - Log.e(TAG, "Clearing focus on an unknown view with id: " + viewId); - return; - } - embeddedView = platformView.getView(); + final PlatformView platformView = platformViews.get(viewId); + if (platformView == null) { + Log.e(TAG, "Clearing focus on an unknown view with id: " + viewId); + return; } - if (embeddedView == null) { + final View view = platformView.getView(); + if (view == null) { Log.e(TAG, "Clearing focus on a null view with id: " + viewId); return; } - embeddedView.clearFocus(); + view.clearFocus(); } private void ensureValidAndroidVersion(int minSdkVersion) { @@ -605,8 +426,7 @@ public void synchronizeToNativeViewHierarchy(boolean yes) { }; @VisibleForTesting - public MotionEvent toMotionEvent( - float density, PlatformViewsChannel.PlatformViewTouch touch, boolean usingVirtualDiplay) { + public MotionEvent toMotionEvent(float density, PlatformViewsChannel.PlatformViewTouch touch) { MotionEventTracker.MotionEventId motionEventId = MotionEventTracker.MotionEventId.from(touch.motionEventId); MotionEvent trackedEvent = motionEventTracker.pop(motionEventId); @@ -622,7 +442,7 @@ public MotionEvent toMotionEvent( parsePointerCoordsList(touch.rawPointerCoords, density) .toArray(new PointerCoords[touch.pointerCount]); - if (!usingVirtualDiplay && trackedEvent != null) { + if (trackedEvent != null) { return MotionEvent.obtain( trackedEvent.getDownTime(), trackedEvent.getEventTime(), @@ -661,9 +481,7 @@ public MotionEvent toMotionEvent( public PlatformViewsController() { registry = new PlatformViewRegistryImpl(); - vdControllers = new HashMap<>(); accessibilityEventsDelegate = new AccessibilityEventsDelegate(); - contextToEmbeddedView = new HashMap<>(); overlayLayerViews = new SparseArray<>(); currentFrameUsedOverlayLayerIds = new HashSet<>(); currentFrameUsedPlatformViewIds = new HashSet<>(); @@ -776,7 +594,6 @@ public void detachFromView() { final FlutterMutatorView view = platformViewParent.valueAt(index); flutterView.removeView(view); } - destroyOverlaySurfaces(); removeOverlaySurfaces(); flutterView = null; @@ -817,29 +634,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 (!contextToEmbeddedView.containsKey(view.getContext())) { - return false; - } - View platformView = contextToEmbeddedView.get(view.getContext()); - if (platformView == view) { - return true; - } - return platformView.checkInputConnectionProxy(view); - } - public PlatformViewRegistry getRegistry() { return registry; } @@ -857,21 +651,16 @@ public void onAttachedToJNI() { * PlatformViewsController} detaches from JNI. */ public void onDetachedFromJNI() { - diposeAllViews(); + flushAllViews(); } public void onPreEngineRestart() { - diposeAllViews(); + flushAllViews(); } @Override @Nullable public View getPlatformViewById(int viewId) { - if (usesVirtualDisplay(viewId)) { - final VirtualDisplayController controller = vdControllers.get(viewId); - return controller.getView(); - } - final PlatformView platformView = platformViews.get(viewId); if (platformView == null) { return null; @@ -879,27 +668,6 @@ public View getPlatformViewById(int viewId) { return platformView.getView(); } - @Override - public boolean usesVirtualDisplay(int 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(); - } - private static boolean validateDirection(int direction) { return direction == View.LAYOUT_DIRECTION_LTR || direction == View.LAYOUT_DIRECTION_RTL; } @@ -961,11 +729,9 @@ private int toLogicalPixels(double physicalPixels) { return (int) Math.round(physicalPixels / getDisplayDensity()); } - private void diposeAllViews() { + private void flushAllViews() { while (platformViews.size() > 0) { - final int viewId = platformViews.keyAt(0); - // Dispose deletes the entry from platformViews and clears associated resources. - channelHandler.dispose(viewId); + 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 188875d89d5af..0000000000000 --- a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java +++ /dev/null @@ -1,482 +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.content.MutableContextWrapper; -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; - } - - // 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, - PlatformView view, - AccessibilityEventsDelegate accessibilityEventsDelegate, - int viewId, - Object createParams, - OnFocusChangeListener focusChangeListener) { - super(new ImmContext(outerContext), display); - this.accessibilityEventsDelegate = accessibilityEventsDelegate; - this.viewId = viewId; - this.createParams = createParams; - this.focusChangeListener = focusChangeListener; - this.outerContext = outerContext; - state = new PresentationState(); - state.platformView = view; - 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; - 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 baseContext = - new PresentationContext(getContext(), state.windowManagerHandler, outerContext); - - View embeddedView = state.platformView.getView(); - if (embeddedView.getContext() instanceof MutableContextWrapper) { - MutableContextWrapper currentContext = (MutableContextWrapper) embeddedView.getContext(); - currentContext.setBaseContext(baseContext); - } else { - throw new IllegalStateException( - "Unexpected platform view context. " - + "When constructing a platform view in the factory, use the context from PlatformViewFactory#create, view id: " - + viewId); - } - - 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 e049d5cccbd44..0000000000000 --- a/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java +++ /dev/null @@ -1,305 +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.util.DisplayMetrics; -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.Log; -import io.flutter.view.TextureRegistry; -import java.util.Locale; - -@TargetApi(20) -class VirtualDisplayController { - private static String TAG = "VirtualDisplayController"; - - public static VirtualDisplayController create( - Context context, - AccessibilityEventsDelegate accessibilityEventsDelegate, - PlatformView view, - TextureRegistry.SurfaceTextureEntry textureEntry, - int width, - int height, - int viewId, - Object createParams, - OnFocusChangeListener focusChangeListener) { - - int selectedWidth = width; - int selectedHeight = height; - - DisplayMetrics metrics = context.getResources().getDisplayMetrics(); - if (selectedWidth == 0 || selectedHeight == 0) { - return null; - } - // Prevent https://github.com/flutter/flutter/issues/2897. - if (selectedWidth > metrics.widthPixels || selectedHeight > metrics.heightPixels) { - float aspectRatio = (float) selectedWidth / (float) selectedHeight; - int maybeWidth = (int) (metrics.heightPixels * aspectRatio); - int maybeHeight = (int) (metrics.widthPixels / aspectRatio); - - if (maybeHeight <= metrics.heightPixels) { - selectedWidth = metrics.widthPixels; - selectedHeight = maybeHeight; - } else if (maybeWidth <= metrics.widthPixels) { - selectedHeight = metrics.heightPixels; - selectedWidth = maybeWidth; - } else { - return null; - } - - String message = - String.format( - Locale.US, - "Resizing virtual display of size: [%d, %d] to size [%d, %d] " - + "since it's larger than the device display size [%d, %d].", - width, - height, - selectedWidth, - selectedHeight, - metrics.widthPixels, - metrics.heightPixels); - Log.w(TAG, message); - } - - textureEntry.surfaceTexture().setDefaultBufferSize(selectedWidth, selectedHeight); - 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", selectedWidth, selectedHeight, densityDpi, surface, 0); - - if (virtualDisplay == null) { - return null; - } - VirtualDisplayController controller = - new VirtualDisplayController( - context, - accessibilityEventsDelegate, - virtualDisplay, - view, - surface, - textureEntry, - focusChangeListener, - viewId, - createParams); - controller.bufferWidth = selectedWidth; - controller.bufferHeight = selectedHeight; - return controller; - } - - @VisibleForTesting SingleViewPresentation presentation; - - private final Context context; - private final AccessibilityEventsDelegate accessibilityEventsDelegate; - private final int densityDpi; - private final TextureRegistry.SurfaceTextureEntry textureEntry; - private final OnFocusChangeListener focusChangeListener; - private final Surface surface; - - private VirtualDisplay virtualDisplay; - private int bufferWidth; - private int bufferHeight; - - private VirtualDisplayController( - Context context, - AccessibilityEventsDelegate accessibilityEventsDelegate, - VirtualDisplay virtualDisplay, - PlatformView view, - 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(), - view, - accessibilityEventsDelegate, - viewId, - createParams, - focusChangeListener); - presentation.show(); - } - - public int getBufferWidth() { - return bufferWidth; - } - - public int getBufferHeight() { - return bufferHeight; - } - - 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(); - - bufferWidth = width; - bufferHeight = height; - 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() { - // Fix rare crash on HuaWei device described in: https://github.com/flutter/engine/pull/9192 - presentation.cancel(); - presentation.detachState(); - 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); - } - - 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 5baaca2ba549d..95ea8fa855437 100644 --- a/shell/platform/android/io/flutter/util/ViewUtils.java +++ b/shell/platform/android/io/flutter/util/ViewUtils.java @@ -10,7 +10,6 @@ import android.os.Build; import android.view.View; import android.view.ViewGroup; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; public final class ViewUtils { @@ -57,58 +56,16 @@ public static int generateViewId(int fallbackId) { * @return True if the current view or any descendant view has focus. */ public static boolean childHasFocus(@Nullable View root) { - return traverseHierarchy(root, (View view) -> view.hasFocus()); - } - - /** - * Returns true if the root or any child view is an instance of the given types. - * - * @param root The root view. - * @param viewTypes The types of views. - * @return true if any child view is an instance of any of the given types. - */ - public static boolean hasChildViewOfType(@Nullable View root, Class[] viewTypes) { - return traverseHierarchy( - root, - (View view) -> { - for (int i = 0; i < viewTypes.length; i++) { - final Class viewType = viewTypes[i]; - if (viewType.isInstance(view)) { - return true; - } - } - return false; - }); - } - - /** Allows to visit a view. */ - public interface ViewVisitor { - boolean run(@NonNull View view); - } - - /** - * Traverses the view hierarchy in pre-order and runs the visitor for each child view including - * the root view. - * - *

If the visitor returns true, the traversal stops, and the method returns true. - * - *

If the visitor returns false, the traversal continues until all views are visited. - * - * @param root The root view. - * @param visitor The visitor. - * @return true if the visitor returned true for a given view. - */ - public static boolean traverseHierarchy(@Nullable View root, @NonNull ViewVisitor visitor) { if (root == null) { return false; } - if (visitor.run(root)) { + if (root.hasFocus()) { return true; } if (root instanceof ViewGroup) { final ViewGroup viewGroup = (ViewGroup) root; for (int idx = 0; idx < viewGroup.getChildCount(); idx++) { - if (traverseHierarchy(viewGroup.getChildAt(idx), visitor)) { + if (childHasFocus(viewGroup.getChildAt(idx))) { return true; } } diff --git a/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/shell/platform/android/io/flutter/view/AccessibilityBridge.java index 28b09507214b3..48f1ab700cc8a 100644 --- a/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -573,26 +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) { - if (platformViewsAccessibilityDelegate.usesVirtualDisplay(semanticsNode.platformViewId)) { - View embeddedView = - platformViewsAccessibilityDelegate.getPlatformViewById(semanticsNode.platformViewId); - if (embeddedView == null) { - return null; - } - 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 @@ -905,19 +885,12 @@ && shouldSetCollectionInfo(semanticsNode)) { View embeddedView = platformViewsAccessibilityDelegate.getPlatformViewById(child.platformViewId); - // Add the embedded view as a child of the current accessibility node if it's not - // using a virtual display. - // - // 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; - } + // Add the embedded view as a child of the current accessibility node if it's using + // hybrid composition. + result.addChild(embeddedView); + } else { + result.addChild(rootAccessibilityView, child.id); } - result.addChild(rootAccessibilityView, child.id); } return result; } @@ -1549,8 +1522,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) { diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index eb498a2335e96..15245de3ef7db 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; @@ -422,14 +421,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/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java index d3fb9a8659efe..d80e59186998b 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -8,7 +8,6 @@ import static org.robolectric.Shadows.shadowOf; import android.content.Context; -import android.content.MutableContextWrapper; import android.content.res.AssetManager; import android.graphics.SurfaceTexture; import android.util.SparseArray; @@ -19,7 +18,6 @@ import android.view.View; import android.view.ViewParent; import android.widget.FrameLayout; -import android.widget.FrameLayout.LayoutParams; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.android.FlutterImageView; @@ -59,119 +57,6 @@ @RunWith(AndroidJUnit4.class) public class PlatformViewsControllerTest { - @Ignore - @Test - public void itNotifiesVirtualDisplayControllersOfViewAttachmentAndDetachment() { - // Setup test structure. - FlutterView fakeFlutterView = new FlutterView(ApplicationProvider.getApplicationContext()); - - // 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(ApplicationProvider.getApplicationContext()); - - // 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(); - 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() { @@ -211,8 +96,7 @@ public void itUsesActionEventTypeFromMotionEventForHybridPlatformViews() { motionEventId.getId()); MotionEvent resolvedEvent = - platformViewsController.toMotionEvent( - /*density=*/ 1, frameWorkTouch, /*usingVirtualDisplay=*/ false); + platformViewsController.toMotionEvent(/*density=*/ 1, frameWorkTouch); assertNotEquals(resolvedEvent.getAction(), frameWorkTouch.action); assertEquals(resolvedEvent.getAction(), original.getAction()); @@ -411,68 +295,6 @@ public void createHybridPlatformViewMessage__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); - - SurfaceView pv = mock(SurfaceView.class); - when(pv.getContext()).thenReturn(mock(MutableContextWrapper.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.contextToEmbeddedView.isEmpty()); - platformViewsController.onDetachedFromJNI(); - assertTrue(platformViewsController.contextToEmbeddedView.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); - - SurfaceView pv = mock(SurfaceView.class); - when(pv.getContext()).thenReturn(mock(MutableContextWrapper.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.contextToEmbeddedView.isEmpty()); - platformViewsController.onDetachedFromJNI(); - assertTrue(platformViewsController.contextToEmbeddedView.isEmpty()); - } - @Test @Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class}) public void createPlatformViewMessage__throwsIfViewHasParent() { @@ -988,13 +810,6 @@ public void destroyOverlaySurfaces__doesNotRemoveOverlayView() { verify(flutterView, never()).removeView(overlayImageView); } - @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() { 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 2cc177fd86648..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 androidx.test.ext.junit.runners.AndroidJUnit4; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; - -@Config(manifest = Config.NONE) -@RunWith(AndroidJUnit4.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 324331d9d1817..86c714c0f3554 100644 --- a/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java +++ b/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java @@ -1527,7 +1527,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); @@ -1565,7 +1564,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); @@ -1600,7 +1598,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); @@ -1615,34 +1612,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); - when(accessibilityDelegate.usesVirtualDisplay(1)).thenReturn(true); - - accessibilityBridge.createAccessibilityNodeInfo(0); - verify(accessibilityViewEmbedder).getRootNode(eq(embeddedView), eq(0), any(Rect.class)); - } - @Test public void releaseDropsChannelMessageHandler() { AccessibilityChannel mockChannel = mock(AccessibilityChannel.class); diff --git a/testing/android_systrace_test.py b/testing/android_systrace_test.py index e6c039db5ac59..2367df9ae6138 100755 --- a/testing/android_systrace_test.py +++ b/testing/android_systrace_test.py @@ -127,7 +127,7 @@ def main(): dest='activity_name', action='store', help='The activity to launch as it appears in AndroidManifest.xml, ' - 'e.g. .PlatformViewsActivity' + 'e.g. .TextPlatformViewActivity' ) parser.add_argument( '--adb-path', diff --git a/testing/run_tests.py b/testing/run_tests.py index c87b2f579dc3e..8afe20956aa8a 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -604,7 +604,7 @@ def RunAndroidTests(android_variant='android_debug_unopt', adb_path=None): RunCmd([ systrace_test, '--adb-path', adb_path, '--apk-path', scenario_apk, '--package-name', 'dev.flutter.scenarios', '--activity-name', - '.PlatformViewsActivity' + '.TextPlatformViewActivity' ]) diff --git a/testing/scenario_app/android/BUILD.gn b/testing/scenario_app/android/BUILD.gn index 9e3b401f61882..67e53aab8b795 100644 --- a/testing/scenario_app/android/BUILD.gn +++ b/testing/scenario_app/android/BUILD.gn @@ -13,19 +13,16 @@ _android_sources = [ "app/src/androidTest/java/dev/flutter/scenariosui/MemoryLeakTests.java", "app/src/androidTest/java/dev/flutter/scenariosui/PlatformTextureUiTests.java", "app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewUiTests.java", - "app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithSurfaceViewUiTest.java", - "app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithTextureViewUiTest.java", "app/src/androidTest/java/dev/flutter/scenariosui/ScreenshotUtil.java", "app/src/androidTest/java/dev/flutter/scenariosui/SpawnEngineTests.java", "app/src/main/AndroidManifest.xml", - "app/src/main/java/dev/flutter/scenarios/PlatformViewsActivity.java", "app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java", "app/src/main/java/dev/flutter/scenarios/StrictModeFlutterActivity.java", - "app/src/main/java/dev/flutter/scenarios/SurfacePlatformViewFactory.java", "app/src/main/java/dev/flutter/scenarios/TestActivity.java", "app/src/main/java/dev/flutter/scenarios/TestableFlutterActivity.java", + "app/src/main/java/dev/flutter/scenarios/TextPlatformView.java", + "app/src/main/java/dev/flutter/scenarios/TextPlatformViewActivity.java", "app/src/main/java/dev/flutter/scenarios/TextPlatformViewFactory.java", - "app/src/main/java/dev/flutter/scenarios/TexturePlatformViewFactory.java", "build.gradle", ] diff --git a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/MemoryLeakTests.java b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/MemoryLeakTests.java index 7fbf4bdb9b218..52f270ad65cd0 100644 --- a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/MemoryLeakTests.java +++ b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/MemoryLeakTests.java @@ -9,7 +9,7 @@ import androidx.test.filters.LargeTest; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; -import dev.flutter.scenarios.PlatformViewsActivity; +import dev.flutter.scenarios.TextPlatformViewActivity; import leakcanary.FailTestOnLeak; import org.junit.Rule; import org.junit.Test; @@ -19,9 +19,9 @@ @LargeTest public class MemoryLeakTests { @Rule @NonNull - public ActivityTestRule activityRule = + public ActivityTestRule activityRule = new ActivityTestRule<>( - PlatformViewsActivity.class, /*initialTouchMode=*/ false, /*launchActivity=*/ false); + TextPlatformViewActivity.class, /*initialTouchMode=*/ false, /*launchActivity=*/ false); @Test @FailTestOnLeak @@ -29,7 +29,6 @@ public void platformViewHybridComposition_launchActivityFinishAndLaunchAgain() t Intent intent = new Intent(Intent.ACTION_MAIN); intent.putExtra("scenario_name", "platform_view"); intent.putExtra("use_android_view", true); - intent.putExtra("view_type", PlatformViewsActivity.TEXT_VIEW_PV); activityRule.launchActivity(intent); } diff --git a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformTextureUiTests.java b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformTextureUiTests.java index a4aa061f2a82c..fa0747b9dccb0 100644 --- a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformTextureUiTests.java +++ b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformTextureUiTests.java @@ -5,12 +5,11 @@ package dev.flutter.scenariosui; import android.content.Intent; -import android.content.pm.ActivityInfo; import androidx.annotation.NonNull; import androidx.test.filters.LargeTest; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; -import dev.flutter.scenarios.PlatformViewsActivity; +import dev.flutter.scenarios.TextPlatformViewActivity; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -22,33 +21,29 @@ public class PlatformTextureUiTests { Intent intent; @Rule @NonNull - public ActivityTestRule activityRule = + public ActivityTestRule activityRule = new ActivityTestRule<>( - PlatformViewsActivity.class, /*initialTouchMode=*/ false, /*launchActivity=*/ false); - - private static String goldName(String suffix) { - return "PlatformTextureUiTests_" + suffix; - } + TextPlatformViewActivity.class, /*initialTouchMode=*/ false, /*launchActivity=*/ false); @Before public void setUp() { intent = new Intent(Intent.ACTION_MAIN); // Render a texture. intent.putExtra("use_android_view", false); - intent.putExtra("view_type", PlatformViewsActivity.TEXT_VIEW_PV); } @Test public void testPlatformView() throws Exception { intent.putExtra("scenario_name", "platform_view"); - ScreenshotUtil.capture(activityRule.launchActivity(intent), goldName("testPlatformView")); + ScreenshotUtil.capture( + activityRule.launchActivity(intent), "PlatformTextureUiTests_testPlatformView"); } @Test public void testPlatformViewMultiple() throws Exception { intent.putExtra("scenario_name", "platform_view_multiple"); ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewMultiple")); + activityRule.launchActivity(intent), "PlatformTextureUiTests_testPlatformViewMultiple"); } @Test @@ -56,64 +51,65 @@ public void testPlatformViewMultipleBackgroundForeground() throws Exception { intent.putExtra("scenario_name", "platform_view_multiple_background_foreground"); ScreenshotUtil.capture( activityRule.launchActivity(intent), - goldName("testPlatformViewMultipleBackgroundForeground")); + "PlatformTextureUiTests_testPlatformViewMultipleBackgroundForeground"); } @Test public void testPlatformViewCliprect() throws Exception { intent.putExtra("scenario_name", "platform_view_cliprect"); ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewCliprect")); + activityRule.launchActivity(intent), "PlatformTextureUiTests_testPlatformViewCliprect"); } @Test public void testPlatformViewCliprrect() throws Exception { intent.putExtra("scenario_name", "platform_view_cliprrect"); ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewCliprrect")); + activityRule.launchActivity(intent), "PlatformTextureUiTests_testPlatformViewCliprrect"); } @Test public void testPlatformViewClippath() throws Exception { intent.putExtra("scenario_name", "platform_view_clippath"); ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewClippath")); + activityRule.launchActivity(intent), "PlatformTextureUiTests_testPlatformViewClippath"); } @Test public void testPlatformViewTransform() throws Exception { intent.putExtra("scenario_name", "platform_view_transform"); ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewTransform")); + activityRule.launchActivity(intent), "PlatformTextureUiTests_testPlatformViewTransform"); } @Test public void testPlatformViewOpacity() throws Exception { intent.putExtra("scenario_name", "platform_view_opacity"); ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewOpacity")); + activityRule.launchActivity(intent), "PlatformTextureUiTests_testPlatformViewOpacity"); } @Test public void testPlatformViewRotate() throws Exception { intent.putExtra("scenario_name", "platform_view_rotate"); - PlatformViewsActivity activity = activityRule.launchActivity(intent); - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - ScreenshotUtil.capture(activity, goldName("testPlatformViewRotate")); + ScreenshotUtil.capture( + activityRule.launchActivity(intent), "PlatformTextureUiTests_testPlatformViewRotate"); } @Test public void testPlatformViewMultipleWithoutOverlays() throws Exception { intent.putExtra("scenario_name", "platform_view_multiple_without_overlays"); ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewMultipleWithoutOverlays")); + activityRule.launchActivity(intent), + "PlatformTextureUiTests_testPlatformViewMultipleWithoutOverlays"); } @Test public void testPlatformViewTwoIntersectingOverlays() throws Exception { intent.putExtra("scenario_name", "platform_view_two_intersecting_overlays"); ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewTwoIntersectingOverlays")); + activityRule.launchActivity(intent), + "PlatformTextureUiTests_testPlatformViewTwoIntersectingOverlays"); } @Test @@ -121,6 +117,6 @@ public void testPlatformViewWithoutOverlayIntersection() throws Exception { intent.putExtra("scenario_name", "platform_view_no_overlay_intersection"); ScreenshotUtil.capture( activityRule.launchActivity(intent), - goldName("testPlatformViewWithoutOverlayIntersection")); + "PlatformTextureUiTests_testPlatformViewWithoutOverlayIntersection"); } } diff --git a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewUiTests.java b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewUiTests.java index 50dc5dfef600b..c9ef50d18cede 100644 --- a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewUiTests.java +++ b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewUiTests.java @@ -5,12 +5,11 @@ package dev.flutter.scenariosui; import android.content.Intent; -import android.content.pm.ActivityInfo; import androidx.annotation.NonNull; import androidx.test.filters.LargeTest; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; -import dev.flutter.scenarios.PlatformViewsActivity; +import dev.flutter.scenarios.TextPlatformViewActivity; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -22,33 +21,29 @@ public class PlatformViewUiTests { Intent intent; @Rule @NonNull - public ActivityTestRule activityRule = + public ActivityTestRule activityRule = new ActivityTestRule<>( - PlatformViewsActivity.class, /*initialTouchMode=*/ false, /*launchActivity=*/ false); - - private static String goldName(String suffix) { - return "PlatformViewUiTests_" + suffix; - } + TextPlatformViewActivity.class, /*initialTouchMode=*/ false, /*launchActivity=*/ false); @Before public void setUp() { intent = new Intent(Intent.ACTION_MAIN); // Render a native android view. intent.putExtra("use_android_view", true); - intent.putExtra("view_type", PlatformViewsActivity.TEXT_VIEW_PV); } @Test public void testPlatformView() throws Exception { intent.putExtra("scenario_name", "platform_view"); - ScreenshotUtil.capture(activityRule.launchActivity(intent), goldName("testPlatformView")); + ScreenshotUtil.capture( + activityRule.launchActivity(intent), "PlatformViewUiTests_testPlatformView"); } @Test public void testPlatformViewMultiple() throws Exception { intent.putExtra("scenario_name", "platform_view_multiple"); ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewMultiple")); + activityRule.launchActivity(intent), "PlatformViewUiTests_testPlatformViewMultiple"); } @Test @@ -56,64 +51,65 @@ public void testPlatformViewMultipleBackgroundForeground() throws Exception { intent.putExtra("scenario_name", "platform_view_multiple_background_foreground"); ScreenshotUtil.capture( activityRule.launchActivity(intent), - goldName("testPlatformViewMultipleBackgroundForeground")); + "PlatformViewUiTests_testPlatformViewMultipleBackgroundForeground"); } @Test public void testPlatformViewCliprect() throws Exception { intent.putExtra("scenario_name", "platform_view_cliprect"); ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewCliprect")); + activityRule.launchActivity(intent), "PlatformViewUiTests_testPlatformViewCliprect"); } @Test public void testPlatformViewCliprrect() throws Exception { intent.putExtra("scenario_name", "platform_view_cliprrect"); ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewCliprrect")); + activityRule.launchActivity(intent), "PlatformViewUiTests_testPlatformViewCliprrect"); } @Test public void testPlatformViewClippath() throws Exception { intent.putExtra("scenario_name", "platform_view_clippath"); ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewClippath")); + activityRule.launchActivity(intent), "PlatformViewUiTests_testPlatformViewClippath"); } @Test public void testPlatformViewTransform() throws Exception { intent.putExtra("scenario_name", "platform_view_transform"); ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewTransform")); + activityRule.launchActivity(intent), "PlatformViewUiTests_testPlatformViewTransform"); } @Test public void testPlatformViewOpacity() throws Exception { intent.putExtra("scenario_name", "platform_view_opacity"); ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewOpacity")); + activityRule.launchActivity(intent), "PlatformViewUiTests_testPlatformViewOpacity"); } @Test public void testPlatformViewRotate() throws Exception { intent.putExtra("scenario_name", "platform_view_rotate"); - PlatformViewsActivity activity = activityRule.launchActivity(intent); - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - ScreenshotUtil.capture(activity, goldName("testPlatformViewRotate")); + ScreenshotUtil.capture( + activityRule.launchActivity(intent), "PlatformViewUiTests_testPlatformViewRotate"); } @Test public void testPlatformViewMultipleWithoutOverlays() throws Exception { intent.putExtra("scenario_name", "platform_view_multiple_without_overlays"); ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewMultipleWithoutOverlays")); + activityRule.launchActivity(intent), + "PlatformViewUiTests_testPlatformViewMultipleWithoutOverlays"); } @Test public void testPlatformViewTwoIntersectingOverlays() throws Exception { intent.putExtra("scenario_name", "platform_view_two_intersecting_overlays"); ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewTwoIntersectingOverlays")); + activityRule.launchActivity(intent), + "PlatformViewUiTests_testPlatformViewTwoIntersectingOverlays"); } @Test @@ -121,6 +117,6 @@ public void testPlatformViewWithoutOverlayIntersection() throws Exception { intent.putExtra("scenario_name", "platform_view_no_overlay_intersection"); ScreenshotUtil.capture( activityRule.launchActivity(intent), - goldName("testPlatformViewWithoutOverlayIntersection")); + "PlatformViewUiTests_testPlatformViewWithoutOverlayIntersection"); } } diff --git a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithSurfaceViewUiTest.java b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithSurfaceViewUiTest.java deleted file mode 100644 index b1fcf35d7d55d..0000000000000 --- a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithSurfaceViewUiTest.java +++ /dev/null @@ -1,134 +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 dev.flutter.scenariosui; - -import android.content.Intent; -import android.content.pm.ActivityInfo; -import androidx.annotation.NonNull; -import androidx.test.filters.LargeTest; -import androidx.test.rule.ActivityTestRule; -import androidx.test.runner.AndroidJUnit4; -import dev.flutter.scenarios.PlatformViewsActivity; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@LargeTest -public class PlatformViewWithSurfaceViewUiTest { - Intent intent; - - @Rule @NonNull - public ActivityTestRule activityRule = - new ActivityTestRule<>( - PlatformViewsActivity.class, /*initialTouchMode=*/ false, /*launchActivity=*/ false); - - private static String goldName(String suffix) { - return "PlatformViewWithSurfaceViewUiTest_" + suffix; - } - - @Before - public void setUp() { - intent = new Intent(Intent.ACTION_MAIN); - // Render a texture. - intent.putExtra("use_android_view", false); - intent.putExtra("view_type", PlatformViewsActivity.SURFACE_VIEW_PV); - } - - @Test - public void testPlatformView() throws Exception { - intent.putExtra("scenario_name", "platform_view"); - ScreenshotUtil.capture(activityRule.launchActivity(intent), goldName("testPlatformView")); - } - - @Test - public void testPlatformViewMultiple() throws Exception { - intent.putExtra("scenario_name", "platform_view_multiple"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewMultiple")); - } - - @Test - public void testPlatformViewMultipleBackgroundForeground() throws Exception { - intent.putExtra("scenario_name", "platform_view_multiple_background_foreground"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), - goldName("testPlatformViewMultipleBackgroundForeground")); - } - - @Test - public void testPlatformViewCliprect() throws Exception { - intent.putExtra("scenario_name", "platform_view_cliprect"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewCliprect")); - } - - @Test - public void testPlatformViewCliprrect() throws Exception { - intent.putExtra("scenario_name", "platform_view_cliprrect"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewCliprrect")); - } - - @Test - public void testPlatformViewClippath() throws Exception { - intent.putExtra("scenario_name", "platform_view_clippath"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewClippath")); - } - - @Test - public void testPlatformViewTransform() throws Exception { - intent.putExtra("scenario_name", "platform_view_transform"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewTransform")); - } - - @Test - public void testPlatformViewOpacity() throws Exception { - intent.putExtra("scenario_name", "platform_view_opacity"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewOpacity")); - } - - @Test - public void testPlatformViewRotate() throws Exception { - intent.putExtra("scenario_name", "platform_view_rotate"); - PlatformViewsActivity activity = activityRule.launchActivity(intent); - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - ScreenshotUtil.capture(activity, goldName("testPlatformViewRotate")); - } - - @Test - public void testPlatformViewMultipleWithoutOverlays() throws Exception { - intent.putExtra("scenario_name", "platform_view_multiple_without_overlays"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewMultipleWithoutOverlays")); - } - - @Test - public void testPlatformViewTwoIntersectingOverlays() throws Exception { - intent.putExtra("scenario_name", "platform_view_two_intersecting_overlays"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewTwoIntersectingOverlays")); - } - - @Test - public void testPlatformViewWithoutOverlayIntersection() throws Exception { - intent.putExtra("scenario_name", "platform_view_no_overlay_intersection"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), - goldName("testPlatformViewWithoutOverlayIntersection")); - } - - @Test - public void testPlatformViewLargerThanDisplaySize() throws Exception { - // Regression test for https://github.com/flutter/flutter/issues/2897. - intent.putExtra("scenario_name", "platform_view_larger_than_display_size"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewLargerThanDisplaySize")); - } -} diff --git a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithTextureViewUiTest.java b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithTextureViewUiTest.java deleted file mode 100644 index 9a8dfd99878ee..0000000000000 --- a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/PlatformViewWithTextureViewUiTest.java +++ /dev/null @@ -1,124 +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 dev.flutter.scenariosui; - -import android.content.Intent; -import android.content.pm.ActivityInfo; -import androidx.annotation.NonNull; -import androidx.test.filters.LargeTest; -import androidx.test.rule.ActivityTestRule; -import androidx.test.runner.AndroidJUnit4; -import dev.flutter.scenarios.PlatformViewsActivity; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@LargeTest -public class PlatformViewWithTextureViewUiTest { - Intent intent; - - @Rule @NonNull - public ActivityTestRule activityRule = - new ActivityTestRule<>( - PlatformViewsActivity.class, /*initialTouchMode=*/ false, /*launchActivity=*/ false); - - private static String goldName(String suffix) { - return "PlatformViewWithTextureViewUiTest_" + suffix; - } - - @Before - public void setUp() { - intent = new Intent(Intent.ACTION_MAIN); - intent.putExtra("view_type", PlatformViewsActivity.TEXTURE_VIEW_PV); - } - - @Test - public void testPlatformView() throws Exception { - intent.putExtra("scenario_name", "platform_view"); - ScreenshotUtil.capture(activityRule.launchActivity(intent), goldName("testPlatformView")); - } - - @Test - public void testPlatformViewMultiple() throws Exception { - intent.putExtra("scenario_name", "platform_view_multiple"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewMultiple")); - } - - @Test - public void testPlatformViewMultipleBackgroundForeground() throws Exception { - intent.putExtra("scenario_name", "platform_view_multiple_background_foreground"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), - goldName("testPlatformViewMultipleBackgroundForeground")); - } - - @Test - public void testPlatformViewCliprect() throws Exception { - intent.putExtra("scenario_name", "platform_view_cliprect"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewCliprect")); - } - - @Test - public void testPlatformViewCliprrect() throws Exception { - intent.putExtra("scenario_name", "platform_view_cliprrect"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewCliprrect")); - } - - @Test - public void testPlatformViewClippath() throws Exception { - intent.putExtra("scenario_name", "platform_view_clippath"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewClippath")); - } - - @Test - public void testPlatformViewTransform() throws Exception { - intent.putExtra("scenario_name", "platform_view_transform"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewTransform")); - } - - @Test - public void testPlatformViewOpacity() throws Exception { - intent.putExtra("scenario_name", "platform_view_opacity"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewOpacity")); - } - - @Test - public void testPlatformViewRotate() throws Exception { - intent.putExtra("scenario_name", "platform_view_rotate"); - PlatformViewsActivity activity = activityRule.launchActivity(intent); - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - ScreenshotUtil.capture(activity, goldName("testPlatformViewRotate")); - } - - @Test - public void testPlatformViewMultipleWithoutOverlays() throws Exception { - intent.putExtra("scenario_name", "platform_view_multiple_without_overlays"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewMultipleWithoutOverlays")); - } - - @Test - public void testPlatformViewTwoIntersectingOverlays() throws Exception { - intent.putExtra("scenario_name", "platform_view_two_intersecting_overlays"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), goldName("testPlatformViewTwoIntersectingOverlays")); - } - - @Test - public void testPlatformViewWithoutOverlayIntersection() throws Exception { - intent.putExtra("scenario_name", "platform_view_no_overlay_intersection"); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), - goldName("testPlatformViewWithoutOverlayIntersection")); - } -} diff --git a/testing/scenario_app/android/app/src/main/AndroidManifest.xml b/testing/scenario_app/android/app/src/main/AndroidManifest.xml index 09f440887c41d..bbd62a00015cc 100644 --- a/testing/scenario_app/android/app/src/main/AndroidManifest.xml +++ b/testing/scenario_app/android/app/src/main/AndroidManifest.xml @@ -9,7 +9,7 @@ android:supportsRtl="true" android:theme="@style/AppTheme"> () { - @Nullable - @Override - public ByteBuffer encodeMessage(@Nullable Object o) { - if (o instanceof String) { - return StringCodec.INSTANCE.encodeMessage((String) o); - } - return null; - } - - @Nullable - @Override - public Object decodeMessage(@Nullable ByteBuffer byteBuffer) { - return StringCodec.INSTANCE.decodeMessage(byteBuffer); - } - }); - } - - @SuppressWarnings("unchecked") - @Override - @NonNull - public PlatformView create(@NonNull Context context, int id, @Nullable Object args) { - return new SurfacePlatformView(context); - } - - private static class SurfacePlatformView implements PlatformView { - static String TAG = "SurfacePlatformView"; - - final SurfaceView surfaceView; - - @SuppressWarnings("unchecked") - SurfacePlatformView(@NonNull final Context context) { - surfaceView = new SurfaceView(context); - surfaceView - .getHolder() - .addCallback( - new SurfaceHolder.Callback() { - @Override - public void surfaceCreated(SurfaceHolder holder) { - Log.i(TAG, "surfaceCreated"); - final Surface surface = holder.getSurface(); - final Canvas canvas = surface.lockHardwareCanvas(); - canvas.drawColor(Color.WHITE); - - final Paint paint = new Paint(); - paint.setColor(Color.RED); - canvas.drawCircle(canvas.getWidth() / 2, canvas.getHeight() / 2, 20, paint); - surface.unlockCanvasAndPost(canvas); - } - - @Override - public void surfaceChanged( - SurfaceHolder holder, int format, int width, int height) { - Log.i(TAG, "surfaceChanged"); - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - Log.i(TAG, "surfaceDestroyed"); - } - }); - } - - @Override - @NonNull - public View getView() { - return surfaceView; - } - - @Override - public void dispose() {} - } -} diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestActivity.java index 8837035e0ebe0..80323a9a41944 100644 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestActivity.java +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestActivity.java @@ -84,7 +84,6 @@ public void onFlutterUiDisplayed() { test.put("name", "animated_color_square"); } test.put("use_android_view", launchIntent.getBooleanExtra("use_android_view", false)); - test.put("view_type", launchIntent.getStringExtra("view_type")); getScenarioParams(test); channel.invokeMethod("set_scenario", test); } diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformView.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformView.java new file mode 100644 index 0000000000000..e834913d0eac4 --- /dev/null +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformView.java @@ -0,0 +1,46 @@ +// 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 dev.flutter.scenarios; + +import android.content.Context; +import android.graphics.Color; +import android.view.Choreographer; +import android.view.View; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.plugin.platform.PlatformView; + +public class TextPlatformView implements PlatformView { + final TextView textView; + + @SuppressWarnings("unchecked") + TextPlatformView(@NonNull final Context context, int id, @Nullable String params) { + textView = new TextView(context); + textView.setTextSize(72); + textView.setBackgroundColor(Color.rgb(255, 255, 255)); + textView.setText(params); + + // Investigate why this is needed to pass some gold tests. + Choreographer.getInstance() + .postFrameCallbackDelayed( + new Choreographer.FrameCallback() { + @Override + public void doFrame(long frameTimeNanos) { + textView.invalidate(); + } + }, + 500); + } + + @Override + @NonNull + public View getView() { + return textView; + } + + @Override + public void dispose() {} +} diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformViewActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformViewActivity.java new file mode 100644 index 0000000000000..b1cfcf5354b66 --- /dev/null +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformViewActivity.java @@ -0,0 +1,21 @@ +// 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 dev.flutter.scenarios; + +import androidx.annotation.NonNull; +import io.flutter.embedding.engine.FlutterEngine; + +public class TextPlatformViewActivity extends TestActivity { + static final String TAG = "Scenarios"; + + @Override + public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { + super.configureFlutterEngine(flutterEngine); + flutterEngine + .getPlatformViewsController() + .getRegistry() + .registerViewFactory("scenarios/textPlatformView", new TextPlatformViewFactory()); + } +} diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformViewFactory.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformViewFactory.java index 93806dea105f5..3b660a0089c05 100644 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformViewFactory.java +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformViewFactory.java @@ -5,10 +5,6 @@ package dev.flutter.scenarios; import android.content.Context; -import android.graphics.Color; -import android.view.Choreographer; -import android.view.View; -import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.plugin.common.MessageCodec; @@ -45,36 +41,4 @@ public PlatformView create(@NonNull Context context, int id, @Nullable Object ar String params = (String) args; return new TextPlatformView(context, id, params); } - - private static class TextPlatformView implements PlatformView { - final TextView textView; - - @SuppressWarnings("unchecked") - TextPlatformView(@NonNull final Context context, int id, @Nullable String params) { - textView = new TextView(context); - textView.setTextSize(72); - textView.setBackgroundColor(Color.WHITE); - textView.setText(params); - - // Investigate why this is needed to pass some gold tests. - Choreographer.getInstance() - .postFrameCallbackDelayed( - new Choreographer.FrameCallback() { - @Override - public void doFrame(long frameTimeNanos) { - textView.invalidate(); - } - }, - 500); - } - - @Override - @NonNull - public View getView() { - return textView; - } - - @Override - public void dispose() {} - } } diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TexturePlatformViewFactory.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TexturePlatformViewFactory.java deleted file mode 100644 index e79dd16ccb4c5..0000000000000 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TexturePlatformViewFactory.java +++ /dev/null @@ -1,112 +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 dev.flutter.scenarios; - -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.SurfaceTexture; -import android.view.Choreographer; -import android.view.TextureView; -import android.view.View; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import io.flutter.Log; -import io.flutter.plugin.common.MessageCodec; -import io.flutter.plugin.common.StringCodec; -import io.flutter.plugin.platform.PlatformView; -import io.flutter.plugin.platform.PlatformViewFactory; -import java.nio.ByteBuffer; - -@TargetApi(23) -public final class TexturePlatformViewFactory extends PlatformViewFactory { - TexturePlatformViewFactory() { - super( - new MessageCodec() { - @Nullable - @Override - public ByteBuffer encodeMessage(@Nullable Object o) { - if (o instanceof String) { - return StringCodec.INSTANCE.encodeMessage((String) o); - } - return null; - } - - @Nullable - @Override - public Object decodeMessage(@Nullable ByteBuffer byteBuffer) { - return StringCodec.INSTANCE.decodeMessage(byteBuffer); - } - }); - } - - @SuppressWarnings("unchecked") - @Override - @NonNull - public PlatformView create(@NonNull Context context, int id, @Nullable Object args) { - return new TexturePlatformView(context); - } - - private static class TexturePlatformView implements PlatformView { - static String TAG = "TexturePlatformView"; - - final TextureView textureView; - - @SuppressWarnings("unchecked") - TexturePlatformView(@NonNull final Context context) { - textureView = new TextureView(context); - textureView.setSurfaceTextureListener( - new TextureView.SurfaceTextureListener() { - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - Log.i(TAG, "onSurfaceTextureAvailable"); - final Canvas canvas = textureView.lockCanvas(); - canvas.drawColor(Color.WHITE); - - final Paint paint = new Paint(); - paint.setColor(Color.GREEN); - canvas.drawCircle(canvas.getWidth() / 2, canvas.getHeight() / 2, 20, paint); - textureView.unlockCanvasAndPost(canvas); - Choreographer.getInstance() - .postFrameCallbackDelayed( - new Choreographer.FrameCallback() { - @Override - public void doFrame(long frameTimeNanos) { - textureView.invalidate(); - } - }, - 500); - } - - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - Log.i(TAG, "onSurfaceTextureDestroyed"); - return true; - } - - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - Log.i(TAG, "onSurfaceTextureSizeChanged"); - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - Log.i(TAG, "onSurfaceTextureUpdated"); - } - }); - } - - @Override - @NonNull - public View getView() { - return textureView; - } - - @Override - public void dispose() {} - } -} diff --git a/testing/scenario_app/lib/src/platform_view.dart b/testing/scenario_app/lib/src/platform_view.dart index 8f1704e6ef6a2..4f99d01244933 100644 --- a/testing/scenario_app/lib/src/platform_view.dart +++ b/testing/scenario_app/lib/src/platform_view.dart @@ -114,42 +114,6 @@ class PlatformViewNoOverlayIntersectionScenario extends Scenario } } - -/// A platform view that is larger than the display size. -/// This is only applicable on Android while using virtual displays. -/// Related issue: https://github.com/flutter/flutter/issues/2897. -class PlatformViewLargerThanDisplaySize extends Scenario - with _BasePlatformViewScenarioMixin { - /// Creates the PlatformView scenario. - /// - /// The [dispatcher] parameter must not be null. - PlatformViewLargerThanDisplaySize( - PlatformDispatcher dispatcher, { - required this.id, - }) : assert(dispatcher != null), - super(dispatcher); - - /// The platform view identifier. - final int id; - - @override - void onBeginFrame(Duration duration) { - final SceneBuilder builder = SceneBuilder(); - - addPlatformView( - id, - dispatcher: dispatcher, - sceneBuilder: builder, - width: 15000, - height: 60000, - ); - - finishBuilder( - builder, - ); - } -} - /// A simple platform view with an overlay that partially intersects with the platform view. class PlatformViewPartialIntersectionScenario extends Scenario with _BasePlatformViewScenarioMixin { @@ -1085,12 +1049,7 @@ void addPlatformView( double height = 500, String viewType = 'scenarios/textPlatformView', }) { - if (scenarioParams['view_type'] is String) { - viewType = scenarioParams['view_type']; - } - final String platformViewKey = '$viewType-$id'; - if (_createdPlatformViews.containsKey(platformViewKey)) { addPlatformViewToSceneBuilder( id, @@ -1101,10 +1060,9 @@ void addPlatformView( ); return; } - bool usesAndroidHybridComposition = false; - if (scenarioParams['use_android_view'] is bool) { - usesAndroidHybridComposition = scenarioParams['use_android_view']; + if (scenarioParams['use_android_view'] != null) { + usesAndroidHybridComposition = scenarioParams['use_android_view'] as bool; } const int _valueTrue = 1; @@ -1121,9 +1079,9 @@ void addPlatformView( _valueMap, if (Platform.isIOS) 3, // 3 entries in map for iOS. if (Platform.isAndroid && !usesAndroidHybridComposition) - 6, // 6 entries in map for texture on Android. + 6, // 6 entries in map for virtual displays on Android. if (Platform.isAndroid && usesAndroidHybridComposition) - 5, // 5 entries in map for hybrid composition on Android. + 5, // 5 entries in map for Android views. _valueString, 'id'.length, ...utf8.encode('id'), diff --git a/testing/scenario_app/lib/src/scenario.dart b/testing/scenario_app/lib/src/scenario.dart index 6e66c862280e9..f0e9267dae1a1 100644 --- a/testing/scenario_app/lib/src/scenario.dart +++ b/testing/scenario_app/lib/src/scenario.dart @@ -26,13 +26,13 @@ abstract class Scenario { /// /// See [PlatformDispatcher.onDrawFrame] for more details. void onDrawFrame() { - if (_didScheduleScreenshot) { - dispatcher.sendPlatformMessage('take_screenshot', null, null); - return; - } - Future.delayed(const Duration(seconds: 2), () { - _didScheduleScreenshot = true; - dispatcher.scheduleFrame(); + Future.delayed(const Duration(seconds: 1), () { + if (_didScheduleScreenshot) { + dispatcher.sendPlatformMessage('take_screenshot', null, null); + } else { + _didScheduleScreenshot = true; + dispatcher.scheduleFrame(); + } }); } diff --git a/testing/scenario_app/lib/src/scenarios.dart b/testing/scenario_app/lib/src/scenarios.dart index 99304222bd311..95554d8a796f9 100644 --- a/testing/scenario_app/lib/src/scenarios.dart +++ b/testing/scenario_app/lib/src/scenarios.dart @@ -24,7 +24,6 @@ Map _scenarios = { 'locale_initialization': () => LocaleInitialization(PlatformDispatcher.instance), 'platform_view': () => PlatformViewScenario(PlatformDispatcher.instance, id: _viewId++), 'platform_view_no_overlay_intersection': () => PlatformViewNoOverlayIntersectionScenario(PlatformDispatcher.instance, id: _viewId++), - 'platform_view_larger_than_display_size': () => PlatformViewLargerThanDisplaySize(PlatformDispatcher.instance, id: _viewId++), 'platform_view_partial_intersection': () => PlatformViewPartialIntersectionScenario(PlatformDispatcher.instance, id: _viewId++), 'platform_view_two_intersecting_overlays': () => PlatformViewTwoIntersectingOverlaysScenario(PlatformDispatcher.instance, id: _viewId++), 'platform_view_one_overlay_two_intersecting_overlays': () => PlatformViewOneOverlayTwoIntersectingOverlaysScenario(PlatformDispatcher.instance, id: _viewId++), diff --git a/tools/android_lint/baseline.xml b/tools/android_lint/baseline.xml index 41e7e89b6a6f4..a3aef00b2236b 100644 --- a/tools/android_lint/baseline.xml +++ b/tools/android_lint/baseline.xml @@ -100,15 +100,4 @@ column="18"/> - - - - diff --git a/tools/android_lint/project.xml b/tools/android_lint/project.xml index 583b66491985d..38b9f65eebcb8 100644 --- a/tools/android_lint/project.xml +++ b/tools/android_lint/project.xml @@ -107,9 +107,7 @@ - -