Unverified Commit f796e62c authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Prepare `ShortcutActivator` and `ShortcutManager` to migrate to `KeyEvent`...

Prepare `ShortcutActivator` and `ShortcutManager` to migrate to `KeyEvent` from `RawKeyEvent`. (#136854)
parent d8ffc739
......@@ -9,6 +9,7 @@ import 'package:flutter/foundation.dart';
import 'binding.dart';
import 'debug.dart';
import 'raw_keyboard.dart';
import 'raw_keyboard_android.dart';
import 'system_channels.dart';
export 'dart:ui' show KeyData;
......@@ -124,6 +125,7 @@ abstract class KeyEvent with Diagnosticable {
required this.logicalKey,
this.character,
required this.timeStamp,
this.deviceType = ui.KeyEventDeviceType.keyboard,
this.synthesized = false,
});
......@@ -206,6 +208,13 @@ abstract class KeyEvent with Diagnosticable {
/// All events share the same timeStamp origin.
final Duration timeStamp;
/// The source device type for the key event.
///
/// Not all platforms supply an accurate type.
///
/// Defaults to [ui.KeyEventDeviceType.keyboard].
final ui.KeyEventDeviceType deviceType;
/// Whether this event is synthesized by Flutter to synchronize key states.
///
/// An non-[synthesized] event is converted from a native event, and a native
......@@ -253,6 +262,7 @@ class KeyDownEvent extends KeyEvent {
super.character,
required super.timeStamp,
super.synthesized,
super.deviceType,
});
}
......@@ -272,6 +282,7 @@ class KeyUpEvent extends KeyEvent {
required super.logicalKey,
required super.timeStamp,
super.synthesized,
super.deviceType,
});
}
......@@ -295,6 +306,7 @@ class KeyRepeatEvent extends KeyEvent {
required super.logicalKey,
super.character,
required super.timeStamp,
super.deviceType,
});
}
......@@ -719,20 +731,20 @@ enum KeyDataTransitMode {
/// to both [KeyMessage.events] and [KeyMessage.rawEvent].
rawKeyData,
/// Key event information is delivered as converted key data, followed
/// by raw key data.
/// Key event information is delivered as converted key data, followed by raw
/// key data.
///
/// Key data ([ui.KeyData]) is a standardized event stream converted from
/// platform's native key event information, sent through the embedder
/// API. Its event model is described in [HardwareKeyboard].
/// platform's native key event information, sent through the embedder API.
/// Its event model is described in [HardwareKeyboard].
///
/// Raw key data is platform's native key event information sent in JSON
/// through a method channel. It is interpreted by subclasses of
/// [RawKeyEventData].
///
/// If the current transit mode is [rawKeyData], the key data is converted to
/// [KeyMessage.events], and the raw key data is converted to
/// [KeyMessage.rawEvent].
/// If the current transit mode is [keyDataThenRawKeyData], then the
/// [KeyEventManager] will use the [ui.KeyData] for [KeyMessage.events], and
/// the raw data for [KeyMessage.rawEvent].
keyDataThenRawKeyData,
}
......@@ -1112,6 +1124,33 @@ class KeyEventManager {
return <String, dynamic>{ 'handled': handled };
}
ui.KeyEventDeviceType _convertDeviceType(RawKeyEvent rawEvent) {
final RawKeyEventData data = rawEvent.data;
// Device type is only available from Android.
if (data is! RawKeyEventDataAndroid) {
return ui.KeyEventDeviceType.keyboard;
}
switch (data.eventSource) {
// https://developer.android.com/reference/android/view/InputDevice#SOURCE_KEYBOARD
case 0x00000101:
return ui.KeyEventDeviceType.keyboard;
// https://developer.android.com/reference/android/view/InputDevice#SOURCE_DPAD
case 0x00000201:
return ui.KeyEventDeviceType.directionalPad;
// https://developer.android.com/reference/android/view/InputDevice#SOURCE_GAMEPAD
case 0x00000401:
return ui.KeyEventDeviceType.gamepad;
// https://developer.android.com/reference/android/view/InputDevice#SOURCE_JOYSTICK
case 0x01000010:
return ui.KeyEventDeviceType.joystick;
// https://developer.android.com/reference/android/view/InputDevice#SOURCE_HDMI
case 0x02000001:
return ui.KeyEventDeviceType.hdmi;
}
return ui.KeyEventDeviceType.keyboard;
}
// Convert the raw event to key events, including synthesizing events for
// modifiers, and store the key events in `_keyEventsSinceLastMessage`.
//
......@@ -1126,6 +1165,7 @@ class KeyEventManager {
final LogicalKeyboardKey? recordedLogicalMain = _hardwareKeyboard.lookUpLayout(physicalKey);
final Duration timeStamp = ServicesBinding.instance.currentSystemFrameTimeStamp;
final String? character = rawEvent.character == '' ? null : rawEvent.character;
final ui.KeyEventDeviceType deviceType = _convertDeviceType(rawEvent);
if (rawEvent is RawKeyDownEvent) {
if (recordedLogicalMain == null) {
mainEvent = KeyDownEvent(
......@@ -1133,6 +1173,7 @@ class KeyEventManager {
logicalKey: logicalKey,
character: character,
timeStamp: timeStamp,
deviceType: deviceType,
);
physicalKeysPressed.add(physicalKey);
} else {
......@@ -1142,6 +1183,7 @@ class KeyEventManager {
logicalKey: recordedLogicalMain,
character: character,
timeStamp: timeStamp,
deviceType: deviceType,
);
}
} else {
......@@ -1153,6 +1195,7 @@ class KeyEventManager {
logicalKey: recordedLogicalMain,
physicalKey: physicalKey,
timeStamp: timeStamp,
deviceType: deviceType,
);
physicalKeysPressed.remove(physicalKey);
}
......@@ -1167,6 +1210,7 @@ class KeyEventManager {
logicalKey: logicalKey,
timeStamp: timeStamp,
synthesized: true,
deviceType: deviceType,
));
} else {
_keyEventsSinceLastMessage.add(KeyUpEvent(
......@@ -1174,6 +1218,7 @@ class KeyEventManager {
logicalKey: _hardwareKeyboard.lookUpLayout(key)!,
timeStamp: timeStamp,
synthesized: true,
deviceType: deviceType,
));
}
}
......@@ -1183,6 +1228,7 @@ class KeyEventManager {
logicalKey: _rawKeyboard.lookUpLayout(key)!,
timeStamp: timeStamp,
synthesized: true,
deviceType: deviceType,
));
}
if (mainEvent != null) {
......@@ -1220,6 +1266,7 @@ class KeyEventManager {
timeStamp: timeStamp,
character: keyData.character,
synthesized: keyData.synthesized,
deviceType: keyData.deviceType,
);
case ui.KeyEventType.up:
assert(keyData.character == null);
......@@ -1228,6 +1275,7 @@ class KeyEventManager {
logicalKey: logicalKey,
timeStamp: timeStamp,
synthesized: keyData.synthesized,
deviceType: keyData.deviceType,
);
case ui.KeyEventType.repeat:
return KeyRepeatEvent(
......@@ -1235,6 +1283,7 @@ class KeyEventManager {
logicalKey: logicalKey,
timeStamp: timeStamp,
character: keyData.character,
deviceType: keyData.deviceType,
);
}
}
......
......@@ -213,11 +213,11 @@ void main() {
testWidgetsWithLeakTracking('isActivatedBy works as expected', (WidgetTester tester) async {
// Collect some key events to use for testing.
final List<RawKeyEvent> events = <RawKeyEvent>[];
final List<KeyEvent> events = <KeyEvent>[];
await tester.pumpWidget(
Focus(
autofocus: true,
onKey: (FocusNode node, RawKeyEvent event) {
onKeyEvent: (FocusNode node, KeyEvent event) {
events.add(event);
return KeyEventResult.ignored;
},
......@@ -229,10 +229,13 @@ void main() {
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(set, events[0]), isTrue);
expect(ShortcutActivator.isActivatedBy(set, events.last), isTrue);
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(set, events.last), isTrue);
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(set, events.last), isFalse);
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
expect(ShortcutActivator.isActivatedBy(set, events[0]), isFalse);
expect(ShortcutActivator.isActivatedBy(set, events.last), isFalse);
});
test('LogicalKeySet diagnostics work.', () {
......@@ -457,11 +460,11 @@ void main() {
testWidgetsWithLeakTracking('isActivatedBy works as expected', (WidgetTester tester) async {
// Collect some key events to use for testing.
final List<RawKeyEvent> events = <RawKeyEvent>[];
final List<KeyEvent> events = <KeyEvent>[];
await tester.pumpWidget(
Focus(
autofocus: true,
onKey: (FocusNode node, RawKeyEvent event) {
onKeyEvent: (FocusNode node, KeyEvent event) {
events.add(event);
return KeyEventResult.ignored;
},
......@@ -471,12 +474,27 @@ void main() {
const SingleActivator singleActivator = SingleActivator(LogicalKeyboardKey.keyA, control: true);
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isTrue);
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isTrue);
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isFalse);
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isFalse);
const SingleActivator noRepeatSingleActivator = SingleActivator(LogicalKeyboardKey.keyA, control: true, includeRepeats: false);
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(noRepeatSingleActivator, events.last), isTrue);
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(noRepeatSingleActivator, events.last), isFalse);
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(singleActivator, events[1]), isTrue);
expect(ShortcutActivator.isActivatedBy(noRepeatSingleActivator, events.last), isFalse);
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
expect(ShortcutActivator.isActivatedBy(singleActivator, events[1]), isFalse);
expect(ShortcutActivator.isActivatedBy(noRepeatSingleActivator, events.last), isFalse);
});
group('diagnostics.', () {
......@@ -1241,11 +1259,11 @@ void main() {
testWidgetsWithLeakTracking('isActivatedBy works as expected', (WidgetTester tester) async {
// Collect some key events to use for testing.
final List<RawKeyEvent> events = <RawKeyEvent>[];
final List<KeyEvent> events = <KeyEvent>[];
await tester.pumpWidget(
Focus(
autofocus: true,
onKey: (FocusNode node, RawKeyEvent event) {
onKeyEvent: (FocusNode node, KeyEvent event) {
events.add(event);
return KeyEventResult.ignored;
},
......@@ -1256,8 +1274,20 @@ void main() {
const CharacterActivator characterActivator = CharacterActivator('a');
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(characterActivator, events.last), isTrue);
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(characterActivator, events.last), isTrue);
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(characterActivator, events.last), isFalse);
const CharacterActivator noRepeatCharacterActivator = CharacterActivator('a', includeRepeats: false);
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(noRepeatCharacterActivator, events.last), isTrue);
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(noRepeatCharacterActivator, events.last), isFalse);
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(characterActivator, events[0]), isTrue);
expect(ShortcutActivator.isActivatedBy(noRepeatCharacterActivator, events.last), isFalse);
});
group('diagnostics.', () {
......@@ -1936,7 +1966,7 @@ class TestAction extends CallbackAction<Intent> {
/// An activator that accepts down events that has [key] as the logical key.
///
/// This class is used only to tests. It is intentionally designed poorly by
/// returning null in [triggers], and checks [key] in [accepts].
/// returning null in [triggers], and checks [key] in [acceptsEvent].
class DumbLogicalActivator extends ShortcutActivator {
const DumbLogicalActivator(this.key);
......@@ -1946,8 +1976,8 @@ class DumbLogicalActivator extends ShortcutActivator {
Iterable<LogicalKeyboardKey>? get triggers => null;
@override
bool accepts(RawKeyEvent event, RawKeyboard state) {
return event is RawKeyDownEvent
bool accepts(KeyEvent event, HardwareKeyboard state) {
return (event is KeyDownEvent || event is KeyRepeatEvent)
&& event.logicalKey == key;
}
......@@ -1980,8 +2010,8 @@ class TestShortcutManager extends ShortcutManager {
List<LogicalKeyboardKey> keys;
@override
KeyEventResult handleKeypress(BuildContext context, RawKeyEvent event) {
if (event is RawKeyDownEvent) {
KeyEventResult handleKeypress(BuildContext context, KeyEvent event) {
if (event is KeyDownEvent || event is KeyRepeatEvent) {
keys.add(event.logicalKey);
}
return super.handleKeypress(context, event);
......
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