Skip to content

Commit c74a738

Browse files
authored
Reland No.2 "Hardware Keyboard: Android" (flutter#33686)
* Original code * Changes * Fix and test Better formatted tests
1 parent 7a981c8 commit c74a738

File tree

16 files changed

+3166
-150
lines changed

16 files changed

+3166
-150
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,7 +1352,10 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/Flutt
13521352
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterTextureView.java
13531353
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java
13541354
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyChannelResponder.java
1355+
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyData.java
1356+
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyEmbedderResponder.java
13551357
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyboardManager.java
1358+
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyboardMap.java
13561359
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/MotionEventTracker.java
13571360
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/RenderMode.java
13581361
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/SplashScreen.java

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':

lib/ui/window/key_data.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ enum class KeyEventType : int64_t {
2828
// a different way in KeyDataPacket.
2929
//
3030
// This structure is unpacked by hooks.dart.
31+
//
32+
// Changes to this struct must also be made to
33+
// io/flutter/embedding/android/KeyData.java.
3134
struct alignas(8) KeyData {
3235
// Timestamp in microseconds from an arbitrary and consistent start point
3336
uint64_t timestamp;

lib/ui/window/key_data_packet.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
namespace flutter {
1515

1616
// A byte stream representing a key event, to be sent to the framework.
17+
//
18+
// Changes to the marshalling format here must also be made to
19+
// io/flutter/embedding/android/KeyData.java.
1720
class KeyDataPacket {
1821
public:
1922
// Build the key data packet by providing information.

shell/platform/android/BUILD.gn

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,10 @@ android_java_sources = [
185185
"io/flutter/embedding/android/FlutterTextureView.java",
186186
"io/flutter/embedding/android/FlutterView.java",
187187
"io/flutter/embedding/android/KeyChannelResponder.java",
188+
"io/flutter/embedding/android/KeyData.java",
189+
"io/flutter/embedding/android/KeyEmbedderResponder.java",
188190
"io/flutter/embedding/android/KeyboardManager.java",
191+
"io/flutter/embedding/android/KeyboardMap.java",
189192
"io/flutter/embedding/android/MotionEventTracker.java",
190193
"io/flutter/embedding/android/RenderMode.java",
191194
"io/flutter/embedding/android/SplashScreen.java",

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

Lines changed: 5 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
package io.flutter.embedding.android;
66

7-
import android.view.KeyCharacterMap;
87
import android.view.KeyEvent;
98
import androidx.annotation.NonNull;
109
import io.flutter.embedding.engine.systemchannels.KeyEventChannel;
@@ -19,66 +18,15 @@ public class KeyChannelResponder implements KeyboardManager.Responder {
1918
private static final String TAG = "KeyChannelResponder";
2019

2120
@NonNull private final KeyEventChannel keyEventChannel;
22-
private int combiningCharacter;
21+
22+
@NonNull
23+
private final KeyboardManager.CharacterCombiner characterCombiner =
24+
new KeyboardManager.CharacterCombiner();
2325

2426
public KeyChannelResponder(@NonNull KeyEventChannel keyEventChannel) {
2527
this.keyEventChannel = keyEventChannel;
2628
}
2729

28-
/**
29-
* Applies the given Unicode character in {@code newCharacterCodePoint} to a previously entered
30-
* Unicode combining character and returns the combination of these characters if a combination
31-
* exists.
32-
*
33-
* <p>This method mutates {@link #combiningCharacter} over time to combine characters.
34-
*
35-
* <p>One of the following things happens in this method:
36-
*
37-
* <ul>
38-
* <li>If no previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint}
39-
* is not a combining character, then {@code newCharacterCodePoint} is returned.
40-
* <li>If no previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint}
41-
* is a combining character, then {@code newCharacterCodePoint} is saved as the {@link
42-
* #combiningCharacter} and null is returned.
43-
* <li>If a previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint} is
44-
* also a combining character, then the {@code newCharacterCodePoint} is combined with the
45-
* existing {@link #combiningCharacter} and null is returned.
46-
* <li>If a previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint} is
47-
* not a combining character, then the {@link #combiningCharacter} is applied to the regular
48-
* {@code newCharacterCodePoint} and the resulting complex character is returned. The {@link
49-
* #combiningCharacter} is cleared.
50-
* </ul>
51-
*
52-
* <p>The following reference explains the concept of a "combining character":
53-
* https://en.wikipedia.org/wiki/Combining_character
54-
*/
55-
Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoint) {
56-
char complexCharacter = (char) newCharacterCodePoint;
57-
boolean isNewCodePointACombiningCharacter =
58-
(newCharacterCodePoint & KeyCharacterMap.COMBINING_ACCENT) != 0;
59-
if (isNewCodePointACombiningCharacter) {
60-
// If a combining character was entered before, combine this one with that one.
61-
int plainCodePoint = newCharacterCodePoint & KeyCharacterMap.COMBINING_ACCENT_MASK;
62-
if (combiningCharacter != 0) {
63-
combiningCharacter = KeyCharacterMap.getDeadChar(combiningCharacter, plainCodePoint);
64-
} else {
65-
combiningCharacter = plainCodePoint;
66-
}
67-
} else {
68-
// The new character is a regular character. Apply combiningCharacter to it, if
69-
// it exists.
70-
if (combiningCharacter != 0) {
71-
int combinedChar = KeyCharacterMap.getDeadChar(combiningCharacter, newCharacterCodePoint);
72-
if (combinedChar > 0) {
73-
complexCharacter = (char) combinedChar;
74-
}
75-
combiningCharacter = 0;
76-
}
77-
}
78-
79-
return complexCharacter;
80-
}
81-
8230
@Override
8331
public void handleEvent(
8432
@NonNull KeyEvent keyEvent, @NonNull OnKeyEventHandledCallback onKeyEventHandledCallback) {
@@ -92,7 +40,7 @@ public void handleEvent(
9240
}
9341

9442
final Character complexCharacter =
95-
applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar());
43+
characterCombiner.applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar());
9644
KeyEventChannel.FlutterKeyEvent flutterEvent =
9745
new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter);
9846

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.embedding.android;
6+
7+
import androidx.annotation.NonNull;
8+
import androidx.annotation.Nullable;
9+
import java.io.UnsupportedEncodingException;
10+
import java.nio.ByteBuffer;
11+
import java.nio.ByteOrder;
12+
13+
/**
14+
* The resulting Flutter key events generated by {@link KeyEmbedderResponder}, and are sent through
15+
* the messenger after being marshalled with {@link #toBytes()}.
16+
*
17+
* <p>This class is the Java adaption of {@code KeyData} and {@code KeyDataPacket} in the C engine.
18+
* Changes made to either side must also be made to the other.
19+
*
20+
* <p>Each {@link KeyData} corresponds to a {@code ui.KeyData} in the framework.
21+
*/
22+
public class KeyData {
23+
private static final String TAG = "KeyData";
24+
25+
/**
26+
* The channel that key data should be sent through.
27+
*
28+
* <p>Must be kept in sync with kFlutterKeyDataChannel in embedder.cc
29+
*/
30+
public static final String CHANNEL = "flutter/keydata";
31+
32+
// The number of fields except for `character`.
33+
private static final int FIELD_COUNT = 5;
34+
private static final int BYTES_PER_FIELD = 8;
35+
36+
/** The action type of the key data. */
37+
public enum Type {
38+
kDown(0),
39+
kUp(1),
40+
kRepeat(2);
41+
42+
private long value;
43+
44+
private Type(long value) {
45+
this.value = value;
46+
}
47+
48+
public long getValue() {
49+
return value;
50+
}
51+
52+
static Type fromLong(long value) {
53+
switch ((int) value) {
54+
case 0:
55+
return kDown;
56+
case 1:
57+
return kUp;
58+
case 2:
59+
return kRepeat;
60+
default:
61+
throw new AssertionError("Unexpected Type value");
62+
}
63+
}
64+
}
65+
66+
/** Creates an empty {@link KeyData}. */
67+
public KeyData() {}
68+
69+
/**
70+
* Unmarshal fields from a buffer.
71+
*
72+
* <p>For the binary format, see {@code lib/ui/window/key_data_packet.h}.
73+
*/
74+
public KeyData(@NonNull ByteBuffer buffer) {
75+
final long charSize = buffer.getLong();
76+
this.timestamp = buffer.getLong();
77+
this.type = Type.fromLong(buffer.getLong());
78+
this.physicalKey = buffer.getLong();
79+
this.logicalKey = buffer.getLong();
80+
this.synthesized = buffer.getLong() != 0;
81+
82+
if (buffer.remaining() != charSize) {
83+
throw new AssertionError(
84+
String.format(
85+
"Unexpected char length: charSize is %d while buffer has position %d, capacity %d, limit %d",
86+
charSize, buffer.position(), buffer.capacity(), buffer.limit()));
87+
}
88+
this.character = null;
89+
if (charSize != 0) {
90+
final byte[] strBytes = new byte[(int) charSize];
91+
buffer.get(strBytes, 0, (int) charSize);
92+
try {
93+
this.character = new String(strBytes, "UTF-8");
94+
} catch (UnsupportedEncodingException e) {
95+
throw new AssertionError("UTF-8 unsupported");
96+
}
97+
}
98+
}
99+
100+
long timestamp;
101+
Type type;
102+
long physicalKey;
103+
long logicalKey;
104+
boolean synthesized;
105+
106+
/** The character of this key data encoded in UTF-8. */
107+
@Nullable String character;
108+
109+
/**
110+
* Marshal the key data to a new byte buffer.
111+
*
112+
* <p>For the binary format, see {@code lib/ui/window/key_data_packet.h}.
113+
*
114+
* @return the marshalled bytes.
115+
*/
116+
ByteBuffer toBytes() {
117+
byte[] charBytes;
118+
try {
119+
charBytes = character == null ? null : character.getBytes("UTF-8");
120+
} catch (UnsupportedEncodingException e) {
121+
throw new AssertionError("UTF-8 not supported");
122+
}
123+
final int charSize = charBytes == null ? 0 : charBytes.length;
124+
final ByteBuffer packet =
125+
ByteBuffer.allocateDirect((1 + FIELD_COUNT) * BYTES_PER_FIELD + charSize);
126+
packet.order(ByteOrder.LITTLE_ENDIAN);
127+
128+
packet.putLong(charSize);
129+
packet.putLong(timestamp);
130+
packet.putLong(type.getValue());
131+
packet.putLong(physicalKey);
132+
packet.putLong(logicalKey);
133+
packet.putLong(synthesized ? 1L : 0L);
134+
if (charBytes != null) {
135+
packet.put(charBytes);
136+
}
137+
138+
return packet;
139+
}
140+
}

0 commit comments

Comments
 (0)