diff --git a/shell/platform/android/io/flutter/embedding/android/KeyEmbedderResponder.java b/shell/platform/android/io/flutter/embedding/android/KeyEmbedderResponder.java index 7d97f5cb46b9c..2a5d5b1a87400 100644 --- a/shell/platform/android/io/flutter/embedding/android/KeyEmbedderResponder.java +++ b/shell/platform/android/io/flutter/embedding/android/KeyEmbedderResponder.java @@ -122,7 +122,7 @@ void updatePressingState(@NonNull Long physicalKey, @Nullable Long logicalKey) { // the current event in consideration. // // Events that should be synthesized before the main event are synthesized - // immediately, while events that should be syntehsized after the main event are appended to + // immediately, while events that should be synthesized after the main event are appended to // `postSynchronize`. // // Although Android KeyEvent defined bitmasks for sided modifiers (SHIFT_LEFT_ON and @@ -133,6 +133,7 @@ void synchronizePressingKey( PressingGoal goal, boolean truePressed, long eventLogicalKey, + long eventPhysicalKey, KeyEvent event, ArrayList postSynchronize) { // During an incoming event, there might be a synthesized Flutter event for each key of each @@ -151,8 +152,8 @@ void synchronizePressingKey( // 2. Derive the pre-event state of the event key (if applicable.) for (int keyIdx = 0; keyIdx < goal.keys.length; keyIdx += 1) { final KeyboardMap.KeyPair key = goal.keys[keyIdx]; - nowStates[keyIdx] = pressingRecords.containsKey(goal.keys[keyIdx].physicalKey); - if (goal.keys[keyIdx].logicalKey == eventLogicalKey) { + nowStates[keyIdx] = pressingRecords.containsKey(key.physicalKey); + if (key.logicalKey == eventLogicalKey) { switch (getEventType(event)) { case kDown: preEventStates[keyIdx] = false; @@ -161,7 +162,7 @@ void synchronizePressingKey( postSynchronize.add( () -> synthesizeEvent( - false, key.logicalKey, key.physicalKey, event.getEventTime())); + false, key.logicalKey, eventPhysicalKey, event.getEventTime())); } break; case kUp: @@ -273,7 +274,12 @@ private boolean handleEventImpl( final ArrayList postSynchronizeEvents = new ArrayList<>(); for (final PressingGoal goal : KeyboardMap.pressingGoals) { synchronizePressingKey( - goal, (event.getMetaState() & goal.mask) != 0, logicalKey, event, postSynchronizeEvents); + goal, + (event.getMetaState() & goal.mask) != 0, + logicalKey, + physicalKey, + event, + postSynchronizeEvents); } for (final TogglingGoal goal : togglingGoals.values()) { diff --git a/shell/platform/android/test/io/flutter/embedding/android/KeyboardManagerTest.java b/shell/platform/android/test/io/flutter/embedding/android/KeyboardManagerTest.java index 66df565ed8c34..799ae861b2352 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/KeyboardManagerTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/KeyboardManagerTest.java @@ -1350,7 +1350,7 @@ public void synchronizeOtherModifiers() { @Test public void synchronizeModifiersForConflictingMetaState() { // Test if ShiftLeft can be correctly synchronized during down events of - // ShiftLeft that has 0 for its metaState. + // ShiftLeft that have 0 for their metaState. final KeyboardTester tester = new KeyboardTester(); final ArrayList calls = new ArrayList<>(); // Even though the event is for ShiftRight, we still set SHIFT | SHIFT_LEFT here. @@ -1396,6 +1396,31 @@ public void synchronizeModifiersForConflictingMetaState() { calls.clear(); } + // Regression test for https://github.com/flutter/flutter/issues/110640 + @Test + public void synchronizeModifiersForZeroedScanCode() { + // Test if ShiftLeft can be correctly synchronized during down events of + // ShiftLeft that have 0 for their metaState and 0 for their scanCode. + final KeyboardTester tester = new KeyboardTester(); + final ArrayList calls = new ArrayList<>(); + + tester.recordEmbedderCallsTo(calls); + tester.respondToTextInputWith(true); // Suppress redispatching + + // Test: DOWN event when the current state is 0 and scanCode is 0. + final KeyEvent keyEvent = new FakeKeyEvent(ACTION_DOWN, 0, KEYCODE_SHIFT_LEFT, 0, '\0', 0); + // Compute physicalKey in the same way as KeyboardManager.getPhysicalKey. + final Long physicalKey = KEYCODE_SHIFT_LEFT | KeyboardMap.kAndroidPlane; + + assertEquals(tester.keyboardManager.handleEvent(keyEvent), true); + assertEquals(calls.size(), 2); + assertEmbedderEventEquals( + calls.get(0).keyData, Type.kDown, physicalKey, LOGICAL_SHIFT_LEFT, null, false); + assertEmbedderEventEquals( + calls.get(1).keyData, Type.kUp, physicalKey, LOGICAL_SHIFT_LEFT, null, true); + calls.clear(); + } + @Test public void normalCapsLockEvents() { final KeyboardTester tester = new KeyboardTester();