@@ -54,6 +54,12 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
54
54
// Initialize the "last seen" text editing values to a non-null value.
55
55
private TextEditState mLastKnownFrameworkTextEditingState ;
56
56
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
+
57
63
@ SuppressLint ("NewApi" )
58
64
public TextInputPlugin (
59
65
@ NonNull View view ,
@@ -99,7 +105,7 @@ public void show() {
99
105
100
106
@ Override
101
107
public void hide () {
102
- if (inputTarget .type == InputTarget .Type .PLATFORM_VIEW ) {
108
+ if (inputTarget .type == InputTarget .Type .PHYSICAL_DISPLAY_PLATFORM_VIEW ) {
103
109
notifyViewExited ();
104
110
} else {
105
111
hideTextInput (mView );
@@ -130,8 +136,8 @@ public void setClient(
130
136
}
131
137
132
138
@ Override
133
- public void setPlatformViewClient (int platformViewId ) {
134
- setPlatformViewTextInputClient (platformViewId );
139
+ public void setPlatformViewClient (int platformViewId , boolean usesVirtualDisplay ) {
140
+ setPlatformViewTextInputClient (platformViewId , usesVirtualDisplay );
135
141
}
136
142
137
143
@ Override
@@ -176,6 +182,36 @@ ImeSyncDeferringInsetsCallback getImeSyncCallback() {
176
182
return imeSyncCallback ;
177
183
}
178
184
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
+
179
215
/**
180
216
* Detaches the text input plugin from the platform views controller.
181
217
*
@@ -259,10 +295,21 @@ public InputConnection createInputConnection(
259
295
return null ;
260
296
}
261
297
262
- if (inputTarget .type == InputTarget .Type .PLATFORM_VIEW ) {
298
+ if (inputTarget .type == InputTarget .Type .PHYSICAL_DISPLAY_PLATFORM_VIEW ) {
263
299
return null ;
264
300
}
265
301
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
+
266
313
outAttrs .inputType =
267
314
inputTypeFromTextInputType (
268
315
configuration .inputType ,
@@ -317,7 +364,9 @@ public InputConnection getLastInputConnection() {
317
364
* input connection.
318
365
*/
319
366
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 ) {
321
370
inputTarget = new InputTarget (InputTarget .Type .NO_TARGET , 0 );
322
371
notifyViewExited ();
323
372
mImm .hideSoftInputFromWindow (mView .getApplicationWindowToken (), 0 );
@@ -378,13 +427,26 @@ void setTextInputClient(int client, TextInputChannel.Configuration configuration
378
427
// setTextInputClient will be followed by a call to setTextInputEditingState.
379
428
// Do a restartInput at that time.
380
429
mRestartInputPending = true ;
430
+ unlockPlatformViewInputConnection ();
381
431
lastClientRect = null ;
382
432
mEditable .addEditingStateListener (this );
383
433
}
384
434
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
+ }
388
450
}
389
451
390
452
private static boolean composingChanged (
@@ -475,10 +537,28 @@ public void inspect(double x, double y) {
475
537
476
538
@ VisibleForTesting
477
539
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
+ }
478
557
mEditable .removeEditingStateListener (this );
479
558
notifyViewExited ();
480
559
updateAutofillConfigurationIfNeeded (null );
481
560
inputTarget = new InputTarget (InputTarget .Type .NO_TARGET , 0 );
561
+ unlockPlatformViewInputConnection ();
482
562
lastClientRect = null ;
483
563
}
484
564
@@ -488,9 +568,12 @@ enum Type {
488
568
// InputConnection is managed by the TextInputPlugin, and events are forwarded to the Flutter
489
569
// framework.
490
570
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 ,
494
577
}
495
578
496
579
public InputTarget (@ NonNull Type type , int id ) {
0 commit comments