Flutter Engine
The Flutter Engine
TextInputPluginTest.java
Go to the documentation of this file.
1package io.flutter.plugin.editing;
2
3import static io.flutter.Build.API_LEVELS;
4import static org.junit.Assert.assertEquals;
5import static org.junit.Assert.assertNotNull;
6import static org.junit.Assert.assertThrows;
7import static org.junit.Assert.assertTrue;
8import static org.mockito.AdditionalMatchers.aryEq;
9import static org.mockito.AdditionalMatchers.gt;
10import static org.mockito.ArgumentMatchers.anyInt;
11import static org.mockito.Mockito.any;
12import static org.mockito.Mockito.atLeast;
13import static org.mockito.Mockito.eq;
14import static org.mockito.Mockito.isNotNull;
15import static org.mockito.Mockito.isNull;
16import static org.mockito.Mockito.mock;
17import static org.mockito.Mockito.never;
18import static org.mockito.Mockito.spy;
19import static org.mockito.Mockito.times;
20import static org.mockito.Mockito.verify;
21import static org.mockito.Mockito.when;
22
23import android.annotation.TargetApi;
24import android.app.Activity;
25import android.content.Context;
26import android.content.res.AssetManager;
27import android.graphics.Insets;
28import android.graphics.Rect;
29import android.os.Build;
30import android.os.Bundle;
31import android.provider.Settings;
32import android.text.InputType;
33import android.text.Selection;
34import android.util.SparseArray;
35import android.util.SparseIntArray;
36import android.view.KeyEvent;
37import android.view.View;
38import android.view.ViewStructure;
39import android.view.WindowInsets;
40import android.view.WindowInsetsAnimation;
41import android.view.autofill.AutofillManager;
42import android.view.autofill.AutofillValue;
43import android.view.inputmethod.CursorAnchorInfo;
44import android.view.inputmethod.EditorInfo;
45import android.view.inputmethod.InputConnection;
46import android.view.inputmethod.InputMethodManager;
47import android.view.inputmethod.InputMethodSubtype;
48import androidx.test.core.app.ApplicationProvider;
49import androidx.test.ext.junit.runners.AndroidJUnit4;
50import io.flutter.embedding.android.FlutterView;
51import io.flutter.embedding.android.KeyboardManager;
52import io.flutter.embedding.engine.FlutterEngine;
53import io.flutter.embedding.engine.FlutterJNI;
54import io.flutter.embedding.engine.dart.DartExecutor;
55import io.flutter.embedding.engine.loader.FlutterLoader;
56import io.flutter.embedding.engine.renderer.FlutterRenderer;
57import io.flutter.embedding.engine.systemchannels.TextInputChannel;
58import io.flutter.embedding.engine.systemchannels.TextInputChannel.TextEditState;
59import io.flutter.plugin.common.BinaryMessenger;
60import io.flutter.plugin.common.JSONMethodCodec;
61import io.flutter.plugin.common.MethodCall;
62import io.flutter.plugin.platform.PlatformViewsController;
63import java.nio.ByteBuffer;
64import java.util.ArrayList;
65import java.util.HashMap;
66import java.util.List;
67import org.json.JSONArray;
68import org.json.JSONException;
69import org.json.JSONObject;
70import org.junit.Before;
71import org.junit.Test;
72import org.junit.runner.RunWith;
73import org.mockito.ArgumentCaptor;
74import org.mockito.Mock;
75import org.mockito.MockitoAnnotations;
76import org.robolectric.Robolectric;
77import org.robolectric.annotation.Config;
78import org.robolectric.annotation.Implementation;
79import org.robolectric.annotation.Implements;
80import org.robolectric.shadow.api.Shadow;
81import org.robolectric.shadows.ShadowAutofillManager;
82import org.robolectric.shadows.ShadowBuild;
83import org.robolectric.shadows.ShadowInputMethodManager;
84
85@Config(
86 manifest = Config.NONE,
87 shadows = {TextInputPluginTest.TestImm.class, TextInputPluginTest.TestAfm.class})
88@RunWith(AndroidJUnit4.class)
89public class TextInputPluginTest {
90 private final Context ctx = ApplicationProvider.getApplicationContext();
93
94 @Before
95 public void setUp() {
96 MockitoAnnotations.openMocks(this);
97 when(mockFlutterJni.isAttached()).thenReturn(true);
98 }
99
100 // Verifies the method and arguments for a captured method call.
101 private void verifyMethodCall(ByteBuffer buffer, String methodName, String[] expectedArgs)
102 throws JSONException {
103 buffer.rewind();
105 assertEquals(methodName, methodCall.method);
106 if (expectedArgs != null) {
107 JSONArray args = methodCall.arguments();
108 assertEquals(expectedArgs.length, args.length());
109 for (int i = 0; i < args.length(); i++) {
110 assertEquals(expectedArgs[i], args.get(i).toString());
111 }
112 }
113 }
114
115 private static void sendToBinaryMessageHandler(
116 BinaryMessenger.BinaryMessageHandler binaryMessageHandler, String method, Object args) {
117 MethodCall methodCall = new MethodCall(method, args);
118 ByteBuffer encodedMethodCall = JSONMethodCodec.INSTANCE.encodeMethodCall(methodCall);
119 binaryMessageHandler.onMessage(
120 (ByteBuffer) encodedMethodCall.flip(), mock(BinaryMessenger.BinaryReply.class));
121 }
122
123 @SuppressWarnings("deprecation")
124 // DartExecutor.send is deprecated.
125 @Test
126 public void textInputPlugin_RequestsReattachOnCreation() throws JSONException {
127 // Initialize a general TextInputPlugin.
128 InputMethodSubtype inputMethodSubtype = mock(InputMethodSubtype.class);
129 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
130 testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
131 View testView = new View(ctx);
132
133 FlutterJNI mockFlutterJni = mock(FlutterJNI.class);
134 DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJni, mock(AssetManager.class)));
135 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
137 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
138
139 ArgumentCaptor<String> channelCaptor = ArgumentCaptor.forClass(String.class);
140 ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
141
142 verify(dartExecutor, times(1)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull());
143 assertEquals("flutter/textinput", channelCaptor.getValue());
144 verifyMethodCall(bufferCaptor.getValue(), "TextInputClient.requestExistingInputState", null);
145 }
146
147 @Test
149 // Initialize a general TextInputPlugin.
150 InputMethodSubtype inputMethodSubtype = mock(InputMethodSubtype.class);
151 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
152 testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
153 View testView = new View(ctx);
154 TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
156 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
157 textInputPlugin.setTextInputClient(
158 0,
160 false,
161 false,
162 true,
163 true,
164 false,
166 null,
167 null,
168 null,
169 null,
170 null,
171 null));
172
173 textInputPlugin.setTextInputEditingState(
174 testView, new TextInputChannel.TextEditState("initial input from framework", 0, 0, -1, -1));
175 assertTrue(textInputPlugin.getEditable().toString().equals("initial input from framework"));
176
177 verify(textInputChannel, times(0))
178 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
179
180 textInputPlugin.setTextInputEditingState(
181 testView,
182 new TextInputChannel.TextEditState("more update from the framework", 1, 2, -1, -1));
183
184 assertTrue(textInputPlugin.getEditable().toString().equals("more update from the framework"));
185 verify(textInputChannel, times(0))
186 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
187 }
188
189 @Test
191 // Initialize a general TextInputPlugin.
192 InputMethodSubtype inputMethodSubtype = mock(InputMethodSubtype.class);
193 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
194 testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
195 View testView = new View(ctx);
196 TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
198 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
199
200 // Here's no textInputPlugin.setTextInputClient()
201 textInputPlugin.setTextInputEditingState(
202 testView, new TextInputChannel.TextEditState("initial input from framework", 0, 0, -1, -1));
203 assertTrue(textInputPlugin.getEditable().toString().equals("initial input from framework"));
204 }
205
206 @Test
208 // Initialize a general TextInputPlugin.
209 InputMethodSubtype inputMethodSubtype = mock(InputMethodSubtype.class);
210 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
211 testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
212 View testView = new View(ctx);
213 TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
215 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
216 textInputPlugin.setTextInputClient(
217 0,
219 false,
220 false,
221 true,
222 true,
223 true, // Enable delta model.
225 null,
226 null,
227 null,
228 null,
229 null,
230 null));
231
232 textInputPlugin.setTextInputEditingState(
233 testView,
234 new TextInputChannel.TextEditState("receiving initial input from framework", 0, 0, -1, -1));
235 assertTrue(
236 textInputPlugin.getEditable().toString().equals("receiving initial input from framework"));
237
238 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
239
240 textInputPlugin.setTextInputEditingState(
241 testView,
243 "receiving more updates from the framework", 1, 2, -1, -1));
244
245 assertTrue(
247 .getEditable()
248 .toString()
249 .equals("receiving more updates from the framework"));
250 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
251 }
252
253 @Test
255 throws NullPointerException {
256 // Initialize a general TextInputPlugin.
257 InputMethodSubtype inputMethodSubtype = mock(InputMethodSubtype.class);
258 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
259 testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
260 View testView = new View(ctx);
261 EditorInfo outAttrs = new EditorInfo();
262 outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
263 TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
265 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
266 CharSequence newText = "I do not fear computers. I fear the lack of them.";
267
268 // Change InputTarget to FRAMEWORK_CLIENT.
269 textInputPlugin.setTextInputClient(
270 0,
272 false,
273 false,
274 true,
275 true,
276 false, // Delta model is disabled.
279 null,
280 null,
281 null,
282 null,
283 null));
284
285 // There's a pending restart since we initialized the text input client. Flush that now.
286 textInputPlugin.setTextInputEditingState(
287 testView, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
288 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
289 verify(textInputChannel, times(0))
290 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
291 assertEquals(
292 0,
294 .extractBatchTextEditingDeltas()
295 .size());
296
297 InputConnection inputConnection =
298 textInputPlugin.createInputConnection(testView, mock(KeyboardManager.class), outAttrs);
299
300 inputConnection.beginBatchEdit();
301 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
302 verify(textInputChannel, times(0))
303 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
304 inputConnection.setComposingText(newText, newText.length());
305 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
306 verify(textInputChannel, times(0))
307 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
308 inputConnection.endBatchEdit();
309
310 assertEquals(
311 0,
313 .extractBatchTextEditingDeltas()
314 .size());
315
316 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
317 verify(textInputChannel, times(1))
318 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
319
320 inputConnection.beginBatchEdit();
321
322 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
323 verify(textInputChannel, times(1))
324 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
325
326 inputConnection.endBatchEdit();
327
328 assertEquals(
329 0,
331 .extractBatchTextEditingDeltas()
332 .size());
333
334 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
335 verify(textInputChannel, times(1))
336 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
337
338 inputConnection.beginBatchEdit();
339
340 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
341 verify(textInputChannel, times(1))
342 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
343
344 // Selection changes so this will trigger an update to the framework through
345 // updateEditingStateWithDeltas after the batch edit has completed and notified all listeners
346 // of the editing state.
347 inputConnection.setSelection(3, 4);
348 assertEquals(Selection.getSelectionStart(textInputPlugin.getEditable()), 3);
349 assertEquals(Selection.getSelectionEnd(textInputPlugin.getEditable()), 4);
350
351 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
352 verify(textInputChannel, times(1))
353 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
354
355 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
356 verify(textInputChannel, times(1))
357 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
358
359 inputConnection.endBatchEdit();
360
361 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
362 verify(textInputChannel, times(2))
363 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
364 }
365
366 @Test
368 throws NullPointerException {
369 // Initialize a general TextInputPlugin.
370 InputMethodSubtype inputMethodSubtype = mock(InputMethodSubtype.class);
371 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
372 testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
373 View testView = new View(ctx);
374 EditorInfo outAttrs = new EditorInfo();
375 outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
376 TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
378 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
379 CharSequence newText = "I do not fear computers. I fear the lack of them.";
380 final TextEditingDelta expectedDelta =
381 new TextEditingDelta("", 0, 0, newText, newText.length(), newText.length(), 0, 49);
382
383 // Change InputTarget to FRAMEWORK_CLIENT.
384 textInputPlugin.setTextInputClient(
385 0,
387 false,
388 false,
389 true,
390 true,
391 true, // Enable delta model.
394 null,
395 null,
396 null,
397 null,
398 null));
399
400 // There's a pending restart since we initialized the text input client. Flush that now.
401 textInputPlugin.setTextInputEditingState(
402 testView, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
403 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
404 verify(textInputChannel, times(0))
405 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
406 assertEquals(
407 0,
409 .extractBatchTextEditingDeltas()
410 .size());
411
412 InputConnection inputConnection =
413 textInputPlugin.createInputConnection(testView, mock(KeyboardManager.class), outAttrs);
414
415 inputConnection.beginBatchEdit();
416 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
417 verify(textInputChannel, times(0))
418 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
419 inputConnection.setComposingText(newText, newText.length());
420 final ArrayList<TextEditingDelta> actualDeltas =
421 ((ListenableEditingState) textInputPlugin.getEditable()).extractBatchTextEditingDeltas();
422 assertEquals(2, actualDeltas.size());
423 final TextEditingDelta delta = actualDeltas.get(1);
424 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
425 verify(textInputChannel, times(0))
426 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
427 inputConnection.endBatchEdit();
428
429 assertEquals(
430 0,
432 .extractBatchTextEditingDeltas()
433 .size());
434
435 // Verify delta is what we expect.
436 assertEquals(expectedDelta.getOldText(), delta.getOldText());
437 assertEquals(expectedDelta.getDeltaText(), delta.getDeltaText());
438 assertEquals(expectedDelta.getDeltaStart(), delta.getDeltaStart());
439 assertEquals(expectedDelta.getDeltaEnd(), delta.getDeltaEnd());
440 assertEquals(expectedDelta.getNewSelectionStart(), delta.getNewSelectionStart());
441 assertEquals(expectedDelta.getNewSelectionEnd(), delta.getNewSelectionEnd());
442 assertEquals(expectedDelta.getNewComposingStart(), delta.getNewComposingStart());
443 assertEquals(expectedDelta.getNewComposingEnd(), delta.getNewComposingEnd());
444
445 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
446 verify(textInputChannel, times(0))
447 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
448
449 inputConnection.beginBatchEdit();
450
451 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
452 verify(textInputChannel, times(0))
453 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
454
455 inputConnection.endBatchEdit();
456
457 assertEquals(
458 0,
460 .extractBatchTextEditingDeltas()
461 .size());
462
463 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
464 verify(textInputChannel, times(0))
465 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
466
467 inputConnection.beginBatchEdit();
468
469 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
470 verify(textInputChannel, times(0))
471 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
472
473 // Selection changes so this will trigger an update to the framework through
474 // updateEditingStateWithDeltas after the batch edit has completed and notified all listeners
475 // of the editing state.
476 inputConnection.setSelection(3, 4);
477 assertEquals(Selection.getSelectionStart(textInputPlugin.getEditable()), 3);
478 assertEquals(Selection.getSelectionEnd(textInputPlugin.getEditable()), 4);
479
480 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
481 verify(textInputChannel, times(0))
482 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
483
484 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
485 verify(textInputChannel, times(0))
486 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
487
488 inputConnection.endBatchEdit();
489
490 verify(textInputChannel, times(2)).updateEditingStateWithDeltas(anyInt(), any());
491 verify(textInputChannel, times(0))
492 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
493 }
494
495 @Test
497 throws NullPointerException {
498 // Initialize a general TextInputPlugin.
499 InputMethodSubtype inputMethodSubtype = mock(InputMethodSubtype.class);
500 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
501 testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
502 View testView = new View(ctx);
503 EditorInfo outAttrs = new EditorInfo();
504 outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
505 TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
507 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
508 CharSequence newText = "I do not fear computers. I fear the lack of them.";
509 final TextEditingDelta expectedDelta =
510 new TextEditingDelta("", 0, 0, newText, newText.length(), newText.length(), 0, 49);
511
512 // Change InputTarget to FRAMEWORK_CLIENT.
513 textInputPlugin.setTextInputClient(
514 0,
516 false,
517 false,
518 true,
519 true,
520 true, // Enable delta model.
523 null,
524 null,
525 null,
526 null,
527 null));
528
529 // There's a pending restart since we initialized the text input client. Flush that now.
530 textInputPlugin.setTextInputEditingState(
531 testView, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
532 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
533 assertEquals(
534 0,
536 .extractBatchTextEditingDeltas()
537 .size());
538
539 InputConnection inputConnection =
540 textInputPlugin.createInputConnection(testView, mock(KeyboardManager.class), outAttrs);
541
542 inputConnection.beginBatchEdit();
543 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
544 inputConnection.setComposingText(newText, newText.length());
545 final ArrayList<TextEditingDelta> actualDeltas =
546 ((ListenableEditingState) textInputPlugin.getEditable()).extractBatchTextEditingDeltas();
547 assertEquals(2, actualDeltas.size());
548 final TextEditingDelta delta = actualDeltas.get(1);
549 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
550 inputConnection.endBatchEdit();
551
552 assertEquals(
553 0,
555 .extractBatchTextEditingDeltas()
556 .size());
557
558 // Verify delta is what we expect.
559 assertEquals(expectedDelta.getOldText(), delta.getOldText());
560 assertEquals(expectedDelta.getDeltaText(), delta.getDeltaText());
561 assertEquals(expectedDelta.getDeltaStart(), delta.getDeltaStart());
562 assertEquals(expectedDelta.getDeltaEnd(), delta.getDeltaEnd());
563 assertEquals(expectedDelta.getNewSelectionStart(), delta.getNewSelectionStart());
564 assertEquals(expectedDelta.getNewSelectionEnd(), delta.getNewSelectionEnd());
565 assertEquals(expectedDelta.getNewComposingStart(), delta.getNewComposingStart());
566 assertEquals(expectedDelta.getNewComposingEnd(), delta.getNewComposingEnd());
567
568 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
569
570 inputConnection.beginBatchEdit();
571
572 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
573
574 inputConnection.endBatchEdit();
575
576 assertEquals(
577 0,
579 .extractBatchTextEditingDeltas()
580 .size());
581
582 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
583
584 inputConnection.beginBatchEdit();
585
586 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
587
588 // Selection changes so this will trigger an update to the framework through
589 // updateEditingStateWithDeltas after the batch edit has completed and notified all listeners
590 // of the editing state.
591 inputConnection.setSelection(3, 4);
592 assertEquals(Selection.getSelectionStart(textInputPlugin.getEditable()), 3);
593 assertEquals(Selection.getSelectionEnd(textInputPlugin.getEditable()), 4);
594
595 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
596
597 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
598
599 inputConnection.endBatchEdit();
600
601 verify(textInputChannel, times(2)).updateEditingStateWithDeltas(anyInt(), any());
602 }
603
604 @Test
606 throws NullPointerException {
607 // Initialize a general TextInputPlugin.
608 InputMethodSubtype inputMethodSubtype = mock(InputMethodSubtype.class);
609 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
610 testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
611 View testView = new View(ctx);
612 EditorInfo outAttrs = new EditorInfo();
613 outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
614 TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
616 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
617 CharSequence newText = "I do not fear computers. I fear the lack of them.";
618 final TextEditingDelta expectedDelta =
620 newText, 0, 49, "I do not fear computers. I fear the lack of them", 48, 48, 0, 48);
621
622 // Change InputTarget to FRAMEWORK_CLIENT.
623 textInputPlugin.setTextInputClient(
624 0,
626 false,
627 false,
628 true,
629 true,
630 true, // Enable delta model.
633 null,
634 null,
635 null,
636 null,
637 null));
638
639 // There's a pending restart since we initialized the text input client. Flush that now.
640 textInputPlugin.setTextInputEditingState(
641 testView, new TextInputChannel.TextEditState(newText.toString(), 49, 49, 0, 49));
642 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
643 assertEquals(
644 0,
646 .extractBatchTextEditingDeltas()
647 .size());
648
649 InputConnection inputConnection =
650 textInputPlugin.createInputConnection(testView, mock(KeyboardManager.class), outAttrs);
651
652 inputConnection.beginBatchEdit();
653 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
654 inputConnection.setComposingText("I do not fear computers. I fear the lack of them", 48);
655 final ArrayList<TextEditingDelta> actualDeltas =
656 ((ListenableEditingState) textInputPlugin.getEditable()).extractBatchTextEditingDeltas();
657 final TextEditingDelta delta = actualDeltas.get(1);
658 System.out.println(delta.getDeltaText());
659 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
660 inputConnection.endBatchEdit();
661
662 assertEquals(
663 0,
665 .extractBatchTextEditingDeltas()
666 .size());
667
668 // Verify delta is what we expect.
669 assertEquals(expectedDelta.getOldText(), delta.getOldText());
670 assertEquals(expectedDelta.getDeltaText(), delta.getDeltaText());
671 assertEquals(expectedDelta.getDeltaStart(), delta.getDeltaStart());
672 assertEquals(expectedDelta.getDeltaEnd(), delta.getDeltaEnd());
673 assertEquals(expectedDelta.getNewSelectionStart(), delta.getNewSelectionStart());
674 assertEquals(expectedDelta.getNewSelectionEnd(), delta.getNewSelectionEnd());
675 assertEquals(expectedDelta.getNewComposingStart(), delta.getNewComposingStart());
676 assertEquals(expectedDelta.getNewComposingEnd(), delta.getNewComposingEnd());
677
678 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
679
680 inputConnection.beginBatchEdit();
681
682 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
683
684 inputConnection.endBatchEdit();
685
686 assertEquals(
687 0,
689 .extractBatchTextEditingDeltas()
690 .size());
691
692 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
693
694 inputConnection.beginBatchEdit();
695
696 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
697
698 // Selection changes so this will trigger an update to the framework through
699 // updateEditingStateWithDeltas after the batch edit has completed and notified all listeners
700 // of the editing state.
701 inputConnection.setSelection(3, 4);
702 assertEquals(Selection.getSelectionStart(textInputPlugin.getEditable()), 3);
703 assertEquals(Selection.getSelectionEnd(textInputPlugin.getEditable()), 4);
704
705 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
706
707 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
708
709 inputConnection.endBatchEdit();
710
711 verify(textInputChannel, times(2)).updateEditingStateWithDeltas(anyInt(), any());
712 }
713
714 @Test
716 throws NullPointerException {
717 // Initialize a general TextInputPlugin.
718 InputMethodSubtype inputMethodSubtype = mock(InputMethodSubtype.class);
719 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
720 testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
721 View testView = new View(ctx);
722 EditorInfo outAttrs = new EditorInfo();
723 outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
724 TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
726 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
727 CharSequence newText = "helfo";
728 final TextEditingDelta expectedDelta = new TextEditingDelta(newText, 0, 5, "hello", 5, 5, 0, 5);
729
730 // Change InputTarget to FRAMEWORK_CLIENT.
731 textInputPlugin.setTextInputClient(
732 0,
734 false,
735 false,
736 true,
737 true,
738 true, // Enable delta model.
741 null,
742 null,
743 null,
744 null,
745 null));
746
747 // There's a pending restart since we initialized the text input client. Flush that now.
748 textInputPlugin.setTextInputEditingState(
749 testView, new TextInputChannel.TextEditState(newText.toString(), 5, 5, 0, 5));
750 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
751 assertEquals(
752 0,
754 .extractBatchTextEditingDeltas()
755 .size());
756
757 InputConnection inputConnection =
758 textInputPlugin.createInputConnection(testView, mock(KeyboardManager.class), outAttrs);
759
760 inputConnection.beginBatchEdit();
761 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
762 inputConnection.setComposingText("hello", 5);
763 final ArrayList<TextEditingDelta> actualDeltas =
764 ((ListenableEditingState) textInputPlugin.getEditable()).extractBatchTextEditingDeltas();
765 final TextEditingDelta delta = actualDeltas.get(1);
766 System.out.println(delta.getDeltaText());
767 verify(textInputChannel, times(0)).updateEditingStateWithDeltas(anyInt(), any());
768 inputConnection.endBatchEdit();
769
770 assertEquals(
771 0,
773 .extractBatchTextEditingDeltas()
774 .size());
775
776 // Verify delta is what we expect.
777 assertEquals(expectedDelta.getOldText(), delta.getOldText());
778 assertEquals(expectedDelta.getDeltaText(), delta.getDeltaText());
779 assertEquals(expectedDelta.getDeltaStart(), delta.getDeltaStart());
780 assertEquals(expectedDelta.getDeltaEnd(), delta.getDeltaEnd());
781 assertEquals(expectedDelta.getNewSelectionStart(), delta.getNewSelectionStart());
782 assertEquals(expectedDelta.getNewSelectionEnd(), delta.getNewSelectionEnd());
783 assertEquals(expectedDelta.getNewComposingStart(), delta.getNewComposingStart());
784 assertEquals(expectedDelta.getNewComposingEnd(), delta.getNewComposingEnd());
785
786 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
787
788 inputConnection.beginBatchEdit();
789
790 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
791
792 inputConnection.endBatchEdit();
793
794 assertEquals(
795 0,
797 .extractBatchTextEditingDeltas()
798 .size());
799
800 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
801
802 inputConnection.beginBatchEdit();
803
804 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
805
806 // Selection changes so this will trigger an update to the framework through
807 // updateEditingStateWithDeltas after the batch edit has completed and notified all listeners
808 // of the editing state.
809 inputConnection.setSelection(3, 4);
810 assertEquals(Selection.getSelectionStart(textInputPlugin.getEditable()), 3);
811 assertEquals(Selection.getSelectionEnd(textInputPlugin.getEditable()), 4);
812
813 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
814
815 verify(textInputChannel, times(1)).updateEditingStateWithDeltas(anyInt(), any());
816
817 inputConnection.endBatchEdit();
818
819 verify(textInputChannel, times(2)).updateEditingStateWithDeltas(anyInt(), any());
820 }
821
822 @Test
823 public void inputConnectionAdaptor_RepeatFilter() throws NullPointerException {
824 // Initialize a general TextInputPlugin.
825 InputMethodSubtype inputMethodSubtype = mock(InputMethodSubtype.class);
826 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
827 testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
828 View testView = new View(ctx);
829 EditorInfo outAttrs = new EditorInfo();
830 outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
831 TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
833 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
834
835 // Change InputTarget to FRAMEWORK_CLIENT.
836 textInputPlugin.setTextInputClient(
837 0,
839 false,
840 false,
841 true,
842 true,
843 false,
846 null,
847 null,
848 null,
849 null,
850 null));
851
852 // There's a pending restart since we initialized the text input client. Flush that now.
853 textInputPlugin.setTextInputEditingState(
854 testView, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
855 verify(textInputChannel, times(0))
856 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
857
858 InputConnectionAdaptor inputConnectionAdaptor =
860 textInputPlugin.createInputConnection(testView, mock(KeyboardManager.class), outAttrs);
861
862 inputConnectionAdaptor.beginBatchEdit();
863 verify(textInputChannel, times(0))
864 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
865 inputConnectionAdaptor.setComposingText("I do not fear computers. I fear the lack of them.", 1);
866 verify(textInputChannel, times(0))
867 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
868 inputConnectionAdaptor.endBatchEdit();
869 verify(textInputChannel, times(1))
870 .updateEditingState(
871 anyInt(),
872 eq("I do not fear computers. I fear the lack of them."),
873 eq(49),
874 eq(49),
875 eq(0),
876 eq(49));
877
878 inputConnectionAdaptor.beginBatchEdit();
879
880 verify(textInputChannel, times(1))
881 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
882
883 inputConnectionAdaptor.endBatchEdit();
884
885 verify(textInputChannel, times(1))
886 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
887
888 inputConnectionAdaptor.beginBatchEdit();
889
890 verify(textInputChannel, times(1))
891 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
892
893 inputConnectionAdaptor.setSelection(3, 4);
894 assertEquals(Selection.getSelectionStart(textInputPlugin.getEditable()), 3);
895 assertEquals(Selection.getSelectionEnd(textInputPlugin.getEditable()), 4);
896
897 verify(textInputChannel, times(1))
898 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
899
900 verify(textInputChannel, times(1))
901 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
902
903 inputConnectionAdaptor.endBatchEdit();
904
905 verify(textInputChannel, times(1))
906 .updateEditingState(
907 anyInt(),
908 eq("I do not fear computers. I fear the lack of them."),
909 eq(3),
910 eq(4),
911 eq(0),
912 eq(49));
913 }
914
915 @Test
917 // Initialize a general TextInputPlugin.
918 InputMethodSubtype inputMethodSubtype = mock(InputMethodSubtype.class);
919 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
920 testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
921 View testView = new View(ctx);
922 TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
924 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
925 textInputPlugin.setTextInputClient(
926 0,
928 false,
929 false,
930 true,
931 true,
932 false,
934 null,
935 null,
936 null,
937 null,
938 null,
939 null));
940 // There's a pending restart since we initialized the text input client. Flush that now.
941 textInputPlugin.setTextInputEditingState(
942 testView, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
943
944 // Move the cursor.
945 assertEquals(1, testImm.getRestartCount(testView));
946 textInputPlugin.setTextInputEditingState(
947 testView, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
948
949 // Verify that we haven't restarted the input.
950 assertEquals(1, testImm.getRestartCount(testView));
951 }
952
953 @Test
955 // Initialize a general TextInputPlugin.
956 InputMethodSubtype inputMethodSubtype = mock(InputMethodSubtype.class);
957 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
958 testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
959 View testView = new View(ctx);
960 TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
962 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
963 textInputPlugin.setTextInputClient(
964 0,
966 false,
967 false,
968 true,
969 true,
970 false,
972 null,
973 null,
974 null,
975 null,
976 null,
977 null));
978 // There's a pending restart since we initialized the text input client. Flush that now. With
979 // changed text, we should
980 // always set the Editable contents.
981 textInputPlugin.setTextInputEditingState(
982 testView, new TextInputChannel.TextEditState("hello", 0, 0, -1, -1));
983 assertEquals(1, testImm.getRestartCount(testView));
984 assertTrue(textInputPlugin.getEditable().toString().equals("hello"));
985
986 // No pending restart, set Editable contents anyways.
987 textInputPlugin.setTextInputEditingState(
988 testView, new TextInputChannel.TextEditState("Shibuyawoo", 0, 0, -1, -1));
989 assertEquals(1, testImm.getRestartCount(testView));
990 assertTrue(textInputPlugin.getEditable().toString().equals("Shibuyawoo"));
991 }
992
993 // See https://github.com/flutter/flutter/issues/29341 and
994 // https://github.com/flutter/flutter/issues/31512
995 // All modern Samsung keybords are affected including non-korean languages and thus
996 // need the restart.
997 // Update: many other keyboards need this too:
998 // https://github.com/flutter/flutter/issues/78827
999 @SuppressWarnings("deprecation") // InputMethodSubtype
1000 @Test
1002 // Initialize a TextInputPlugin that needs to be always restarted.
1003 InputMethodSubtype inputMethodSubtype =
1004 new InputMethodSubtype(0, 0, /*locale=*/ "en", "", "", false, false);
1005 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
1006 testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
1007 View testView = new View(ctx);
1008 TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
1010 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1011 textInputPlugin.setTextInputClient(
1012 0,
1014 false,
1015 false,
1016 true,
1017 true,
1018 false,
1021 null,
1022 null,
1023 null,
1024 null,
1025 null));
1026 // There's a pending restart since we initialized the text input client. Flush that now.
1027 textInputPlugin.setTextInputEditingState(
1028 testView, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1029 assertEquals(1, testImm.getRestartCount(testView));
1030 InputConnection connection =
1031 textInputPlugin.createInputConnection(
1032 testView, mock(KeyboardManager.class), new EditorInfo());
1033 connection.setComposingText("POWERRRRR", 1);
1034
1035 textInputPlugin.setTextInputEditingState(
1036 testView, new TextInputChannel.TextEditState("UNLIMITED POWERRRRR", 0, 0, 10, 19));
1037 // Does not restart since the composing text is not changed.
1038 assertEquals(1, testImm.getRestartCount(testView));
1039
1040 connection.finishComposingText();
1041 // Does not restart since the composing text is committed by the IME.
1042 assertEquals(1, testImm.getRestartCount(testView));
1043
1044 // Does not restart since the composing text is changed by the IME.
1045 connection.setComposingText("POWERRRRR", 1);
1046 assertEquals(1, testImm.getRestartCount(testView));
1047
1048 // The framework tries to commit the composing region.
1049 textInputPlugin.setTextInputEditingState(
1050 testView, new TextInputChannel.TextEditState("POWERRRRR", 0, 0, -1, -1));
1051
1052 // Verify that we've restarted the input.
1053 assertEquals(2, testImm.getRestartCount(testView));
1054 }
1055
1056 @Test
1058 // Index OOB:
1059 assertThrows(IndexOutOfBoundsException.class, () -> new TextEditState("", 0, -9, -1, -1));
1060 assertThrows(IndexOutOfBoundsException.class, () -> new TextEditState("", -9, 0, -1, -1));
1061 assertThrows(IndexOutOfBoundsException.class, () -> new TextEditState("", 0, 1, -1, -1));
1062 assertThrows(IndexOutOfBoundsException.class, () -> new TextEditState("", 1, 0, -1, -1));
1063 assertThrows(IndexOutOfBoundsException.class, () -> new TextEditState("Text", 0, 0, 1, 5));
1064 assertThrows(IndexOutOfBoundsException.class, () -> new TextEditState("Text", 0, 0, 5, 1));
1065 assertThrows(IndexOutOfBoundsException.class, () -> new TextEditState("Text", 0, 0, 5, 5));
1066
1067 // Invalid Selections:
1068 assertThrows(IndexOutOfBoundsException.class, () -> new TextEditState("", -1, -2, -1, -1));
1069 assertThrows(IndexOutOfBoundsException.class, () -> new TextEditState("", -2, -1, -1, -1));
1070 assertThrows(IndexOutOfBoundsException.class, () -> new TextEditState("", -9, -9, -1, -1));
1071
1072 // Invalid Composing Ranges:
1073 assertThrows(IndexOutOfBoundsException.class, () -> new TextEditState("Text", 0, 0, -9, -1));
1074 assertThrows(IndexOutOfBoundsException.class, () -> new TextEditState("Text", 0, 0, -1, -9));
1075 assertThrows(IndexOutOfBoundsException.class, () -> new TextEditState("Text", 0, 0, -9, -9));
1076 assertThrows(IndexOutOfBoundsException.class, () -> new TextEditState("Text", 0, 0, 2, 1));
1077
1078 // Valid values (does not throw):
1079 // Nothing selected/composing:
1080 TextEditState state = new TextEditState("", -1, -1, -1, -1);
1081 assertEquals("", state.text);
1082 assertEquals(-1, state.selectionStart);
1083 assertEquals(-1, state.selectionEnd);
1084 assertEquals(-1, state.composingStart);
1085 assertEquals(-1, state.composingEnd);
1086 // Collapsed selection.
1087 state = new TextEditState("x", 0, 0, 0, 1);
1088 assertEquals(0, state.selectionStart);
1089 assertEquals(0, state.selectionEnd);
1090 // Reversed Selection.
1091 state = new TextEditState("REEEE", 4, 2, -1, -1);
1092 assertEquals(4, state.selectionStart);
1093 assertEquals(2, state.selectionEnd);
1094 // A collapsed selection and composing range.
1095 state = new TextEditState("text", 0, 0, 0, 0);
1096 assertEquals("text", state.text);
1097 assertEquals(0, state.selectionStart);
1098 assertEquals(0, state.selectionEnd);
1099 assertEquals(0, state.composingStart);
1100 assertEquals(0, state.composingEnd);
1101 }
1102
1103 @Test
1105 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
1106 testImm.setCurrentInputMethodSubtype(null);
1107
1108 View testView = new View(ctx);
1109 TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
1111 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1112 textInputPlugin.setTextInputClient(
1113 0,
1115 false,
1116 false,
1117 true,
1118 true,
1119 false,
1121 null,
1122 null,
1123 null,
1124 null,
1125 null,
1126 null));
1127 // There's a pending restart since we initialized the text input client. Flush that now.
1128 textInputPlugin.setTextInputEditingState(
1129 testView, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1130 assertEquals(1, testImm.getRestartCount(testView));
1131 }
1132
1133 @Test
1135 // Initialize a general TextInputPlugin.
1136 InputMethodSubtype inputMethodSubtype = mock(InputMethodSubtype.class);
1137 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
1138 testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
1139 View testView = new View(ctx);
1140 TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
1142 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1143 textInputPlugin.setTextInputClient(
1144 0,
1146 false,
1147 false,
1148 true,
1149 true,
1150 false,
1152 null,
1153 null,
1154 null,
1155 null,
1156 null,
1157 null));
1158 // There's a pending restart since we initialized the text input client. Flush that now.
1159 textInputPlugin.setTextInputEditingState(
1160 testView, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1161 assertEquals(1, testImm.getRestartCount(testView));
1162
1163 // A restart is always forced when calling clearTextInputClient().
1165 assertEquals(2, testImm.getRestartCount(testView));
1166 }
1167
1168 @Test
1170 View testView = new View(ctx);
1171 TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
1173 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1174 verify(textInputChannel, times(1)).setTextInputMethodHandler(isNotNull());
1175 textInputPlugin.destroy();
1176 verify(textInputChannel, times(1)).setTextInputMethodHandler(isNull());
1177 }
1178
1179 @SuppressWarnings("deprecation")
1180 // DartExecutor.send is deprecated.
1181 private void verifyInputConnection(TextInputChannel.TextInputType textInputType)
1182 throws JSONException {
1183 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
1184 FlutterJNI mockFlutterJni = mock(FlutterJNI.class);
1185 View testView = new View(ctx);
1186 DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJni, mock(AssetManager.class)));
1187 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
1189 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1190 textInputPlugin.setTextInputClient(
1191 0,
1193 false,
1194 false,
1195 true,
1196 true,
1197 false,
1199 new TextInputChannel.InputType(textInputType, false, false),
1200 null,
1201 null,
1202 null,
1203 null,
1204 null));
1205 // There's a pending restart since we initialized the text input client. Flush that now.
1206 textInputPlugin.setTextInputEditingState(
1207 testView, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1208
1209 ArgumentCaptor<String> channelCaptor = ArgumentCaptor.forClass(String.class);
1210 ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
1211 verify(dartExecutor, times(1)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull());
1212 assertEquals("flutter/textinput", channelCaptor.getValue());
1213 verifyMethodCall(bufferCaptor.getValue(), "TextInputClient.requestExistingInputState", null);
1214 InputConnectionAdaptor connection =
1216 textInputPlugin.createInputConnection(
1217 testView, mock(KeyboardManager.class), new EditorInfo());
1218
1219 connection.handleKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
1220 verify(dartExecutor, times(2)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull());
1221 assertEquals("flutter/textinput", channelCaptor.getValue());
1222 verifyMethodCall(
1223 bufferCaptor.getValue(),
1224 "TextInputClient.performAction",
1225 new String[] {"0", "TextInputAction.done"});
1226 connection.handleKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER));
1227
1228 connection.handleKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_NUMPAD_ENTER));
1229 verify(dartExecutor, times(3)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull());
1230 assertEquals("flutter/textinput", channelCaptor.getValue());
1231 verifyMethodCall(
1232 bufferCaptor.getValue(),
1233 "TextInputClient.performAction",
1234 new String[] {"0", "TextInputAction.done"});
1235 }
1236
1237 @Test
1238 public void inputConnection_createsActionFromEnter() throws JSONException {
1239 verifyInputConnection(TextInputChannel.TextInputType.TEXT);
1240 }
1241
1242 @Test
1244 verifyInputConnection(TextInputChannel.TextInputType.NONE);
1245 }
1246
1247 @SuppressWarnings("deprecation") // InputMethodSubtype
1248 @Test
1249 public void inputConnection_finishComposingTextUpdatesIMM() throws JSONException {
1250 ShadowBuild.setManufacturer("samsung");
1251 InputMethodSubtype inputMethodSubtype =
1252 new InputMethodSubtype(0, 0, /*locale=*/ "en", "", "", false, false);
1253 Settings.Secure.putString(
1254 ctx.getContentResolver(),
1255 Settings.Secure.DEFAULT_INPUT_METHOD,
1256 "com.sec.android.inputmethod/.SamsungKeypad");
1257 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
1258 testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
1259 FlutterJNI mockFlutterJni = mock(FlutterJNI.class);
1260 View testView = new View(ctx);
1261 DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJni, mock(AssetManager.class)));
1262 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
1264 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1265 textInputPlugin.setTextInputClient(
1266 0,
1268 false,
1269 false,
1270 true,
1271 true,
1272 false,
1275 null,
1276 null,
1277 null,
1278 null,
1279 null));
1280 // There's a pending restart since we initialized the text input client. Flush that now.
1281 textInputPlugin.setTextInputEditingState(
1282 testView, new TextInputChannel.TextEditState("text", 0, 0, -1, -1));
1283 InputConnection connection =
1284 textInputPlugin.createInputConnection(
1285 testView, mock(KeyboardManager.class), new EditorInfo());
1286
1287 connection.requestCursorUpdates(
1288 InputConnection.CURSOR_UPDATE_MONITOR | InputConnection.CURSOR_UPDATE_IMMEDIATE);
1289
1290 connection.finishComposingText();
1291
1292 assertEquals(-1, testImm.getLastCursorAnchorInfo().getComposingTextStart());
1293 assertEquals(0, testImm.getLastCursorAnchorInfo().getComposingText().length());
1294 }
1295
1296 @Test
1298 View testView = new View(ctx);
1299 DartExecutor dartExecutor = mock(DartExecutor.class);
1300 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
1302 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1303 textInputPlugin.setTextInputClient(
1304 0,
1306 false,
1307 false,
1308 true,
1309 true,
1310 false,
1313 null,
1314 null,
1315 null,
1316 null,
1317 null));
1318
1319 InputConnection connection =
1320 textInputPlugin.createInputConnection(
1321 testView, mock(KeyboardManager.class), new EditorInfo());
1322 assertNotNull(connection);
1323 }
1324
1325 @Test
1327 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
1328 View testView = new View(ctx);
1329 DartExecutor dartExecutor = mock(DartExecutor.class);
1330 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
1332 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1333 textInputPlugin.setTextInputClient(
1334 0,
1336 false,
1337 false,
1338 true,
1339 true,
1340 false,
1343 null,
1344 null,
1345 null,
1346 null,
1347 null));
1348
1350 assertEquals(testImm.isSoftInputVisible(), false);
1351 }
1352
1353 @Test
1355 // Regression test for https://github.com/flutter/flutter/issues/71679.
1356 View testView = new View(ctx);
1357 DartExecutor dartExecutor = mock(DartExecutor.class);
1358 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
1360 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1361 textInputPlugin.setTextInputClient(
1362 0,
1364 false,
1365 false,
1366 false, // Disable suggestions.
1367 true,
1368 false,
1371 null,
1372 null,
1373 null,
1374 null,
1375 null));
1376
1377 EditorInfo editorInfo = new EditorInfo();
1378 InputConnection connection =
1379 textInputPlugin.createInputConnection(testView, mock(KeyboardManager.class), editorInfo);
1380
1381 assertEquals(
1382 editorInfo.inputType,
1383 InputType.TYPE_CLASS_TEXT
1384 | InputType.TYPE_TEXT_FLAG_MULTI_LINE
1385 | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
1386 | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
1387 }
1388
1389 // -------- Start: Autofill Tests -------
1390 @Test
1392 if (Build.VERSION.SDK_INT < API_LEVELS.API_26) {
1393 return;
1394 }
1395 FlutterView testView = new FlutterView(ctx);
1396 TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
1398 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1399 final TextInputChannel.Configuration.Autofill autofill =
1401 "1", new String[] {}, null, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1402
1403 final TextInputChannel.Configuration config =
1405 false,
1406 false,
1407 true,
1408 true,
1409 false,
1411 null,
1412 null,
1413 null,
1414 autofill,
1415 null,
1416 null);
1417
1418 textInputPlugin.setTextInputClient(
1419 0,
1421 false,
1422 false,
1423 true,
1424 true,
1425 false,
1427 null,
1428 null,
1429 null,
1430 autofill,
1431 null,
1432 new TextInputChannel.Configuration[] {config}));
1433
1434 final ViewStructure viewStructure = mock(ViewStructure.class);
1435 final ViewStructure[] children = {mock(ViewStructure.class), mock(ViewStructure.class)};
1436
1437 when(viewStructure.newChild(anyInt()))
1438 .thenAnswer(invocation -> children[(int) invocation.getArgument(0)]);
1439
1440 textInputPlugin.onProvideAutofillVirtualStructure(viewStructure, 0);
1441
1442 verify(viewStructure).newChild(0);
1443
1444 verify(children[0]).setAutofillId(any(), eq("1".hashCode()));
1445 // The flutter application sends an empty hint list, don't set hints.
1446 verify(children[0], never()).setAutofillHints(aryEq(new String[] {}));
1447 verify(children[0]).setDimens(anyInt(), anyInt(), anyInt(), anyInt(), gt(0), gt(0));
1448 }
1449
1450 @Test
1452 if (Build.VERSION.SDK_INT < API_LEVELS.API_26) {
1453 return;
1454 }
1455 FlutterView testView = new FlutterView(ctx);
1456 TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
1458 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1459 final TextInputChannel.Configuration.Autofill autofill =
1461 "1", new String[] {}, null, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1462
1463 final TextInputChannel.Configuration config =
1465 false,
1466 false,
1467 true,
1468 true,
1469 false,
1471 null,
1472 null,
1473 null,
1474 null,
1475 null,
1476 null);
1477
1478 textInputPlugin.setTextInputClient(0, config);
1479
1480 final ViewStructure viewStructure = mock(ViewStructure.class);
1481
1482 textInputPlugin.onProvideAutofillVirtualStructure(viewStructure, 0);
1483
1484 verify(viewStructure, times(0)).newChild(anyInt());
1485 }
1486
1487 @Test
1488 public void autofill_hintText() {
1489 if (Build.VERSION.SDK_INT < API_LEVELS.API_26) {
1490 return;
1491 }
1492 FlutterView testView = new FlutterView(ctx);
1493 TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
1495 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1496 final TextInputChannel.Configuration.Autofill autofill =
1498 "1",
1499 new String[] {},
1500 "placeholder",
1501 new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1502
1503 final TextInputChannel.Configuration config =
1505 false,
1506 false,
1507 true,
1508 true,
1509 false,
1511 null,
1512 null,
1513 null,
1514 autofill,
1515 null,
1516 null);
1517
1518 textInputPlugin.setTextInputClient(0, config);
1519
1520 final ViewStructure viewStructure = mock(ViewStructure.class);
1521 final ViewStructure[] children = {mock(ViewStructure.class), mock(ViewStructure.class)};
1522
1523 when(viewStructure.newChild(anyInt()))
1524 .thenAnswer(invocation -> children[(int) invocation.getArgument(0)]);
1525
1526 textInputPlugin.onProvideAutofillVirtualStructure(viewStructure, 0);
1527 verify(children[0]).setHint("placeholder");
1528 }
1529
1530 @Config(minSdk = API_LEVELS.API_26)
1531 @SuppressWarnings("deprecation")
1532 @Test
1534 if (Build.VERSION.SDK_INT < API_LEVELS.API_26) {
1535 return;
1536 }
1537 FlutterView testView = getTestView();
1538 TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
1540 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1541 final TextInputChannel.Configuration.Autofill autofill1 =
1543 "1",
1544 new String[] {"HINT1"},
1545 "placeholder1",
1546 new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1547 final TextInputChannel.Configuration.Autofill autofill2 =
1549 "2",
1550 new String[] {"HINT2", "EXTRA"},
1551 null,
1552 new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1553
1554 final TextInputChannel.Configuration config1 =
1556 false,
1557 false,
1558 true,
1559 true,
1560 false,
1562 null,
1563 null,
1564 null,
1565 autofill1,
1566 null,
1567 null);
1568 final TextInputChannel.Configuration config2 =
1570 false,
1571 false,
1572 true,
1573 true,
1574 false,
1576 null,
1577 null,
1578 null,
1579 autofill2,
1580 null,
1581 null);
1582
1583 textInputPlugin.setTextInputClient(
1584 0,
1586 false,
1587 false,
1588 true,
1589 true,
1590 false,
1592 null,
1593 null,
1594 null,
1595 autofill1,
1596 null,
1597 new TextInputChannel.Configuration[] {config1, config2}));
1598
1599 final ViewStructure viewStructure = mock(ViewStructure.class);
1600 final ViewStructure[] children = {mock(ViewStructure.class), mock(ViewStructure.class)};
1601
1602 when(viewStructure.newChild(anyInt()))
1603 .thenAnswer(invocation -> children[(int) invocation.getArgument(0)]);
1604
1605 textInputPlugin.onProvideAutofillVirtualStructure(viewStructure, 0);
1606
1607 verify(viewStructure).newChild(0);
1608 verify(viewStructure).newChild(1);
1609
1610 verify(children[0]).setAutofillId(any(), eq("1".hashCode()));
1611 verify(children[0]).setAutofillHints(aryEq(new String[] {"HINT1"}));
1612 verify(children[0]).setDimens(anyInt(), anyInt(), anyInt(), anyInt(), gt(0), gt(0));
1613 verify(children[0]).setHint("placeholder1");
1614
1615 verify(children[1]).setAutofillId(any(), eq("2".hashCode()));
1616 verify(children[1]).setAutofillHints(aryEq(new String[] {"HINT2", "EXTRA"}));
1617 verify(children[1]).setDimens(anyInt(), anyInt(), anyInt(), anyInt(), gt(0), gt(0));
1618 verify(children[1], times(0)).setHint(any());
1619 }
1620
1621 @SuppressWarnings("deprecation")
1622 @Config(minSdk = API_LEVELS.API_26)
1623 @Test
1625 if (Build.VERSION.SDK_INT < API_LEVELS.API_26) {
1626 return;
1627 }
1628 // Migrate to ActivityScenario by following https://github.com/robolectric/robolectric/pull/4736
1629 FlutterView testView = getTestView();
1630 TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
1632 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1633 final TextInputChannel.Configuration.Autofill autofill =
1635 "1",
1636 new String[] {"HINT1"},
1637 "placeholder",
1638 new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1639
1640 // Autofill should still work without AutofillGroup.
1641 textInputPlugin.setTextInputClient(
1642 0,
1644 false,
1645 false,
1646 true,
1647 true,
1648 false,
1650 null,
1651 null,
1652 null,
1653 autofill,
1654 null,
1655 null));
1656
1657 final ViewStructure viewStructure = mock(ViewStructure.class);
1658 final ViewStructure[] children = {mock(ViewStructure.class)};
1659
1660 when(viewStructure.newChild(anyInt()))
1661 .thenAnswer(invocation -> children[(int) invocation.getArgument(0)]);
1662
1663 textInputPlugin.onProvideAutofillVirtualStructure(viewStructure, 0);
1664
1665 verify(viewStructure).newChild(0);
1666
1667 verify(children[0]).setAutofillId(any(), eq("1".hashCode()));
1668 verify(children[0]).setAutofillHints(aryEq(new String[] {"HINT1"}));
1669 verify(children[0]).setHint("placeholder");
1670 // Verifies that the child has a non-zero size.
1671 verify(children[0]).setDimens(anyInt(), anyInt(), anyInt(), anyInt(), gt(0), gt(0));
1672 }
1673
1674 @Config(minSdk = API_LEVELS.API_26)
1675 @Test
1677 if (Build.VERSION.SDK_INT < API_LEVELS.API_26) {
1678 return;
1679 }
1680
1681 TestAfm testAfm = Shadow.extract(ctx.getSystemService(AutofillManager.class));
1682 FlutterView testView = getTestView();
1683 TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
1685 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1686
1687 // Set up an autofill scenario with 2 fields.
1688 final TextInputChannel.Configuration.Autofill autofill1 =
1690 "1",
1691 new String[] {"HINT1"},
1692 "placeholder1",
1693 new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1694 final TextInputChannel.Configuration.Autofill autofill2 =
1696 "2",
1697 new String[] {"HINT2", "EXTRA"},
1698 null,
1699 new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1700
1701 final TextInputChannel.Configuration config1 =
1703 false,
1704 false,
1705 true,
1706 true,
1707 false,
1709 null,
1710 null,
1711 null,
1712 autofill1,
1713 null,
1714 null);
1715 final TextInputChannel.Configuration config2 =
1717 false,
1718 false,
1719 true,
1720 true,
1721 false,
1723 null,
1724 null,
1725 null,
1726 autofill2,
1727 null,
1728 null);
1729
1730 // Set client. This should call notifyViewExited on the FlutterView if the previous client is
1731 // also eligible for autofill.
1732 final TextInputChannel.Configuration autofillConfiguration =
1734 false,
1735 false,
1736 true,
1737 true,
1738 false,
1740 null,
1741 null,
1742 null,
1743 autofill1,
1744 null,
1745 new TextInputChannel.Configuration[] {config1, config2});
1746
1747 textInputPlugin.setTextInputClient(0, autofillConfiguration);
1748
1749 // notifyViewExited should not be called as this is the first client we set.
1750 assertEquals(testAfm.empty, testAfm.exitId);
1751
1752 // The framework updates the text, call notifyValueChanged.
1753 textInputPlugin.setTextInputEditingState(
1754 testView, new TextInputChannel.TextEditState("new text", -1, -1, -1, -1));
1755 assertEquals("new text", testAfm.changeString);
1756 assertEquals("1".hashCode(), testAfm.changeVirtualId);
1757
1758 // The input method updates the text, call notifyValueChanged.
1759 testAfm.resetStates();
1760 final KeyboardManager mockKeyboardManager = mock(KeyboardManager.class);
1761 InputConnectionAdaptor adaptor =
1763 testView,
1764 0,
1765 mock(TextInputChannel.class),
1766 mockKeyboardManager,
1768 new EditorInfo());
1769 adaptor.commitText("input from IME ", 1);
1770
1771 assertEquals("input from IME new text", testAfm.changeString);
1772 assertEquals("1".hashCode(), testAfm.changeVirtualId);
1773
1774 // notifyViewExited should be called on the previous client.
1775 testAfm.resetStates();
1776 textInputPlugin.setTextInputClient(
1777 1,
1779 false,
1780 false,
1781 true,
1782 true,
1783 false,
1785 null,
1786 null,
1787 null,
1788 null,
1789 null,
1790 null));
1791
1792 assertEquals("1".hashCode(), testAfm.exitId);
1793
1794 // TextInputPlugin#clearTextInputClient calls notifyViewExited.
1795 testAfm.resetStates();
1796 textInputPlugin.setTextInputClient(3, autofillConfiguration);
1797 assertEquals(testAfm.empty, testAfm.exitId);
1799 assertEquals("1".hashCode(), testAfm.exitId);
1800
1801 // TextInputPlugin#destroy calls notifyViewExited.
1802 testAfm.resetStates();
1803 textInputPlugin.setTextInputClient(4, autofillConfiguration);
1804 assertEquals(testAfm.empty, testAfm.exitId);
1805 textInputPlugin.destroy();
1806 assertEquals("1".hashCode(), testAfm.exitId);
1807 }
1808
1809 @Config(minSdk = API_LEVELS.API_26)
1810 @SuppressWarnings("deprecation")
1811 @Test
1813 if (Build.VERSION.SDK_INT < API_LEVELS.API_26) {
1814 return;
1815 }
1816
1817 TestAfm testAfm = Shadow.extract(ctx.getSystemService(AutofillManager.class));
1818 FlutterView testView = getTestView();
1819 TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
1821 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1822
1823 // Set up an autofill scenario with 2 fields.
1824 final TextInputChannel.Configuration.Autofill autofill1 =
1826 "1",
1827 new String[] {"HINT1"},
1828 null,
1829 new TextInputChannel.TextEditState("field 1", 0, 0, -1, -1));
1830 final TextInputChannel.Configuration.Autofill autofill2 =
1832 "2",
1833 new String[] {"HINT2", "EXTRA"},
1834 null,
1835 new TextInputChannel.TextEditState("field 2", 0, 0, -1, -1));
1836
1837 final TextInputChannel.Configuration config1 =
1839 false,
1840 false,
1841 true,
1842 true,
1843 false,
1845 null,
1846 null,
1847 null,
1848 autofill1,
1849 null,
1850 null);
1851 final TextInputChannel.Configuration config2 =
1853 false,
1854 false,
1855 true,
1856 true,
1857 false,
1859 null,
1860 null,
1861 null,
1862 autofill2,
1863 null,
1864 null);
1865
1866 final TextInputChannel.Configuration autofillConfiguration =
1868 false,
1869 false,
1870 true,
1871 true,
1872 false,
1874 null,
1875 null,
1876 null,
1877 autofill1,
1878 null,
1879 new TextInputChannel.Configuration[] {config1, config2});
1880
1881 textInputPlugin.setTextInputClient(0, autofillConfiguration);
1882 textInputPlugin.setTextInputEditingState(
1883 testView, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1884
1885 final SparseArray<AutofillValue> autofillValues = new SparseArray();
1886 autofillValues.append("1".hashCode(), AutofillValue.forText("focused field"));
1887 autofillValues.append("2".hashCode(), AutofillValue.forText("unfocused field"));
1888
1889 // Autofill both fields.
1890 textInputPlugin.autofill(autofillValues);
1891
1892 // Verify the Editable has been updated.
1893 assertTrue(textInputPlugin.getEditable().toString().equals("focused field"));
1894
1895 // The autofill value of the focused field is sent via updateEditingState.
1896 verify(textInputChannel, times(1))
1897 .updateEditingState(anyInt(), eq("focused field"), eq(13), eq(13), eq(-1), eq(-1));
1898
1899 final ArgumentCaptor<HashMap> mapCaptor = ArgumentCaptor.forClass(HashMap.class);
1900
1901 verify(textInputChannel, times(1)).updateEditingStateWithTag(anyInt(), mapCaptor.capture());
1902 final TextInputChannel.TextEditState editState =
1903 (TextInputChannel.TextEditState) mapCaptor.getValue().get("2");
1904 assertEquals(editState.text, "unfocused field");
1905 }
1906
1907 @Config(minSdk = API_LEVELS.API_26)
1908 @Test
1910 if (Build.VERSION.SDK_INT < API_LEVELS.API_26) {
1911 return;
1912 }
1913 FlutterView testView = new FlutterView(ctx);
1914 TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
1916 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1917 // Set up an autofill scenario with 2 fields.
1918 final TextInputChannel.Configuration.Autofill autofillConfig =
1920 "1",
1921 new String[] {"HINT1"},
1922 "placeholder1",
1923 new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1924 final TextInputChannel.Configuration config =
1926 false,
1927 false,
1928 true,
1929 true,
1930 false,
1932 null,
1933 null,
1934 null,
1935 autofillConfig,
1936 null,
1937 null);
1938
1939 textInputPlugin.setTextInputClient(0, config);
1940 textInputPlugin.setTextInputEditingState(
1941 testView, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1943
1944 final SparseArray<AutofillValue> autofillValues = new SparseArray();
1945 autofillValues.append("1".hashCode(), AutofillValue.forText("focused field"));
1946 autofillValues.append("2".hashCode(), AutofillValue.forText("unfocused field"));
1947
1948 // Autofill both fields.
1949 textInputPlugin.autofill(autofillValues);
1950
1951 verify(textInputChannel, never()).updateEditingStateWithTag(anyInt(), any());
1952 verify(textInputChannel, never())
1953 .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt());
1954 }
1955
1956 @Config(minSdk = API_LEVELS.API_26)
1957 @Test
1959 if (Build.VERSION.SDK_INT < API_LEVELS.API_26) {
1960 return;
1961 }
1962
1963 TestAfm testAfm = Shadow.extract(ctx.getSystemService(AutofillManager.class));
1964 FlutterView testView = getTestView();
1965 TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
1967 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
1968
1969 // Set up an autofill scenario with 2 fields.
1970 final TextInputChannel.Configuration.Autofill autofill1 =
1972 "1",
1973 new String[] {"HINT1"},
1974 "null",
1975 new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1976 final TextInputChannel.Configuration.Autofill autofill2 =
1978 "2",
1979 new String[] {"HINT2", "EXTRA"},
1980 "null",
1982 "Unfocused fields need love like everything does", 0, 0, -1, -1));
1983
1984 final TextInputChannel.Configuration config1 =
1986 false,
1987 false,
1988 true,
1989 true,
1990 false,
1992 null,
1993 null,
1994 null,
1995 autofill1,
1996 null,
1997 null);
1998 final TextInputChannel.Configuration config2 =
2000 false,
2001 false,
2002 true,
2003 true,
2004 false,
2006 null,
2007 null,
2008 null,
2009 autofill2,
2010 null,
2011 null);
2012
2013 final TextInputChannel.Configuration autofillConfiguration =
2015 false,
2016 false,
2017 true,
2018 true,
2019 false,
2021 null,
2022 null,
2023 null,
2024 autofill1,
2025 null,
2026 new TextInputChannel.Configuration[] {config1, config2});
2027
2028 textInputPlugin.setTextInputClient(0, autofillConfiguration);
2029
2030 // notifyValueChanged should be called for unfocused fields.
2031 assertEquals("2".hashCode(), testAfm.changeVirtualId);
2032 assertEquals("Unfocused fields need love like everything does", testAfm.changeString);
2033 }
2034 // -------- End: Autofill Tests -------
2035
2036 @SuppressWarnings("deprecation")
2037 private FlutterView getTestView() {
2038 // TODO(reidbaker): https://github.com/flutter/flutter/issues/133151
2039 return new FlutterView(Robolectric.setupActivity(Activity.class));
2040 }
2041
2042 @SuppressWarnings("deprecation")
2043 // setMessageHandler is deprecated.
2044 @Test
2046 ArgumentCaptor<BinaryMessenger.BinaryMessageHandler> binaryMessageHandlerCaptor =
2047 ArgumentCaptor.forClass(BinaryMessenger.BinaryMessageHandler.class);
2048 DartExecutor mockBinaryMessenger = mock(DartExecutor.class);
2049 TextInputChannel.TextInputMethodHandler mockHandler =
2051 TextInputChannel textInputChannel = new TextInputChannel(mockBinaryMessenger);
2052
2053 textInputChannel.setTextInputMethodHandler(mockHandler);
2054
2055 verify(mockBinaryMessenger, times(1))
2056 .setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture());
2057
2058 BinaryMessenger.BinaryMessageHandler binaryMessageHandler =
2059 binaryMessageHandlerCaptor.getValue();
2060
2061 sendToBinaryMessageHandler(binaryMessageHandler, "TextInput.requestAutofill", null);
2062 verify(mockHandler, times(1)).requestAutofill();
2063
2064 sendToBinaryMessageHandler(binaryMessageHandler, "TextInput.finishAutofillContext", true);
2065 verify(mockHandler, times(1)).finishAutofillContext(true);
2066
2067 sendToBinaryMessageHandler(binaryMessageHandler, "TextInput.finishAutofillContext", false);
2068 verify(mockHandler, times(1)).finishAutofillContext(false);
2069 }
2070
2071 @SuppressWarnings("deprecation")
2072 // setMessageHandler is deprecated.
2073 @Test
2074 public void sendAppPrivateCommand_dataIsEmpty() throws JSONException {
2075 ArgumentCaptor<BinaryMessenger.BinaryMessageHandler> binaryMessageHandlerCaptor =
2076 ArgumentCaptor.forClass(BinaryMessenger.BinaryMessageHandler.class);
2077 DartExecutor mockBinaryMessenger = mock(DartExecutor.class);
2078 TextInputChannel textInputChannel = new TextInputChannel(mockBinaryMessenger);
2079
2080 EventHandler mockEventHandler = mock(EventHandler.class);
2081 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
2082 testImm.setEventHandler(mockEventHandler);
2083
2084 View testView = new View(ctx);
2086 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
2087
2088 verify(mockBinaryMessenger, times(1))
2089 .setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture());
2090
2091 JSONObject arguments = new JSONObject();
2092 arguments.put("action", "actionCommand");
2093 arguments.put("data", "");
2094
2095 BinaryMessenger.BinaryMessageHandler binaryMessageHandler =
2096 binaryMessageHandlerCaptor.getValue();
2097 sendToBinaryMessageHandler(binaryMessageHandler, "TextInput.sendAppPrivateCommand", arguments);
2098 verify(mockEventHandler, times(1))
2099 .sendAppPrivateCommand(any(View.class), eq("actionCommand"), eq(null));
2100 }
2101
2102 @SuppressWarnings("deprecation")
2103 // setMessageHandler is deprecated.
2104 @Test
2105 public void sendAppPrivateCommand_hasData() throws JSONException {
2106 ArgumentCaptor<BinaryMessenger.BinaryMessageHandler> binaryMessageHandlerCaptor =
2107 ArgumentCaptor.forClass(BinaryMessenger.BinaryMessageHandler.class);
2108 DartExecutor mockBinaryMessenger = mock(DartExecutor.class);
2109 TextInputChannel textInputChannel = new TextInputChannel(mockBinaryMessenger);
2110
2111 EventHandler mockEventHandler = mock(EventHandler.class);
2112 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
2113 testImm.setEventHandler(mockEventHandler);
2114
2115 View testView = new View(ctx);
2117 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
2118
2119 verify(mockBinaryMessenger, times(1))
2120 .setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture());
2121
2122 JSONObject arguments = new JSONObject();
2123 arguments.put("action", "actionCommand");
2124 arguments.put("data", "actionData");
2125
2126 ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
2127 BinaryMessenger.BinaryMessageHandler binaryMessageHandler =
2128 binaryMessageHandlerCaptor.getValue();
2129 sendToBinaryMessageHandler(binaryMessageHandler, "TextInput.sendAppPrivateCommand", arguments);
2130 verify(mockEventHandler, times(1))
2131 .sendAppPrivateCommand(any(View.class), eq("actionCommand"), bundleCaptor.capture());
2132 assertEquals("actionData", bundleCaptor.getValue().getCharSequence("data"));
2133 }
2134
2135 @Test
2136 @TargetApi(API_LEVELS.API_30)
2137 @Config(sdk = API_LEVELS.API_30)
2138 @SuppressWarnings("deprecation")
2139 // getWindowSystemUiVisibility, SYSTEM_UI_FLAG_LAYOUT_STABLE.
2140 // flutter#133074 tracks migration work.
2141 public void ime_windowInsetsSync_notLaidOutBehindNavigation_excludesNavigationBars() {
2142 FlutterView testView = spy(getTestView());
2143 when(testView.getWindowSystemUiVisibility()).thenReturn(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
2144
2145 TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
2147 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
2148 ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
2149 FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
2150 FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
2151 when(flutterEngine.getRenderer()).thenReturn(flutterRenderer);
2152 testView.attachToFlutterEngine(flutterEngine);
2153
2154 WindowInsetsAnimation animation = mock(WindowInsetsAnimation.class);
2155 when(animation.getTypeMask()).thenReturn(WindowInsets.Type.ime());
2156
2157 List<WindowInsetsAnimation> animationList = new ArrayList();
2158 animationList.add(animation);
2159
2160 ArgumentCaptor<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
2161 ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);
2162
2163 WindowInsets.Builder builder = new WindowInsets.Builder();
2164
2165 // Set the initial insets and verify that they were set and the bottom view inset is correct
2166 imeSyncCallback.getInsetsListener().onApplyWindowInsets(testView, builder.build());
2167
2168 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2169 assertEquals(0, viewportMetricsCaptor.getValue().viewInsetBottom);
2170
2171 // Call onPrepare and set the lastWindowInsets - these should be stored for the end of the
2172 // animation instead of being applied immediately
2173 imeSyncCallback.getAnimationCallback().onPrepare(animation);
2174 builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 100));
2175 builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 0));
2176 imeSyncCallback.getInsetsListener().onApplyWindowInsets(testView, builder.build());
2177
2178 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2179 assertEquals(0, viewportMetricsCaptor.getValue().viewInsetBottom);
2180
2181 // Call onStart and apply new insets - these should be ignored completely
2182 imeSyncCallback.getAnimationCallback().onStart(animation, null);
2183 builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 50));
2184 builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 40));
2185 imeSyncCallback.getInsetsListener().onApplyWindowInsets(testView, builder.build());
2186
2187 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2188 assertEquals(0, viewportMetricsCaptor.getValue().viewInsetBottom);
2189
2190 // Progress the animation and ensure that the navigation bar insets have been subtracted
2191 // from the IME insets
2192 builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 25));
2193 builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 40));
2194 imeSyncCallback.getAnimationCallback().onProgress(builder.build(), animationList);
2195
2196 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2197 assertEquals(0, viewportMetricsCaptor.getValue().viewInsetBottom);
2198
2199 builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 50));
2200 builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 40));
2201 imeSyncCallback.getAnimationCallback().onProgress(builder.build(), animationList);
2202
2203 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2204 assertEquals(10, viewportMetricsCaptor.getValue().viewInsetBottom);
2205
2206 // End the animation and ensure that the bottom insets match the lastWindowInsets that we set
2207 // during onPrepare
2208 imeSyncCallback.getAnimationCallback().onEnd(animation);
2209
2210 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2211 assertEquals(100, viewportMetricsCaptor.getValue().viewInsetBottom);
2212 }
2213
2214 @Test
2215 @TargetApi(API_LEVELS.API_30)
2216 @Config(sdk = API_LEVELS.API_30)
2217 @SuppressWarnings("deprecation")
2218 // getWindowSystemUiVisibility
2219 // flutter#133074 tracks migration work.
2220 public void ime_windowInsetsSync_laidOutBehindNavigation_includesNavigationBars() {
2221 FlutterView testView = spy(getTestView());
2222 when(testView.getWindowSystemUiVisibility())
2223 .thenReturn(
2224 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
2225
2226 TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
2228 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
2229 ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
2230 FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
2231 FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
2232 when(flutterEngine.getRenderer()).thenReturn(flutterRenderer);
2233 testView.attachToFlutterEngine(flutterEngine);
2234
2235 WindowInsetsAnimation animation = mock(WindowInsetsAnimation.class);
2236 when(animation.getTypeMask()).thenReturn(WindowInsets.Type.ime());
2237
2238 List<WindowInsetsAnimation> animationList = new ArrayList();
2239 animationList.add(animation);
2240
2241 ArgumentCaptor<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
2242 ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);
2243
2244 WindowInsets.Builder builder = new WindowInsets.Builder();
2245
2246 // Set the initial insets and verify that they were set and the bottom view inset is correct
2247 imeSyncCallback.getInsetsListener().onApplyWindowInsets(testView, builder.build());
2248
2249 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2250 assertEquals(0, viewportMetricsCaptor.getValue().viewInsetBottom);
2251
2252 // Call onPrepare and set the lastWindowInsets - these should be stored for the end of the
2253 // animation instead of being applied immediately
2254 imeSyncCallback.getAnimationCallback().onPrepare(animation);
2255 builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 100));
2256 builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 0));
2257 imeSyncCallback.getInsetsListener().onApplyWindowInsets(testView, builder.build());
2258
2259 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2260 assertEquals(0, viewportMetricsCaptor.getValue().viewInsetBottom);
2261
2262 // Call onStart and apply new insets - these should be ignored completely
2263 imeSyncCallback.getAnimationCallback().onStart(animation, null);
2264 builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 50));
2265 builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 40));
2266 imeSyncCallback.getInsetsListener().onApplyWindowInsets(testView, builder.build());
2267
2268 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2269 assertEquals(0, viewportMetricsCaptor.getValue().viewInsetBottom);
2270
2271 // Progress the animation and ensure that the navigation bar insets have not been
2272 // subtracted from the IME insets
2273 builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 25));
2274 builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 40));
2275 imeSyncCallback.getAnimationCallback().onProgress(builder.build(), animationList);
2276
2277 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2278 assertEquals(25, viewportMetricsCaptor.getValue().viewInsetBottom);
2279
2280 builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 50));
2281 builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 40));
2282 imeSyncCallback.getAnimationCallback().onProgress(builder.build(), animationList);
2283
2284 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2285 assertEquals(50, viewportMetricsCaptor.getValue().viewInsetBottom);
2286
2287 // End the animation and ensure that the bottom insets match the lastWindowInsets that we set
2288 // during onPrepare
2289 imeSyncCallback.getAnimationCallback().onEnd(animation);
2290
2291 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2292 assertEquals(100, viewportMetricsCaptor.getValue().viewInsetBottom);
2293 }
2294
2295 @Test
2296 @TargetApi(API_LEVELS.API_30)
2297 @Config(sdk = API_LEVELS.API_30)
2298 @SuppressWarnings("deprecation")
2299 // getWindowSystemUiVisibility, SYSTEM_UI_FLAG_LAYOUT_STABLE
2300 // flutter#133074 tracks migration work.
2301 public void lastWindowInsets_updatedOnSecondOnProgressCall() {
2302 FlutterView testView = spy(getTestView());
2303 when(testView.getWindowSystemUiVisibility()).thenReturn(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
2304
2305 TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
2307 new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
2308 ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
2309 FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
2310 FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
2311 when(flutterEngine.getRenderer()).thenReturn(flutterRenderer);
2312 testView.attachToFlutterEngine(flutterEngine);
2313
2314 WindowInsetsAnimation imeAnimation = mock(WindowInsetsAnimation.class);
2315 when(imeAnimation.getTypeMask()).thenReturn(WindowInsets.Type.ime());
2316 WindowInsetsAnimation navigationBarAnimation = mock(WindowInsetsAnimation.class);
2317 when(navigationBarAnimation.getTypeMask()).thenReturn(WindowInsets.Type.navigationBars());
2318
2319 List<WindowInsetsAnimation> animationList = new ArrayList();
2320 animationList.add(imeAnimation);
2321 animationList.add(navigationBarAnimation);
2322
2323 ArgumentCaptor<FlutterRenderer.ViewportMetrics> viewportMetricsCaptor =
2324 ArgumentCaptor.forClass(FlutterRenderer.ViewportMetrics.class);
2325
2326 WindowInsets.Builder builder = new WindowInsets.Builder();
2327
2328 // Set the initial insets and verify that they were set and the bottom view padding is correct
2329 builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 1000));
2330 builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 100));
2331 imeSyncCallback.getInsetsListener().onApplyWindowInsets(testView, builder.build());
2332
2333 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2334 assertEquals(100, viewportMetricsCaptor.getValue().viewPaddingBottom);
2335
2336 // Call onPrepare and set the lastWindowInsets - these should be stored for the end of the
2337 // animation instead of being applied immediately
2338 imeSyncCallback.getAnimationCallback().onPrepare(imeAnimation);
2339 builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 0));
2340 builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 100));
2341 imeSyncCallback.getInsetsListener().onApplyWindowInsets(testView, builder.build());
2342
2343 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2344 assertEquals(100, viewportMetricsCaptor.getValue().viewPaddingBottom);
2345
2346 // Call onPrepare again and apply new insets - these should overrite lastWindowInsets
2347 imeSyncCallback.getAnimationCallback().onPrepare(navigationBarAnimation);
2348 builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 0));
2349 builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 0));
2350 imeSyncCallback.getInsetsListener().onApplyWindowInsets(testView, builder.build());
2351
2352 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2353 assertEquals(100, viewportMetricsCaptor.getValue().viewPaddingBottom);
2354
2355 // Progress the animation and ensure that the navigation bar insets have not been
2356 // subtracted from the IME insets
2357 builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 500));
2358 builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 0));
2359 imeSyncCallback.getAnimationCallback().onProgress(builder.build(), animationList);
2360
2361 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2362 assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingBottom);
2363
2364 builder.setInsets(WindowInsets.Type.ime(), Insets.of(0, 0, 0, 250));
2365 builder.setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, 0));
2366 imeSyncCallback.getAnimationCallback().onProgress(builder.build(), animationList);
2367
2368 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2369 assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingBottom);
2370
2371 // End the animation and ensure that the bottom insets match the lastWindowInsets that we set
2372 // during onPrepare
2373 imeSyncCallback.getAnimationCallback().onEnd(imeAnimation);
2374
2375 verify(flutterRenderer, atLeast(1)).setViewportMetrics(viewportMetricsCaptor.capture());
2376 assertEquals(0, viewportMetricsCaptor.getValue().viewPaddingBottom);
2377 }
2378
2379 interface EventHandler {
2380 void sendAppPrivateCommand(View view, String action, Bundle data);
2381 }
2382
2383 @Implements(InputMethodManager.class)
2384 public static class TestImm extends ShadowInputMethodManager {
2385 private InputMethodSubtype currentInputMethodSubtype;
2386 private SparseIntArray restartCounter = new SparseIntArray();
2387 private CursorAnchorInfo cursorAnchorInfo;
2388 private ArrayList<Integer> selectionUpdateValues;
2389 private boolean trackSelection = false;
2390 private EventHandler handler;
2391
2392 public TestImm() {
2393 selectionUpdateValues = new ArrayList<Integer>();
2394 }
2395
2396 @Implementation
2397 public InputMethodSubtype getCurrentInputMethodSubtype() {
2398 return currentInputMethodSubtype;
2399 }
2400
2401 @Implementation
2402 public void restartInput(View view) {
2403 int count = restartCounter.get(view.hashCode(), /*defaultValue=*/ 0) + 1;
2404 restartCounter.put(view.hashCode(), count);
2405 }
2406
2407 public void setCurrentInputMethodSubtype(InputMethodSubtype inputMethodSubtype) {
2408 this.currentInputMethodSubtype = inputMethodSubtype;
2409 }
2410
2411 public int getRestartCount(View view) {
2412 return restartCounter.get(view.hashCode(), /*defaultValue=*/ 0);
2413 }
2414
2415 public void setEventHandler(EventHandler eventHandler) {
2416 handler = eventHandler;
2417 }
2418
2419 @Implementation
2420 public void sendAppPrivateCommand(View view, String action, Bundle data) {
2421 handler.sendAppPrivateCommand(view, action, data);
2422 }
2423
2424 @Implementation
2425 public void updateCursorAnchorInfo(View view, CursorAnchorInfo cursorAnchorInfo) {
2426 this.cursorAnchorInfo = cursorAnchorInfo;
2427 }
2428
2429 // We simply store the values to verify later.
2430 @Implementation
2431 public void updateSelection(
2432 View view, int selStart, int selEnd, int candidatesStart, int candidatesEnd) {
2433 if (trackSelection) {
2434 this.selectionUpdateValues.add(selStart);
2435 this.selectionUpdateValues.add(selEnd);
2436 this.selectionUpdateValues.add(candidatesStart);
2437 this.selectionUpdateValues.add(candidatesEnd);
2438 }
2439 }
2440
2441 // only track values when enabled via this.
2442 public void setTrackSelection(boolean val) {
2443 trackSelection = val;
2444 }
2445
2446 // Returns true if the last updateSelection call passed the following values.
2447 public ArrayList<Integer> getSelectionUpdateValues() {
2448 return selectionUpdateValues;
2449 }
2450
2451 public CursorAnchorInfo getLastCursorAnchorInfo() {
2452 return cursorAnchorInfo;
2453 }
2454 }
2455
2456 @Implements(AutofillManager.class)
2457 public static class TestAfm extends ShadowAutofillManager {
2458 public static int empty = -999;
2459
2461 int changeVirtualId = empty;
2463
2464 int enterId = empty;
2465 int exitId = empty;
2466
2467 @Implementation
2468 public void cancel() {
2469 finishState = "cancel";
2470 }
2471
2472 public void commit() {
2473 finishState = "commit";
2474 }
2475
2476 public void notifyViewEntered(View view, int virtualId, Rect absBounds) {
2477 enterId = virtualId;
2478 }
2479
2480 public void notifyViewExited(View view, int virtualId) {
2481 exitId = virtualId;
2482 }
2483
2484 public void notifyValueChanged(View view, int virtualId, AutofillValue value) {
2485 if (Build.VERSION.SDK_INT < API_LEVELS.API_26) {
2486 return;
2487 }
2488 changeVirtualId = virtualId;
2489 changeString = value.getTextValue().toString();
2490 }
2491
2492 public void resetStates() {
2493 finishState = null;
2494 changeVirtualId = empty;
2495 changeString = null;
2496 enterId = empty;
2497 exitId = empty;
2498 }
2499 }
2500}
static SkISize times(const SkISize &size, float factor)
int count
Definition: FontMgrTest.cpp:50
static bool eq(const SkM44 &a, const SkM44 &b, float tol)
Definition: M44Test.cpp:18
bool equals(SkDrawable *a, SkDrawable *b)
static final int API_26
Definition: Build.java:16
static final int API_30
Definition: Build.java:20
void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine)
void setTextInputMethodHandler(@Nullable TextInputMethodHandler textInputMethodHandler)
MethodCall decodeMethodCall(@NonNull ByteBuffer message)
static final JSONMethodCodec INSTANCE
boolean setComposingText(CharSequence text, int newCursorPosition)
boolean commitText(CharSequence text, int newCursorPosition)
void notifyViewEntered(View view, int virtualId, Rect absBounds)
void notifyValueChanged(View view, int virtualId, AutofillValue value)
void updateCursorAnchorInfo(View view, CursorAnchorInfo cursorAnchorInfo)
void updateSelection(View view, int selStart, int selEnd, int candidatesStart, int candidatesEnd)
void setCurrentInputMethodSubtype(InputMethodSubtype inputMethodSubtype)
void sendAppPrivateCommand(View view, String action, Bundle data)
EMSCRIPTEN_KEEPALIVE void empty()
AtkStateType state
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
static ::testing::Matcher< GBytes * > MethodCall(const std::string &name, ::testing::Matcher< FlValue * > args)
uint8_t value
void sendAppPrivateCommand(View view, String action, Bundle data)
FlutterTextInputPlugin * textInputPlugin
def Build(configs, env, options)
Definition: build.py:232
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
TRect< Scalar > Rect
Definition: rect.h:769
SIT bool any(const Vec< 1, T > &x)
Definition: SkVx.h:530
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63