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

Implement delayed key event synthesis support for Android (#59358)

parent 91bdf158
...@@ -467,6 +467,13 @@ class RawKeyUpEvent extends RawKeyEvent { ...@@ -467,6 +467,13 @@ class RawKeyUpEvent extends RawKeyEvent {
}) : super(data: data, character: character); }) : super(data: data, character: character);
} }
/// A callback type used by [RawKeyboard.keyEventHandler] to send key events to
/// a handler that can determine if the key has been handled or not.
///
/// The handler should return true if the key has been handled, and false if the
/// key was not handled. It must not return null.
typedef RawKeyEventHandler = bool Function(RawKeyEvent event);
/// An interface for listening to raw key events. /// An interface for listening to raw key events.
/// ///
/// Raw key events pass through as much information as possible from the /// Raw key events pass through as much information as possible from the
...@@ -477,6 +484,9 @@ class RawKeyUpEvent extends RawKeyEvent { ...@@ -477,6 +484,9 @@ class RawKeyUpEvent extends RawKeyEvent {
/// buttons that are represented as keys. Typically used by games and other apps /// buttons that are represented as keys. Typically used by games and other apps
/// that use keyboards for purposes other than text entry. /// that use keyboards for purposes other than text entry.
/// ///
/// These key events are typically only key events generated by a hardware
/// keyboard, and not those from software keyboards or input method editors.
///
/// See also: /// See also:
/// ///
/// * [RawKeyDownEvent] and [RawKeyUpEvent], the classes used to describe /// * [RawKeyDownEvent] and [RawKeyUpEvent], the classes used to describe
...@@ -494,20 +504,61 @@ class RawKeyboard { ...@@ -494,20 +504,61 @@ class RawKeyboard {
final List<ValueChanged<RawKeyEvent>> _listeners = <ValueChanged<RawKeyEvent>>[]; final List<ValueChanged<RawKeyEvent>> _listeners = <ValueChanged<RawKeyEvent>>[];
/// Calls the listener every time the user presses or releases a key. /// Register a listener that is called every time the user presses or releases
/// a hardware keyboard key.
///
/// Since the listeners have no way to indicate what they did with the event,
/// listeners are assumed to not handle the key event. These events will also
/// be distributed to other listeners, and to the [keyEventHandler].
///
/// Most applications prefer to use the focus system (see [Focus] and
/// [FocusManager]) to receive key events to the focused control instead of
/// this kind of passive listener.
/// ///
/// Listeners can be removed with [removeListener]. /// Listeners can be removed with [removeListener].
void addListener(ValueChanged<RawKeyEvent> listener) { void addListener(ValueChanged<RawKeyEvent> listener) {
_listeners.add(listener); _listeners.add(listener);
} }
/// Stop calling the listener every time the user presses or releases a key. /// Stop calling the given listener every time the user presses or releases a
/// hardware keyboard key.
/// ///
/// Listeners can be added with [addListener]. /// Listeners can be added with [addListener].
void removeListener(ValueChanged<RawKeyEvent> listener) { void removeListener(ValueChanged<RawKeyEvent> listener) {
_listeners.remove(listener); _listeners.remove(listener);
} }
/// A handler for hardware keyboard events that will stop propagation if the
/// handler returns true.
///
/// Key events on the platform are given to Flutter to be handled by the
/// engine. If they are not handled, then the platform will continue to
/// distribute the keys (i.e. propagate them) to other (possibly non-Flutter)
/// components in the application. The return value from this handler tells
/// the platform to either stop propagation (by returning true: "event
/// handled"), or pass the event on to other controls (false: "event not
/// handled").
///
/// This handler is normally set by the [FocusManager] so that it can control
/// the key event propagation to focused widgets.
///
/// Most applications can use the focus system (see [Focus] and
/// [FocusManager]) to receive key events. If you are not using the
/// [FocusManager] to manage focus, then to be able to stop propagation of the
/// event by indicating that the event was handled, set this attribute to a
/// [RawKeyEventHandler]. Otherwise, key events will be assumed to not have
/// been handled by Flutter, and will also be sent to other (possibly
/// non-Flutter) controls in the application.
///
/// See also:
///
/// * [Focus.onKey], a [Focus] callback attribute that will be given key
/// events distributed by the [FocusManager] based on the current primary
/// focus.
/// * [addListener], to add passive key event listeners that do not stop event
/// propagation.
RawKeyEventHandler keyEventHandler;
Future<dynamic> _handleKeyEvent(dynamic message) async { Future<dynamic> _handleKeyEvent(dynamic message) async {
final RawKeyEvent event = RawKeyEvent.fromMessage(message as Map<String, dynamic>); final RawKeyEvent event = RawKeyEvent.fromMessage(message as Map<String, dynamic>);
if (event == null) { if (event == null) {
...@@ -534,14 +585,19 @@ class RawKeyboard { ...@@ -534,14 +585,19 @@ class RawKeyboard {
// Make sure that the modifiers reflect reality, in case a modifier key was // Make sure that the modifiers reflect reality, in case a modifier key was
// pressed/released while the app didn't have focus. // pressed/released while the app didn't have focus.
_synchronizeModifiers(event); _synchronizeModifiers(event);
if (_listeners.isEmpty) { // Send the event to passive listeners.
return;
}
for (final ValueChanged<RawKeyEvent> listener in List<ValueChanged<RawKeyEvent>>.from(_listeners)) { for (final ValueChanged<RawKeyEvent> listener in List<ValueChanged<RawKeyEvent>>.from(_listeners)) {
if (_listeners.contains(listener)) { if (_listeners.contains(listener)) {
listener(event); listener(event);
} }
} }
// Send the key event to the keyEventHandler, then send the appropriate
// response to the platform so that it can resolve the event's handling.
// Defaults to false if keyEventHandler is null.
final bool handled = keyEventHandler != null && keyEventHandler(event);
assert(handled != null, 'keyEventHandler returned null, which is not allowed');
return <String, dynamic>{ 'handled': handled };
} }
static final Map<_ModifierSidePair, Set<PhysicalKeyboardKey>> _modifierKeyMap = <_ModifierSidePair, Set<PhysicalKeyboardKey>>{ static final Map<_ModifierSidePair, Set<PhysicalKeyboardKey>> _modifierKeyMap = <_ModifierSidePair, Set<PhysicalKeyboardKey>>{
......
...@@ -1416,7 +1416,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier { ...@@ -1416,7 +1416,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
/// from the [WidgetsBinding] singleton). /// from the [WidgetsBinding] singleton).
FocusManager() { FocusManager() {
rootScope._manager = this; rootScope._manager = this;
RawKeyboard.instance.addListener(_handleRawKeyEvent); RawKeyboard.instance.keyEventHandler = _handleRawKeyEvent;
GestureBinding.instance.pointerRouter.addGlobalRoute(_handlePointerEvent); GestureBinding.instance.pointerRouter.addGlobalRoute(_handlePointerEvent);
} }
...@@ -1605,7 +1605,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier { ...@@ -1605,7 +1605,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
} }
} }
void _handleRawKeyEvent(RawKeyEvent event) { bool _handleRawKeyEvent(RawKeyEvent event) {
// Update highlightMode first, since things responding to the keys might // Update highlightMode first, since things responding to the keys might
// look at the highlight mode, and it should be accurate. // look at the highlight mode, and it should be accurate.
_lastInteractionWasTouch = false; _lastInteractionWasTouch = false;
...@@ -1616,7 +1616,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier { ...@@ -1616,7 +1616,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
// onKey on the way up, and if one responds that they handled it, stop. // onKey on the way up, and if one responds that they handled it, stop.
if (_primaryFocus == null) { if (_primaryFocus == null) {
assert(_focusDebug('No primary focus for key event, ignored: $event')); assert(_focusDebug('No primary focus for key event, ignored: $event'));
return; return false;
} }
bool handled = false; bool handled = false;
for (final FocusNode node in <FocusNode>[_primaryFocus, ..._primaryFocus.ancestors]) { for (final FocusNode node in <FocusNode>[_primaryFocus, ..._primaryFocus.ancestors]) {
...@@ -1629,6 +1629,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier { ...@@ -1629,6 +1629,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
if (!handled) { if (!handled) {
assert(_focusDebug('Key event not handled by anyone: $event.')); assert(_focusDebug('Key event not handled by anyone: $event.'));
} }
return handled;
} }
/// The node that currently has the primary focus. /// The node that currently has the primary focus.
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
class _ModifierCheck { class _ModifierCheck {
...@@ -508,6 +509,48 @@ void main() { ...@@ -508,6 +509,48 @@ void main() {
final RawKeyEventDataAndroid data = repeatCountEvent.data as RawKeyEventDataAndroid; final RawKeyEventDataAndroid data = repeatCountEvent.data as RawKeyEventDataAndroid;
expect(data.repeatCount, equals(42)); expect(data.repeatCount, equals(42));
}); });
testWidgets('Key events are responded to correctly.', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);
// Generate the data for a regular key down event.
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
LogicalKeyboardKey.keyA,
platform: 'android',
isDown: true,
);
Map<String, dynamic> message;
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData data) {
message = SystemChannels.keyEvent.codec.decodeMessage(data) as Map<String, dynamic>;
},
);
expect(message, equals(<String, dynamic>{ 'handled': false }));
// Set up a widget that will receive focused text events.
final FocusNode focusNode = FocusNode(debugLabel: 'Test Node');
await tester.pumpWidget(
Focus(
focusNode: focusNode,
onKey: (FocusNode node, RawKeyEvent event) {
return true; // handle all events.
},
child: const SizedBox(),
),
);
focusNode.requestFocus();
await tester.pump();
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData data) {
message = SystemChannels.keyEvent.codec.decodeMessage(data) as Map<String, dynamic>;
},
);
expect(message, equals(<String, dynamic>{ 'handled': true }));
ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler(SystemChannels.keyEvent.name, null);
});
}); });
group('RawKeyEventDataFuchsia', () { group('RawKeyEventDataFuchsia', () {
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{ const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
......
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