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

This commit is contained in:
Greg Spencer 2020-06-24 21:23:02 -07:00 committed by GitHub
parent 91bdf15858
commit c68758fab1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 108 additions and 8 deletions

View file

@ -467,6 +467,13 @@ class RawKeyUpEvent extends RawKeyEvent {
}) : 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.
///
/// Raw key events pass through as much information as possible from the
@ -477,6 +484,9 @@ class RawKeyUpEvent extends RawKeyEvent {
/// buttons that are represented as keys. Typically used by games and other apps
/// 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:
///
/// * [RawKeyDownEvent] and [RawKeyUpEvent], the classes used to describe
@ -494,20 +504,61 @@ class RawKeyboard {
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].
void addListener(ValueChanged<RawKeyEvent> 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].
void removeListener(ValueChanged<RawKeyEvent> 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 {
final RawKeyEvent event = RawKeyEvent.fromMessage(message as Map<String, dynamic>);
if (event == null) {
@ -534,14 +585,19 @@ 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);
if (_listeners.isEmpty) {
return;
}
// Send the event to passive listeners.
for (final ValueChanged<RawKeyEvent> listener in List<ValueChanged<RawKeyEvent>>.from(_listeners)) {
if (_listeners.contains(listener)) {
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>>{

View file

@ -1416,7 +1416,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
/// from the [WidgetsBinding] singleton).
FocusManager() {
rootScope._manager = this;
RawKeyboard.instance.addListener(_handleRawKeyEvent);
RawKeyboard.instance.keyEventHandler = _handleRawKeyEvent;
GestureBinding.instance.pointerRouter.addGlobalRoute(_handlePointerEvent);
}
@ -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
// look at the highlight mode, and it should be accurate.
_lastInteractionWasTouch = false;
@ -1616,7 +1616,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
// onKey on the way up, and if one responds that they handled it, stop.
if (_primaryFocus == null) {
assert(_focusDebug('No primary focus for key event, ignored: $event'));
return;
return false;
}
bool handled = false;
for (final FocusNode node in <FocusNode>[_primaryFocus, ..._primaryFocus.ancestors]) {
@ -1629,6 +1629,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
if (!handled) {
assert(_focusDebug('Key event not handled by anyone: $event.'));
}
return handled;
}
/// The node that currently has the primary focus.

View file

@ -6,6 +6,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
class _ModifierCheck {
@ -508,6 +509,48 @@ void main() {
final RawKeyEventDataAndroid data = repeatCountEvent.data as RawKeyEventDataAndroid;
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', () {
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{