Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
KeyboardManagerTest.java
Go to the documentation of this file.
1package io.flutter.embedding.android;
2
3import static android.view.KeyEvent.*;
4import static io.flutter.embedding.android.KeyData.Type;
5import static io.flutter.util.KeyCodes.*;
6import static org.junit.Assert.assertEquals;
7import static org.junit.Assert.assertNotNull;
8import static org.junit.Assert.assertNull;
9import static org.junit.Assert.assertTrue;
10import static org.mockito.Mockito.any;
11import static org.mockito.Mockito.doAnswer;
12import static org.mockito.Mockito.eq;
13import static org.mockito.Mockito.mock;
14import static org.mockito.Mockito.times;
15import static org.mockito.Mockito.verify;
16
17import android.view.KeyCharacterMap;
18import android.view.KeyEvent;
19import androidx.annotation.NonNull;
20import androidx.annotation.Nullable;
21import androidx.test.ext.junit.runners.AndroidJUnit4;
22import io.flutter.embedding.android.KeyData.DeviceType;
23import io.flutter.plugin.common.BinaryMessenger;
24import io.flutter.plugin.common.JSONMessageCodec;
25import io.flutter.util.FakeKeyEvent;
26import java.nio.ByteBuffer;
27import java.util.ArrayList;
28import java.util.List;
29import java.util.Map;
30import java.util.function.BiConsumer;
31import java.util.function.Consumer;
32import java.util.stream.Collectors;
33import org.json.JSONException;
34import org.json.JSONObject;
35import org.junit.Before;
36import org.junit.Test;
37import org.junit.runner.RunWith;
38import org.mockito.Mock;
39import org.mockito.MockitoAnnotations;
40import org.mockito.invocation.InvocationOnMock;
41import org.robolectric.annotation.Config;
42
43@Config(manifest = Config.NONE)
44@RunWith(AndroidJUnit4.class)
45public class KeyboardManagerTest {
46 public static final int SCAN_KEY_A = 0x1e;
47 public static final int SCAN_DIGIT1 = 0x2;
48 public static final int SCAN_SHIFT_LEFT = 0x2a;
49 public static final int SCAN_SHIFT_RIGHT = 0x36;
50 public static final int SCAN_CONTROL_LEFT = 0x1d;
51 public static final int SCAN_CONTROL_RIGHT = 0x61;
52 public static final int SCAN_ALT_LEFT = 0x38;
53 public static final int SCAN_ALT_RIGHT = 0x64;
54 public static final int SCAN_ARROW_LEFT = 0x69;
55 public static final int SCAN_ARROW_RIGHT = 0x6a;
56 public static final int SCAN_CAPS_LOCK = 0x3a;
57
58 public static final boolean DOWN_EVENT = true;
59 public static final boolean UP_EVENT = false;
60 public static final boolean SHIFT_LEFT_EVENT = true;
61 public static final boolean SHIFT_RIGHT_EVENT = false;
62
63 private static final int DEAD_KEY = '`' | KeyCharacterMap.COMBINING_ACCENT;
64
65 /**
66 * Records a message that {@link KeyboardManager} sends to outside.
67 *
68 * <p>A call record can originate from many sources, indicated by its {@link type}. Different
69 * types will have different fields filled, leaving others empty.
70 */
71 static class CallRecord {
72 enum Kind {
73 /**
74 * The channel responder sent a message through the key event channel.
75 *
76 * <p>This call record will have a non-null {@link channelObject}, with an optional {@link
77 * reply}.
78 */
80 /**
81 * The embedder responder sent a message through the key data channel.
82 *
83 * <p>This call record will have a non-null {@link keyData}, with an optional {@link reply}.
84 */
86 }
87
88 /**
89 * Construct an empty call record.
90 *
91 * <p>Use the static functions to constuct specific types instead.
92 */
93 private CallRecord() {}
94
96
97 /**
98 * The callback given by the keyboard manager.
99 *
100 * <p>It might be null, which probably means it is a synthesized event and requires no reply.
101 * Otherwise, invoke this callback with whether the event is handled for the keyboard manager to
102 * continue processing the key event.
103 */
104 public Consumer<Boolean> reply;
105 /** The data for a call record of kind {@link Kind.kChannel}. */
106 public JSONObject channelObject;
107 /** The data for a call record of kind {@link Kind.kEmbedder}. */
109
110 /** Construct a call record of kind {@link Kind.kChannel}. */
112 @NonNull JSONObject channelObject, @Nullable Consumer<Boolean> reply) {
113 final CallRecord record = new CallRecord();
114 record.kind = Kind.kChannel;
115 record.channelObject = channelObject;
116 record.reply = reply;
117 return record;
118 }
119
120 /** Construct a call record of kind {@link Kind.kEmbedder}. */
121 static CallRecord embedderCall(@NonNull KeyData keyData, @Nullable Consumer<Boolean> reply) {
122 final CallRecord record = new CallRecord();
123 record.kind = Kind.kEmbedder;
124 record.keyData = keyData;
125 record.reply = reply;
126 return record;
127 }
128 }
129
130 /**
131 * Build a response to a channel message sent by the channel responder.
132 *
133 * @param handled whether the event is handled.
134 */
135 static ByteBuffer buildJsonResponse(boolean handled) {
136 JSONObject body = new JSONObject();
137 try {
138 body.put("handled", handled);
139 } catch (JSONException e) {
140 assertNull(e);
141 }
142 ByteBuffer binaryReply = JSONMessageCodec.INSTANCE.encodeMessage(body);
143 binaryReply.rewind();
144 return binaryReply;
145 }
146
147 /**
148 * Build a response to an embedder message sent by the embedder responder.
149 *
150 * @param handled whether the event is handled.
151 */
152 static ByteBuffer buildBinaryResponse(boolean handled) {
153 byte[] body = new byte[1];
154 body[0] = (byte) (handled ? 1 : 0);
155 final ByteBuffer binaryReply = ByteBuffer.wrap(body);
156 binaryReply.rewind();
157 return binaryReply;
158 }
159
160 /**
161 * Used to configure how to process a channel message.
162 *
163 * <p>When the channel responder sends a channel message, this functional interface will be
164 * invoked. Its first argument will be the detailed data. The second argument will be a nullable
165 * reply callback, which should be called to mock the reply from the framework.
166 */
167 @FunctionalInterface
168 static interface ChannelCallHandler extends BiConsumer<JSONObject, Consumer<Boolean>> {}
169
170 /**
171 * Used to configure how to process an embedder message.
172 *
173 * <p>When the embedder responder sends a key data, this functional interface will be invoked. Its
174 * first argument will be the detailed data. The second argument will be a nullable reply
175 * callback, which should be called to mock the reply from the framework.
176 */
177 @FunctionalInterface
178 static interface EmbedderCallHandler extends BiConsumer<KeyData, Consumer<Boolean>> {}
179
180 static class KeyboardTester {
181 public KeyboardTester() {
182 respondToChannelCallsWith(false);
183 respondToEmbedderCallsWith(false);
184 respondToTextInputWith(false);
185
186 BinaryMessenger mockMessenger = mock(BinaryMessenger.class);
187 doAnswer(invocation -> onMessengerMessage(invocation))
188 .when(mockMessenger)
189 .send(any(String.class), any(ByteBuffer.class), eq(null));
190 doAnswer(invocation -> onMessengerMessage(invocation))
191 .when(mockMessenger)
192 .send(any(String.class), any(ByteBuffer.class), any(BinaryMessenger.BinaryReply.class));
193
194 mockView = mock(KeyboardManager.ViewDelegate.class);
195 doAnswer(invocation -> mockMessenger).when(mockView).getBinaryMessenger();
196 doAnswer(invocation -> textInputResult)
197 .when(mockView)
198 .onTextInputKeyEvent(any(KeyEvent.class));
199 doAnswer(
200 invocation -> {
201 KeyEvent event = invocation.getArgument(0);
202 boolean handled = keyboardManager.handleEvent(event);
203 assertEquals(handled, false);
204 return null;
205 })
206 .when(mockView)
207 .redispatch(any(KeyEvent.class));
208
209 keyboardManager = new KeyboardManager(mockView);
210 }
211
212 public @Mock KeyboardManager.ViewDelegate mockView;
214
215 /** Set channel calls to respond immediately with the given response. */
216 public void respondToChannelCallsWith(boolean handled) {
217 channelHandler =
218 (JSONObject data, Consumer<Boolean> reply) -> {
219 if (reply != null) {
220 reply.accept(handled);
221 }
222 };
223 }
224
225 /**
226 * Record channel calls to the given storage.
227 *
228 * <p>They are not responded to until the stored callbacks are manually called.
229 */
230 public void recordChannelCallsTo(@NonNull ArrayList<CallRecord> storage) {
231 channelHandler =
232 (JSONObject data, Consumer<Boolean> reply) -> {
233 storage.add(CallRecord.channelCall(data, reply));
234 };
235 }
236
237 /** Set embedder calls to respond immediately with the given response. */
238 public void respondToEmbedderCallsWith(boolean handled) {
239 embedderHandler =
240 (KeyData keyData, Consumer<Boolean> reply) -> {
241 if (reply != null) {
242 reply.accept(handled);
243 }
244 };
245 }
246
247 /**
248 * Record embedder calls to the given storage.
249 *
250 * <p>They are not responded to until the stored callbacks are manually called.
251 */
252 public void recordEmbedderCallsTo(@NonNull ArrayList<CallRecord> storage) {
253 embedderHandler =
254 (KeyData keyData, Consumer<Boolean> reply) ->
255 storage.add(CallRecord.embedderCall(keyData, reply));
256 }
257
258 /** Set text calls to respond with the given response. */
259 public void respondToTextInputWith(boolean response) {
260 textInputResult = response;
261 }
262
263 private ChannelCallHandler channelHandler;
264 private EmbedderCallHandler embedderHandler;
265 private Boolean textInputResult;
266
267 private Object onMessengerMessage(@NonNull InvocationOnMock invocation) {
268 final String channel = invocation.getArgument(0);
269 final ByteBuffer buffer = invocation.getArgument(1);
270 buffer.rewind();
271
272 final BinaryMessenger.BinaryReply reply = invocation.getArgument(2);
273 if (channel == "flutter/keyevent") {
274 // Parse a channel call.
275 final JSONObject jsonObject = (JSONObject) JSONMessageCodec.INSTANCE.decodeMessage(buffer);
276 final Consumer<Boolean> jsonReply =
277 reply == null ? null : handled -> reply.reply(buildJsonResponse(handled));
278 channelHandler.accept(jsonObject, jsonReply);
279 } else if (channel == "flutter/keydata") {
280 // Parse an embedder call.
281 final KeyData keyData = new KeyData(buffer);
282 final Consumer<Boolean> booleanReply =
283 reply == null ? null : handled -> reply.reply(buildBinaryResponse(handled));
284 embedderHandler.accept(keyData, booleanReply);
285 } else {
286 assertTrue(false);
287 }
288 return null;
289 }
290 }
291
292 /**
293 * Assert that the channel call is an event that matches the given data.
294 *
295 * <p>For now this function only validates key code, but not scancode or characters.
296 *
297 * @param data the target data to be tested.
298 * @param type the type of the data, usually "keydown" or "keyup".
299 * @param keyCode the key code.
300 */
302 @NonNull JSONObject message, @NonNull String type, @NonNull Integer keyCode) {
303 try {
304 assertEquals(type, message.get("type"));
305 assertEquals("android", message.get("keymap"));
306 assertEquals(keyCode, message.get("keyCode"));
307 } catch (JSONException e) {
308 assertNull(e);
309 }
310 }
311
312 /** Assert that the embedder call is an event that matches the given data. */
314 @NonNull KeyData data,
315 Type type,
316 long physicalKey,
317 long logicalKey,
318 String character,
319 boolean synthesized,
320 DeviceType deviceType) {
321 assertEquals(type, data.type);
322 assertEquals(physicalKey, data.physicalKey);
323 assertEquals(logicalKey, data.logicalKey);
324 assertEquals(character, data.character);
325 assertEquals(synthesized, data.synthesized);
326 assertEquals(deviceType, data.deviceType);
327 }
328
329 static void verifyEmbedderEvents(List<CallRecord> receivedCalls, KeyData[] expectedData) {
330 assertEquals(receivedCalls.size(), expectedData.length);
331 for (int idx = 0; idx < receivedCalls.size(); idx += 1) {
332 final KeyData data = expectedData[idx];
333 assertEmbedderEventEquals(
334 receivedCalls.get(idx).keyData,
335 data.type,
336 data.physicalKey,
337 data.logicalKey,
338 data.character,
339 data.synthesized,
340 data.deviceType);
341 }
342 }
343
345 Type type,
346 long physicalKey,
347 long logicalKey,
348 @Nullable String characters,
349 boolean synthesized,
350 DeviceType deviceType) {
351 final KeyData result = new KeyData();
352 result.physicalKey = physicalKey;
353 result.logicalKey = logicalKey;
354 result.timestamp = 0x0;
355 result.type = type;
356 result.character = characters;
357 result.synthesized = synthesized;
358 result.deviceType = deviceType;
359 return result;
360 }
361
362 /**
363 * Start a new tester, generate a ShiftRight event under the specified condition, and return the
364 * output events for that event.
365 *
366 * @param preEventLeftPressed Whether ShiftLeft was recorded as pressed before the event.
367 * @param preEventRightPressed Whether ShiftRight was recorded as pressed before the event.
368 * @param rightEventIsDown Whether the dispatched event is a key down of key up of ShiftRight.
369 * @param truePressed Whether Shift is pressed as shown in the metaState of the event.
370 * @return
371 */
373 boolean preEventLeftPressed,
374 boolean preEventRightPressed,
375 boolean rightEventIsDown,
376 boolean truePressed) {
377 final ArrayList<CallRecord> calls = new ArrayList<>();
378 // Even though the event is for ShiftRight, we still set SHIFT | SHIFT_LEFT here.
379 // See the comment in synchronizePressingKey for the reason.
380 final int SHIFT_LEFT_ON = META_SHIFT_LEFT_ON | META_SHIFT_ON;
381
383 tester.respondToTextInputWith(true); // Suppress redispatching
384 if (preEventLeftPressed) {
385 tester.keyboardManager.handleEvent(
386 new FakeKeyEvent(
387 ACTION_DOWN, SCAN_SHIFT_LEFT, KEYCODE_SHIFT_LEFT, 0, '\0', SHIFT_LEFT_ON));
388 }
389 if (preEventRightPressed) {
390 tester.keyboardManager.handleEvent(
391 new FakeKeyEvent(
392 ACTION_DOWN, SCAN_SHIFT_RIGHT, KEYCODE_SHIFT_RIGHT, 0, '\0', SHIFT_LEFT_ON));
393 }
394 tester.recordEmbedderCallsTo(calls);
395 tester.keyboardManager.handleEvent(
396 new FakeKeyEvent(
397 rightEventIsDown ? ACTION_DOWN : ACTION_UP,
398 SCAN_SHIFT_RIGHT,
399 KEYCODE_SHIFT_RIGHT,
400 0,
401 '\0',
402 truePressed ? SHIFT_LEFT_ON : 0));
403 return calls.stream()
404 .filter(data -> data.keyData.physicalKey != 0)
405 .collect(Collectors.toList());
406 }
407
408 public static KeyData buildShiftKeyData(boolean isLeft, boolean isDown, boolean isSynthesized) {
409 final KeyData data = new KeyData();
410 data.type = isDown ? Type.kDown : Type.kUp;
411 data.physicalKey = isLeft ? PHYSICAL_SHIFT_LEFT : PHYSICAL_SHIFT_RIGHT;
412 data.logicalKey = isLeft ? LOGICAL_SHIFT_LEFT : LOGICAL_SHIFT_RIGHT;
413 data.synthesized = isSynthesized;
414 data.deviceType = KeyData.DeviceType.kKeyboard;
415 return data;
416 }
417
418 /**
419 * Print each byte of the given buffer as a hex (such as "0a" for 0x0a), and return the
420 * concatenated string.
421 *
422 * <p>Used to compare binary content in byte buffers.
423 */
424 static String printBufferBytes(@NonNull ByteBuffer buffer) {
425 final String[] results = new String[buffer.capacity()];
426 for (int byteIdx = 0; byteIdx < buffer.capacity(); byteIdx += 1) {
427 results[byteIdx] = String.format("%02x", buffer.get(byteIdx));
428 }
429 return String.join("", results);
430 }
431
432 @Before
433 public void setUp() {
434 MockitoAnnotations.openMocks(this);
435 }
436
437 // Tests start
438
439 @Test
441 // Test data1: Non-empty character, synthesized.
442 final KeyData data1 = new KeyData();
443 data1.physicalKey = 0x0a;
444 data1.logicalKey = 0x0b;
445 data1.timestamp = 0x0c;
446 data1.type = Type.kRepeat;
447 data1.character = "A";
448 data1.synthesized = true;
449 data1.deviceType = DeviceType.kKeyboard;
450
451 final ByteBuffer data1Buffer = data1.toBytes();
452
453 assertEquals(
454 ""
455 + "0100000000000000"
456 + "0c00000000000000"
457 + "0200000000000000"
458 + "0a00000000000000"
459 + "0b00000000000000"
460 + "0100000000000000"
461 + "0000000000000000"
462 + "41",
463 printBufferBytes(data1Buffer));
464 // `position` is considered as the message size.
465 assertEquals(57, data1Buffer.position());
466
467 data1Buffer.rewind();
468 final KeyData data1Loaded = new KeyData(data1Buffer);
469 assertEquals(data1Loaded.timestamp, data1.timestamp);
470
471 // Test data2: Empty character, not synthesized.
472 final KeyData data2 = new KeyData();
473 data2.physicalKey = 0xaaaabbbbccccl;
474 data2.logicalKey = 0x666677778888l;
475 data2.timestamp = 0x333344445555l;
476 data2.type = Type.kUp;
477 data2.character = null;
478 data2.synthesized = false;
479 data2.deviceType = DeviceType.kDirectionalPad;
480
481 final ByteBuffer data2Buffer = data2.toBytes();
482
483 assertEquals(
484 ""
485 + "0000000000000000"
486 + "5555444433330000"
487 + "0100000000000000"
488 + "ccccbbbbaaaa0000"
489 + "8888777766660000"
490 + "0000000000000000"
491 + "0100000000000000",
492 printBufferBytes(data2Buffer));
493
494 data2Buffer.rewind();
495 final KeyData data2Loaded = new KeyData(data2Buffer);
496 assertEquals(data2Loaded.timestamp, data2.timestamp);
497 }
498
499 @Test
501 final KeyboardManager.CharacterCombiner combiner = new KeyboardManager.CharacterCombiner();
502 assertEquals(0, (int) combiner.applyCombiningCharacterToBaseCharacter(0));
503 assertEquals('B', (int) combiner.applyCombiningCharacterToBaseCharacter('B'));
504 assertEquals('B', (int) combiner.applyCombiningCharacterToBaseCharacter('B'));
505 assertEquals('A', (int) combiner.applyCombiningCharacterToBaseCharacter('A'));
506 assertEquals(0, (int) combiner.applyCombiningCharacterToBaseCharacter(0));
507 assertEquals(0, (int) combiner.applyCombiningCharacterToBaseCharacter(0));
508
509 assertEquals('`', (int) combiner.applyCombiningCharacterToBaseCharacter(DEAD_KEY));
510 assertEquals('`', (int) combiner.applyCombiningCharacterToBaseCharacter(DEAD_KEY));
511 assertEquals('À', (int) combiner.applyCombiningCharacterToBaseCharacter('A'));
512
513 assertEquals('`', (int) combiner.applyCombiningCharacterToBaseCharacter(DEAD_KEY));
514 assertEquals(0, (int) combiner.applyCombiningCharacterToBaseCharacter(0));
515 // The 0 input should remove the combining state.
516 assertEquals('A', (int) combiner.applyCombiningCharacterToBaseCharacter('A'));
517
518 assertEquals(0, (int) combiner.applyCombiningCharacterToBaseCharacter(0));
519 assertEquals('`', (int) combiner.applyCombiningCharacterToBaseCharacter(DEAD_KEY));
520 assertEquals('À', (int) combiner.applyCombiningCharacterToBaseCharacter('A'));
521 }
522
523 @Test
526 final KeyEvent keyEvent = new FakeKeyEvent(ACTION_DOWN, 65);
527 final ArrayList<CallRecord> calls = new ArrayList<>();
528
529 tester.recordChannelCallsTo(calls);
530
531 final boolean result = tester.keyboardManager.handleEvent(keyEvent);
532
533 assertEquals(true, result);
534 assertEquals(calls.size(), 1);
535 assertChannelEventEquals(calls.get(0).channelObject, "keydown", 65);
536
537 // Don't send the key event to the text plugin if the only primary responder
538 // hasn't responded.
539 verify(tester.mockView, times(0)).onTextInputKeyEvent(any(KeyEvent.class));
540 verify(tester.mockView, times(0)).redispatch(any(KeyEvent.class));
541 }
542
543 @Test
546 final KeyEvent keyEvent = new FakeKeyEvent(ACTION_DOWN, 65);
547 final ArrayList<CallRecord> calls = new ArrayList<>();
548
549 tester.recordChannelCallsTo(calls);
550
551 final boolean result = tester.keyboardManager.handleEvent(keyEvent);
552
553 assertEquals(true, result);
554 assertEquals(calls.size(), 1);
555 assertChannelEventEquals(calls.get(0).channelObject, "keydown", 65);
556
557 // Don't send the key event to the text plugin if the only primary responder
558 // hasn't responded.
559 verify(tester.mockView, times(0)).onTextInputKeyEvent(any(KeyEvent.class));
560 verify(tester.mockView, times(0)).redispatch(any(KeyEvent.class));
561
562 // If a primary responder handles the key event the propagation stops.
563 assertNotNull(calls.get(0).reply);
564 calls.get(0).reply.accept(true);
565 verify(tester.mockView, times(0)).onTextInputKeyEvent(any(KeyEvent.class));
566 verify(tester.mockView, times(0)).redispatch(any(KeyEvent.class));
567 }
568
569 @Test
572 final KeyEvent keyEvent = new FakeKeyEvent(ACTION_DOWN, SCAN_KEY_A, KEYCODE_A, 0, 'a', 0);
573 final ArrayList<CallRecord> calls = new ArrayList<>();
574
575 tester.recordEmbedderCallsTo(calls);
576
577 final boolean result = tester.keyboardManager.handleEvent(keyEvent);
578
579 assertEquals(true, result);
580 assertEquals(calls.size(), 1);
581 assertEmbedderEventEquals(
582 calls.get(0).keyData,
583 Type.kDown,
584 PHYSICAL_KEY_A,
585 LOGICAL_KEY_A,
586 "a",
587 false,
588 DeviceType.kKeyboard);
589
590 // Don't send the key event to the text plugin if the only primary responder
591 // hasn't responded.
592 verify(tester.mockView, times(0)).onTextInputKeyEvent(any(KeyEvent.class));
593 verify(tester.mockView, times(0)).redispatch(any(KeyEvent.class));
594
595 // If a primary responder handles the key event the propagation stops.
596 assertNotNull(calls.get(0).reply);
597 calls.get(0).reply.accept(true);
598 verify(tester.mockView, times(0)).onTextInputKeyEvent(any(KeyEvent.class));
599 verify(tester.mockView, times(0)).redispatch(any(KeyEvent.class));
600 }
601
602 @Test
604 // Regression test for https://github.com/flutter/flutter/issues/141662.
605 final BinaryMessenger mockMessenger = mock(BinaryMessenger.class);
606 doAnswer(
607 invocation -> {
608 final BinaryMessenger.BinaryReply reply = invocation.getArgument(2);
609 // Simulate a null reply.
610 // In release mode, a null reply might happen when the engine sends a message
611 // before the framework has started.
612 reply.reply(null);
613 return null;
614 })
615 .when(mockMessenger)
616 .send(any(String.class), any(ByteBuffer.class), any(BinaryMessenger.BinaryReply.class));
617
618 final KeyboardManager.ViewDelegate mockView = mock(KeyboardManager.ViewDelegate.class);
619 doAnswer(invocation -> mockMessenger).when(mockView).getBinaryMessenger();
620
621 final KeyboardManager keyboardManager = new KeyboardManager(mockView);
622 final KeyEvent keyEvent = new FakeKeyEvent(ACTION_DOWN, SCAN_KEY_A, KEYCODE_A, 0, 'a', 0);
623
624 boolean exceptionThrown = false;
625 try {
626 final boolean result = keyboardManager.handleEvent(keyEvent);
627 } catch (Exception exception) {
628 exceptionThrown = true;
629 }
630
631 assertEquals(false, exceptionThrown);
632 }
633
634 @Test
637 final ArrayList<CallRecord> calls = new ArrayList<>();
638
639 tester.recordChannelCallsTo(calls);
640 tester.recordEmbedderCallsTo(calls);
641 tester.respondToTextInputWith(true);
642
643 final boolean result =
644 tester.keyboardManager.handleEvent(
645 new FakeKeyEvent(ACTION_DOWN, SCAN_KEY_A, KEYCODE_A, 0, 'a', 0));
646
647 assertEquals(true, result);
648 assertEquals(calls.size(), 2);
649 assertEmbedderEventEquals(
650 calls.get(0).keyData,
651 Type.kDown,
652 PHYSICAL_KEY_A,
653 LOGICAL_KEY_A,
654 "a",
655 false,
656 DeviceType.kKeyboard);
657 assertChannelEventEquals(calls.get(1).channelObject, "keydown", KEYCODE_A);
658
659 verify(tester.mockView, times(0)).onTextInputKeyEvent(any(KeyEvent.class));
660 verify(tester.mockView, times(0)).redispatch(any(KeyEvent.class));
661
662 calls.get(0).reply.accept(true);
663 verify(tester.mockView, times(0)).onTextInputKeyEvent(any(KeyEvent.class));
664 verify(tester.mockView, times(0)).redispatch(any(KeyEvent.class));
665
666 calls.get(1).reply.accept(true);
667 verify(tester.mockView, times(0)).onTextInputKeyEvent(any(KeyEvent.class));
668 verify(tester.mockView, times(0)).redispatch(any(KeyEvent.class));
669 }
670
671 @Test
674 final KeyEvent keyEvent = new FakeKeyEvent(ACTION_DOWN, 65);
675 final ArrayList<CallRecord> calls = new ArrayList<>();
676
677 tester.recordChannelCallsTo(calls);
678
679 final boolean result = tester.keyboardManager.handleEvent(keyEvent);
680
681 assertEquals(true, result);
682 assertEquals(calls.size(), 1);
683 assertChannelEventEquals(calls.get(0).channelObject, "keydown", 65);
684
685 // Don't send the key event to the text plugin if the only primary responder
686 // hasn't responded.
687 verify(tester.mockView, times(0)).onTextInputKeyEvent(any(KeyEvent.class));
688 verify(tester.mockView, times(0)).redispatch(any(KeyEvent.class));
689
690 // If no primary responder handles the key event the propagates to the text
691 // input plugin.
692 assertNotNull(calls.get(0).reply);
693 // Let text input plugin handle the key event.
694 tester.respondToTextInputWith(true);
695 calls.get(0).reply.accept(false);
696
697 verify(tester.mockView, times(1)).onTextInputKeyEvent(keyEvent);
698 verify(tester.mockView, times(0)).redispatch(any(KeyEvent.class));
699 }
700
701 @Test
704 final KeyEvent keyEvent = new FakeKeyEvent(ACTION_DOWN, 65);
705 final ArrayList<CallRecord> calls = new ArrayList<>();
706
707 tester.recordChannelCallsTo(calls);
708
709 final boolean result = tester.keyboardManager.handleEvent(keyEvent);
710
711 assertEquals(true, result);
712 assertEquals(calls.size(), 1);
713 assertChannelEventEquals(calls.get(0).channelObject, "keydown", 65);
714
715 // Don't send the key event to the text plugin if the only primary responder
716 // hasn't responded.
717 verify(tester.mockView, times(0)).onTextInputKeyEvent(any(KeyEvent.class));
718 verify(tester.mockView, times(0)).redispatch(any(KeyEvent.class));
719
720 // Neither the primary responders nor text input plugin handles the event.
721 tester.respondToTextInputWith(false);
722 calls.get(0).reply.accept(false);
723
724 verify(tester.mockView, times(1)).onTextInputKeyEvent(keyEvent);
725 verify(tester.mockView, times(1)).redispatch(keyEvent);
726 }
727
728 @Test
731 final ArrayList<CallRecord> calls = new ArrayList<>();
732
733 tester.recordChannelCallsTo(calls);
734
735 final KeyEvent keyEvent = new FakeKeyEvent(ACTION_DOWN, 65);
736 final boolean result = tester.keyboardManager.handleEvent(keyEvent);
737
738 assertEquals(true, result);
739 assertEquals(calls.size(), 1);
740 assertChannelEventEquals(calls.get(0).channelObject, "keydown", 65);
741
742 // Don't send the key event to the text plugin if the only primary responder
743 // hasn't responded.
744 verify(tester.mockView, times(0)).onTextInputKeyEvent(any(KeyEvent.class));
745 verify(tester.mockView, times(0)).redispatch(any(KeyEvent.class));
746
747 // Neither the primary responders nor text input plugin handles the event.
748 tester.respondToTextInputWith(false);
749 calls.get(0).reply.accept(false);
750
751 verify(tester.mockView, times(1)).onTextInputKeyEvent(keyEvent);
752 verify(tester.mockView, times(1)).redispatch(keyEvent);
753
754 // It's redispatched to the keyboard manager, but no eventual key calls.
755 assertEquals(calls.size(), 1);
756 }
757
758 @Test
759 public void tapLowerA() {
761 final ArrayList<CallRecord> calls = new ArrayList<>();
762
763 tester.recordEmbedderCallsTo(calls);
764 tester.respondToTextInputWith(true); // Suppress redispatching
765
766 assertEquals(
767 true,
768 tester.keyboardManager.handleEvent(
769 new FakeKeyEvent(ACTION_DOWN, SCAN_KEY_A, KEYCODE_A, 0, 'a', 0)));
770
771 verifyEmbedderEvents(
772 calls,
773 new KeyData[] {
774 buildKeyData(Type.kDown, PHYSICAL_KEY_A, LOGICAL_KEY_A, "a", false, DeviceType.kKeyboard),
775 });
776 calls.clear();
777
778 assertEquals(
779 true,
780 tester.keyboardManager.handleEvent(
781 new FakeKeyEvent(ACTION_DOWN, SCAN_KEY_A, KEYCODE_A, 1, 'a', 0)));
782 verifyEmbedderEvents(
783 calls,
784 new KeyData[] {
785 buildKeyData(
786 Type.kRepeat, PHYSICAL_KEY_A, LOGICAL_KEY_A, "a", false, DeviceType.kKeyboard),
787 });
788 calls.clear();
789
790 assertEquals(
791 true,
792 tester.keyboardManager.handleEvent(
793 new FakeKeyEvent(ACTION_UP, SCAN_KEY_A, KEYCODE_A, 0, 'a', 0)));
794 verifyEmbedderEvents(
795 calls,
796 new KeyData[] {
797 buildKeyData(Type.kUp, PHYSICAL_KEY_A, LOGICAL_KEY_A, null, false, DeviceType.kKeyboard),
798 });
799 calls.clear();
800 }
801
802 @Test
803 public void tapUpperA() {
805 final ArrayList<CallRecord> calls = new ArrayList<>();
806
807 tester.recordEmbedderCallsTo(calls);
808 tester.respondToTextInputWith(true); // Suppress redispatching
809
810 // ShiftLeft
811 assertEquals(
812 true,
813 tester.keyboardManager.handleEvent(
814 new FakeKeyEvent(ACTION_DOWN, SCAN_SHIFT_LEFT, KEYCODE_SHIFT_LEFT, 0, '\0', 0x41)));
815 verifyEmbedderEvents(
816 calls,
817 new KeyData[] {
818 buildKeyData(
819 Type.kDown,
820 PHYSICAL_SHIFT_LEFT,
821 LOGICAL_SHIFT_LEFT,
822 null,
823 false,
824 DeviceType.kKeyboard),
825 });
826 calls.clear();
827
828 assertEquals(
829 true,
830 tester.keyboardManager.handleEvent(
831 new FakeKeyEvent(ACTION_DOWN, SCAN_KEY_A, KEYCODE_A, 0, 'A', 0x41)));
832 verifyEmbedderEvents(
833 calls,
834 new KeyData[] {
835 buildKeyData(Type.kDown, PHYSICAL_KEY_A, LOGICAL_KEY_A, "A", false, DeviceType.kKeyboard),
836 });
837 calls.clear();
838
839 assertEquals(
840 true,
841 tester.keyboardManager.handleEvent(
842 new FakeKeyEvent(ACTION_UP, SCAN_KEY_A, KEYCODE_A, 0, 'A', 0x41)));
843 verifyEmbedderEvents(
844 calls,
845 new KeyData[] {
846 buildKeyData(Type.kUp, PHYSICAL_KEY_A, LOGICAL_KEY_A, null, false, DeviceType.kKeyboard),
847 });
848 calls.clear();
849
850 // ShiftLeft
851 assertEquals(
852 true,
853 tester.keyboardManager.handleEvent(
854 new FakeKeyEvent(ACTION_UP, SCAN_SHIFT_LEFT, KEYCODE_SHIFT_LEFT, 0, '\0', 0)));
855 verifyEmbedderEvents(
856 calls,
857 new KeyData[] {
858 buildKeyData(
859 Type.kUp, PHYSICAL_SHIFT_LEFT, LOGICAL_SHIFT_LEFT, null, false, DeviceType.kKeyboard),
860 });
861 calls.clear();
862 }
863
864 @Test
865 public void eventsWithMintedCodes() {
867 final ArrayList<CallRecord> calls = new ArrayList<>();
868
869 tester.recordEmbedderCallsTo(calls);
870 tester.respondToTextInputWith(true); // Suppress redispatching
871
872 // Zero scan code.
873 assertEquals(
874 true,
875 tester.keyboardManager.handleEvent(
876 new FakeKeyEvent(ACTION_DOWN, 0, KEYCODE_ENTER, 0, '\n', 0)));
877 verifyEmbedderEvents(
878 calls,
879 new KeyData[] {
880 buildKeyData(Type.kDown, 0x1100000042L, LOGICAL_ENTER, "\n", false, DeviceType.kKeyboard),
881 });
882 calls.clear();
883
884 // Zero scan code and zero key code.
885 assertEquals(
886 true, tester.keyboardManager.handleEvent(new FakeKeyEvent(ACTION_DOWN, 0, 0, 0, '\0', 0)));
887 verifyEmbedderEvents(
888 calls,
889 new KeyData[] {
890 buildKeyData(Type.kDown, 0, 0, null, true, DeviceType.kKeyboard),
891 });
892 calls.clear();
893
894 // Unrecognized scan code. (Fictional test)
895 assertEquals(
896 true,
897 tester.keyboardManager.handleEvent(
898 new FakeKeyEvent(ACTION_DOWN, 0xDEADBEEF, 0, 0, '\0', 0)));
899 verifyEmbedderEvents(
900 calls,
901 new KeyData[] {
902 buildKeyData(Type.kDown, 0x11DEADBEEFL, 0x1100000000L, null, false, DeviceType.kKeyboard),
903 });
904 calls.clear();
905
906 // Zero key code. (Fictional test; I have yet to find a real case.)
907 assertEquals(
908 true,
909 tester.keyboardManager.handleEvent(
910 new FakeKeyEvent(ACTION_DOWN, SCAN_ARROW_LEFT, 0, 0, '\0', 0)));
911 verifyEmbedderEvents(
912 calls,
913 new KeyData[] {
914 buildKeyData(
915 Type.kDown, PHYSICAL_ARROW_LEFT, 0x1100000000L, null, false, DeviceType.kKeyboard),
916 });
917 calls.clear();
918
919 // Unrecognized key code. (Fictional test)
920 assertEquals(
921 true,
922 tester.keyboardManager.handleEvent(
923 new FakeKeyEvent(ACTION_DOWN, SCAN_ARROW_RIGHT, 0xDEADBEEF, 0, '\0', 0)));
924 verifyEmbedderEvents(
925 calls,
926 new KeyData[] {
927 buildKeyData(
928 Type.kDown, PHYSICAL_ARROW_RIGHT, 0x11DEADBEEFL, null, false, DeviceType.kKeyboard),
929 });
930 calls.clear();
931 }
932
933 @Test
936 final ArrayList<CallRecord> calls = new ArrayList<>();
937
938 tester.recordEmbedderCallsTo(calls);
939 tester.respondToTextInputWith(true); // Suppress redispatching
940
941 assertEquals(
942 true,
943 tester.keyboardManager.handleEvent(
944 new FakeKeyEvent(ACTION_DOWN, SCAN_KEY_A, KEYCODE_A, 0, 'a', 0)));
945 verifyEmbedderEvents(
946 calls,
947 new KeyData[] {
948 buildKeyData(Type.kDown, PHYSICAL_KEY_A, LOGICAL_KEY_A, "a", false, DeviceType.kKeyboard),
949 });
950 calls.clear();
951
952 assertEquals(
953 true,
954 tester.keyboardManager.handleEvent(
955 new FakeKeyEvent(ACTION_DOWN, SCAN_KEY_A, KEYCODE_A, 0, 'a', 0)));
956 assertEquals(calls.size(), 2);
957 assertEmbedderEventEquals(
958 calls.get(0).keyData,
959 Type.kUp,
960 PHYSICAL_KEY_A,
961 LOGICAL_KEY_A,
962 null,
963 true,
964 DeviceType.kKeyboard);
965 assertEmbedderEventEquals(
966 calls.get(1).keyData,
967 Type.kDown,
968 PHYSICAL_KEY_A,
969 LOGICAL_KEY_A,
970 "a",
971 false,
972 DeviceType.kKeyboard);
973 calls.clear();
974 }
975
976 @Test
979 final ArrayList<CallRecord> calls = new ArrayList<>();
980
981 tester.recordEmbedderCallsTo(calls);
982 tester.respondToTextInputWith(true); // Suppress redispatching
983
984 assertEquals(
985 true,
986 tester.keyboardManager.handleEvent(
987 new FakeKeyEvent(ACTION_UP, SCAN_KEY_A, KEYCODE_A, 0, 'a', 0)));
988 verifyEmbedderEvents(
989 calls,
990 new KeyData[] {
991 buildKeyData(Type.kDown, 0l, 0l, null, true, DeviceType.kKeyboard),
992 });
993 calls.clear();
994 }
995
996 @Test
997 public void modifierKeys() {
999 final ArrayList<CallRecord> calls = new ArrayList<>();
1000
1001 tester.recordEmbedderCallsTo(calls);
1002 tester.respondToTextInputWith(true); // Suppress redispatching
1003
1004 // ShiftLeft
1005 tester.keyboardManager.handleEvent(
1006 new FakeKeyEvent(ACTION_DOWN, SCAN_SHIFT_LEFT, KEYCODE_SHIFT_LEFT, 0, '\0', 0x41));
1007 verifyEmbedderEvents(
1008 calls,
1009 new KeyData[] {
1010 buildKeyData(
1011 Type.kDown,
1012 PHYSICAL_SHIFT_LEFT,
1013 LOGICAL_SHIFT_LEFT,
1014 null,
1015 false,
1016 DeviceType.kKeyboard),
1017 });
1018 calls.clear();
1019
1020 tester.keyboardManager.handleEvent(
1021 new FakeKeyEvent(ACTION_UP, SCAN_SHIFT_LEFT, KEYCODE_SHIFT_LEFT, 0, '\0', 0));
1022 verifyEmbedderEvents(
1023 calls,
1024 new KeyData[] {
1025 buildKeyData(
1026 Type.kUp, PHYSICAL_SHIFT_LEFT, LOGICAL_SHIFT_LEFT, null, false, DeviceType.kKeyboard),
1027 });
1028 calls.clear();
1029
1030 // ShiftRight
1031 tester.keyboardManager.handleEvent(
1032 new FakeKeyEvent(ACTION_DOWN, SCAN_SHIFT_RIGHT, KEYCODE_SHIFT_RIGHT, 0, '\0', 0x41));
1033 verifyEmbedderEvents(
1034 calls,
1035 new KeyData[] {
1036 buildKeyData(
1037 Type.kDown,
1038 PHYSICAL_SHIFT_RIGHT,
1039 LOGICAL_SHIFT_RIGHT,
1040 null,
1041 false,
1042 DeviceType.kKeyboard),
1043 });
1044 calls.clear();
1045
1046 tester.keyboardManager.handleEvent(
1047 new FakeKeyEvent(ACTION_UP, SCAN_SHIFT_RIGHT, KEYCODE_SHIFT_RIGHT, 0, '\0', 0));
1048 verifyEmbedderEvents(
1049 calls,
1050 new KeyData[] {
1051 buildKeyData(
1052 Type.kUp,
1053 PHYSICAL_SHIFT_RIGHT,
1054 LOGICAL_SHIFT_RIGHT,
1055 null,
1056 false,
1057 DeviceType.kKeyboard),
1058 });
1059 calls.clear();
1060
1061 // ControlLeft
1062 tester.keyboardManager.handleEvent(
1063 new FakeKeyEvent(ACTION_DOWN, SCAN_CONTROL_LEFT, KEYCODE_CTRL_LEFT, 0, '\0', 0x3000));
1064 verifyEmbedderEvents(
1065 calls,
1066 new KeyData[] {
1067 buildKeyData(
1068 Type.kDown,
1069 PHYSICAL_CONTROL_LEFT,
1070 LOGICAL_CONTROL_LEFT,
1071 null,
1072 false,
1073 DeviceType.kKeyboard),
1074 });
1075 calls.clear();
1076
1077 tester.keyboardManager.handleEvent(
1078 new FakeKeyEvent(ACTION_UP, SCAN_CONTROL_LEFT, KEYCODE_CTRL_LEFT, 0, '\0', 0));
1079 verifyEmbedderEvents(
1080 calls,
1081 new KeyData[] {
1082 buildKeyData(
1083 Type.kUp,
1084 PHYSICAL_CONTROL_LEFT,
1085 LOGICAL_CONTROL_LEFT,
1086 null,
1087 false,
1088 DeviceType.kKeyboard),
1089 });
1090 calls.clear();
1091
1092 // ControlRight
1093 tester.keyboardManager.handleEvent(
1094 new FakeKeyEvent(ACTION_DOWN, SCAN_CONTROL_RIGHT, KEYCODE_CTRL_RIGHT, 0, '\0', 0x3000));
1095 verifyEmbedderEvents(
1096 calls,
1097 new KeyData[] {
1098 buildKeyData(
1099 Type.kDown,
1100 PHYSICAL_CONTROL_RIGHT,
1101 LOGICAL_CONTROL_RIGHT,
1102 null,
1103 false,
1104 DeviceType.kKeyboard),
1105 });
1106 calls.clear();
1107
1108 tester.keyboardManager.handleEvent(
1109 new FakeKeyEvent(ACTION_UP, SCAN_CONTROL_RIGHT, KEYCODE_CTRL_RIGHT, 0, '\0', 0));
1110 verifyEmbedderEvents(
1111 calls,
1112 new KeyData[] {
1113 buildKeyData(
1114 Type.kUp,
1115 PHYSICAL_CONTROL_RIGHT,
1116 LOGICAL_CONTROL_RIGHT,
1117 null,
1118 false,
1119 DeviceType.kKeyboard),
1120 });
1121 calls.clear();
1122
1123 // AltLeft
1124 tester.keyboardManager.handleEvent(
1125 new FakeKeyEvent(ACTION_DOWN, SCAN_ALT_LEFT, KEYCODE_ALT_LEFT, 0, '\0', 0x12));
1126 verifyEmbedderEvents(
1127 calls,
1128 new KeyData[] {
1129 buildKeyData(
1130 Type.kDown, PHYSICAL_ALT_LEFT, LOGICAL_ALT_LEFT, null, false, DeviceType.kKeyboard),
1131 });
1132 calls.clear();
1133
1134 tester.keyboardManager.handleEvent(
1135 new FakeKeyEvent(ACTION_UP, SCAN_ALT_LEFT, KEYCODE_ALT_LEFT, 0, '\0', 0));
1136 verifyEmbedderEvents(
1137 calls,
1138 new KeyData[] {
1139 buildKeyData(
1140 Type.kUp, PHYSICAL_ALT_LEFT, LOGICAL_ALT_LEFT, null, false, DeviceType.kKeyboard),
1141 });
1142 calls.clear();
1143
1144 // AltRight
1145 tester.keyboardManager.handleEvent(
1146 new FakeKeyEvent(ACTION_DOWN, SCAN_ALT_RIGHT, KEYCODE_ALT_RIGHT, 0, '\0', 0x12));
1147 verifyEmbedderEvents(
1148 calls,
1149 new KeyData[] {
1150 buildKeyData(
1151 Type.kDown, PHYSICAL_ALT_RIGHT, LOGICAL_ALT_RIGHT, null, false, DeviceType.kKeyboard),
1152 });
1153 calls.clear();
1154
1155 tester.keyboardManager.handleEvent(
1156 new FakeKeyEvent(ACTION_UP, SCAN_ALT_RIGHT, KEYCODE_ALT_RIGHT, 0, '\0', 0));
1157 verifyEmbedderEvents(
1158 calls,
1159 new KeyData[] {
1160 buildKeyData(
1161 Type.kUp, PHYSICAL_ALT_RIGHT, LOGICAL_ALT_RIGHT, null, false, DeviceType.kKeyboard),
1162 });
1163 calls.clear();
1164 }
1165
1166 @Test
1167 public void nonUsKeys() {
1168 final KeyboardTester tester = new KeyboardTester();
1169 final ArrayList<CallRecord> calls = new ArrayList<>();
1170
1171 tester.recordEmbedderCallsTo(calls);
1172 tester.respondToTextInputWith(true); // Suppress redispatching
1173
1174 // French 1
1175 tester.keyboardManager.handleEvent(
1176 new FakeKeyEvent(ACTION_DOWN, SCAN_DIGIT1, KEYCODE_1, 0, '1', 0));
1177 verifyEmbedderEvents(
1178 calls,
1179 new KeyData[] {
1180 buildKeyData(
1181 Type.kDown, PHYSICAL_DIGIT1, LOGICAL_DIGIT1, "1", false, DeviceType.kKeyboard),
1182 });
1183 calls.clear();
1184
1185 tester.keyboardManager.handleEvent(
1186 new FakeKeyEvent(ACTION_UP, SCAN_DIGIT1, KEYCODE_1, 0, '1', 0));
1187 verifyEmbedderEvents(
1188 calls,
1189 new KeyData[] {
1190 buildKeyData(
1191 Type.kUp, PHYSICAL_DIGIT1, LOGICAL_DIGIT1, null, false, DeviceType.kKeyboard),
1192 });
1193 calls.clear();
1194
1195 // French Shift-1
1196 tester.keyboardManager.handleEvent(
1197 new FakeKeyEvent(ACTION_DOWN, SCAN_SHIFT_LEFT, KEYCODE_SHIFT_LEFT, 0, '\0', 0x41));
1198 calls.clear();
1199
1200 tester.keyboardManager.handleEvent(
1201 new FakeKeyEvent(ACTION_DOWN, SCAN_DIGIT1, KEYCODE_1, 0, '&', 0x41));
1202 verifyEmbedderEvents(
1203 calls,
1204 new KeyData[] {
1205 buildKeyData(
1206 Type.kDown, PHYSICAL_DIGIT1, LOGICAL_DIGIT1, "&", false, DeviceType.kKeyboard),
1207 });
1208 calls.clear();
1209
1210 tester.keyboardManager.handleEvent(
1211 new FakeKeyEvent(ACTION_UP, SCAN_DIGIT1, KEYCODE_1, 0, '&', 0x41));
1212 verifyEmbedderEvents(
1213 calls,
1214 new KeyData[] {
1215 buildKeyData(
1216 Type.kUp, PHYSICAL_DIGIT1, LOGICAL_DIGIT1, null, false, DeviceType.kKeyboard),
1217 });
1218 calls.clear();
1219
1220 tester.keyboardManager.handleEvent(
1221 new FakeKeyEvent(ACTION_UP, SCAN_SHIFT_LEFT, KEYCODE_SHIFT_LEFT, 0, '\0', 0));
1222 calls.clear();
1223
1224 // Russian lowerA
1225 tester.keyboardManager.handleEvent(
1226 new FakeKeyEvent(ACTION_DOWN, SCAN_KEY_A, KEYCODE_A, 0, '\u0444', 0));
1227 verifyEmbedderEvents(
1228 calls,
1229 new KeyData[] {
1230 buildKeyData(Type.kDown, PHYSICAL_KEY_A, LOGICAL_KEY_A, "Ñ„", false, DeviceType.kKeyboard),
1231 });
1232 calls.clear();
1233
1234 tester.keyboardManager.handleEvent(
1235 new FakeKeyEvent(ACTION_UP, SCAN_KEY_A, KEYCODE_A, 0, '\u0444', 0));
1236 verifyEmbedderEvents(
1237 calls,
1238 new KeyData[] {
1239 buildKeyData(Type.kUp, PHYSICAL_KEY_A, LOGICAL_KEY_A, null, false, DeviceType.kKeyboard),
1240 });
1241 calls.clear();
1242 }
1243
1244 @Test
1246 // Test if ShiftLeft can be synchronized during events of ArrowLeft.
1247 final KeyboardTester tester = new KeyboardTester();
1248 final ArrayList<CallRecord> calls = new ArrayList<>();
1249
1250 tester.recordEmbedderCallsTo(calls);
1251 tester.respondToTextInputWith(true); // Suppress redispatching
1252
1253 final int SHIFT_LEFT_ON = META_SHIFT_LEFT_ON | META_SHIFT_ON;
1254
1255 assertEquals(
1256 true,
1257 tester.keyboardManager.handleEvent(
1258 new FakeKeyEvent(
1259 ACTION_DOWN, SCAN_ARROW_LEFT, KEYCODE_DPAD_LEFT, 0, '\0', SHIFT_LEFT_ON)));
1260 assertEquals(calls.size(), 2);
1261 assertEmbedderEventEquals(
1262 calls.get(0).keyData,
1263 Type.kDown,
1264 PHYSICAL_SHIFT_LEFT,
1265 LOGICAL_SHIFT_LEFT,
1266 null,
1267 true,
1268 DeviceType.kKeyboard);
1269 calls.clear();
1270
1271 assertEquals(
1272 true,
1273 tester.keyboardManager.handleEvent(
1274 new FakeKeyEvent(ACTION_UP, SCAN_ARROW_LEFT, KEYCODE_DPAD_LEFT, 0, '\0', 0)));
1275 assertEquals(calls.size(), 2);
1276 assertEmbedderEventEquals(
1277 calls.get(0).keyData,
1278 Type.kUp,
1279 PHYSICAL_SHIFT_LEFT,
1280 LOGICAL_SHIFT_LEFT,
1281 null,
1282 true,
1283 DeviceType.kKeyboard);
1284 calls.clear();
1285 }
1286
1287 @Test
1289 // Test if ShiftLeft can be synchronized during events of ShiftLeft.
1290 final KeyboardTester tester = new KeyboardTester();
1291 final ArrayList<CallRecord> calls = new ArrayList<>();
1292
1293 tester.recordEmbedderCallsTo(calls);
1294 tester.respondToTextInputWith(true); // Suppress redispatching
1295
1296 final int SHIFT_LEFT_ON = META_SHIFT_LEFT_ON | META_SHIFT_ON;
1297 // All 6 cases (3 types x 2 states) are arranged in the following order so that the starting
1298 // states for each case are the desired states.
1299
1300 // Repeat event when current state is 0.
1301 assertEquals(
1302 true,
1303 tester.keyboardManager.handleEvent(
1304 new FakeKeyEvent(
1305 ACTION_DOWN, SCAN_SHIFT_LEFT, KEYCODE_SHIFT_LEFT, 1, '\0', SHIFT_LEFT_ON)));
1306 assertEquals(calls.size(), 1);
1307 assertEmbedderEventEquals(
1308 calls.get(0).keyData,
1309 Type.kDown,
1310 PHYSICAL_SHIFT_LEFT,
1311 LOGICAL_SHIFT_LEFT,
1312 null,
1313 false,
1314 DeviceType.kKeyboard);
1315 calls.clear();
1316
1317 // Down event when the current state is 1.
1318 assertEquals(
1319 true,
1320 tester.keyboardManager.handleEvent(
1321 new FakeKeyEvent(
1322 ACTION_DOWN, SCAN_SHIFT_LEFT, KEYCODE_SHIFT_LEFT, 0, '\0', SHIFT_LEFT_ON)));
1323 assertEquals(calls.size(), 2);
1324 assertEmbedderEventEquals(
1325 calls.get(0).keyData,
1326 Type.kUp,
1327 PHYSICAL_SHIFT_LEFT,
1328 LOGICAL_SHIFT_LEFT,
1329 null,
1330 true,
1331 DeviceType.kKeyboard);
1332 assertEmbedderEventEquals(
1333 calls.get(1).keyData,
1334 Type.kDown,
1335 PHYSICAL_SHIFT_LEFT,
1336 LOGICAL_SHIFT_LEFT,
1337 null,
1338 false,
1339 DeviceType.kKeyboard);
1340 calls.clear();
1341
1342 // Up event when the current state is 1.
1343 assertEquals(
1344 true,
1345 tester.keyboardManager.handleEvent(
1346 new FakeKeyEvent(ACTION_UP, SCAN_SHIFT_LEFT, KEYCODE_SHIFT_LEFT, 0, '\0', 0)));
1347 assertEquals(calls.size(), 1);
1348 assertEmbedderEventEquals(
1349 calls.get(0).keyData,
1350 Type.kUp,
1351 PHYSICAL_SHIFT_LEFT,
1352 LOGICAL_SHIFT_LEFT,
1353 null,
1354 false,
1355 DeviceType.kKeyboard);
1356 calls.clear();
1357
1358 // Up event when the current state is 0.
1359 assertEquals(
1360 true,
1361 tester.keyboardManager.handleEvent(
1362 new FakeKeyEvent(ACTION_UP, SCAN_SHIFT_LEFT, KEYCODE_SHIFT_LEFT, 0, '\0', 0)));
1363 assertEquals(calls.size(), 1);
1364 assertEmbedderEventEquals(
1365 calls.get(0).keyData, Type.kDown, 0l, 0l, null, true, DeviceType.kKeyboard);
1366 calls.clear();
1367
1368 // Down event when the current state is 0.
1369 assertEquals(
1370 true,
1371 tester.keyboardManager.handleEvent(
1372 new FakeKeyEvent(
1373 ACTION_DOWN, SCAN_SHIFT_LEFT, KEYCODE_SHIFT_LEFT, 0, '\0', SHIFT_LEFT_ON)));
1374 assertEquals(calls.size(), 1);
1375 assertEmbedderEventEquals(
1376 calls.get(0).keyData,
1377 Type.kDown,
1378 PHYSICAL_SHIFT_LEFT,
1379 LOGICAL_SHIFT_LEFT,
1380 null,
1381 false,
1382 DeviceType.kKeyboard);
1383 calls.clear();
1384
1385 // Repeat event when the current state is 1.
1386 assertEquals(
1387 true,
1388 tester.keyboardManager.handleEvent(
1389 new FakeKeyEvent(
1390 ACTION_DOWN, SCAN_SHIFT_LEFT, KEYCODE_SHIFT_LEFT, 1, '\0', SHIFT_LEFT_ON)));
1391 assertEquals(calls.size(), 1);
1392 assertEmbedderEventEquals(
1393 calls.get(0).keyData,
1394 Type.kRepeat,
1395 PHYSICAL_SHIFT_LEFT,
1396 LOGICAL_SHIFT_LEFT,
1397 null,
1398 false,
1399 DeviceType.kKeyboard);
1400 calls.clear();
1401 }
1402
1403 @Test
1405 // Test if ShiftLeft can be synchronized during events of ShiftRight. The following events seem
1406 // to have weird metaStates that don't follow Android's documentation (always using left masks)
1407 // but are indeed observed on ChromeOS.
1408
1409 // UP_EVENT, truePressed: false
1410
1411 verifyEmbedderEvents(testShiftRightEvent(false, false, UP_EVENT, false), new KeyData[] {});
1412 verifyEmbedderEvents(
1413 testShiftRightEvent(false, true, UP_EVENT, false),
1414 new KeyData[] {
1415 buildShiftKeyData(SHIFT_RIGHT_EVENT, UP_EVENT, false),
1416 });
1417 verifyEmbedderEvents(
1418 testShiftRightEvent(true, false, UP_EVENT, false),
1419 new KeyData[] {
1420 buildShiftKeyData(SHIFT_LEFT_EVENT, UP_EVENT, true),
1421 });
1422 verifyEmbedderEvents(
1423 testShiftRightEvent(true, true, UP_EVENT, false),
1424 new KeyData[] {
1425 buildShiftKeyData(SHIFT_LEFT_EVENT, UP_EVENT, true),
1426 buildShiftKeyData(SHIFT_RIGHT_EVENT, UP_EVENT, false),
1427 });
1428
1429 // UP_EVENT, truePressed: true
1430
1431 verifyEmbedderEvents(
1432 testShiftRightEvent(false, false, UP_EVENT, true),
1433 new KeyData[] {
1434 buildShiftKeyData(SHIFT_LEFT_EVENT, DOWN_EVENT, true),
1435 });
1436 verifyEmbedderEvents(
1437 testShiftRightEvent(false, true, UP_EVENT, true),
1438 new KeyData[] {
1439 buildShiftKeyData(SHIFT_LEFT_EVENT, DOWN_EVENT, true),
1440 buildShiftKeyData(SHIFT_RIGHT_EVENT, UP_EVENT, false),
1441 });
1442 verifyEmbedderEvents(testShiftRightEvent(true, false, UP_EVENT, true), new KeyData[] {});
1443 verifyEmbedderEvents(
1444 testShiftRightEvent(true, true, UP_EVENT, true),
1445 new KeyData[] {
1446 buildShiftKeyData(SHIFT_RIGHT_EVENT, UP_EVENT, false),
1447 });
1448
1449 // DOWN_EVENT, truePressed: false - skipped, because they're impossible.
1450
1451 // DOWN_EVENT, truePressed: true
1452
1453 verifyEmbedderEvents(
1454 testShiftRightEvent(false, false, DOWN_EVENT, true),
1455 new KeyData[] {
1456 buildShiftKeyData(SHIFT_RIGHT_EVENT, DOWN_EVENT, false),
1457 });
1458 verifyEmbedderEvents(
1459 testShiftRightEvent(false, true, DOWN_EVENT, true),
1460 new KeyData[] {
1461 buildShiftKeyData(SHIFT_RIGHT_EVENT, UP_EVENT, true),
1462 buildShiftKeyData(SHIFT_RIGHT_EVENT, DOWN_EVENT, false),
1463 });
1464 verifyEmbedderEvents(
1465 testShiftRightEvent(true, false, DOWN_EVENT, true),
1466 new KeyData[] {
1467 buildShiftKeyData(SHIFT_RIGHT_EVENT, DOWN_EVENT, false),
1468 });
1469 verifyEmbedderEvents(
1470 testShiftRightEvent(true, true, DOWN_EVENT, true),
1471 new KeyData[] {
1472 buildShiftKeyData(SHIFT_RIGHT_EVENT, UP_EVENT, true),
1473 buildShiftKeyData(SHIFT_RIGHT_EVENT, DOWN_EVENT, false),
1474 });
1475 }
1476
1477 @Test
1479 // Test if other modifiers can be synchronized during events of ArrowLeft. Only the minimal
1480 // cases are used here since the full logic has been tested on ShiftLeft.
1481 final KeyboardTester tester = new KeyboardTester();
1482 final ArrayList<CallRecord> calls = new ArrayList<>();
1483
1484 tester.recordEmbedderCallsTo(calls);
1485 tester.respondToTextInputWith(true); // Suppress redispatching
1486
1487 assertEquals(
1488 true,
1489 tester.keyboardManager.handleEvent(
1490 new FakeKeyEvent(
1491 ACTION_DOWN, SCAN_ARROW_LEFT, KEYCODE_DPAD_LEFT, 0, '\0', META_CTRL_ON)));
1492 assertEquals(calls.size(), 2);
1493 assertEmbedderEventEquals(
1494 calls.get(0).keyData,
1495 Type.kDown,
1496 PHYSICAL_CONTROL_LEFT,
1497 LOGICAL_CONTROL_LEFT,
1498 null,
1499 true,
1500 DeviceType.kKeyboard);
1501 calls.clear();
1502
1503 assertEquals(
1504 true,
1505 tester.keyboardManager.handleEvent(
1506 new FakeKeyEvent(ACTION_UP, SCAN_ARROW_LEFT, KEYCODE_DPAD_LEFT, 0, '\0', 0)));
1507 assertEquals(calls.size(), 2);
1508 assertEmbedderEventEquals(
1509 calls.get(0).keyData,
1510 Type.kUp,
1511 PHYSICAL_CONTROL_LEFT,
1512 LOGICAL_CONTROL_LEFT,
1513 null,
1514 true,
1515 DeviceType.kKeyboard);
1516 calls.clear();
1517
1518 assertEquals(
1519 true,
1520 tester.keyboardManager.handleEvent(
1521 new FakeKeyEvent(
1522 ACTION_DOWN, SCAN_ARROW_LEFT, KEYCODE_DPAD_LEFT, 0, '\0', META_ALT_ON)));
1523 assertEquals(calls.size(), 2);
1524 assertEmbedderEventEquals(
1525 calls.get(0).keyData,
1526 Type.kDown,
1527 PHYSICAL_ALT_LEFT,
1528 LOGICAL_ALT_LEFT,
1529 null,
1530 true,
1531 DeviceType.kKeyboard);
1532 calls.clear();
1533
1534 assertEquals(
1535 true,
1536 tester.keyboardManager.handleEvent(
1537 new FakeKeyEvent(ACTION_UP, SCAN_ARROW_LEFT, KEYCODE_DPAD_LEFT, 0, '\0', 0)));
1538 assertEquals(calls.size(), 2);
1539 assertEmbedderEventEquals(
1540 calls.get(0).keyData,
1541 Type.kUp,
1542 PHYSICAL_ALT_LEFT,
1543 LOGICAL_ALT_LEFT,
1544 null,
1545 true,
1546 DeviceType.kKeyboard);
1547 calls.clear();
1548 }
1549
1550 // Regression test for https://github.com/flutter/flutter/issues/108124
1551 @Test
1553 // Test if ShiftLeft can be correctly synchronized during down events of
1554 // ShiftLeft that have 0 for their metaState.
1555 final KeyboardTester tester = new KeyboardTester();
1556 final ArrayList<CallRecord> calls = new ArrayList<>();
1557 // Even though the event is for ShiftRight, we still set SHIFT | SHIFT_LEFT here.
1558 // See the comment in synchronizePressingKey for the reason.
1559 final int SHIFT_LEFT_ON = META_SHIFT_LEFT_ON | META_SHIFT_ON;
1560
1561 tester.recordEmbedderCallsTo(calls);
1562 tester.respondToTextInputWith(true); // Suppress redispatching
1563
1564 // Test: Down event when the current state is 0.
1565 assertEquals(
1566 true,
1567 tester.keyboardManager.handleEvent(
1568 new FakeKeyEvent(ACTION_DOWN, SCAN_SHIFT_LEFT, KEYCODE_SHIFT_LEFT, 0, '\0', 0)));
1569 assertEquals(calls.size(), 2);
1570 assertEmbedderEventEquals(
1571 calls.get(0).keyData,
1572 Type.kDown,
1573 PHYSICAL_SHIFT_LEFT,
1574 LOGICAL_SHIFT_LEFT,
1575 null,
1576 false,
1577 DeviceType.kKeyboard);
1578 assertEmbedderEventEquals(
1579 calls.get(1).keyData,
1580 Type.kUp,
1581 PHYSICAL_SHIFT_LEFT,
1582 LOGICAL_SHIFT_LEFT,
1583 null,
1584 true,
1585 DeviceType.kKeyboard);
1586 calls.clear();
1587
1588 // A normal down event.
1589 assertEquals(
1590 true,
1591 tester.keyboardManager.handleEvent(
1592 new FakeKeyEvent(
1593 ACTION_DOWN, SCAN_SHIFT_LEFT, KEYCODE_SHIFT_LEFT, 0, '\0', SHIFT_LEFT_ON)));
1594 assertEquals(calls.size(), 1);
1595 assertEmbedderEventEquals(
1596 calls.get(0).keyData,
1597 Type.kDown,
1598 PHYSICAL_SHIFT_LEFT,
1599 LOGICAL_SHIFT_LEFT,
1600 null,
1601 false,
1602 DeviceType.kKeyboard);
1603 calls.clear();
1604
1605 // Test: Repeat event when the current state is 0.
1606 assertEquals(
1607 true,
1608 tester.keyboardManager.handleEvent(
1609 new FakeKeyEvent(ACTION_DOWN, SCAN_SHIFT_LEFT, KEYCODE_SHIFT_LEFT, 1, '\0', 0)));
1610 assertEquals(calls.size(), 2);
1611 assertEmbedderEventEquals(
1612 calls.get(0).keyData,
1613 Type.kRepeat,
1614 PHYSICAL_SHIFT_LEFT,
1615 LOGICAL_SHIFT_LEFT,
1616 null,
1617 false,
1618 DeviceType.kKeyboard);
1619 assertEmbedderEventEquals(
1620 calls.get(1).keyData,
1621 Type.kUp,
1622 PHYSICAL_SHIFT_LEFT,
1623 LOGICAL_SHIFT_LEFT,
1624 null,
1625 true,
1626 DeviceType.kKeyboard);
1627 calls.clear();
1628 }
1629
1630 // Regression test for https://github.com/flutter/flutter/issues/110640
1631 @Test
1633 // Test if ShiftLeft can be correctly synchronized during down events of
1634 // ShiftLeft that have 0 for their metaState and 0 for their scanCode.
1635 final KeyboardTester tester = new KeyboardTester();
1636 final ArrayList<CallRecord> calls = new ArrayList<>();
1637
1638 tester.recordEmbedderCallsTo(calls);
1639 tester.respondToTextInputWith(true); // Suppress redispatching
1640
1641 // Test: DOWN event when the current state is 0 and scanCode is 0.
1642 final KeyEvent keyEvent = new FakeKeyEvent(ACTION_DOWN, 0, KEYCODE_SHIFT_LEFT, 0, '\0', 0);
1643 // Compute physicalKey in the same way as KeyboardManager.getPhysicalKey.
1644 final Long physicalKey = KEYCODE_SHIFT_LEFT | KeyboardMap.kAndroidPlane;
1645
1646 assertEquals(tester.keyboardManager.handleEvent(keyEvent), true);
1647 assertEquals(calls.size(), 2);
1648 assertEmbedderEventEquals(
1649 calls.get(0).keyData,
1650 Type.kDown,
1651 physicalKey,
1652 LOGICAL_SHIFT_LEFT,
1653 null,
1654 false,
1655 DeviceType.kKeyboard);
1656 assertEmbedderEventEquals(
1657 calls.get(1).keyData,
1658 Type.kUp,
1659 physicalKey,
1660 LOGICAL_SHIFT_LEFT,
1661 null,
1662 true,
1663 DeviceType.kKeyboard);
1664 calls.clear();
1665 }
1666
1667 @Test
1668 public void normalCapsLockEvents() {
1669 final KeyboardTester tester = new KeyboardTester();
1670 final ArrayList<CallRecord> calls = new ArrayList<>();
1671
1672 tester.recordEmbedderCallsTo(calls);
1673 tester.respondToTextInputWith(true); // Suppress redispatching
1674
1675 // The following two events seem to have weird metaStates that don't follow Android's
1676 // documentation (CapsLock flag set on down events) but are indeed observed on ChromeOS.
1677
1678 assertEquals(
1679 true,
1680 tester.keyboardManager.handleEvent(
1681 new FakeKeyEvent(
1682 ACTION_DOWN, SCAN_CAPS_LOCK, KEYCODE_CAPS_LOCK, 0, '\0', META_CAPS_LOCK_ON)));
1683 assertEquals(calls.size(), 1);
1684 assertEmbedderEventEquals(
1685 calls.get(0).keyData,
1686 Type.kDown,
1687 PHYSICAL_CAPS_LOCK,
1688 LOGICAL_CAPS_LOCK,
1689 null,
1690 false,
1691 DeviceType.kKeyboard);
1692 calls.clear();
1693
1694 assertEquals(
1695 true,
1696 tester.keyboardManager.handleEvent(
1697 new FakeKeyEvent(ACTION_UP, SCAN_CAPS_LOCK, KEYCODE_CAPS_LOCK, 0, '\0', 0)));
1698 assertEquals(calls.size(), 1);
1699 assertEmbedderEventEquals(
1700 calls.get(0).keyData,
1701 Type.kUp,
1702 PHYSICAL_CAPS_LOCK,
1703 LOGICAL_CAPS_LOCK,
1704 null,
1705 false,
1706 DeviceType.kKeyboard);
1707 calls.clear();
1708
1709 assertEquals(
1710 true,
1711 tester.keyboardManager.handleEvent(
1712 new FakeKeyEvent(
1713 ACTION_DOWN, SCAN_ARROW_LEFT, KEYCODE_DPAD_LEFT, 0, '\0', META_CAPS_LOCK_ON)));
1714 assertEquals(calls.size(), 1);
1715 assertEmbedderEventEquals(
1716 calls.get(0).keyData,
1717 Type.kDown,
1718 PHYSICAL_ARROW_LEFT,
1719 LOGICAL_ARROW_LEFT,
1720 null,
1721 false,
1722 DeviceType.kKeyboard);
1723 calls.clear();
1724
1725 assertEquals(
1726 true,
1727 tester.keyboardManager.handleEvent(
1728 new FakeKeyEvent(
1729 ACTION_UP, SCAN_ARROW_LEFT, KEYCODE_DPAD_LEFT, 0, '\0', META_CAPS_LOCK_ON)));
1730 assertEquals(calls.size(), 1);
1731 assertEmbedderEventEquals(
1732 calls.get(0).keyData,
1733 Type.kUp,
1734 PHYSICAL_ARROW_LEFT,
1735 LOGICAL_ARROW_LEFT,
1736 null,
1737 false,
1738 DeviceType.kKeyboard);
1739 calls.clear();
1740
1741 assertEquals(
1742 true,
1743 tester.keyboardManager.handleEvent(
1744 new FakeKeyEvent(
1745 ACTION_DOWN, SCAN_CAPS_LOCK, KEYCODE_CAPS_LOCK, 0, '\0', META_CAPS_LOCK_ON)));
1746 assertEquals(calls.size(), 1);
1747 assertEmbedderEventEquals(
1748 calls.get(0).keyData,
1749 Type.kDown,
1750 PHYSICAL_CAPS_LOCK,
1751 LOGICAL_CAPS_LOCK,
1752 null,
1753 false,
1754 DeviceType.kKeyboard);
1755 calls.clear();
1756
1757 assertEquals(
1758 true,
1759 tester.keyboardManager.handleEvent(
1760 new FakeKeyEvent(ACTION_UP, SCAN_CAPS_LOCK, KEYCODE_CAPS_LOCK, 0, '\0', 0)));
1761 assertEquals(calls.size(), 1);
1762 assertEmbedderEventEquals(
1763 calls.get(0).keyData,
1764 Type.kUp,
1765 PHYSICAL_CAPS_LOCK,
1766 LOGICAL_CAPS_LOCK,
1767 null,
1768 false,
1769 DeviceType.kKeyboard);
1770 calls.clear();
1771
1772 assertEquals(
1773 true,
1774 tester.keyboardManager.handleEvent(
1775 new FakeKeyEvent(ACTION_DOWN, SCAN_ARROW_LEFT, KEYCODE_DPAD_LEFT, 0, '\0', 0)));
1776 assertEquals(calls.size(), 1);
1777 assertEmbedderEventEquals(
1778 calls.get(0).keyData,
1779 Type.kDown,
1780 PHYSICAL_ARROW_LEFT,
1781 LOGICAL_ARROW_LEFT,
1782 null,
1783 false,
1784 DeviceType.kKeyboard);
1785 calls.clear();
1786
1787 assertEquals(
1788 true,
1789 tester.keyboardManager.handleEvent(
1790 new FakeKeyEvent(ACTION_UP, SCAN_ARROW_LEFT, KEYCODE_DPAD_LEFT, 0, '\0', 0)));
1791 assertEquals(calls.size(), 1);
1792 assertEmbedderEventEquals(
1793 calls.get(0).keyData,
1794 Type.kUp,
1795 PHYSICAL_ARROW_LEFT,
1796 LOGICAL_ARROW_LEFT,
1797 null,
1798 false,
1799 DeviceType.kKeyboard);
1800 calls.clear();
1801 }
1802
1803 @Test
1804 public void synchronizeCapsLock() {
1805 final KeyboardTester tester = new KeyboardTester();
1806 final ArrayList<CallRecord> calls = new ArrayList<>();
1807
1808 tester.recordEmbedderCallsTo(calls);
1809 tester.respondToTextInputWith(true); // Suppress redispatching
1810
1811 assertEquals(
1812 true,
1813 tester.keyboardManager.handleEvent(
1814 new FakeKeyEvent(
1815 ACTION_DOWN, SCAN_ARROW_LEFT, KEYCODE_DPAD_LEFT, 0, '\0', META_CAPS_LOCK_ON)));
1816 assertEquals(calls.size(), 3);
1817 assertEmbedderEventEquals(
1818 calls.get(0).keyData,
1819 Type.kDown,
1820 PHYSICAL_CAPS_LOCK,
1821 LOGICAL_CAPS_LOCK,
1822 null,
1823 true,
1824 DeviceType.kKeyboard);
1825 assertEmbedderEventEquals(
1826 calls.get(1).keyData,
1827 Type.kUp,
1828 PHYSICAL_CAPS_LOCK,
1829 LOGICAL_CAPS_LOCK,
1830 null,
1831 true,
1832 DeviceType.kKeyboard);
1833 assertEmbedderEventEquals(
1834 calls.get(2).keyData,
1835 Type.kDown,
1836 PHYSICAL_ARROW_LEFT,
1837 LOGICAL_ARROW_LEFT,
1838 null,
1839 false,
1840 DeviceType.kKeyboard);
1841 calls.clear();
1842
1843 assertEquals(
1844 true,
1845 tester.keyboardManager.handleEvent(
1846 new FakeKeyEvent(
1847 ACTION_DOWN, SCAN_CAPS_LOCK, KEYCODE_CAPS_LOCK, 0, '\0', META_CAPS_LOCK_ON)));
1848 assertEquals(calls.size(), 1);
1849 assertEmbedderEventEquals(
1850 calls.get(0).keyData,
1851 Type.kDown,
1852 PHYSICAL_CAPS_LOCK,
1853 LOGICAL_CAPS_LOCK,
1854 null,
1855 false,
1856 DeviceType.kKeyboard);
1857 calls.clear();
1858
1859 assertEquals(
1860 true,
1861 tester.keyboardManager.handleEvent(
1862 new FakeKeyEvent(
1863 ACTION_UP, SCAN_ARROW_LEFT, KEYCODE_DPAD_LEFT, 0, '\0', META_CAPS_LOCK_ON)));
1864 assertEquals(calls.size(), 3);
1865 assertEmbedderEventEquals(
1866 calls.get(0).keyData,
1867 Type.kUp,
1868 PHYSICAL_CAPS_LOCK,
1869 LOGICAL_CAPS_LOCK,
1870 null,
1871 true,
1872 DeviceType.kKeyboard);
1873 assertEmbedderEventEquals(
1874 calls.get(1).keyData,
1875 Type.kDown,
1876 PHYSICAL_CAPS_LOCK,
1877 LOGICAL_CAPS_LOCK,
1878 null,
1879 true,
1880 DeviceType.kKeyboard);
1881 assertEmbedderEventEquals(
1882 calls.get(2).keyData,
1883 Type.kUp,
1884 PHYSICAL_ARROW_LEFT,
1885 LOGICAL_ARROW_LEFT,
1886 null,
1887 false,
1888 DeviceType.kKeyboard);
1889 calls.clear();
1890
1891 assertEquals(
1892 true,
1893 tester.keyboardManager.handleEvent(
1894 new FakeKeyEvent(ACTION_UP, SCAN_CAPS_LOCK, KEYCODE_CAPS_LOCK, 0, '\0', 0)));
1895 assertEquals(calls.size(), 1);
1896 assertEmbedderEventEquals(
1897 calls.get(0).keyData,
1898 Type.kUp,
1899 PHYSICAL_CAPS_LOCK,
1900 LOGICAL_CAPS_LOCK,
1901 null,
1902 false,
1903 DeviceType.kKeyboard);
1904 calls.clear();
1905 }
1906
1907 @Test
1908 public void getKeyboardState() {
1909 final KeyboardTester tester = new KeyboardTester();
1910
1911 tester.respondToTextInputWith(true); // Suppress redispatching.
1912
1913 // Initial pressed state is empty.
1914 assertEquals(tester.keyboardManager.getKeyboardState(), Map.of());
1915
1916 tester.keyboardManager.handleEvent(
1917 new FakeKeyEvent(ACTION_DOWN, SCAN_KEY_A, KEYCODE_A, 1, 'a', 0));
1918 assertEquals(tester.keyboardManager.getKeyboardState(), Map.of(PHYSICAL_KEY_A, LOGICAL_KEY_A));
1919
1920 tester.keyboardManager.handleEvent(
1921 new FakeKeyEvent(ACTION_UP, SCAN_KEY_A, KEYCODE_A, 0, 'a', 0));
1922 assertEquals(tester.keyboardManager.getKeyboardState(), Map.of());
1923 }
1924}
static SkISize times(const SkISize &size, float factor)
static bool eq(const SkM44 &a, const SkM44 &b, float tol)
Definition M44Test.cpp:18
static CallRecord embedderCall(@NonNull KeyData keyData, @Nullable Consumer< Boolean > reply)
static CallRecord channelCall( @NonNull JSONObject channelObject, @Nullable Consumer< Boolean > reply)
void recordEmbedderCallsTo(@NonNull ArrayList< CallRecord > storage)
void recordChannelCallsTo(@NonNull ArrayList< CallRecord > storage)
static void assertChannelEventEquals( @NonNull JSONObject message, @NonNull String type, @NonNull Integer keyCode)
static void assertEmbedderEventEquals( @NonNull KeyData data, Type type, long physicalKey, long logicalKey, String character, boolean synthesized, DeviceType deviceType)
static ByteBuffer buildJsonResponse(boolean handled)
static KeyData buildShiftKeyData(boolean isLeft, boolean isDown, boolean isSynthesized)
static String printBufferBytes(@NonNull ByteBuffer buffer)
static KeyData buildKeyData(Type type, long physicalKey, long logicalKey, @Nullable String characters, boolean synthesized, DeviceType deviceType)
static ByteBuffer buildBinaryResponse(boolean handled)
static void verifyEmbedderEvents(List< CallRecord > receivedCalls, KeyData[] expectedData)
static List< CallRecord > testShiftRightEvent(boolean preEventLeftPressed, boolean preEventRightPressed, boolean rightEventIsDown, boolean truePressed)
boolean handleEvent(@NonNull KeyEvent keyEvent)
FlKeyEvent * event
static const uint8_t buffer[]
GAsyncResult * result
Win32Message message