Flutter Engine
The Flutter Engine
InputConnectionAdaptorTest.java
Go to the documentation of this file.
1package io.flutter.plugin.editing;
2
3import static org.junit.Assert.assertEquals;
4import static org.junit.Assert.assertFalse;
5import static org.junit.Assert.assertNull;
6import static org.junit.Assert.assertTrue;
7import static org.mockito.ArgumentMatchers.any;
8import static org.mockito.ArgumentMatchers.anyInt;
9import static org.mockito.ArgumentMatchers.isNull;
10import static org.mockito.Mockito.anyString;
11import static org.mockito.Mockito.eq;
12import static org.mockito.Mockito.mock;
13import static org.mockito.Mockito.never;
14import static org.mockito.Mockito.spy;
15import static org.mockito.Mockito.times;
16import static org.mockito.Mockito.verify;
17import static org.mockito.Mockito.when;
18
19import android.content.ClipDescription;
20import android.content.ClipboardManager;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.res.AssetManager;
24import android.net.Uri;
25import android.os.Bundle;
26import android.text.InputType;
27import android.text.Selection;
28import android.text.SpannableStringBuilder;
29import android.view.KeyEvent;
30import android.view.View;
31import android.view.inputmethod.CursorAnchorInfo;
32import android.view.inputmethod.EditorInfo;
33import android.view.inputmethod.ExtractedText;
34import android.view.inputmethod.ExtractedTextRequest;
35import android.view.inputmethod.InputConnection;
36import android.view.inputmethod.InputContentInfo;
37import android.view.inputmethod.InputMethodManager;
38import androidx.core.view.inputmethod.InputConnectionCompat;
39import androidx.test.core.app.ApplicationProvider;
40import androidx.test.ext.junit.runners.AndroidJUnit4;
41import com.ibm.icu.lang.UCharacter;
42import com.ibm.icu.lang.UProperty;
43import io.flutter.embedding.android.KeyboardManager;
44import io.flutter.embedding.engine.FlutterJNI;
45import io.flutter.embedding.engine.dart.DartExecutor;
46import io.flutter.embedding.engine.systemchannels.TextInputChannel;
47import io.flutter.plugin.common.JSONMethodCodec;
48import io.flutter.plugin.common.MethodCall;
49import io.flutter.util.FakeKeyEvent;
50import java.io.ByteArrayInputStream;
51import java.nio.ByteBuffer;
52import java.nio.charset.Charset;
53import org.json.JSONArray;
54import org.json.JSONException;
55import org.junit.Before;
56import org.junit.Test;
57import org.junit.runner.RunWith;
58import org.mockito.ArgumentCaptor;
59import org.mockito.Mock;
60import org.mockito.MockitoAnnotations;
61import org.robolectric.Shadows;
62import org.robolectric.annotation.Config;
63import org.robolectric.annotation.Implementation;
64import org.robolectric.annotation.Implements;
65import org.robolectric.shadow.api.Shadow;
66import org.robolectric.shadows.ShadowContentResolver;
67import org.robolectric.shadows.ShadowInputMethodManager;
68
69@Config(
70 manifest = Config.NONE,
71 shadows = {InputConnectionAdaptorTest.TestImm.class})
72@RunWith(AndroidJUnit4.class)
74 private final Context ctx = ApplicationProvider.getApplicationContext();
75 private ContentResolver contentResolver;
76 private ShadowContentResolver shadowContentResolver;
77
79 // Verifies the method and arguments for a captured method call.
80 private void verifyMethodCall(ByteBuffer buffer, String methodName, String[] expectedArgs)
81 throws JSONException {
82 buffer.rewind();
84 assertEquals(methodName, methodCall.method);
85 if (expectedArgs != null) {
86 JSONArray args = methodCall.arguments();
87 assertEquals(expectedArgs.length, args.length());
88 for (int i = 0; i < args.length(); i++) {
89 assertEquals(expectedArgs[i], args.get(i).toString());
90 }
91 }
92 }
93
94 @Before
95 public void setUp() {
96 MockitoAnnotations.openMocks(this);
97 contentResolver = ctx.getContentResolver();
98 shadowContentResolver = Shadows.shadowOf(contentResolver);
99 }
100
101 @Test
102 public void inputConnectionAdaptor_ReceivesEnter() throws NullPointerException {
103 View testView = new View(ctx);
104 FlutterJNI mockFlutterJni = mock(FlutterJNI.class);
105 DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJni, mock(AssetManager.class)));
106 int inputTargetId = 0;
107 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
108 ListenableEditingState mEditable = new ListenableEditingState(null, testView);
109 Selection.setSelection(mEditable, 0, 0);
110 ListenableEditingState spyEditable = spy(mEditable);
111 EditorInfo outAttrs = new EditorInfo();
112 outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
113
114 InputConnectionAdaptor inputConnectionAdaptor =
116 testView, inputTargetId, textInputChannel, mockKeyboardManager, spyEditable, outAttrs);
117
118 // Send an enter key and make sure the Editable received it.
119 FakeKeyEvent keyEvent = new FakeKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, '\n');
120 inputConnectionAdaptor.handleKeyEvent(keyEvent);
121 verify(spyEditable, times(1)).insert(eq(0), anyString());
122 }
123
124 @Test
126 int selStart = 5;
127 ListenableEditingState editable = sampleEditable(selStart, selStart);
128 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
129
130 boolean didConsume = adaptor.performContextMenuAction(android.R.id.selectAll);
131
132 assertTrue(didConsume);
133 assertEquals(0, Selection.getSelectionStart(editable));
134 assertEquals(editable.length(), Selection.getSelectionEnd(editable));
135 }
136
137 @SuppressWarnings("deprecation")
138 // ClipboardManager.hasText is deprecated.
139 @Test
141 ClipboardManager clipboardManager = ctx.getSystemService(ClipboardManager.class);
142 int selStart = 6;
143 int selEnd = 11;
144 ListenableEditingState editable = sampleEditable(selStart, selEnd);
145 CharSequence textToBeCut = editable.subSequence(selStart, selEnd);
146 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
147
148 boolean didConsume = adaptor.performContextMenuAction(android.R.id.cut);
149
150 assertTrue(didConsume);
151 assertTrue(clipboardManager.hasText());
152 assertEquals(textToBeCut, clipboardManager.getPrimaryClip().getItemAt(0).getText());
153 assertFalse(editable.toString().contains(textToBeCut));
154 }
155
156 @SuppressWarnings("deprecation")
157 // ClipboardManager.hasText is deprecated.
158 @Test
160 ClipboardManager clipboardManager = ctx.getSystemService(ClipboardManager.class);
161 int selStart = 6;
162 int selEnd = 11;
163 ListenableEditingState editable = sampleEditable(selStart, selEnd);
164 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
165
166 assertFalse(clipboardManager.hasText());
167
168 boolean didConsume = adaptor.performContextMenuAction(android.R.id.copy);
169
170 assertTrue(didConsume);
171 assertTrue(clipboardManager.hasText());
172 assertEquals(
173 editable.subSequence(selStart, selEnd),
174 clipboardManager.getPrimaryClip().getItemAt(0).getText());
175 }
176
177 @SuppressWarnings("deprecation")
178 // ClipboardManager.setText is deprecated.
179 @Test
181 ClipboardManager clipboardManager = ctx.getSystemService(ClipboardManager.class);
182 String textToBePasted = "deadbeef";
183 clipboardManager.setText(textToBePasted);
184 ListenableEditingState editable = sampleEditable(0, 0);
185 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
186
187 boolean didConsume = adaptor.performContextMenuAction(android.R.id.paste);
188
189 assertTrue(didConsume);
190 assertTrue(editable.toString().startsWith(textToBePasted));
191 }
192
193 @SuppressWarnings("deprecation")
194 // DartExecutor.send is deprecated.
195 @Test
196 public void testCommitContent() throws JSONException {
197 View testView = new View(ctx);
198 int client = 0;
199 FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
200 DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
201 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
202 ListenableEditingState editable = sampleEditable(0, 0);
203 InputConnectionAdaptor adaptor =
205 testView,
206 client,
207 textInputChannel,
208 mockKeyboardManager,
209 editable,
210 null,
211 mockFlutterJNI);
212
213 String uri = "content://mock/uri/test/commitContent";
214 Charset charset = Charset.forName("UTF-8");
215 String fakeImageData = "fake image data";
216 byte[] fakeImageDataBytes = fakeImageData.getBytes(charset);
217 shadowContentResolver.registerInputStream(
218 Uri.parse(uri), new ByteArrayInputStream(fakeImageDataBytes));
219
220 boolean commitContentSuccess =
221 adaptor.commitContent(
222 new InputContentInfo(
223 Uri.parse(uri),
224 new ClipDescription("commitContent test", new String[] {"image/png"})),
225 InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION,
226 null);
227 assertTrue(commitContentSuccess);
228
229 ArgumentCaptor<String> channelCaptor = ArgumentCaptor.forClass(String.class);
230 ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
231 verify(dartExecutor, times(1)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull());
232 assertEquals("flutter/textinput", channelCaptor.getValue());
233
234 String fakeImageDataIntString = "";
235 for (int i = 0; i < fakeImageDataBytes.length; i++) {
236 int byteAsInt = fakeImageDataBytes[i];
237 fakeImageDataIntString += byteAsInt;
238 if (i < (fakeImageDataBytes.length - 1)) {
239 fakeImageDataIntString += ",";
240 }
241 }
242 verifyMethodCall(
243 bufferCaptor.getValue(),
244 "TextInputClient.performAction",
245 new String[] {
246 "0",
247 "TextInputAction.commitContent",
248 "{\"data\":["
249 + fakeImageDataIntString
250 + "],\"mimeType\":\"image\\/png\",\"uri\":\"content:\\/\\/mock\\/uri\\/test\\/commitContent\"}"
251 });
252 }
253
254 @SuppressWarnings("deprecation")
255 // DartExecutor.send is deprecated.
256 @Test
257 public void testPerformPrivateCommand_dataIsNull() throws JSONException {
258 View testView = new View(ctx);
259 int client = 0;
260 FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
261 DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
262 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
263 ListenableEditingState editable = sampleEditable(0, 0);
264 InputConnectionAdaptor adaptor =
266 testView,
267 client,
268 textInputChannel,
269 mockKeyboardManager,
270 editable,
271 null,
272 mockFlutterJNI);
273 adaptor.performPrivateCommand("actionCommand", null);
274
275 ArgumentCaptor<String> channelCaptor = ArgumentCaptor.forClass(String.class);
276 ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
277 verify(dartExecutor, times(1)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull());
278 assertEquals("flutter/textinput", channelCaptor.getValue());
279 verifyMethodCall(
280 bufferCaptor.getValue(),
281 "TextInputClient.performPrivateCommand",
282 new String[] {"0", "{\"action\":\"actionCommand\"}"});
283 }
284
285 @SuppressWarnings("deprecation")
286 // DartExecutor.send is deprecated.
287 @Test
288 public void testPerformPrivateCommand_dataIsByteArray() throws JSONException {
289 View testView = new View(ctx);
290 int client = 0;
291 FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
292 DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
293 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
294 ListenableEditingState editable = sampleEditable(0, 0);
295 InputConnectionAdaptor adaptor =
297 testView,
298 client,
299 textInputChannel,
300 mockKeyboardManager,
301 editable,
302 null,
303 mockFlutterJNI);
304
305 Bundle bundle = new Bundle();
306 byte[] buffer = new byte[] {'a', 'b', 'c', 'd'};
307 bundle.putByteArray("keyboard_layout", buffer);
308 adaptor.performPrivateCommand("actionCommand", bundle);
309
310 ArgumentCaptor<String> channelCaptor = ArgumentCaptor.forClass(String.class);
311 ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
312 verify(dartExecutor, times(1)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull());
313 assertEquals("flutter/textinput", channelCaptor.getValue());
314 verifyMethodCall(
315 bufferCaptor.getValue(),
316 "TextInputClient.performPrivateCommand",
317 new String[] {
318 "0", "{\"data\":{\"keyboard_layout\":[97,98,99,100]},\"action\":\"actionCommand\"}"
319 });
320 }
321
322 @SuppressWarnings("deprecation")
323 // DartExecutor.send is deprecated.
324 @Test
325 public void testPerformPrivateCommand_dataIsByte() throws JSONException {
326 View testView = new View(ctx);
327 int client = 0;
328 FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
329 DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
330 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
331 ListenableEditingState editable = sampleEditable(0, 0);
332 InputConnectionAdaptor adaptor =
334 testView,
335 client,
336 textInputChannel,
337 mockKeyboardManager,
338 editable,
339 null,
340 mockFlutterJNI);
341
342 Bundle bundle = new Bundle();
343 byte b = 3;
344 bundle.putByte("keyboard_layout", b);
345 adaptor.performPrivateCommand("actionCommand", bundle);
346
347 ArgumentCaptor<String> channelCaptor = ArgumentCaptor.forClass(String.class);
348 ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
349 verify(dartExecutor, times(1)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull());
350 assertEquals("flutter/textinput", channelCaptor.getValue());
351 verifyMethodCall(
352 bufferCaptor.getValue(),
353 "TextInputClient.performPrivateCommand",
354 new String[] {"0", "{\"data\":{\"keyboard_layout\":3},\"action\":\"actionCommand\"}"});
355 }
356
357 @SuppressWarnings("deprecation")
358 // DartExecutor.send is deprecated.
359 @Test
360 public void testPerformPrivateCommand_dataIsCharArray() throws JSONException {
361 View testView = new View(ctx);
362 int client = 0;
363 FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
364 DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
365 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
366 ListenableEditingState editable = sampleEditable(0, 0);
367 InputConnectionAdaptor adaptor =
369 testView,
370 client,
371 textInputChannel,
372 mockKeyboardManager,
373 editable,
374 null,
375 mockFlutterJNI);
376
377 Bundle bundle = new Bundle();
378 char[] buffer = new char[] {'a', 'b', 'c', 'd'};
379 bundle.putCharArray("keyboard_layout", buffer);
380 adaptor.performPrivateCommand("actionCommand", bundle);
381
382 ArgumentCaptor<String> channelCaptor = ArgumentCaptor.forClass(String.class);
383 ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
384 verify(dartExecutor, times(1)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull());
385 assertEquals("flutter/textinput", channelCaptor.getValue());
386 verifyMethodCall(
387 bufferCaptor.getValue(),
388 "TextInputClient.performPrivateCommand",
389 new String[] {
390 "0",
391 "{\"data\":{\"keyboard_layout\":[\"a\",\"b\",\"c\",\"d\"]},\"action\":\"actionCommand\"}"
392 });
393 }
394
395 @SuppressWarnings("deprecation")
396 // DartExecutor.send is deprecated.
397 @Test
398 public void testPerformPrivateCommand_dataIsChar() throws JSONException {
399 View testView = new View(ctx);
400 int client = 0;
401 FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
402 DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
403 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
404 ListenableEditingState editable = sampleEditable(0, 0);
405 InputConnectionAdaptor adaptor =
407 testView,
408 client,
409 textInputChannel,
410 mockKeyboardManager,
411 editable,
412 null,
413 mockFlutterJNI);
414
415 Bundle bundle = new Bundle();
416 char b = 'a';
417 bundle.putChar("keyboard_layout", b);
418 adaptor.performPrivateCommand("actionCommand", bundle);
419
420 ArgumentCaptor<String> channelCaptor = ArgumentCaptor.forClass(String.class);
421 ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
422 verify(dartExecutor, times(1)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull());
423 assertEquals("flutter/textinput", channelCaptor.getValue());
424 verifyMethodCall(
425 bufferCaptor.getValue(),
426 "TextInputClient.performPrivateCommand",
427 new String[] {"0", "{\"data\":{\"keyboard_layout\":\"a\"},\"action\":\"actionCommand\"}"});
428 }
429
430 @SuppressWarnings("deprecation")
431 // DartExecutor.send is deprecated.
432 @Test
433 public void testPerformPrivateCommand_dataIsCharSequenceArray() throws JSONException {
434 View testView = new View(ctx);
435 int client = 0;
436 FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
437 DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
438 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
439 ListenableEditingState editable = sampleEditable(0, 0);
440 InputConnectionAdaptor adaptor =
442 testView,
443 client,
444 textInputChannel,
445 mockKeyboardManager,
446 editable,
447 null,
448 mockFlutterJNI);
449
450 Bundle bundle = new Bundle();
451 CharSequence charSequence1 = new StringBuffer("abc");
452 CharSequence charSequence2 = new StringBuffer("efg");
453 CharSequence[] value = {charSequence1, charSequence2};
454 bundle.putCharSequenceArray("keyboard_layout", value);
455 adaptor.performPrivateCommand("actionCommand", bundle);
456
457 ArgumentCaptor<String> channelCaptor = ArgumentCaptor.forClass(String.class);
458 ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
459 verify(dartExecutor, times(1)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull());
460 assertEquals("flutter/textinput", channelCaptor.getValue());
461 verifyMethodCall(
462 bufferCaptor.getValue(),
463 "TextInputClient.performPrivateCommand",
464 new String[] {
465 "0", "{\"data\":{\"keyboard_layout\":[\"abc\",\"efg\"]},\"action\":\"actionCommand\"}"
466 });
467 }
468
469 @SuppressWarnings("deprecation")
470 // DartExecutor.send is deprecated.
471 @Test
472 public void testPerformPrivateCommand_dataIsCharSequence() throws JSONException {
473 View testView = new View(ctx);
474 int client = 0;
475 FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
476 DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
477 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
478 ListenableEditingState editable = sampleEditable(0, 0);
479 InputConnectionAdaptor adaptor =
481 testView,
482 client,
483 textInputChannel,
484 mockKeyboardManager,
485 editable,
486 null,
487 mockFlutterJNI);
488
489 Bundle bundle = new Bundle();
490 CharSequence charSequence = new StringBuffer("abc");
491 bundle.putCharSequence("keyboard_layout", charSequence);
492 adaptor.performPrivateCommand("actionCommand", bundle);
493
494 ArgumentCaptor<String> channelCaptor = ArgumentCaptor.forClass(String.class);
495 ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
496 verify(dartExecutor, times(1)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull());
497 assertEquals("flutter/textinput", channelCaptor.getValue());
498 verifyMethodCall(
499 bufferCaptor.getValue(),
500 "TextInputClient.performPrivateCommand",
501 new String[] {
502 "0", "{\"data\":{\"keyboard_layout\":\"abc\"},\"action\":\"actionCommand\"}"
503 });
504 }
505
506 @SuppressWarnings("deprecation")
507 // DartExecutor.send is deprecated.
508 @Test
509 public void testPerformPrivateCommand_dataIsFloat() throws JSONException {
510 View testView = new View(ctx);
511 int client = 0;
512 FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
513 DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
514 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
515 ListenableEditingState editable = sampleEditable(0, 0);
516 InputConnectionAdaptor adaptor =
518 testView,
519 client,
520 textInputChannel,
521 mockKeyboardManager,
522 editable,
523 null,
524 mockFlutterJNI);
525
526 Bundle bundle = new Bundle();
527 float value = 0.5f;
528 bundle.putFloat("keyboard_layout", value);
529 adaptor.performPrivateCommand("actionCommand", bundle);
530
531 ArgumentCaptor<String> channelCaptor = ArgumentCaptor.forClass(String.class);
532 ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
533 verify(dartExecutor, times(1)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull());
534 assertEquals("flutter/textinput", channelCaptor.getValue());
535 verifyMethodCall(
536 bufferCaptor.getValue(),
537 "TextInputClient.performPrivateCommand",
538 new String[] {"0", "{\"data\":{\"keyboard_layout\":0.5},\"action\":\"actionCommand\"}"});
539 }
540
541 @SuppressWarnings("deprecation")
542 // DartExecutor.send is deprecated.
543 @Test
544 public void testPerformPrivateCommand_dataIsFloatArray() throws JSONException {
545 View testView = new View(ctx);
546 int client = 0;
547 FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
548 DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
549 TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
550 ListenableEditingState editable = sampleEditable(0, 0);
551 InputConnectionAdaptor adaptor =
553 testView,
554 client,
555 textInputChannel,
556 mockKeyboardManager,
557 editable,
558 null,
559 mockFlutterJNI);
560
561 Bundle bundle = new Bundle();
562 float[] value = {0.5f, 0.6f};
563 bundle.putFloatArray("keyboard_layout", value);
564 adaptor.performPrivateCommand("actionCommand", bundle);
565
566 ArgumentCaptor<String> channelCaptor = ArgumentCaptor.forClass(String.class);
567 ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
568 verify(dartExecutor, times(1)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull());
569 assertEquals("flutter/textinput", channelCaptor.getValue());
570 verifyMethodCall(
571 bufferCaptor.getValue(),
572 "TextInputClient.performPrivateCommand",
573 new String[] {
574 "0", "{\"data\":{\"keyboard_layout\":[0.5,0.6]},\"action\":\"actionCommand\"}"
575 });
576 }
577
578 @Test
580 // Regression test for https://github.com/flutter/flutter/issues/101569.
581 int selStart = 5;
582 int selEnd = 10;
583 ListenableEditingState editable = sampleEditable(selStart, selEnd);
584 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
585
586 KeyEvent shiftKeyUp = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT);
587 boolean didConsume = adaptor.handleKeyEvent(shiftKeyUp);
588
589 assertFalse(didConsume);
590 assertEquals(selStart, Selection.getSelectionStart(editable));
591 assertEquals(selEnd, Selection.getSelectionEnd(editable));
592 }
593
594 @Test
596 int selStart = 5;
597 ListenableEditingState editable = sampleEditable(selStart, selStart);
598 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
599
600 KeyEvent leftKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT);
601 boolean didConsume = adaptor.handleKeyEvent(leftKeyDown);
602
603 assertTrue(didConsume);
604 assertEquals(selStart - 1, Selection.getSelectionStart(editable));
605 assertEquals(selStart - 1, Selection.getSelectionEnd(editable));
606 }
607
608 @Test
610 int selStart = 75;
611 ListenableEditingState editable = sampleEditable(selStart, selStart, SAMPLE_EMOJI_TEXT);
612 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
613
614 KeyEvent downKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT);
615 boolean didConsume;
616
617 // Normal Character
618 didConsume = adaptor.handleKeyEvent(downKeyDown);
619 assertTrue(didConsume);
620 assertEquals(Selection.getSelectionStart(editable), 74);
621
622 // Non-Spacing Mark
623 didConsume = adaptor.handleKeyEvent(downKeyDown);
624 assertTrue(didConsume);
625 assertEquals(Selection.getSelectionStart(editable), 73);
626 didConsume = adaptor.handleKeyEvent(downKeyDown);
627 assertTrue(didConsume);
628 assertEquals(Selection.getSelectionStart(editable), 72);
629
630 // Keycap
631 didConsume = adaptor.handleKeyEvent(downKeyDown);
632 assertTrue(didConsume);
633 assertEquals(Selection.getSelectionStart(editable), 69);
634
635 // Keycap with invalid base
636 adaptor.setSelection(68, 68);
637 didConsume = adaptor.handleKeyEvent(downKeyDown);
638 assertTrue(didConsume);
639 assertEquals(Selection.getSelectionStart(editable), 66);
640 adaptor.setSelection(67, 67);
641 didConsume = adaptor.handleKeyEvent(downKeyDown);
642 assertTrue(didConsume);
643 assertEquals(Selection.getSelectionStart(editable), 66);
644
645 // Zero Width Joiner
646 didConsume = adaptor.handleKeyEvent(downKeyDown);
647 assertTrue(didConsume);
648 assertEquals(Selection.getSelectionStart(editable), 55);
649
650 // Zero Width Joiner with invalid base
651 didConsume = adaptor.handleKeyEvent(downKeyDown);
652 assertTrue(didConsume);
653 assertEquals(Selection.getSelectionStart(editable), 53);
654 didConsume = adaptor.handleKeyEvent(downKeyDown);
655 assertTrue(didConsume);
656 assertEquals(Selection.getSelectionStart(editable), 52);
657 didConsume = adaptor.handleKeyEvent(downKeyDown);
658 assertTrue(didConsume);
659 assertEquals(Selection.getSelectionStart(editable), 51);
660
661 // ----- Start Emoji Tag Sequence with invalid base testing ----
662 // Delete base tag
663 adaptor.setSelection(39, 39);
664 didConsume = adaptor.handleKeyEvent(downKeyDown);
665 assertTrue(didConsume);
666 assertEquals(Selection.getSelectionStart(editable), 37);
667
668 // Delete the sequence
669 adaptor.setSelection(49, 49);
670 for (int i = 0; i < 6; i++) {
671 didConsume = adaptor.handleKeyEvent(downKeyDown);
672 assertTrue(didConsume);
673 }
674 assertEquals(Selection.getSelectionStart(editable), 37);
675 // ----- End Emoji Tag Sequence with invalid base testing ----
676
677 // Emoji Tag Sequence
678 didConsume = adaptor.handleKeyEvent(downKeyDown);
679 assertTrue(didConsume);
680 assertEquals(Selection.getSelectionStart(editable), 23);
681
682 // Variation Selector with invalid base
683 adaptor.setSelection(22, 22);
684 didConsume = adaptor.handleKeyEvent(downKeyDown);
685 assertTrue(didConsume);
686 assertEquals(Selection.getSelectionStart(editable), 21);
687 adaptor.setSelection(22, 22);
688 didConsume = adaptor.handleKeyEvent(downKeyDown);
689 assertTrue(didConsume);
690 assertEquals(Selection.getSelectionStart(editable), 21);
691
692 // Variation Selector
693 didConsume = adaptor.handleKeyEvent(downKeyDown);
694 assertTrue(didConsume);
695 assertEquals(Selection.getSelectionStart(editable), 19);
696
697 // Emoji Modifier
698 didConsume = adaptor.handleKeyEvent(downKeyDown);
699 assertTrue(didConsume);
700 assertEquals(Selection.getSelectionStart(editable), 16);
701
702 // Emoji Modifier with invalid base
703 adaptor.setSelection(14, 14);
704 didConsume = adaptor.handleKeyEvent(downKeyDown);
705 assertTrue(didConsume);
706 assertEquals(Selection.getSelectionStart(editable), 13);
707 adaptor.setSelection(14, 14);
708 didConsume = adaptor.handleKeyEvent(downKeyDown);
709 assertTrue(didConsume);
710 assertEquals(Selection.getSelectionStart(editable), 13);
711
712 // Line Feed
713 adaptor.setSelection(12, 12);
714 didConsume = adaptor.handleKeyEvent(downKeyDown);
715 assertTrue(didConsume);
716 assertEquals(Selection.getSelectionStart(editable), 11);
717
718 // Carriage Return
719 adaptor.setSelection(12, 12);
720 didConsume = adaptor.handleKeyEvent(downKeyDown);
721 assertTrue(didConsume);
722 assertEquals(Selection.getSelectionStart(editable), 11);
723
724 // Carriage Return and Line Feed
725 didConsume = adaptor.handleKeyEvent(downKeyDown);
726 assertTrue(didConsume);
727 assertEquals(Selection.getSelectionStart(editable), 9);
728
729 // Regional Indicator Symbol odd
730 didConsume = adaptor.handleKeyEvent(downKeyDown);
731 assertTrue(didConsume);
732 assertEquals(Selection.getSelectionStart(editable), 7);
733
734 // Regional Indicator Symbol even
735 didConsume = adaptor.handleKeyEvent(downKeyDown);
736 assertTrue(didConsume);
737 assertEquals(Selection.getSelectionStart(editable), 3);
738
739 // Simple Emoji
740 didConsume = adaptor.handleKeyEvent(downKeyDown);
741 assertTrue(didConsume);
742 assertEquals(Selection.getSelectionStart(editable), 1);
743
744 // First CodePoint
745 didConsume = adaptor.handleKeyEvent(downKeyDown);
746 assertTrue(didConsume);
747 assertEquals(Selection.getSelectionStart(editable), 0);
748 }
749
750 @Test
752 int selStart = 5;
753 int selEnd = 40;
754 ListenableEditingState editable = sampleEditable(selStart, selEnd);
755 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
756
757 KeyEvent leftKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT);
758 boolean didConsume = adaptor.handleKeyEvent(leftKeyDown);
759
760 assertTrue(didConsume);
761 assertEquals(selStart, Selection.getSelectionStart(editable));
762 assertEquals(selEnd - 1, Selection.getSelectionEnd(editable));
763 }
764
765 @Test
767 int selStart = 5;
768 ListenableEditingState editable = sampleEditable(selStart, selStart);
769 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
770
771 KeyEvent shiftLeftKeyDown =
772 new KeyEvent(
773 0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT, 0, KeyEvent.META_SHIFT_ON);
774 boolean didConsume = adaptor.handleKeyEvent(shiftLeftKeyDown);
775
776 assertTrue(didConsume);
777 assertEquals(selStart, Selection.getSelectionStart(editable));
778 assertEquals(selStart - 1, Selection.getSelectionEnd(editable));
779 }
780
781 @Test
783 int selStart = 5;
784 ListenableEditingState editable = sampleEditable(selStart, selStart);
785 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
786
787 KeyEvent rightKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT);
788 boolean didConsume = adaptor.handleKeyEvent(rightKeyDown);
789
790 assertTrue(didConsume);
791 assertEquals(selStart + 1, Selection.getSelectionStart(editable));
792 assertEquals(selStart + 1, Selection.getSelectionEnd(editable));
793 }
794
795 @Test
797 int selStart = 0;
798 // Seven region indicator characters. The first six should be considered as
799 // three region indicators, and the final seventh character should be
800 // considered to be on its own because it has no partner.
801 String SAMPLE_REGION_TEXT = "🇷🇷🇷🇷🇷🇷🇷";
802 ListenableEditingState editable = sampleEditable(selStart, selStart, SAMPLE_REGION_TEXT);
803 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
804
805 KeyEvent downKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT);
806 boolean didConsume;
807
808 // The cursor moves over two region indicators at a time.
809 didConsume = adaptor.handleKeyEvent(downKeyDown);
810 assertTrue(didConsume);
811 assertEquals(Selection.getSelectionStart(editable), 4);
812 didConsume = adaptor.handleKeyEvent(downKeyDown);
813 assertTrue(didConsume);
814 assertEquals(Selection.getSelectionStart(editable), 8);
815 didConsume = adaptor.handleKeyEvent(downKeyDown);
816 assertTrue(didConsume);
817 assertEquals(Selection.getSelectionStart(editable), 12);
818
819 // When there is only one region indicator left with no pair, the cursor
820 // moves over that single region indicator.
821 didConsume = adaptor.handleKeyEvent(downKeyDown);
822 assertTrue(didConsume);
823 assertEquals(Selection.getSelectionStart(editable), 14);
824
825 // If the cursor is placed in the middle of a region indicator pair, it
826 // moves over only the second half of the pair.
827 adaptor.setSelection(6, 6);
828 didConsume = adaptor.handleKeyEvent(downKeyDown);
829 assertTrue(didConsume);
830 assertEquals(Selection.getSelectionStart(editable), 8);
831 }
832
833 @Test
835 int selStart = 0;
836 ListenableEditingState editable = sampleEditable(selStart, selStart, SAMPLE_EMOJI_TEXT);
837 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
838
839 KeyEvent downKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT);
840 boolean didConsume;
841
842 // First CodePoint
843 didConsume = adaptor.handleKeyEvent(downKeyDown);
844 assertTrue(didConsume);
845 assertEquals(Selection.getSelectionStart(editable), 1);
846
847 // Simple Emoji
848 didConsume = adaptor.handleKeyEvent(downKeyDown);
849 assertTrue(didConsume);
850 assertEquals(Selection.getSelectionStart(editable), 3);
851
852 // Regional Indicator Symbol even
853 didConsume = adaptor.handleKeyEvent(downKeyDown);
854 assertTrue(didConsume);
855 assertEquals(Selection.getSelectionStart(editable), 7);
856
857 // Regional Indicator Symbol odd
858 didConsume = adaptor.handleKeyEvent(downKeyDown);
859 assertTrue(didConsume);
860 assertEquals(Selection.getSelectionStart(editable), 9);
861
862 // Carriage Return
863 didConsume = adaptor.handleKeyEvent(downKeyDown);
864 assertTrue(didConsume);
865 assertEquals(Selection.getSelectionStart(editable), 10);
866
867 // Line Feed and Carriage Return
868 didConsume = adaptor.handleKeyEvent(downKeyDown);
869 assertTrue(didConsume);
870 assertEquals(Selection.getSelectionStart(editable), 12);
871
872 // Line Feed
873 didConsume = adaptor.handleKeyEvent(downKeyDown);
874 assertTrue(didConsume);
875 assertEquals(Selection.getSelectionStart(editable), 13);
876
877 // Modified Emoji
878 didConsume = adaptor.handleKeyEvent(downKeyDown);
879 assertTrue(didConsume);
880 assertEquals(Selection.getSelectionStart(editable), 16);
881
882 // Emoji Modifier
883 adaptor.setSelection(14, 14);
884 didConsume = adaptor.handleKeyEvent(downKeyDown);
885 assertTrue(didConsume);
886 assertEquals(Selection.getSelectionStart(editable), 16);
887
888 // Emoji Modifier with invalid base
889 adaptor.setSelection(18, 18);
890 didConsume = adaptor.handleKeyEvent(downKeyDown);
891 assertTrue(didConsume);
892 assertEquals(Selection.getSelectionStart(editable), 19);
893
894 // Variation Selector
895 didConsume = adaptor.handleKeyEvent(downKeyDown);
896 assertTrue(didConsume);
897 assertEquals(Selection.getSelectionStart(editable), 21);
898
899 // Variation Selector with invalid base
900 adaptor.setSelection(22, 22);
901 didConsume = adaptor.handleKeyEvent(downKeyDown);
902 assertTrue(didConsume);
903 assertEquals(Selection.getSelectionStart(editable), 23);
904
905 // Emoji Tag Sequence
906 for (int i = 0; i < 7; i++) {
907 didConsume = adaptor.handleKeyEvent(downKeyDown);
908 assertTrue(didConsume);
909 assertEquals(Selection.getSelectionStart(editable), 25 + 2 * i);
910 }
911 assertEquals(Selection.getSelectionStart(editable), 37);
912
913 // ----- Start Emoji Tag Sequence with invalid base testing ----
914 // Pass the sequence
915 adaptor.setSelection(39, 39);
916 for (int i = 0; i < 6; i++) {
917 didConsume = adaptor.handleKeyEvent(downKeyDown);
918 assertTrue(didConsume);
919 assertEquals(Selection.getSelectionStart(editable), 41 + 2 * i);
920 }
921 assertEquals(Selection.getSelectionStart(editable), 51);
922 // ----- End Emoji Tag Sequence with invalid base testing ----
923
924 // Zero Width Joiner with invalid base
925 didConsume = adaptor.handleKeyEvent(downKeyDown);
926 assertTrue(didConsume);
927 assertEquals(Selection.getSelectionStart(editable), 52);
928 didConsume = adaptor.handleKeyEvent(downKeyDown);
929 assertTrue(didConsume);
930 assertEquals(Selection.getSelectionStart(editable), 53);
931 didConsume = adaptor.handleKeyEvent(downKeyDown);
932 assertTrue(didConsume);
933 assertEquals(Selection.getSelectionStart(editable), 55);
934
935 // Zero Width Joiner
936 didConsume = adaptor.handleKeyEvent(downKeyDown);
937 assertTrue(didConsume);
938 assertEquals(Selection.getSelectionStart(editable), 66);
939
940 // Keycap with invalid base
941 adaptor.setSelection(67, 67);
942 didConsume = adaptor.handleKeyEvent(downKeyDown);
943 assertTrue(didConsume);
944 assertEquals(Selection.getSelectionStart(editable), 68);
945 didConsume = adaptor.handleKeyEvent(downKeyDown);
946 assertTrue(didConsume);
947 assertEquals(Selection.getSelectionStart(editable), 69);
948
949 // Keycap
950 didConsume = adaptor.handleKeyEvent(downKeyDown);
951 assertTrue(didConsume);
952 assertEquals(Selection.getSelectionStart(editable), 72);
953
954 // Non-Spacing Mark
955 didConsume = adaptor.handleKeyEvent(downKeyDown);
956 assertTrue(didConsume);
957 assertEquals(Selection.getSelectionStart(editable), 73);
958 didConsume = adaptor.handleKeyEvent(downKeyDown);
959 assertTrue(didConsume);
960 assertEquals(Selection.getSelectionStart(editable), 74);
961
962 // Normal Character
963 didConsume = adaptor.handleKeyEvent(downKeyDown);
964 assertTrue(didConsume);
965 assertEquals(Selection.getSelectionStart(editable), 75);
966 }
967
968 @Test
970 int selStart = 5;
971 int selEnd = 40;
972 ListenableEditingState editable = sampleEditable(selStart, selEnd);
973 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
974
975 KeyEvent rightKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT);
976 boolean didConsume = adaptor.handleKeyEvent(rightKeyDown);
977
978 assertTrue(didConsume);
979 assertEquals(selStart, Selection.getSelectionStart(editable));
980 assertEquals(selEnd + 1, Selection.getSelectionEnd(editable));
981 }
982
983 @Test
985 int selStart = 5;
986 ListenableEditingState editable = sampleEditable(selStart, selStart);
987 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
988
989 KeyEvent shiftRightKeyDown =
990 new KeyEvent(
991 0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT, 0, KeyEvent.META_SHIFT_ON);
992 boolean didConsume = adaptor.handleKeyEvent(shiftRightKeyDown);
993
994 assertTrue(didConsume);
995 assertEquals(selStart, Selection.getSelectionStart(editable));
996 assertEquals(selStart + 1, Selection.getSelectionEnd(editable));
997 }
998
999 @Test
1001 int selStart = SAMPLE_TEXT.indexOf('\n') + 4;
1002 ListenableEditingState editable = sampleEditable(selStart, selStart);
1003 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
1004
1005 KeyEvent upKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP);
1006 boolean didConsume = adaptor.handleKeyEvent(upKeyDown);
1007
1008 assertTrue(didConsume);
1009 // Checks the caret moved left (to some previous character). Selection.moveUp() behaves
1010 // different in tests than on a real device, we can't verify the exact position.
1011 assertTrue(Selection.getSelectionStart(editable) < selStart);
1012 }
1013
1014 @Test
1016 int selStart = 4;
1017 ListenableEditingState editable = sampleEditable(selStart, selStart);
1018 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
1019
1020 KeyEvent downKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN);
1021 boolean didConsume = adaptor.handleKeyEvent(downKeyDown);
1022
1023 assertTrue(didConsume);
1024 // Checks the caret moved right (to some following character). Selection.moveDown() behaves
1025 // different in tests than on a real device, we can't verify the exact position.
1026 assertTrue(Selection.getSelectionStart(editable) > selStart);
1027 }
1028
1029 @Test
1031 // Regression test for https://github.com/flutter/flutter/issues/76283.
1032 ListenableEditingState editable = sampleEditable(-1, -1);
1033 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
1034
1035 KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN);
1036 boolean didConsume = adaptor.handleKeyEvent(keyEvent);
1037 assertFalse(didConsume);
1038 assertEquals(Selection.getSelectionStart(editable), -1);
1039 assertEquals(Selection.getSelectionEnd(editable), -1);
1040
1041 keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP);
1042 didConsume = adaptor.handleKeyEvent(keyEvent);
1043 assertFalse(didConsume);
1044 assertEquals(Selection.getSelectionStart(editable), -1);
1045 assertEquals(Selection.getSelectionEnd(editable), -1);
1046
1047 keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT);
1048 didConsume = adaptor.handleKeyEvent(keyEvent);
1049 assertFalse(didConsume);
1050 assertEquals(Selection.getSelectionStart(editable), -1);
1051 assertEquals(Selection.getSelectionEnd(editable), -1);
1052
1053 keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT);
1054 didConsume = adaptor.handleKeyEvent(keyEvent);
1055 assertFalse(didConsume);
1056 assertEquals(Selection.getSelectionStart(editable), -1);
1057 assertEquals(Selection.getSelectionEnd(editable), -1);
1058 }
1059
1060 @Test
1062 int selStart = 5;
1063
1064 ListenableEditingState editable = sampleEditable(selStart, selStart);
1065 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
1066
1067 ExtractedText extractedText = adaptor.getExtractedText(null, 0);
1068
1069 assertEquals(extractedText.text, SAMPLE_TEXT);
1070 assertEquals(extractedText.selectionStart, selStart);
1071 assertEquals(extractedText.selectionEnd, selStart);
1072 }
1073
1074 @Test
1076 ListenableEditingState editable = sampleEditable(5, 5);
1077 View testView = new View(ctx);
1078 InputConnectionAdaptor adaptor =
1080 testView,
1081 1,
1082 mock(TextInputChannel.class),
1083 mockKeyboardManager,
1084 editable,
1085 new EditorInfo());
1086 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
1087
1088 testImm.resetStates();
1089
1090 ExtractedTextRequest request = new ExtractedTextRequest();
1091 request.token = 123;
1092
1093 ExtractedText extractedText = adaptor.getExtractedText(request, 0);
1094 assertEquals(5, extractedText.selectionStart);
1095 assertEquals(5, extractedText.selectionEnd);
1096 assertFalse(extractedText.text instanceof SpannableStringBuilder);
1097
1098 // Move the cursor. Should not report extracted text.
1099 adaptor.setSelection(2, 3);
1100 assertNull(testImm.lastExtractedText);
1101
1102 // Now request monitoring, and update the request text flag.
1103 request.flags = InputConnection.GET_TEXT_WITH_STYLES;
1104 extractedText = adaptor.getExtractedText(request, InputConnection.GET_EXTRACTED_TEXT_MONITOR);
1105 assertEquals(2, extractedText.selectionStart);
1106 assertEquals(3, extractedText.selectionEnd);
1107 assertTrue(extractedText.text instanceof SpannableStringBuilder);
1108
1109 adaptor.setSelection(3, 5);
1110 assertEquals(3, testImm.lastExtractedText.selectionStart);
1111 assertEquals(5, testImm.lastExtractedText.selectionEnd);
1112 assertTrue(testImm.lastExtractedText.text instanceof SpannableStringBuilder);
1113
1114 // Stop monitoring.
1115 testImm.resetStates();
1116 extractedText = adaptor.getExtractedText(request, 0);
1117 assertEquals(3, extractedText.selectionStart);
1118 assertEquals(5, extractedText.selectionEnd);
1119 assertTrue(extractedText.text instanceof SpannableStringBuilder);
1120
1121 adaptor.setSelection(1, 3);
1122 assertNull(testImm.lastExtractedText);
1123 }
1124
1125 @Test
1126 public void testCursorAnchorInfo() {
1127 ListenableEditingState editable = sampleEditable(5, 5);
1128 View testView = new View(ctx);
1129 InputConnectionAdaptor adaptor =
1131 testView,
1132 1,
1133 mock(TextInputChannel.class),
1134 mockKeyboardManager,
1135 editable,
1136 new EditorInfo());
1137 TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
1138
1139 testImm.resetStates();
1140
1141 // Monitoring only. Does not send update immediately.
1142 adaptor.requestCursorUpdates(InputConnection.CURSOR_UPDATE_MONITOR);
1143 assertNull(testImm.lastCursorAnchorInfo);
1144
1145 // Monitor selection changes.
1146 adaptor.setSelection(0, 1);
1147 CursorAnchorInfo cursorAnchorInfo = testImm.lastCursorAnchorInfo;
1148 assertEquals(0, cursorAnchorInfo.getSelectionStart());
1149 assertEquals(1, cursorAnchorInfo.getSelectionEnd());
1150
1151 // Turn monitoring off.
1152 testImm.resetStates();
1153 assertNull(testImm.lastCursorAnchorInfo);
1154 adaptor.requestCursorUpdates(InputConnection.CURSOR_UPDATE_IMMEDIATE);
1155 cursorAnchorInfo = testImm.lastCursorAnchorInfo;
1156 assertEquals(0, cursorAnchorInfo.getSelectionStart());
1157 assertEquals(1, cursorAnchorInfo.getSelectionEnd());
1158
1159 // No more updates.
1160 testImm.resetStates();
1161 adaptor.setSelection(1, 3);
1162 assertNull(testImm.lastCursorAnchorInfo);
1163 }
1164
1165 @Test
1167 ListenableEditingState editable = sampleEditable(5, 5);
1168 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable, mockKeyboardManager);
1169
1170 KeyEvent shiftKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT);
1171
1172 boolean didConsume = adaptor.handleKeyEvent(shiftKeyDown);
1173 assertFalse(didConsume);
1174 verify(mockKeyboardManager, never()).handleEvent(shiftKeyDown);
1175 }
1176
1177 @Test
1179 ListenableEditingState editable = sampleEditable(5, 5);
1180 when(mockKeyboardManager.handleEvent(any())).thenReturn(true);
1181 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable, mockKeyboardManager);
1182
1183 KeyEvent shiftKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT);
1184
1185 // Call sendKeyEvent instead of handleKeyEvent.
1186 boolean didConsume = adaptor.sendKeyEvent(shiftKeyDown);
1187 assertTrue(didConsume);
1188 verify(mockKeyboardManager, times(1)).handleEvent(shiftKeyDown);
1189 }
1190
1191 @Test
1193 ListenableEditingState editable = sampleEditable(5, 5);
1194 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
1195
1196 KeyEvent downKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
1197
1198 for (int i = 0; i < 4; i++) {
1199 boolean didConsume = adaptor.handleKeyEvent(downKeyDown);
1200 assertFalse(didConsume);
1201 }
1202 assertEquals(5, Selection.getSelectionStart(editable));
1203 }
1204
1205 @Test
1207 ListenableEditingState editable = sampleEditable(0, 0);
1208 InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable);
1209
1210 FakeKeyEvent keyEvent = new FakeKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, '\b');
1211 boolean didConsume = adaptor.handleKeyEvent(keyEvent);
1212
1213 assertFalse(didConsume);
1214 }
1215
1216 @Test
1218 final ListenableEditingState editable = sampleEditable(0, 0);
1219 InputConnectionAdaptor adaptor = spy(sampleInputConnectionAdaptor(editable));
1220 for (int i = 0; i < 5; i++) {
1221 adaptor.beginBatchEdit();
1222 }
1223 adaptor.endBatchEdit();
1224 verify(adaptor, times(1)).endBatchEdit();
1225 adaptor.closeConnection();
1226 verify(adaptor, times(4)).endBatchEdit();
1227 }
1228
1229 private static final String SAMPLE_TEXT =
1230 "Lorem ipsum dolor sit amet," + "\nconsectetur adipiscing elit.";
1231
1232 private static final String SAMPLE_EMOJI_TEXT =
1233 "a" // First CodePoint
1234 + "😂" // Simple Emoji
1235 + "🇮🇷" // Regional Indicator Symbol even
1236 + "🇷" // Regional Indicator Symbol odd
1237 + "\r\n" // Carriage Return and Line Feed
1238 + "\r\n"
1239 + "✋🏿" // Emoji Modifier
1240 + "✋🏿"
1241 + "⚠️" // Variant Selector
1242 + "⚠️"
1243 + "🏴󠁧󠁢󠁥󠁮󠁧󠁿" // Emoji Tag Sequence
1244 + "🏴󠁧󠁢󠁥󠁮󠁧󠁿"
1245 + "a‍👨" // Zero Width Joiner
1246 + "👨‍👩‍👧‍👦"
1247 + "5️⃣" // Keycap
1248 + "5️⃣"
1249 + "عَ" // Non-Spacing Mark
1250 + "a"; // Normal Character
1251
1252 private static final String SAMPLE_RTL_TEXT = "متن ساختگی" + "\nبرای تستfor test😊";
1253
1254 private static ListenableEditingState sampleEditable(int selStart, int selEnd) {
1255 ListenableEditingState sample =
1256 new ListenableEditingState(null, new View(ApplicationProvider.getApplicationContext()));
1257 sample.replace(0, 0, SAMPLE_TEXT);
1258 Selection.setSelection(sample, selStart, selEnd);
1259 return sample;
1260 }
1261
1262 private static ListenableEditingState sampleEditable(int selStart, int selEnd, String text) {
1263 ListenableEditingState sample =
1264 new ListenableEditingState(null, new View(ApplicationProvider.getApplicationContext()));
1265 sample.replace(0, 0, text);
1266 Selection.setSelection(sample, selStart, selEnd);
1267 return sample;
1268 }
1269
1270 private static InputConnectionAdaptor sampleInputConnectionAdaptor(
1271 ListenableEditingState editable) {
1272 return sampleInputConnectionAdaptor(editable, mock(KeyboardManager.class));
1273 }
1274
1275 private static InputConnectionAdaptor sampleInputConnectionAdaptor(
1276 ListenableEditingState editable, KeyboardManager mockKeyboardManager) {
1277 View testView = new View(ApplicationProvider.getApplicationContext());
1278 int client = 0;
1279 TextInputChannel textInputChannel = mock(TextInputChannel.class);
1280 FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
1281 when(mockFlutterJNI.isCodePointEmoji(anyInt()))
1282 .thenAnswer((invocation) -> Emoji.isEmoji((int) invocation.getArguments()[0]));
1283 when(mockFlutterJNI.isCodePointEmojiModifier(anyInt()))
1284 .thenAnswer((invocation) -> Emoji.isEmojiModifier((int) invocation.getArguments()[0]));
1285 when(mockFlutterJNI.isCodePointEmojiModifierBase(anyInt()))
1286 .thenAnswer((invocation) -> Emoji.isEmojiModifierBase((int) invocation.getArguments()[0]));
1287 when(mockFlutterJNI.isCodePointVariantSelector(anyInt()))
1288 .thenAnswer((invocation) -> Emoji.isVariationSelector((int) invocation.getArguments()[0]));
1289 when(mockFlutterJNI.isCodePointRegionalIndicator(anyInt()))
1290 .thenAnswer(
1291 (invocation) -> Emoji.isRegionalIndicatorSymbol((int) invocation.getArguments()[0]));
1292 return new InputConnectionAdaptor(
1293 testView, client, textInputChannel, mockKeyboardManager, editable, null, mockFlutterJNI);
1294 }
1295
1296 private static class Emoji {
1297 public static boolean isEmoji(int codePoint) {
1298 return UCharacter.hasBinaryProperty(codePoint, UProperty.EMOJI);
1299 }
1300
1301 public static boolean isEmojiModifier(int codePoint) {
1302 return UCharacter.hasBinaryProperty(codePoint, UProperty.EMOJI_MODIFIER);
1303 }
1304
1305 public static boolean isEmojiModifierBase(int codePoint) {
1306 return UCharacter.hasBinaryProperty(codePoint, UProperty.EMOJI_MODIFIER_BASE);
1307 }
1308
1309 public static boolean isRegionalIndicatorSymbol(int codePoint) {
1310 return UCharacter.hasBinaryProperty(codePoint, UProperty.REGIONAL_INDICATOR);
1311 }
1312
1313 public static boolean isVariationSelector(int codePoint) {
1314 return UCharacter.hasBinaryProperty(codePoint, UProperty.VARIATION_SELECTOR);
1315 }
1316 }
1317
1318 private class TestTextInputChannel extends TextInputChannel {
1319 public TestTextInputChannel(DartExecutor dartExecutor) {
1320 super(dartExecutor);
1321 }
1322
1323 public int inputClientId;
1324 public String text;
1325 public int selectionStart;
1326 public int selectionEnd;
1327 public int composingStart;
1328 public int composingEnd;
1329 public int updateEditingStateInvocations = 0;
1330
1331 @Override
1332 public void updateEditingState(
1333 int inputClientId,
1334 String text,
1335 int selectionStart,
1336 int selectionEnd,
1337 int composingStart,
1338 int composingEnd) {
1339 this.inputClientId = inputClientId;
1340 this.text = text;
1341 this.selectionStart = selectionStart;
1342 this.selectionEnd = selectionEnd;
1343 this.composingStart = composingStart;
1344 this.composingEnd = composingEnd;
1345 updateEditingStateInvocations++;
1346 }
1347 }
1348
1349 @Implements(InputMethodManager.class)
1350 public static class TestImm extends ShadowInputMethodManager {
1351 public static int empty = -999;
1352 CursorAnchorInfo lastCursorAnchorInfo;
1353 int lastExtractedTextToken = empty;
1354 ExtractedText lastExtractedText;
1355
1356 int lastSelectionStart = empty;
1357 int lastSelectionEnd = empty;
1358 int lastCandidatesStart = empty;
1359 int lastCandidatesEnd = empty;
1360
1361 public TestImm() {}
1362
1363 @Implementation
1364 public void updateCursorAnchorInfo(View view, CursorAnchorInfo cursorAnchorInfo) {
1365 lastCursorAnchorInfo = cursorAnchorInfo;
1366 }
1367
1368 @Implementation
1369 public void updateExtractedText(View view, int token, ExtractedText text) {
1370 lastExtractedTextToken = token;
1371 lastExtractedText = text;
1372 }
1373
1374 @Implementation
1375 public void updateSelection(
1376 View view, int selStart, int selEnd, int candidatesStart, int candidatesEnd) {
1377 lastSelectionStart = selStart;
1378 lastSelectionEnd = selEnd;
1379 lastCandidatesStart = candidatesStart;
1380 lastCandidatesEnd = candidatesEnd;
1381 }
1382
1383 public void resetStates() {
1384 lastExtractedText = null;
1385 lastExtractedTextToken = empty;
1386
1387 lastSelectionStart = empty;
1388 lastSelectionEnd = empty;
1389 lastCandidatesStart = empty;
1390 lastCandidatesEnd = empty;
1391
1392 lastCursorAnchorInfo = null;
1393 }
1394 }
1395}
static SkISize times(const SkISize &size, float factor)
static bool eq(const SkM44 &a, const SkM44 &b, float tol)
Definition: M44Test.cpp:18
MethodCall decodeMethodCall(@NonNull ByteBuffer message)
static final JSONMethodCodec INSTANCE
void updateCursorAnchorInfo(View view, CursorAnchorInfo cursorAnchorInfo)
void updateSelection(View view, int selStart, int selEnd, int candidatesStart, int candidatesEnd)
void updateExtractedText(View view, int token, ExtractedText text)
boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts)
boolean performPrivateCommand(String action, Bundle data)
SpannableStringBuilder replace(int start, int end, CharSequence tb, int tbstart, int tbend)
static bool b
EMSCRIPTEN_KEEPALIVE void empty()
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
uint8_t value
std::u16string text
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
SIT bool any(const Vec< 1, T > &x)
Definition: SkVx.h:530