@@ -54,6 +54,12 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
5454 // Initialize the "last seen" text editing values to a non-null value.
5555 private TextEditState mLastKnownFrameworkTextEditingState ;
5656
57+ // When true following calls to createInputConnection will return the cached lastInputConnection
58+ // if the input
59+ // target is a platform view. See the comments on lockPlatformViewInputConnection for more
60+ // details.
61+ private boolean isInputConnectionLocked ;
62+
5763 @ SuppressLint ("NewApi" )
5864 public TextInputPlugin (
5965 @ NonNull View view ,
@@ -99,7 +105,7 @@ public void show() {
99105
100106 @ Override
101107 public void hide () {
102- if (inputTarget .type == InputTarget .Type .PLATFORM_VIEW ) {
108+ if (inputTarget .type == InputTarget .Type .PHYSICAL_DISPLAY_PLATFORM_VIEW ) {
103109 notifyViewExited ();
104110 } else {
105111 hideTextInput (mView );
@@ -130,8 +136,8 @@ public void setClient(
130136 }
131137
132138 @ Override
133- public void setPlatformViewClient (int platformViewId ) {
134- setPlatformViewTextInputClient (platformViewId );
139+ public void setPlatformViewClient (int platformViewId , boolean usesVirtualDisplay ) {
140+ setPlatformViewTextInputClient (platformViewId , usesVirtualDisplay );
135141 }
136142
137143 @ Override
@@ -176,6 +182,36 @@ ImeSyncDeferringInsetsCallback getImeSyncCallback() {
176182 return imeSyncCallback ;
177183 }
178184
185+ /**
186+ * Use the current platform view input connection until unlockPlatformViewInputConnection is
187+ * called.
188+ *
189+ * <p>The current input connection instance is cached and any following call to @{link
190+ * createInputConnection} returns the cached connection until unlockPlatformViewInputConnection is
191+ * called.
192+ *
193+ * <p>This is a no-op if the current input target isn't a platform view.
194+ *
195+ * <p>This is used to preserve an input connection when moving a platform view from one virtual
196+ * display to another.
197+ */
198+ public void lockPlatformViewInputConnection () {
199+ if (inputTarget .type == InputTarget .Type .VIRTUAL_DISPLAY_PLATFORM_VIEW ) {
200+ isInputConnectionLocked = true ;
201+ }
202+ }
203+
204+ /**
205+ * Unlocks the input connection.
206+ *
207+ * <p>See also: @{link lockPlatformViewInputConnection}.
208+ */
209+ public void unlockPlatformViewInputConnection () {
210+ if (inputTarget .type == InputTarget .Type .VIRTUAL_DISPLAY_PLATFORM_VIEW ) {
211+ isInputConnectionLocked = false ;
212+ }
213+ }
214+
179215 /**
180216 * Detaches the text input plugin from the platform views controller.
181217 *
@@ -259,10 +295,21 @@ public InputConnection createInputConnection(
259295 return null ;
260296 }
261297
262- if (inputTarget .type == InputTarget .Type .PLATFORM_VIEW ) {
298+ if (inputTarget .type == InputTarget .Type .PHYSICAL_DISPLAY_PLATFORM_VIEW ) {
263299 return null ;
264300 }
265301
302+ if (inputTarget .type == InputTarget .Type .VIRTUAL_DISPLAY_PLATFORM_VIEW ) {
303+ if (isInputConnectionLocked ) {
304+ return lastInputConnection ;
305+ }
306+ lastInputConnection =
307+ platformViewsController
308+ .getPlatformViewById (inputTarget .id )
309+ .onCreateInputConnection (outAttrs );
310+ return lastInputConnection ;
311+ }
312+
266313 outAttrs .inputType =
267314 inputTypeFromTextInputType (
268315 configuration .inputType ,
@@ -317,7 +364,9 @@ public InputConnection getLastInputConnection() {
317364 * input connection.
318365 */
319366 public void clearPlatformViewClient (int platformViewId ) {
320- if (inputTarget .type == InputTarget .Type .PLATFORM_VIEW && inputTarget .id == platformViewId ) {
367+ if ((inputTarget .type == InputTarget .Type .VIRTUAL_DISPLAY_PLATFORM_VIEW
368+ || inputTarget .type == InputTarget .Type .PHYSICAL_DISPLAY_PLATFORM_VIEW )
369+ && inputTarget .id == platformViewId ) {
321370 inputTarget = new InputTarget (InputTarget .Type .NO_TARGET , 0 );
322371 notifyViewExited ();
323372 mImm .hideSoftInputFromWindow (mView .getApplicationWindowToken (), 0 );
@@ -378,13 +427,26 @@ void setTextInputClient(int client, TextInputChannel.Configuration configuration
378427 // setTextInputClient will be followed by a call to setTextInputEditingState.
379428 // Do a restartInput at that time.
380429 mRestartInputPending = true ;
430+ unlockPlatformViewInputConnection ();
381431 lastClientRect = null ;
382432 mEditable .addEditingStateListener (this );
383433 }
384434
385- private void setPlatformViewTextInputClient (int platformViewId ) {
386- inputTarget = new InputTarget (InputTarget .Type .PLATFORM_VIEW , platformViewId );
387- lastInputConnection = null ;
435+ private void setPlatformViewTextInputClient (int platformViewId , boolean usesVirtualDisplay ) {
436+ if (usesVirtualDisplay ) {
437+ // We need to make sure that the Flutter view is focused so that no imm operations get short
438+ // circuited.
439+ // Not asking for focus here specifically manifested in a bug on API 28 devices where the
440+ // platform view's request to show a keyboard was ignored.
441+ mView .requestFocus ();
442+ inputTarget = new InputTarget (InputTarget .Type .VIRTUAL_DISPLAY_PLATFORM_VIEW , platformViewId );
443+ mImm .restartInput (mView );
444+ mRestartInputPending = false ;
445+ } else {
446+ inputTarget =
447+ new InputTarget (InputTarget .Type .PHYSICAL_DISPLAY_PLATFORM_VIEW , platformViewId );
448+ lastInputConnection = null ;
449+ }
388450 }
389451
390452 private static boolean composingChanged (
@@ -475,10 +537,28 @@ public void inspect(double x, double y) {
475537
476538 @ VisibleForTesting
477539 void clearTextInputClient () {
540+ if (inputTarget .type == InputTarget .Type .VIRTUAL_DISPLAY_PLATFORM_VIEW ) {
541+ // This only applies to platform views that use a virtual display.
542+ // Focus changes in the framework tree have no guarantees on the order focus nodes are
543+ // notified. A node that lost focus may be notified before or after a node that gained focus.
544+ // When moving the focus from a Flutter text field to an AndroidView, it is possible that the
545+ // Flutter text field's focus node will be notified that it lost focus after the AndroidView
546+ // was notified that it gained focus. When this happens the text field will send a
547+ // clearTextInput command which we ignore.
548+ // By doing this we prevent the framework from clearing a platform view input client (the only
549+ // way to do so is to set a new framework text client). I don't see an obvious use case for
550+ // "clearing" a platform view's text input client, and it may be error prone as we don't know
551+ // how the platform view manages the input connection and we probably shouldn't interfere.
552+ // If we ever want to allow the framework to clear a platform view text client we should
553+ // probably consider changing the focus manager such that focus nodes that lost focus are
554+ // notified before focus nodes that gained focus as part of the same focus event.
555+ return ;
556+ }
478557 mEditable .removeEditingStateListener (this );
479558 notifyViewExited ();
480559 updateAutofillConfigurationIfNeeded (null );
481560 inputTarget = new InputTarget (InputTarget .Type .NO_TARGET , 0 );
561+ unlockPlatformViewInputConnection ();
482562 lastClientRect = null ;
483563 }
484564
@@ -488,9 +568,12 @@ enum Type {
488568 // InputConnection is managed by the TextInputPlugin, and events are forwarded to the Flutter
489569 // framework.
490570 FRAMEWORK_CLIENT ,
491- // InputConnection is managed by a platform view that is embeded in the Android view
492- // hierarchy.
493- PLATFORM_VIEW ,
571+ // InputConnection is managed by a platform view that is presented on a virtual display.
572+ VIRTUAL_DISPLAY_PLATFORM_VIEW ,
573+ // InputConnection is managed by a platform view that is embedded in the activity's view
574+ // hierarchy. This view hierarchy is displayed in a physical display within the aplication
575+ // display area.
576+ PHYSICAL_DISPLAY_PLATFORM_VIEW ,
494577 }
495578
496579 public InputTarget (@ NonNull Type type , int id ) {
0 commit comments