Implement delayed key event synthesis support for Android (#59358)
This commit is contained in:
parent
91bdf15858
commit
c68758fab1
@ -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 {
|
|||||||
/// 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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
// 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 {
|
|||||||
/// 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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
// 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 {
|
|||||||
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 @@
|
|||||||
|
|
||||||
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() {
|
|||||||
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>{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user