Skip to content

[Android] After focusing on a text field, no printable key events can be received ever #104031

@dkwingsmt

Description

@dkwingsmt

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:

  1. Start this application on Android (maybe Chromebook/ARC++)
  2. Click the colored box, press some letter keys. Key events will correctly be shown.
  3. Click the text field. (You can type something, but it's not needed.)
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: desktopRunning on desktopa: text inputEntering text in a text field or keyboard related problemsplatform-androidAndroid applications specificallyteam-androidOwned by Android platform teamtriaged-androidTriaged by Android platform team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions