5package io.flutter.embedding.android;
9import androidx.annotation.NonNull;
10import androidx.annotation.Nullable;
12import io.flutter.embedding.android.KeyboardMap.PressingGoal;
13import io.flutter.embedding.android.KeyboardMap.TogglingGoal;
14import io.flutter.plugin.common.BinaryMessenger;
15import java.util.ArrayList;
16import java.util.Collections;
17import java.util.HashMap;
27 private static final String TAG =
"KeyEmbedderResponder";
30 private static KeyData.Type getEventType(KeyEvent
event) {
31 final boolean isRepeatEvent =
event.getRepeatCount() > 0;
32 switch (
event.getAction()) {
33 case KeyEvent.ACTION_DOWN:
35 case KeyEvent.ACTION_UP:
38 throw new AssertionError(
"Unexpected event type");
48 @NonNull
private final HashMap<Long, Long> pressingRecords =
new HashMap<>();
53 @NonNull
private final HashMap<Long, TogglingGoal> togglingGoals =
new HashMap<>();
56 private final KeyboardManager.CharacterCombiner characterCombiner =
60 this.messenger = messenger;
62 togglingGoals.put(goal.logicalKey, goal);
66 private static long keyOfPlane(
long key,
long plane) {
74 private Long getPhysicalKey(@NonNull KeyEvent
event) {
75 final long scancode =
event.getScanCode();
80 return keyOfPlane(
event.getKeyCode(), KeyboardMap.kAndroidPlane);
82 final Long byMapping = KeyboardMap.scanCodeToPhysical.get(
scancode);
83 if (byMapping !=
null) {
86 return keyOfPlane(
event.getScanCode(), KeyboardMap.kAndroidPlane);
92 private Long getLogicalKey(@NonNull KeyEvent
event) {
93 final Long byMapping = KeyboardMap.keyCodeToLogical.get((
long)
event.getKeyCode());
94 if (byMapping !=
null) {
97 return keyOfPlane(
event.getKeyCode(), KeyboardMap.kAndroidPlane);
108 if (logicalKey !=
null) {
109 final Long previousValue = pressingRecords.put(physicalKey, logicalKey);
110 if (previousValue !=
null) {
111 throw new AssertionError(
"The key was not empty");
114 final Long previousValue = pressingRecords.remove(physicalKey);
115 if (previousValue ==
null) {
116 throw new AssertionError(
"The key was empty");
139 long eventLogicalKey,
140 long eventPhysicalKey,
142 ArrayList<Runnable> postSynchronize) {
152 final boolean[] nowStates =
new boolean[goal.
keys.length];
153 final Boolean[] preEventStates =
new Boolean[goal.
keys.length];
154 boolean postEventAnyPressed =
false;
157 for (
int keyIdx = 0; keyIdx < goal.
keys.length; keyIdx += 1) {
158 final KeyboardMap.KeyPair
key = goal.
keys[keyIdx];
159 nowStates[keyIdx] = pressingRecords.containsKey(
key.physicalKey);
160 if (
key.logicalKey == eventLogicalKey) {
161 switch (getEventType(
event)) {
163 preEventStates[keyIdx] =
false;
164 postEventAnyPressed =
true;
169 false,
key.logicalKey, eventPhysicalKey,
event.getEventTime()));
176 preEventStates[keyIdx] = nowStates[keyIdx];
186 false,
key.logicalKey,
key.physicalKey,
event.getEventTime()));
188 preEventStates[keyIdx] = nowStates[keyIdx];
189 postEventAnyPressed =
true;
193 postEventAnyPressed = postEventAnyPressed || nowStates[keyIdx];
200 for (
int keyIdx = 0; keyIdx < goal.
keys.length; keyIdx += 1) {
201 if (preEventStates[keyIdx] !=
null) {
204 if (postEventAnyPressed) {
205 preEventStates[keyIdx] = nowStates[keyIdx];
207 preEventStates[keyIdx] =
true;
208 postEventAnyPressed =
true;
211 if (!postEventAnyPressed) {
212 preEventStates[0] =
true;
215 for (
int keyIdx = 0; keyIdx < goal.
keys.length; keyIdx += 1) {
216 if (preEventStates[keyIdx] !=
null) {
219 preEventStates[keyIdx] =
false;
224 for (
int keyIdx = 0; keyIdx < goal.
keys.length; keyIdx += 1) {
225 if (nowStates[keyIdx] != preEventStates[keyIdx]) {
226 final KeyboardMap.KeyPair
key = goal.
keys[keyIdx];
228 preEventStates[keyIdx],
key.logicalKey,
key.physicalKey,
event.getEventTime());
253 if (goal.
enabled != trueEnabled) {
254 final boolean firstIsDown = !pressingRecords.containsKey(goal.
physicalKey);
269 private boolean handleEventImpl(
270 @NonNull KeyEvent
event, @NonNull OnKeyEventHandledCallback onKeyEventHandledCallback) {
272 if (
event.getScanCode() == 0 &&
event.getKeyCode() == 0) {
275 final Long physicalKey = getPhysicalKey(
event);
276 final Long logicalKey = getLogicalKey(
event);
278 final ArrayList<Runnable> postSynchronizeEvents =
new ArrayList<>();
279 for (
final PressingGoal goal : KeyboardMap.pressingGoals) {
282 (
event.getMetaState() & goal.mask) != 0,
286 postSynchronizeEvents);
289 for (
final TogglingGoal goal : togglingGoals.values()) {
294 switch (
event.getAction()) {
295 case KeyEvent.ACTION_DOWN:
298 case KeyEvent.ACTION_UP:
307 final Long lastLogicalRecord = pressingRecords.get(physicalKey);
309 if (lastLogicalRecord ==
null) {
310 type = KeyData.Type.kDown;
314 if (
event.getRepeatCount() > 0) {
315 type = KeyData.Type.kRepeat;
317 synthesizeEvent(
false, lastLogicalRecord, physicalKey,
event.getEventTime());
318 type = KeyData.Type.kDown;
321 final char complexChar =
322 characterCombiner.applyCombiningCharacterToBaseCharacter(
event.getUnicodeChar());
323 if (complexChar != 0) {
327 if (lastLogicalRecord ==
null) {
331 type = KeyData.Type.kUp;
335 if (
type != KeyData.Type.kRepeat) {
338 if (
type == KeyData.Type.kDown) {
339 final TogglingGoal maybeTogglingGoal = togglingGoals.get(logicalKey);
340 if (maybeTogglingGoal !=
null) {
341 maybeTogglingGoal.enabled = !maybeTogglingGoal.enabled;
345 final KeyData
output =
new KeyData();
347 switch (
event.getSource()) {
349 case InputDevice.SOURCE_KEYBOARD:
350 output.deviceType = KeyData.DeviceType.kKeyboard;
352 case InputDevice.SOURCE_DPAD:
353 output.deviceType = KeyData.DeviceType.kDirectionalPad;
355 case InputDevice.SOURCE_GAMEPAD:
356 output.deviceType = KeyData.DeviceType.kGamepad;
358 case InputDevice.SOURCE_JOYSTICK:
359 output.deviceType = KeyData.DeviceType.kJoystick;
361 case InputDevice.SOURCE_HDMI:
362 output.deviceType = KeyData.DeviceType.kHdmi;
366 output.timestamp =
event.getEventTime();
368 output.logicalKey = logicalKey;
369 output.physicalKey = physicalKey;
371 output.synthesized =
false;
372 output.deviceType = KeyData.DeviceType.kKeyboard;
374 sendKeyEvent(
output, onKeyEventHandledCallback);
375 for (
final Runnable postSyncEvent : postSynchronizeEvents) {
381 private void synthesizeEvent(
boolean isDown, Long logicalKey, Long physicalKey,
long timestamp) {
382 final KeyData
output =
new KeyData();
383 output.timestamp = timestamp;
384 output.type = isDown ? KeyData.Type.kDown : KeyData.Type.kUp;
385 output.logicalKey = logicalKey;
386 output.physicalKey = physicalKey;
387 output.character =
null;
388 output.synthesized =
true;
389 output.deviceType = KeyData.DeviceType.kKeyboard;
390 if (physicalKey != 0 && logicalKey != 0) {
393 sendKeyEvent(
output,
null);
396 private void sendKeyEvent(KeyData
data, OnKeyEventHandledCallback onKeyEventHandledCallback) {
397 final BinaryMessenger.BinaryReply handleMessageReply =
398 onKeyEventHandledCallback ==
null
401 Boolean handled =
false;
408 Log.w(TAG,
"A null reply was received when sending a key event to the framework.");
410 onKeyEventHandledCallback.onKeyEventHandled(handled);
413 messenger.send(KeyData.CHANNEL,
data.toBytes(), handleMessageReply);
432 final boolean sentAny = handleEventImpl(
event, onKeyEventHandledCallback);
434 synthesizeEvent(
true, 0
L, 0
L, 0
L);
435 onKeyEventHandledCallback.onKeyEventHandled(
true);
446 return Collections.unmodifiableMap(pressingRecords);
void handleEvent( @NonNull KeyEvent event, @NonNull OnKeyEventHandledCallback onKeyEventHandledCallback)
void synchronizePressingKey(PressingGoal goal, boolean truePressed, long eventLogicalKey, long eventPhysicalKey, KeyEvent event, ArrayList< Runnable > postSynchronize)
Map< Long, Long > getPressedState()
void synchronizeTogglingKey(TogglingGoal goal, boolean trueEnabled, long eventLogicalKey, KeyEvent event)
void updatePressingState(@NonNull Long physicalKey, @Nullable Long logicalKey)
KeyEmbedderResponder(BinaryMessenger messenger)
static final long kValueMask
static TogglingGoal[] getTogglingGoals()
void Log(const char *format,...) SK_PRINTF_LIKE(1
std::shared_ptr< const fml::Mapping > data