diff --git a/packages/flutter/lib/src/services/raw_keyboard.dart b/packages/flutter/lib/src/services/raw_keyboard.dart index 430a5484265..9fa39572b36 100644 --- a/packages/flutter/lib/src/services/raw_keyboard.dart +++ b/packages/flutter/lib/src/services/raw_keyboard.dart @@ -581,6 +581,11 @@ class RawKeyboard { // Make sure that the modifiers reflect reality, in case a modifier key was // pressed/released while the app didn't have focus. _synchronizeModifiers(event); + assert(event is! RawKeyDownEvent || _keysPressed.isNotEmpty, + 'Attempted to send a key down event when no keys are in keysPressed. ' + "This state can occur if the key event being sent doesn't properly " + 'set its modifier flags. This was the event: $event and its data: ' + '${event.data}'); // Send the event to passive listeners. for (final ValueChanged listener in List>.from(_listeners)) { if (_listeners.contains(listener)) { diff --git a/packages/flutter/lib/src/widgets/shortcuts.dart b/packages/flutter/lib/src/widgets/shortcuts.dart index 2b534051d1d..09e5447f12f 100644 --- a/packages/flutter/lib/src/widgets/shortcuts.dart +++ b/packages/flutter/lib/src/widgets/shortcuts.dart @@ -306,7 +306,21 @@ class ShortcutManager extends ChangeNotifier with Diagnosticable { return false; } assert(context != null); - final LogicalKeySet keySet = keysPressed ?? LogicalKeySet.fromSet(RawKeyboard.instance.keysPressed); + LogicalKeySet keySet = keysPressed; + if (keySet == null) { + assert(RawKeyboard.instance.keysPressed.isNotEmpty, + 'Received a key down event when no keys are in keysPressed. ' + "This state can occur if the key event being sent doesn't properly " + 'set its modifier flags. This was the event: $event and its data: ' + '${event.data}'); + // Avoid the crash in release mode, since it's easy to miss a particular + // bad key sequence in testing, and so shouldn't crash the app in release. + if (RawKeyboard.instance.keysPressed.isNotEmpty) { + keySet = LogicalKeySet.fromSet(RawKeyboard.instance.keysPressed); + } else { + return false; + } + } Intent matchedIntent = _shortcuts[keySet]; if (matchedIntent == null) { // If there's not a more specific match, We also look for any keys that diff --git a/packages/flutter/test/services/raw_keyboard_test.dart b/packages/flutter/test/services/raw_keyboard_test.dart index b77e99f9bea..14197cc9dab 100644 --- a/packages/flutter/test/services/raw_keyboard_test.dart +++ b/packages/flutter/test/services/raw_keyboard_test.dart @@ -4,6 +4,7 @@ // @dart = 2.8 +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -277,6 +278,36 @@ void main() { ), ); }); + + testWidgets('RawKeyboard asserts if no keys are in keysPressed after receiving a key down event', (WidgetTester tester) async { + FlutterErrorDetails errorDetails; + final FlutterExceptionHandler oldHandler = FlutterError.onError; + FlutterError.onError = (FlutterErrorDetails details) { + errorDetails = details; + }; + try { + await ServicesBinding.instance.defaultBinaryMessenger + .handlePlatformMessage( + SystemChannels.keyEvent.name, + SystemChannels.keyEvent.codec.encodeMessage(const { + 'type': 'keydown', + 'keymap': 'android', + 'keyCode': 0x3b, // Left shift key keyCode + 'scanCode': 0x2a, + 'metaState': 0x0, // No shift key metaState set! + 'source': 0x101, + 'deviceId': 1, + }), + (ByteData data) {}, + ); + } finally { + FlutterError.onError = oldHandler; + } + expect(errorDetails, isNotNull); + expect(errorDetails.stack, isNotNull); + final String fullErrorMessage = errorDetails.toString().replaceAll('\n', ' '); + expect(fullErrorMessage, contains('Attempted to send a key down event when no keys are in keysPressed')); + }); }); group('RawKeyEventDataAndroid', () {