-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Description
This occurs during tests on a Chromebook over ARC++. It's possibly the problem of ARC++ instead of the general Android embedding. (I don't have an Android device that can connect to a keyboard for now.) Update: I've tested it on an Android emulator and could not reproduce it. I think it's a ChromeOS specific problem.
Reproduction
Test code: The following widget starts with a text field and a colored box. Clicking the colored box makes it focused, and key events afterwards are printed.
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
List<KeyEvent> events = <KeyEvent>[];
bool _handleKey(KeyEvent event) {
setState(() {
if (events.length > 4)
events.removeRange(0, 1);
events.add(event);
});
return true;
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.ltr,
child: Column(
children: <Widget>[
const TextField(),
...events.expand((KeyEvent event) =>
<Widget>[
Text('${event.runtimeType}'),
Text('${event.logicalKey}'),
Text('${event.physicalKey}'),
Text('${event.character}'),
],
),
Focus(
child: Center(
child: SizedBox(
width: 100,
height: 100,
child: FocusableArea(
onKey: _handleKey,
),
),
),
),
],
)
);
}
}
class FocusableArea extends StatefulWidget {
FocusableArea({required this.onKey});
final ValueChanged<KeyEvent> onKey;
@override
State<StatefulWidget> createState() => _FocusableAreaState();
}
class _FocusableAreaState extends State<FocusableArea> {
final FocusNode node = FocusNode();
bool _isFocused = false;
KeyEventResult _handleKeyEvent(FocusNode node, KeyEvent event) {
widget.onKey(event);
return KeyEventResult.handled;
}
@override
Widget build(BuildContext context) {
return Focus(
onKeyEvent: _handleKeyEvent,
onFocusChange: (bool isFocused) {
setState(() {
_isFocused = isFocused;
});
},
focusNode: node,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
print('tapped ${DateTime.now().microsecondsSinceEpoch}');
if (_isFocused) {
node.unfocus();
} else {
node.requestFocus();
}
},
child: ColoredBox(
color: _isFocused ? Colors.blue : Colors.grey,
),
),
);
}
}Steps:
- Start this application on Android (maybe Chromebook/ARC++)
- Click the colored box, press some letter keys. Key events will correctly be shown.
- Click the text field. (You can type something, but it's not needed.)
- Click back to the colored box. Press some keys.
Expected: Key events will continue to be shown.
Actual: Letter key events are no longer shown. However, non-printable keys (such as arrow keys) still work.
flutter doctor -v:
Details
[✓] Flutter (Channel android-key-embedder, 3.1.0-0.0.pre.670, on Debian GNU/Linux rodete 5.15.15-1rodete2-amd64, locale en_US.utf8)
• Flutter version 3.1.0-0.0.pre.670 at /usr/local/google/home/tongmu/dev/flutter
• Upstream repository git@github.com:dkwingsmt/flutter.git
• Framework revision 7483ede64e (9 minutes ago), 2022-05-17 18:49:34 -0700
• Engine revision b8ca6a5e0e
• Dart version 2.18.0 (build 2.18.0-106.0.dev)
• DevTools version 2.13.1
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
• Android SDK at /usr/local/google/home/tongmu/Android/Sdk
• Platform android-31, build-tools 30.0.3
• Java binary at: /opt/android-studio-with-blaze-2020.3/jre/bin/java
• Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7249189)
• All Android licenses accepted.
[✓] Chrome - develop for the web
• Chrome at google-chrome
[✓] Linux toolchain - develop for Linux desktop
• Debian clang version 13.0.1-3+build2
• cmake version 3.23.0
• ninja version 1.8.2
• pkg-config version 0.29.2
[✓] Android Studio (version 3.5)
• Android Studio at /usr/local/google/home/tongmu/src/android-studio
• Flutter plugin version 43.0.1
• Dart plugin version 191.8593
• Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)
[✓] Android Studio (version 2020.3)
• Android Studio at /opt/android-studio-with-blaze-2020.3
• Flutter plugin version 65.2.1
• Dart plugin version 203.8452
• Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7249189)
[✓] VS Code (version 1.67.1)
• VS Code at /usr/share/code
• Flutter extension version 3.40.0
[✓] Connected device (3 available)
• Google Pixelbook (mobile) • B8E1D029412D760B8256 • android-x64 • Android 11 (API 30)
• Linux (desktop) • linux • linux-x64 • Debian GNU/Linux rodete 5.15.15-1rodete2-amd64
• Chrome (web) • chrome • web-javascript • Google Chrome 101.0.4951.64
[✓] HTTP Host Availability
• All required HTTP hosts are available
• No issues found!
Initial analysis
In step 4, FlutterView.dispatchKeyEvent are no longer called. It feels like that the input method intercepted the key events and handled them before they're dispatched to views, and that interception is not released after switching the focus.
Stack trace for key presses during step 2 (a working key event listener):
Details
I/System.out( 8606): io.flutter.embedding.android.FlutterView.dispatchKeyEvent(FlutterView.java:893) I/System.out( 8606): android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1959) I/chatty ( 8606): uid=10072(com.example.test_app) identical 1 line I/System.out( 8606): android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1959) I/System.out( 8606): com.android.internal.policy.DecorView.superDispatchKeyEvent(DecorView.java:476) I/System.out( 8606): com.android.internal.policy.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1867) I/System.out( 8606): android.app.Activity.dispatchKeyEvent(Activity.java:4095) I/System.out( 8606): com.android.internal.policy.DecorView.dispatchKeyEvent(DecorView.java:390) I/System.out( 8606): android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:5992) I/System.out( 8606): android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:5860) I/System.out( 8606): android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5355) I/System.out( 8606): android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5412) I/System.out( 8606): android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5378) I/System.out( 8606): android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:5530) I/System.out( 8606): android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5386) I/System.out( 8606): android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:5587) I/System.out( 8606): android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5359) I/System.out( 8606): android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5412) I/System.out( 8606): android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5378) I/System.out( 8606): android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5386) I/System.out( 8606): android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5359) I/System.out( 8606): android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5412) I/System.out( 8606): android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5378) I/System.out( 8606): android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:5563) I/System.out( 8606): android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:5721) I/System.out( 8606): android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:3179) I/System.out( 8606): android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:2721) I/System.out( 8606): android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:2712) I/System.out( 8606): android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished(InputMethodManager.java:3156) I/System.out( 8606): android.view.InputEventSender.dispatchInputEventFinished(InputEventSender.java:143) I/System.out( 8606): android.os.MessageQueue.nativePollOnce(Native Method) I/System.out( 8606): android.os.MessageQueue.next(MessageQueue.java:335) I/System.out( 8606): android.os.Looper.loop(Looper.java:183) I/System.out( 8606): android.app.ActivityThread.main(ActivityThread.java:7737) I/System.out( 8606): java.lang.reflect.Method.invoke(Native Method) I/System.out( 8606): com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) I/System.out( 8606): com.android.internal.os.ZygoteInit.main(ZygoteInit.java:954)
Stack trace for key pressed during step 3 (a text field):
Details
I/System.out( 8606): io.flutter.plugin.editing.TextInputPlugin.didChangeEditingState(TextInputPlugin.java:543) I/System.out( 8606): io.flutter.plugin.editing.ListenableEditingState.notifyListener(ListenableEditingState.java:249) I/System.out( 8606): io.flutter.plugin.editing.ListenableEditingState.notifyListenersIfNeeded(ListenableEditingState.java:257) I/System.out( 8606): io.flutter.plugin.editing.ListenableEditingState.endBatchEdit(ListenableEditingState.java:129) I/System.out( 8606): io.flutter.plugin.editing.InputConnectionAdaptor.endBatchEdit(InputConnectionAdaptor.java:148) I/System.out( 8606): android.view.inputmethod.BaseInputConnection.replaceText(BaseInputConnection.java:869) I/System.out( 8606): android.view.inputmethod.BaseInputConnection.commitText(BaseInputConnection.java:197) I/System.out( 8606): io.flutter.plugin.editing.InputConnectionAdaptor.commitText(InputConnectionAdaptor.java:154) I/System.out( 8606): com.android.internal.view.IInputConnectionWrapper.executeMessage(IInputConnectionWrapper.java:345) I/System.out( 8606): com.android.internal.view.IInputConnectionWrapper$MyHandler.handleMessage(IInputConnectionWrapper.java:93) I/System.out( 8606): android.os.Handler.dispatchMessage(Handler.java:106) I/System.out( 8606): android.os.Looper.loop(Looper.java:223) I/System.out( 8606): android.app.ActivityThread.main(ActivityThread.java:7737) I/System.out( 8606): java.lang.reflect.Method.invoke(Native Method) I/System.out( 8606): com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) I/System.out( 8606): com.android.internal.os.ZygoteInit.main(ZygoteInit.java:954)
Android's InputEventSender receives a Finished with a event result handled. It seems to me that in order for the key event to be correct dispatched to View, handled must be false, and I guess it's not the case after step 3.