diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index df3e9d93065de..37986439e4166 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -477,7 +477,8 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) { // in a way that Flutter understands. textInputPlugin = new TextInputPlugin( this, - this.flutterEngine.getDartExecutor() + this.flutterEngine.getDartExecutor(), + null ); androidKeyProcessor = new AndroidKeyProcessor( this.flutterEngine.getKeyEventChannel(), 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 2a470011c84a7..21bc466991c6a 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java @@ -26,6 +26,13 @@ public class PlatformViewsChannel { private final MethodChannel channel; private PlatformViewsHandler handler; + public void invokeViewFocused(int viewId) { + if (channel == null) { + return; + } + channel.invokeMethod("viewFocused", viewId); + } + private final MethodChannel.MethodCallHandler parsingHandler = new MethodChannel.MethodCallHandler() { @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { @@ -51,6 +58,9 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) { case "setDirection": setDirection(call, result); break; + case "clearFocus": + clearFocus(call, result); + break; default: result.notImplemented(); } @@ -172,6 +182,20 @@ private void setDirection(@NonNull MethodCall call, @NonNull MethodChannel.Resul ); } } + + private void clearFocus(MethodCall call, MethodChannel.Result result) { + int viewId = call.arguments(); + try { + handler.clearFocus(viewId); + result.success(null); + } catch (IllegalStateException exception) { + result.error( + "error", + exception.getMessage(), + null + ); + } + } }; /** @@ -241,6 +265,11 @@ void resizePlatformView( */ // TODO(mattcarroll): Introduce an annotation for @TextureId void setDirection(int viewId, int direction); + + /** + * Clears the focus from the platform view with a give id if it is currently focused. + */ + void clearFocus(int viewId); } /** 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 728ae40797775..ec43f402ea0e6 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java @@ -70,6 +70,10 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) { result.error("error", exception.getMessage(), null); } break; + case "TextInput.setPlatformViewClient": + final int id = (int) args; + textInputMethodHandler.setPlatformViewClient(id); + break; case "TextInput.setEditingState": try { final JSONObject editingState = (JSONObject) args; @@ -218,6 +222,16 @@ public interface TextInputMethodHandler { // TODO(mattcarroll): javadoc void setClient(int textInputClientId, @NonNull Configuration configuration); + /** + * Sets a platform view as the text input client. + * + * Subsequent calls to createInputConnection will be delegated to the platform view until a + * different client is set. + * + * @param id the ID of the platform view to be set as a text input client. + */ + void setPlatformViewClient(int id); + // TODO(mattcarroll): javadoc void setEditingState(@NonNull TextEditState editingState); diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 051befd6fab4e..0e654277f341e 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -18,7 +18,7 @@ import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.systemchannels.TextInputChannel; -import io.flutter.view.FlutterView; +import io.flutter.plugin.platform.PlatformViewsController; /** * Android implementation of the text input plugin. @@ -30,7 +30,8 @@ public class TextInputPlugin { private final InputMethodManager mImm; @NonNull private final TextInputChannel textInputChannel; - private int mClient = 0; + @NonNull + private InputTarget inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); @Nullable private TextInputChannel.Configuration configuration; @Nullable @@ -39,7 +40,13 @@ public class TextInputPlugin { @Nullable private InputConnection lastInputConnection; - public TextInputPlugin(View view, @NonNull DartExecutor dartExecutor) { + private PlatformViewsController platformViewsController; + + // 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; + + public TextInputPlugin(View view, @NonNull DartExecutor dartExecutor, PlatformViewsController platformViewsController) { mView = view; mImm = (InputMethodManager) view.getContext().getSystemService( Context.INPUT_METHOD_SERVICE); @@ -61,6 +68,11 @@ public void setClient(int textInputClientId, TextInputChannel.Configuration conf setTextInputClient(textInputClientId, configuration); } + @Override + public void setPlatformViewClient(int platformViewId) { + setPlatformViewTextInputClient(platformViewId); + } + @Override public void setEditingState(TextInputChannel.TextEditState editingState) { setTextInputEditingState(mView, editingState); @@ -71,6 +83,8 @@ public void clearClient() { clearTextInputClient(); } }); + this.platformViewsController = platformViewsController; + platformViewsController.attachTextInputPlugin(this); } @NonNull @@ -78,6 +92,40 @@ public InputMethodManager getInputMethodManager() { return mImm; } + /*** + * 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.PLATFORM_VIEW) { + isInputConnectionLocked = true; + } + } + + /** + * Unlocks the input connection. + * + * See also: @{link lockPlatformViewInputConnection}. + */ + public void unlockPlatformViewInputConnection() { + isInputConnectionLocked = false; + } + + /** + * Detaches the text input plugin from the platform views controller. + * + * The TextInputPlugin instance should not be used after calling this. + */ + public void destroy() { + platformViewsController.detachTextInputPlugin(); + } + private static int inputTypeFromTextInputType( TextInputChannel.InputType type, boolean obscureText, @@ -128,8 +176,16 @@ private static int inputTypeFromTextInputType( } public InputConnection createInputConnection(View view, EditorInfo outAttrs) { - if (mClient == 0) { + if (inputTarget.type == InputTarget.Type.NO_TARGET) { lastInputConnection = null; + return null; + } + + if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) { + if (isInputConnectionLocked) { + return lastInputConnection; + } + lastInputConnection = platformViewsController.getPlatformViewById(inputTarget.id).onCreateInputConnection(outAttrs); return lastInputConnection; } @@ -158,7 +214,7 @@ public InputConnection createInputConnection(View view, EditorInfo outAttrs) { InputConnectionAdaptor connection = new InputConnectionAdaptor( view, - mClient, + inputTarget.id, textInputChannel, mEditable ); @@ -180,17 +236,28 @@ private void showTextInput(View view) { } private void hideTextInput(View view) { + // Note: a race condition may lead to us hiding the keyboard here just after a platform view has shown it. + // This can only potentially happen when switching focus from a Flutter text field to a platform view's text + // field(by text field here I mean anything that keeps the keyboard open). + // See: https://github.com/flutter/flutter/issues/34169 mImm.hideSoftInputFromWindow(view.getApplicationWindowToken(), 0); } private void setTextInputClient(int client, TextInputChannel.Configuration configuration) { - mClient = client; + inputTarget = new InputTarget(InputTarget.Type.FRAMEWORK_CLIENT, client); this.configuration = configuration; mEditable = Editable.Factory.getInstance().newEditable(""); // setTextInputClient will be followed by a call to setTextInputEditingState. // Do a restartInput at that time. mRestartInputPending = true; + unlockPlatformViewInputConnection(); + } + + private void setPlatformViewTextInputClient(int platformViewId) { + inputTarget = new InputTarget(InputTarget.Type.PLATFORM_VIEW, platformViewId); + mImm.restartInput(mView); + mRestartInputPending = false; } private void applyStateToSelection(TextInputChannel.TextEditState state) { @@ -220,6 +287,45 @@ private void setTextInputEditingState(View view, TextInputChannel.TextEditState } private void clearTextInputClient() { - mClient = 0; + if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) { + // 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 views + // 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; + } + inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); + unlockPlatformViewInputConnection(); + } + + static private class InputTarget { + enum Type { + NO_TARGET, + // InputConnection is managed by the TextInputPlugin, and events are forwarded to the Flutter framework. + FRAMEWORK_CLIENT, + // InputConnection is managed by an embedded platform view. + PLATFORM_VIEW + } + + public InputTarget(@NonNull Type type, int id) { + this.type = type; + this.id = id; + } + + @NonNull + Type type; + // The ID of the input target. + // + // For framework clients this is the framework input connection client ID. + // For platform views this is the platform view's ID. + int id; } } diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java index 3b81a505552ce..b26c60ff6ffe8 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsAccessibilityDelegate.java @@ -11,7 +11,6 @@ * Facilitates interaction between the accessibility bridge and embedded platform views. */ public interface PlatformViewsAccessibilityDelegate { - /** * Returns the root of the view hierarchy for the platform view with the requested id, or null if there is no * corresponding view. diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 2e2b621c7bf88..e6b8f387b3791 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -23,12 +23,15 @@ import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.StandardMethodCodec; +import io.flutter.plugin.editing.TextInputPlugin; import io.flutter.view.AccessibilityBridge; import io.flutter.view.TextureRegistry; +import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; /** @@ -51,6 +54,8 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega // The texture registry maintaining the textures into which the embedded views will be rendered. private TextureRegistry textureRegistry; + private TextInputPlugin textInputPlugin; + // The system channel used to communicate with the framework about platform views. private PlatformViewsChannel platformViewsChannel; @@ -59,6 +64,11 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega private final HashMap vdControllers; + // The set of root views for all active virtual displays managed by this controller. + // This allows an O(1) check whether a view is managed by this controller(by checking if it's root view is in this + // set). This is used by isPlatformView. + private final HashSet vdRootViews; + private final PlatformViewsChannel.PlatformViewsHandler channelHandler = new PlatformViewsChannel.PlatformViewsHandler() { @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override @@ -92,14 +102,19 @@ public long createPlatformView(@NonNull PlatformViewsChannel.PlatformViewCreatio TextureRegistry.SurfaceTextureEntry textureEntry = textureRegistry.createSurfaceTexture(); VirtualDisplayController vdController = VirtualDisplayController.create( - context, - accessibilityEventsDelegate, - viewFactory, - textureEntry, - toPhysicalPixels(request.logicalWidth), - toPhysicalPixels(request.logicalHeight), - request.viewId, - createParams + context, + accessibilityEventsDelegate, + viewFactory, + textureEntry, + physicalWidth, + physicalHeight, + request.viewId, + createParams, + (view, hasFocus) -> { + if (hasFocus) { + platformViewsChannel.invokeViewFocused(request.viewId); + } + } ); if (vdController == null) { @@ -108,7 +123,9 @@ public long createPlatformView(@NonNull PlatformViewsChannel.PlatformViewCreatio } vdControllers.put(request.viewId, vdController); - vdController.getView().setLayoutDirection(request.direction); + View platformView = vdController.getView(); + platformView.setLayoutDirection(request.direction); + vdRootViews.add(platformView.getRootView()); // TODO(amirh): copy accessibility nodes to the FlutterView's accessibility tree. @@ -125,6 +142,9 @@ public void disposePlatformView(int viewId) { + viewId); } + View rootView = vdController.getView().getRootView(); + vdRootViews.remove(rootView); + vdController.dispose(); vdControllers.remove(viewId); } @@ -143,11 +163,28 @@ public void resizePlatformView(@NonNull PlatformViewsChannel.PlatformViewResizeR int physicalHeight = toPhysicalPixels(request.newLogicalHeight); validateVirtualDisplayDimensions(physicalWidth, physicalHeight); + if (textInputPlugin != null) { + // 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. + textInputPlugin.lockPlatformViewInputConnection(); + } + vdRootViews.remove(vdController.getView().getRootView()); vdController.resize( - physicalWidth, - physicalHeight, - onComplete + physicalWidth, + physicalHeight, + new Runnable() { + @Override + public void run() { + if (textInputPlugin != null) { + textInputPlugin.unlockPlatformViewInputConnection(); + } + onComplete.run(); + } + } ); + vdRootViews.add(vdController.getView().getRootView()); } @Override @@ -207,6 +244,12 @@ public void setDirection(int viewId, int direction) { view.setLayoutDirection(direction); } + @Override + public void clearFocus(int viewId) { + View view = vdControllers.get(viewId).getView(); + view.clearFocus(); + } + private void ensureValidAndroidVersion() { if (Build.VERSION.SDK_INT < MINIMAL_SDK) { Log.e(TAG, "Trying to use platform views with API " + Build.VERSION.SDK_INT @@ -221,6 +264,7 @@ public PlatformViewsController() { registry = new PlatformViewRegistryImpl(); vdControllers = new HashMap<>(); accessibilityEventsDelegate = new AccessibilityEventsDelegate(); + vdRootViews = new HashSet<>(); } /** @@ -270,6 +314,33 @@ public void detachAccessibiltyBridge() { accessibilityEventsDelegate.setAccessibilityBridge(null); } + /** + * Attaches this controller to a text input plugin. + * + * While a text input plugin is available, the platform views controller interacts with it to facilitate + * delegation of text input connections to platform views. + * + * A platform views controller should be attached to a text input plugin whenever it is possible for the Flutter + * framework to receive text input. + */ + public void attachTextInputPlugin(TextInputPlugin textInputPlugin) { + this.textInputPlugin = textInputPlugin; + } + + /** + * Detaches this controller from the currently attached text input plugin. + */ + public void detachTextInputPlugin() { + textInputPlugin = null; + } + + /** + * Returns true if the view is a platform view managed by this controller. + */ + public boolean isPlatformView(View view) { + return vdRootViews.contains(view.getRootView()); + } + public PlatformViewRegistry getRegistry() { return registry; } diff --git a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java index baec72717d8d3..d48343e273a8b 100644 --- a/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java +++ b/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java @@ -19,6 +19,7 @@ import java.lang.reflect.*; import static android.content.Context.WINDOW_SERVICE; +import static android.view.View.OnFocusChangeListener; /* * A presentation used for hosting a single Android view in a virtual display. @@ -61,6 +62,8 @@ static class PresentationState { // 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; @@ -78,6 +81,8 @@ static class PresentationState { private PresentationState state; + private boolean startFocused = false; + /** * Creates a presentation that will use the view factory to create a new * platform view in the presentation's onCreate, and attach it. @@ -88,13 +93,15 @@ public SingleViewPresentation( PlatformViewFactory viewFactory, AccessibilityEventsDelegate accessibilityEventsDelegate, int viewId, - Object createParams + Object createParams, + OnFocusChangeListener focusChangeListener ) { super(outerContext, display); this.viewFactory = viewFactory; this.accessibilityEventsDelegate = accessibilityEventsDelegate; this.viewId = viewId; this.createParams = createParams; + this.focusChangeListener = focusChangeListener; state = new PresentationState(); getWindow().setFlags( WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, @@ -113,16 +120,20 @@ public SingleViewPresentation( Context outerContext, Display display, AccessibilityEventsDelegate accessibilityEventsDelegate, - PresentationState state + PresentationState state, + OnFocusChangeListener focusChangeListener, + boolean startFocused ) { super(outerContext, display); this.accessibilityEventsDelegate = accessibilityEventsDelegate; viewFactory = null; this.state = state; + this.focusChangeListener = focusChangeListener; getWindow().setFlags( WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE ); + this.startFocused = startFocused; } @Override @@ -148,6 +159,14 @@ protected void onCreate(Bundle savedInstanceState) { 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); } diff --git a/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java b/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java index 2f1ce898d8975..eb59218ca6649 100644 --- a/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java +++ b/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java @@ -9,11 +9,14 @@ import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.os.Build; +import android.util.Log; import android.view.Surface; import android.view.View; import android.view.ViewTreeObserver; import io.flutter.view.TextureRegistry; +import static android.view.View.OnFocusChangeListener; + @TargetApi(Build.VERSION_CODES.KITKAT_WATCH) class VirtualDisplayController { @@ -25,7 +28,8 @@ public static VirtualDisplayController create( int width, int height, int viewId, - Object createParams + Object createParams, + OnFocusChangeListener focusChangeListener ) { textureEntry.surfaceTexture().setDefaultBufferSize(width, height); Surface surface = new Surface(textureEntry.surfaceTexture()); @@ -46,13 +50,14 @@ public static VirtualDisplayController create( } return new VirtualDisplayController( - context, accessibilityEventsDelegate, virtualDisplay, viewFactory, surface, textureEntry, viewId, createParams); + context, accessibilityEventsDelegate, virtualDisplay, viewFactory, surface, textureEntry, focusChangeListener, viewId, createParams); } private final Context context; private final AccessibilityEventsDelegate accessibilityEventsDelegate; private final int densityDpi; private final TextureRegistry.SurfaceTextureEntry textureEntry; + private final OnFocusChangeListener focusChangeListener; private VirtualDisplay virtualDisplay; private SingleViewPresentation presentation; private Surface surface; @@ -65,21 +70,30 @@ private VirtualDisplayController( PlatformViewFactory viewFactory, Surface surface, TextureRegistry.SurfaceTextureEntry textureEntry, + OnFocusChangeListener focusChangeListener, int viewId, Object createParams ) { this.context = context; this.accessibilityEventsDelegate = accessibilityEventsDelegate; this.textureEntry = textureEntry; + this.focusChangeListener = focusChangeListener; this.surface = surface; this.virtualDisplay = virtualDisplay; densityDpi = context.getResources().getDisplayMetrics().densityDpi; presentation = new SingleViewPresentation( - context, this.virtualDisplay.getDisplay(), viewFactory, accessibilityEventsDelegate, viewId, createParams); + context, + this.virtualDisplay.getDisplay(), + viewFactory, + accessibilityEventsDelegate, + viewId, + createParams, + focusChangeListener); presentation.show(); } public void resize(final int width, final int height, final Runnable onNewSizeFrameAvailable) { + boolean isFocused = getView().isFocused(); final SingleViewPresentation.PresentationState presentationState = presentation.detachState(); // We detach the surface to prevent it being destroyed when releasing the vd. // @@ -125,7 +139,13 @@ public void run() { public void onViewDetachedFromWindow(View v) {} }); - presentation = new SingleViewPresentation(context, virtualDisplay.getDisplay(), accessibilityEventsDelegate, presentationState); + presentation = new SingleViewPresentation( + context, + virtualDisplay.getDisplay(), + accessibilityEventsDelegate, + presentationState, + focusChangeListener, + isFocused); presentation.show(); } diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 1893a22e717e7..28e3338857f1e 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -191,9 +191,11 @@ public void surfaceDestroyed(SurfaceHolder holder) { PlatformPlugin platformPlugin = new PlatformPlugin(activity, platformChannel); addActivityLifecycleListener(platformPlugin); mImm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - mTextInputPlugin = new TextInputPlugin(this, dartExecutor); + PlatformViewsController platformViewsController = mNativeView.getPluginRegistry().getPlatformViewsController(); + mTextInputPlugin = new TextInputPlugin(this, dartExecutor, platformViewsController); androidKeyProcessor = new AndroidKeyProcessor(keyEventChannel, mTextInputPlugin); androidTouchProcessor = new AndroidTouchProcessor(flutterRenderer); + mNativeView.getPluginRegistry().getPlatformViewsController().attachTextInputPlugin(mTextInputPlugin); // Send initial platform information to Dart sendLocalesToDart(getResources().getConfiguration()); @@ -395,6 +397,12 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) { return mTextInputPlugin.createInputConnection(this, outAttrs); } + @Override + public boolean checkInputConnectionProxy(View view) { + PlatformViewsController platformViewsController = mNativeView.getPluginRegistry().getPlatformViewsController(); + return platformViewsController.isPlatformView(view); + } + @Override public boolean onTouchEvent(MotionEvent event) { if (!isAttached()) {