Added keyEvent support for iOS 13.4+ (flutter/engine#20972)

This commit is contained in:
puelo 2020-10-20 01:22:02 +02:00 committed by GitHub
parent 4e7a837cfa
commit 8ca4f647dd
4 changed files with 217 additions and 0 deletions

View File

@ -322,6 +322,14 @@ FLUTTER_EXPORT
*/
@property(nonatomic, readonly) FlutterBasicMessageChannel* settingsChannel;
/**
* The `FlutterBasicMessageChannel` used for communicating key events
* from physical keyboards
*
* Can be nil after `destroyContext` is called.
*/
@property(nonatomic, readonly) FlutterBasicMessageChannel* keyEventChannel;
/**
* The `NSURL` of the observatory for the service isolate.
*

View File

@ -81,6 +81,7 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
fml::scoped_nsobject<FlutterBasicMessageChannel> _lifecycleChannel;
fml::scoped_nsobject<FlutterBasicMessageChannel> _systemChannel;
fml::scoped_nsobject<FlutterBasicMessageChannel> _settingsChannel;
fml::scoped_nsobject<FlutterBasicMessageChannel> _keyEventChannel;
int64_t _nextTextureId;
@ -350,6 +351,9 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
- (FlutterBasicMessageChannel*)settingsChannel {
return _settingsChannel.get();
}
- (FlutterBasicMessageChannel*)keyEventChannel {
return _keyEventChannel.get();
}
- (NSURL*)observatoryUrl {
return [_publisher.get() url];
@ -364,6 +368,7 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
_lifecycleChannel.reset();
_systemChannel.reset();
_settingsChannel.reset();
_keyEventChannel.reset();
}
- (void)startProfiler:(NSString*)threadLabel {
@ -436,6 +441,11 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
binaryMessenger:self.binaryMessenger
codec:[FlutterJSONMessageCodec sharedInstance]]);
_keyEventChannel.reset([[FlutterBasicMessageChannel alloc]
initWithName:@"flutter/keyevent"
binaryMessenger:self.binaryMessenger
codec:[FlutterJSONMessageCodec sharedInstance]]);
_textInputPlugin.reset([[FlutterTextInputPlugin alloc] init]);
_textInputPlugin.get().textInputDelegate = self;

View File

@ -1024,6 +1024,58 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
[self updateViewportMetrics];
}
- (void)dispatchPresses:(NSSet<UIPress*>*)presses API_AVAILABLE(ios(13.4)) {
if (@available(iOS 13.4, *)) {
for (UIPress* press in presses) {
if (press.key == nil || press.phase == UIPressPhaseStationary ||
press.phase == UIPressPhaseChanged) {
continue;
}
NSMutableDictionary* keyMessage = [@{
@"keymap" : @"ios",
@"type" : @"unknown",
@"keyCode" : @(press.key.keyCode),
@"modifiers" : @(press.key.modifierFlags),
@"characters" : press.key.characters,
@"charactersIgnoringModifiers" : press.key.charactersIgnoringModifiers
} mutableCopy];
if (press.phase == UIPressPhaseBegan) {
keyMessage[@"type"] = @"keydown";
} else if (press.phase == UIPressPhaseEnded || press.phase == UIPressPhaseCancelled) {
keyMessage[@"type"] = @"keyup";
}
[[_engine.get() keyEventChannel] sendMessage:keyMessage];
}
}
}
- (void)pressesBegan:(NSSet<UIPress*>*)presses withEvent:(UIEvent*)event API_AVAILABLE(ios(9.0)) {
if (@available(iOS 13.4, *)) {
[self dispatchPresses:presses];
}
}
- (void)pressesChanged:(NSSet<UIPress*>*)presses withEvent:(UIEvent*)event API_AVAILABLE(ios(9.0)) {
if (@available(iOS 13.4, *)) {
[self dispatchPresses:presses];
}
}
- (void)pressesEnded:(NSSet<UIPress*>*)presses withEvent:(UIEvent*)event API_AVAILABLE(ios(9.0)) {
if (@available(iOS 13.4, *)) {
[self dispatchPresses:presses];
}
}
- (void)pressesCancelled:(NSSet<UIPress*>*)presses
withEvent:(UIEvent*)event API_AVAILABLE(ios(9.0)) {
if (@available(iOS 13.4, *)) {
[self dispatchPresses:presses];
}
}
#pragma mark - Orientation updates
- (void)onOrientationPreferencesUpdated:(NSNotification*)notification {

View File

@ -62,6 +62,7 @@ typedef enum UIAccessibilityContrast : NSInteger {
@interface FlutterViewController (Tests)
- (void)surfaceUpdated:(BOOL)appeared;
- (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences;
- (void)dispatchPresses:(NSSet<UIPress*>*)presses;
@end
@implementation FlutterViewControllerTest
@ -549,4 +550,150 @@ typedef enum UIAccessibilityContrast : NSInteger {
OCMVerify([engine notifyLowMemory]);
}
- (void)testValidKeyUpEvent API_AVAILABLE(ios(13.4)) {
if (@available(iOS 13.4, *)) {
// noop
} else {
return;
}
id engine = OCMClassMock([FlutterEngine class]);
id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]);
OCMStub([engine keyEventChannel]).andReturn(keyEventChannel);
FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine
nibName:nil
bundle:nil];
id testSet = [self fakeUiPressSetForPhase:UIPressPhaseBegan
keyCode:UIKeyboardHIDUsageKeyboardA
modifierFlags:UIKeyModifierShift
characters:@"a"
charactersIgnoringModifiers:@"A"];
// Exercise behavior under test.
[vc dispatchPresses:testSet];
// Verify behavior.
OCMVerify([keyEventChannel
sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
return [message[@"keymap"] isEqualToString:@"ios"] &&
[message[@"type"] isEqualToString:@"keydown"] &&
[message[@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]] &&
[message[@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:131072]] &&
[message[@"characters"] isEqualToString:@"a"] &&
[message[@"charactersIgnoringModifiers"] isEqualToString:@"A"];
}]]);
// Clean up mocks
[engine stopMocking];
[keyEventChannel stopMocking];
}
- (void)testValidKeyDownEvent API_AVAILABLE(ios(13.4)) {
if (@available(iOS 13.4, *)) {
// noop
} else {
return;
}
id engine = OCMClassMock([FlutterEngine class]);
id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]);
OCMStub([engine keyEventChannel]).andReturn(keyEventChannel);
FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine
nibName:nil
bundle:nil];
id testSet = [self fakeUiPressSetForPhase:UIPressPhaseEnded
keyCode:UIKeyboardHIDUsageKeyboardA
modifierFlags:UIKeyModifierShift
characters:@"a"
charactersIgnoringModifiers:@"A"];
// Exercise behavior under test.
[vc dispatchPresses:testSet];
// Verify behavior.
OCMVerify([keyEventChannel
sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
return [message[@"keymap"] isEqualToString:@"ios"] &&
[message[@"type"] isEqualToString:@"keyup"] &&
[message[@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]] &&
[message[@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:131072]] &&
[message[@"characters"] isEqualToString:@"a"] &&
[message[@"charactersIgnoringModifiers"] isEqualToString:@"A"];
}]]);
// Clean up mocks
[engine stopMocking];
[keyEventChannel stopMocking];
}
- (void)testIgnoredKeyEvents API_AVAILABLE(ios(13.4)) {
if (@available(iOS 13.4, *)) {
// noop
} else {
return;
}
id engine = OCMClassMock([FlutterEngine class]);
id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]);
OCMStub([engine keyEventChannel]).andReturn(keyEventChannel);
FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine
nibName:nil
bundle:nil];
id emptySet = [NSSet set];
id ignoredSet = [self fakeUiPressSetForPhase:UIPressPhaseStationary
keyCode:UIKeyboardHIDUsageKeyboardA
modifierFlags:UIKeyModifierShift
characters:@"a"
charactersIgnoringModifiers:@"A"];
id mockUiPress = OCMClassMock([UIPress class]);
OCMStub([mockUiPress phase]).andReturn(UIPressPhaseBegan);
id emptyKeySet = [NSSet setWithArray:@[ mockUiPress ]];
// Exercise behavior under test.
[vc dispatchPresses:emptySet];
[vc dispatchPresses:ignoredSet];
[vc dispatchPresses:emptyKeySet];
// Verify behavior.
OCMVerify(never(), [keyEventChannel sendMessage:[OCMArg any]]);
// Clean up mocks
[engine stopMocking];
[keyEventChannel stopMocking];
}
- (NSSet<UIPress*>*)fakeUiPressSetForPhase:(UIPressPhase)phase
keyCode:(UIKeyboardHIDUsage)keyCode
modifierFlags:(UIKeyModifierFlags)modifierFlags
characters:(NSString*)characters
charactersIgnoringModifiers:(NSString*)charactersIgnoringModifiers
API_AVAILABLE(ios(13.4)) {
if (@available(iOS 13.4, *)) {
// noop
} else {
return [NSSet set];
}
id mockUiPress = OCMClassMock([UIPress class]);
OCMStub([mockUiPress phase]).andReturn(phase);
id mockUiKey = OCMClassMock([UIKey class]);
OCMStub([mockUiKey keyCode]).andReturn(keyCode);
OCMStub([mockUiKey modifierFlags]).andReturn(modifierFlags);
OCMStub([mockUiKey characters]).andReturn(characters);
OCMStub([mockUiKey charactersIgnoringModifiers]).andReturn(charactersIgnoringModifiers);
OCMStub([mockUiPress key]).andReturn(mockUiKey);
return [NSSet setWithArray:@[ mockUiPress ]];
}
@end