Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 16834f3

Browse files
committed
Runnable and more tests
1 parent 177a50a commit 16834f3

File tree

4 files changed

+187
-25
lines changed

4 files changed

+187
-25
lines changed

lib/ui/key.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ class KeyData {
146146

147147
String? _escapeCharacter() {
148148
if (character == null) {
149-
return character ?? '<none>';
149+
return '<none>';
150150
}
151151
switch (character!) {
152152
case '\n':

shell/platform/android/io/flutter/embedding/android/KeyData.java

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
package io.flutter.embedding.android;
66

77
import androidx.annotation.NonNull;
8+
import java.io.UnsupportedEncodingException;
89
import java.nio.ByteBuffer;
910
import java.nio.ByteOrder;
10-
import java.nio.CharBuffer;
11-
import java.nio.charset.StandardCharsets;
1211

1312
/**
1413
* A {@link KeyboardManager.Responder} of {@link KeyboardManager} that handles events by sending
@@ -70,9 +69,15 @@ public KeyData(@NonNull ByteBuffer buffer) {
7069
String.format(
7170
"Unexpected char length: charSize is %d while buffer has position %d, capacity %d, limit %d",
7271
charSize, buffer.position(), buffer.capacity(), buffer.limit()));
72+
this.character = null;
7373
if (charSize != 0) {
74-
final CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer);
75-
this.character = charBuffer.toString();
74+
final byte[] strBytes = new byte[(int) charSize];
75+
buffer.get(strBytes, 0, (int) charSize);
76+
try {
77+
this.character = new String(strBytes, "UTF-8");
78+
} catch (UnsupportedEncodingException e) {
79+
throw new AssertionError("UTF-8 unsupported");
80+
}
7681
}
7782
}
7883

@@ -86,9 +91,14 @@ public KeyData(@NonNull ByteBuffer buffer) {
8691
String character;
8792

8893
ByteBuffer toBytes() {
89-
final ByteBuffer charBuffer =
90-
character == null ? null : StandardCharsets.UTF_8.encode(character);
91-
final int charSize = charBuffer == null ? 0 : charBuffer.capacity();
94+
byte[] charBytes;
95+
try {
96+
charBytes = character == null ? null : character.getBytes("UTF-8");
97+
} catch (UnsupportedEncodingException e) {
98+
throw new AssertionError("UTF-8 not supported");
99+
}
100+
final int charSize = charBytes == null ? 0 : charBytes.length;
101+
System.out.println(charSize);
92102
final ByteBuffer packet =
93103
ByteBuffer.allocateDirect((1 + FIELD_COUNT) * BYTES_PER_FIELD + charSize);
94104
packet.order(ByteOrder.LITTLE_ENDIAN);
@@ -99,16 +109,10 @@ ByteBuffer toBytes() {
99109
packet.putLong(physicalKey);
100110
packet.putLong(logicalKey);
101111
packet.putLong(synthesized ? 1l : 0l);
102-
if (charBuffer != null) {
103-
packet.put(charBuffer);
104-
}
105-
106-
// Verify that the packet is the expected size.
107-
if (packet.position() != packet.capacity()) {
108-
throw new AssertionError("Packet is not filled");
112+
if (charBytes != null) {
113+
packet.put(charBytes);
109114
}
110115

111-
packet.rewind();
112116
return packet;
113117
}
114118
}

shell/platform/android/io/flutter/embedding/android/KeyEmbedderResponder.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ public KeyEmbedderResponder(BinaryMessenger messenger) {
6161
* https://en.wikipedia.org/wiki/Combining_character
6262
*/
6363
Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoint) {
64+
if (newCharacterCodePoint == 0) {
65+
combiningCharacter = 0;
66+
return 0;
67+
}
6468
char complexCharacter = (char) newCharacterCodePoint;
6569
boolean isNewCodePointACombiningCharacter =
6670
(newCharacterCodePoint & KeyCharacterMap.COMBINING_ACCENT) != 0;
@@ -136,7 +140,7 @@ private boolean handleEventImpl(
136140
return false;
137141
}
138142

139-
Character character = 0;
143+
String character = null;
140144

141145
// TODO(dkwingsmt): repeat logic
142146
KeyData.Type type;
@@ -148,7 +152,10 @@ private boolean handleEventImpl(
148152
// pressed one. This can happen during repeated events.
149153
type = KeyData.Type.kRepeat;
150154
}
151-
character = applyCombiningCharacterToBaseCharacter(event.getUnicodeChar());
155+
final char complexChar = applyCombiningCharacterToBaseCharacter(event.getUnicodeChar());
156+
if (complexChar != 0) {
157+
character = "" + complexChar;
158+
}
152159
} else { // is_down_event false
153160
if (lastLogicalRecord == null) {
154161
// The physical key has been released before. It might indicate a missed
@@ -169,7 +176,7 @@ private boolean handleEventImpl(
169176
output.type = type;
170177
output.logicalKey = logicalKey;
171178
output.physicalKey = physicalKey;
172-
output.character = "" + character;
179+
output.character = character;
173180
output.synthesized = false;
174181

175182
sendKeyEvent(output, onKeyEventHandledCallback);

shell/platform/android/test/io/flutter/embedding/android/KeyboardManagerTest.java

Lines changed: 157 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -299,18 +299,86 @@ static void assertEmbedderEventEquals(
299299
assertEquals(synthesized, data.synthesized);
300300
}
301301

302+
/**
303+
* Print each byte of the given buffer as a hex (such as "0a" for 0x0a), and return the
304+
* concatenated string.
305+
*/
306+
static String printBufferBytes(@NonNull ByteBuffer buffer) {
307+
final String[] results = new String[buffer.capacity()];
308+
for (int byteIdx = 0; byteIdx < buffer.capacity(); byteIdx += 1) {
309+
results[byteIdx] = String.format("%02x", buffer.get(byteIdx));
310+
}
311+
return String.join("", results);
312+
}
313+
302314
@Before
303315
public void setUp() {
304316
MockitoAnnotations.openMocks(this);
305317
}
306318

307319
// Tests start
308320

321+
@Test
322+
public void serializeAndDeserializeKeyData() {
323+
// Test data1: Non-empty character, synthesized.
324+
final KeyData data1 = new KeyData();
325+
data1.physicalKey = 0x0a;
326+
data1.logicalKey = 0x0b;
327+
data1.timestamp = 0x0c;
328+
data1.type = KeyData.Type.kRepeat;
329+
data1.character = "A";
330+
data1.synthesized = true;
331+
332+
final ByteBuffer data1Buffer = data1.toBytes();
333+
334+
assertEquals(
335+
""
336+
+ "0100000000000000"
337+
+ "0c00000000000000"
338+
+ "0200000000000000"
339+
+ "0a00000000000000"
340+
+ "0b00000000000000"
341+
+ "0100000000000000"
342+
+ "41",
343+
printBufferBytes(data1Buffer));
344+
// `position` is considered as the message size.
345+
assertEquals(49, data1Buffer.position());
346+
347+
data1Buffer.rewind();
348+
final KeyData data1Loaded = new KeyData(data1Buffer);
349+
assertEquals(data1Loaded.timestamp, data1.timestamp);
350+
351+
// Test data2: Empty character, not synthesized.
352+
final KeyData data2 = new KeyData();
353+
data2.physicalKey = 0xaaaabbbbccccl;
354+
data2.logicalKey = 0x666677778888l;
355+
data2.timestamp = 0x333344445555l;
356+
data2.type = KeyData.Type.kUp;
357+
data2.character = null;
358+
data2.synthesized = false;
359+
360+
final ByteBuffer data2Buffer = data2.toBytes();
361+
362+
assertEquals(
363+
""
364+
+ "0000000000000000"
365+
+ "5555444433330000"
366+
+ "0100000000000000"
367+
+ "ccccbbbbaaaa0000"
368+
+ "8888777766660000"
369+
+ "0000000000000000",
370+
printBufferBytes(data2Buffer));
371+
372+
data2Buffer.rewind();
373+
final KeyData data2Loaded = new KeyData(data2Buffer);
374+
assertEquals(data2Loaded.timestamp, data2.timestamp);
375+
}
376+
309377
@Test
310378
public void respondsTrueWhenHandlingNewEvents() {
311379
final KeyboardTester tester = new KeyboardTester();
312380
final KeyEvent keyEvent = new FakeKeyEvent(KeyEvent.ACTION_DOWN, 65);
313-
final ArrayList<CallRecord> calls = new ArrayList<CallRecord>();
381+
final ArrayList<CallRecord> calls = new ArrayList<>();
314382

315383
tester.recordChannelCallsTo(calls);
316384

@@ -330,7 +398,7 @@ public void respondsTrueWhenHandlingNewEvents() {
330398
public void channelReponderHandlesEvents() {
331399
final KeyboardTester tester = new KeyboardTester();
332400
final KeyEvent keyEvent = new FakeKeyEvent(KeyEvent.ACTION_DOWN, 65);
333-
final ArrayList<CallRecord> calls = new ArrayList<CallRecord>();
401+
final ArrayList<CallRecord> calls = new ArrayList<>();
334402

335403
tester.recordChannelCallsTo(calls);
336404

@@ -356,7 +424,7 @@ public void channelReponderHandlesEvents() {
356424
public void embedderReponderHandlesEvents() {
357425
final KeyboardTester tester = new KeyboardTester();
358426
final KeyEvent keyEvent = new FakeKeyEvent(100, KeyEvent.ACTION_DOWN, 0x1d, 0, 0, 0x1e, 'a');
359-
final ArrayList<CallRecord> calls = new ArrayList<CallRecord>();
427+
final ArrayList<CallRecord> calls = new ArrayList<>();
360428

361429
tester.recordEmbedderCallsTo(calls);
362430

@@ -383,7 +451,7 @@ public void embedderReponderHandlesEvents() {
383451
public void textInputHandlesEventsIfNoRespondersDo() {
384452
final KeyboardTester tester = new KeyboardTester();
385453
final KeyEvent keyEvent = new FakeKeyEvent(KeyEvent.ACTION_DOWN, 65);
386-
final ArrayList<CallRecord> calls = new ArrayList<CallRecord>();
454+
final ArrayList<CallRecord> calls = new ArrayList<>();
387455

388456
tester.recordChannelCallsTo(calls);
389457

@@ -413,7 +481,7 @@ public void textInputHandlesEventsIfNoRespondersDo() {
413481
public void redispatchEventsIfTextInputDoesntHandle() {
414482
final KeyboardTester tester = new KeyboardTester();
415483
final KeyEvent keyEvent = new FakeKeyEvent(KeyEvent.ACTION_DOWN, 65);
416-
final ArrayList<CallRecord> calls = new ArrayList<CallRecord>();
484+
final ArrayList<CallRecord> calls = new ArrayList<>();
417485

418486
tester.recordChannelCallsTo(calls);
419487

@@ -439,7 +507,7 @@ public void redispatchEventsIfTextInputDoesntHandle() {
439507
@Test
440508
public void redispatchedEventsAreCorrectlySkipped() {
441509
final KeyboardTester tester = new KeyboardTester();
442-
final ArrayList<CallRecord> calls = new ArrayList<CallRecord>();
510+
final ArrayList<CallRecord> calls = new ArrayList<>();
443511

444512
tester.recordChannelCallsTo(calls);
445513

@@ -465,4 +533,87 @@ public void redispatchedEventsAreCorrectlySkipped() {
465533
// It's redispatched to the keyboard manager, but no eventual key calls.
466534
assertEquals(calls.size(), 1);
467535
}
536+
537+
@Test
538+
public void tapLowerA() {
539+
final KeyboardTester tester = new KeyboardTester();
540+
final ArrayList<CallRecord> calls = new ArrayList<>();
541+
542+
tester.recordEmbedderCallsTo(calls);
543+
tester.respondToTextInputWith(true); // Suppress redispatching
544+
545+
assertEquals(
546+
true,
547+
tester.keyboardManager.handleEvent(
548+
new FakeKeyEvent(100, KeyEvent.ACTION_DOWN, 0x1d, 0, 0, 0x1e, 'a')));
549+
assertEquals(calls.size(), 1);
550+
assertEmbedderEventEquals(
551+
calls.get(0).embedderObject, KeyData.Type.kDown, 0x00070004l, 0x00000000061l, "a", false);
552+
calls.clear();
553+
554+
assertEquals(
555+
true,
556+
tester.keyboardManager.handleEvent(
557+
new FakeKeyEvent(100, KeyEvent.ACTION_DOWN, 0x1d, 1, 0, 0x1e, 'a')));
558+
assertEquals(calls.size(), 1);
559+
assertEmbedderEventEquals(
560+
calls.get(0).embedderObject, KeyData.Type.kRepeat, 0x00070004l, 0x00000000061l, "a", false);
561+
calls.clear();
562+
563+
assertEquals(
564+
true,
565+
tester.keyboardManager.handleEvent(
566+
new FakeKeyEvent(100, KeyEvent.ACTION_UP, 0x1d, 0, 0, 0x1e, 'a')));
567+
assertEquals(calls.size(), 1);
568+
assertEmbedderEventEquals(
569+
calls.get(0).embedderObject, KeyData.Type.kUp, 0x00070004l, 0x00000000061l, null, false);
570+
calls.clear();
571+
}
572+
573+
@Test
574+
public void tapUpperA() {
575+
final KeyboardTester tester = new KeyboardTester();
576+
final ArrayList<CallRecord> calls = new ArrayList<>();
577+
578+
tester.recordEmbedderCallsTo(calls);
579+
tester.respondToTextInputWith(true); // Suppress redispatching
580+
581+
// ShiftLeft
582+
assertEquals(
583+
true,
584+
tester.keyboardManager.handleEvent(
585+
new FakeKeyEvent(100, KeyEvent.ACTION_DOWN, 0x3b, 0, 0x41, 0x2a, '\0')));
586+
assertEquals(calls.size(), 1);
587+
assertEmbedderEventEquals(
588+
calls.get(0).embedderObject, KeyData.Type.kDown, 0x000700e1l, 0x200000102l, null, false);
589+
calls.clear();
590+
591+
assertEquals(
592+
true,
593+
tester.keyboardManager.handleEvent(
594+
new FakeKeyEvent(100, KeyEvent.ACTION_DOWN, 0x1d, 0, 0x41, 0x1e, 'A')));
595+
assertEquals(calls.size(), 1);
596+
assertEmbedderEventEquals(
597+
calls.get(0).embedderObject, KeyData.Type.kDown, 0x00070004l, 0x00000000061l, "A", false);
598+
calls.clear();
599+
600+
assertEquals(
601+
true,
602+
tester.keyboardManager.handleEvent(
603+
new FakeKeyEvent(100, KeyEvent.ACTION_UP, 0x1d, 0, 0x41, 0x1e, 'A')));
604+
assertEquals(calls.size(), 1);
605+
assertEmbedderEventEquals(
606+
calls.get(0).embedderObject, KeyData.Type.kUp, 0x00070004l, 0x00000000061l, null, false);
607+
calls.clear();
608+
609+
// ShiftLeft
610+
assertEquals(
611+
true,
612+
tester.keyboardManager.handleEvent(
613+
new FakeKeyEvent(100, KeyEvent.ACTION_UP, 0x3b, 0, 0x41, 0x2a, '\0')));
614+
assertEquals(calls.size(), 1);
615+
assertEmbedderEventEquals(
616+
calls.get(0).embedderObject, KeyData.Type.kUp, 0x000700e1l, 0x200000102l, null, false);
617+
calls.clear();
618+
}
468619
}

0 commit comments

Comments
 (0)