Unverified Commit 7959c395 authored by Tong Mu's avatar Tong Mu Committed by GitHub

[Keyboard] Correctly convert down events that are immediately synthesized released (#99200)

parent b44cbe1d
...@@ -887,6 +887,7 @@ class KeyEventManager { ...@@ -887,6 +887,7 @@ class KeyEventManager {
final PhysicalKeyboardKey physicalKey = rawEvent.physicalKey; final PhysicalKeyboardKey physicalKey = rawEvent.physicalKey;
final LogicalKeyboardKey logicalKey = rawEvent.logicalKey; final LogicalKeyboardKey logicalKey = rawEvent.logicalKey;
final Set<PhysicalKeyboardKey> physicalKeysPressed = _hardwareKeyboard.physicalKeysPressed; final Set<PhysicalKeyboardKey> physicalKeysPressed = _hardwareKeyboard.physicalKeysPressed;
final List<KeyEvent> eventAfterwards = <KeyEvent>[];
final KeyEvent? mainEvent; final KeyEvent? mainEvent;
final LogicalKeyboardKey? recordedLogicalMain = _hardwareKeyboard.lookUpLayout(physicalKey); final LogicalKeyboardKey? recordedLogicalMain = _hardwareKeyboard.lookUpLayout(physicalKey);
final Duration timeStamp = ServicesBinding.instance.currentSystemFrameTimeStamp; final Duration timeStamp = ServicesBinding.instance.currentSystemFrameTimeStamp;
...@@ -923,6 +924,17 @@ class KeyEventManager { ...@@ -923,6 +924,17 @@ class KeyEventManager {
} }
} }
for (final PhysicalKeyboardKey key in physicalKeysPressed.difference(_rawKeyboard.physicalKeysPressed)) { for (final PhysicalKeyboardKey key in physicalKeysPressed.difference(_rawKeyboard.physicalKeysPressed)) {
if (key == physicalKey) {
// Somehow, a down event is dispatched but the key is absent from
// keysPressed. Synthesize a up event for the key, but this event must
// be added after the main key down event.
eventAfterwards.add(KeyUpEvent(
physicalKey: key,
logicalKey: logicalKey,
timeStamp: timeStamp,
synthesized: true,
));
} else {
_keyEventsSinceLastMessage.add(KeyUpEvent( _keyEventsSinceLastMessage.add(KeyUpEvent(
physicalKey: key, physicalKey: key,
logicalKey: _hardwareKeyboard.lookUpLayout(key)!, logicalKey: _hardwareKeyboard.lookUpLayout(key)!,
...@@ -930,6 +942,7 @@ class KeyEventManager { ...@@ -930,6 +942,7 @@ class KeyEventManager {
synthesized: true, synthesized: true,
)); ));
} }
}
for (final PhysicalKeyboardKey key in _rawKeyboard.physicalKeysPressed.difference(physicalKeysPressed)) { for (final PhysicalKeyboardKey key in _rawKeyboard.physicalKeysPressed.difference(physicalKeysPressed)) {
_keyEventsSinceLastMessage.add(KeyDownEvent( _keyEventsSinceLastMessage.add(KeyDownEvent(
physicalKey: key, physicalKey: key,
...@@ -938,9 +951,11 @@ class KeyEventManager { ...@@ -938,9 +951,11 @@ class KeyEventManager {
synthesized: true, synthesized: true,
)); ));
} }
if (mainEvent != null) if (mainEvent != null) {
_keyEventsSinceLastMessage.add(mainEvent); _keyEventsSinceLastMessage.add(mainEvent);
} }
_keyEventsSinceLastMessage.addAll(eventAfterwards);
}
/// Reset the inferred platform transit mode and related states. /// Reset the inferred platform transit mode and related states.
/// ///
......
...@@ -195,6 +195,56 @@ void main() { ...@@ -195,6 +195,56 @@ void main() {
logs.clear(); logs.clear();
}, variant: KeySimulatorTransitModeVariant.all()); }, variant: KeySimulatorTransitModeVariant.all());
// Regression test for https://github.com/flutter/flutter/issues/99196 .
//
// In rawKeyData mode, if a key down event is dispatched but immediately
// synthesized to be released, the old logic would trigger a Null check
// _CastError on _hardwareKeyboard.lookUpLayout(key). The original scenario
// that this is triggered on Android is unknown. Here we make up a scenario
// where a ShiftLeft key down is dispatched but the modifier bit is not set.
testWidgets('Correctly convert down events that are synthesized released', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
final List<KeyEvent> events = <KeyEvent>[];
await tester.pumpWidget(
KeyboardListener(
autofocus: true,
focusNode: focusNode,
child: Container(),
onKeyEvent: (KeyEvent event) {
events.add(event);
},
),
);
// Dispatch an arbitrary event to bypass the pressedKeys check.
await simulateKeyDownEvent(LogicalKeyboardKey.keyA, platform: 'web');
// Dispatch an
final Map<String, dynamic> data2 = KeyEventSimulator.getKeyData(
LogicalKeyboardKey.shiftLeft,
platform: 'web',
)..['metaState'] = 0;
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data2),
(ByteData? data) {},
);
expect(events, hasLength(3));
expect(events[1], isA<KeyDownEvent>());
expect(events[1].logicalKey, LogicalKeyboardKey.shiftLeft);
expect(events[1].synthesized, false);
expect(events[2], isA<KeyUpEvent>());
expect(events[2].logicalKey, LogicalKeyboardKey.shiftLeft);
expect(events[2].synthesized, true);
expect(ServicesBinding.instance.keyboard.physicalKeysPressed, equals(<PhysicalKeyboardKey>{
PhysicalKeyboardKey.keyA,
}));
}, variant: const KeySimulatorTransitModeVariant(<KeyDataTransitMode>{
KeyDataTransitMode.rawKeyData,
}));
testWidgets('Instantly dispatch synthesized key events when the queue is empty', (WidgetTester tester) async { testWidgets('Instantly dispatch synthesized key events when the queue is empty', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(); final FocusNode focusNode = FocusNode();
final List<int> logs = <int>[]; final List<int> logs = <int>[];
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment