Skip to content

Fixes FocusHighlightMode on Android when typing in software keyboard#180753

Merged
auto-submit[bot] merged 5 commits intoflutter:masterfrom
romaingyh:fix/android-ime-highlight-mode
Mar 4, 2026
Merged

Fixes FocusHighlightMode on Android when typing in software keyboard#180753
auto-submit[bot] merged 5 commits intoflutter:masterfrom
romaingyh:fix/android-ime-highlight-mode

Conversation

@romaingyh
Copy link
Contributor

When typing in a textfield on android mobile, the backspace key produces a KeyEvent message that switches the FocusHighlightMode from touch to traditional.

This PR ignores key messages when raw data include the FLAG_SOFT_KEYBOARD or when the device id is VIRTUAL_KEYBOARD

Fixes #180746

I know it uses deprecated APIs RawKeyEvent and RawKeyEventData but I couldn't find any other solutions and the initial system is also based on deprecated KeyMessage.

Side note:
I think the var _lastInteractionRequiresTraditionalHighlights name is a bit misleading. When it's true the mode is switched to touch and when it's false to traditional. Shouldn't it be the opposite?

Pre-launch Checklist

@github-actions github-actions bot added framework flutter/packages/flutter repository. See also f: labels. f: focus Focus traversal, gaining or losing focus labels Jan 9, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses an issue on Android where typing on the software keyboard incorrectly changes the FocusHighlightMode. The fix introduces a check to identify key events from the software keyboard using Android-specific flags and device IDs, preventing these events from altering the highlight mode. A corresponding test case has been added to ensure the fix works as expected.


// ignore: use_if_null_to_convert_nulls_to_bools
if (_lastInteractionRequiresTraditionalHighlights != false) {
if (!isFromVirtualKeyboard && _lastInteractionRequiresTraditionalHighlights != false) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve readability and adhere to the Effective Dart style guide, you can use the null-coalescing operator (??) to handle the nullable boolean. This also allows for the removal of the use_if_null_to_convert_nulls_to_bools lint ignore.

    if (!isFromVirtualKeyboard && (_lastInteractionRequiresTraditionalHighlights ?? true)) {
References
  1. The Effective Dart: Style guide recommends using the ?? operator to convert null to a boolean value, instead of comparing with != false. (link)

@loic-sharma loic-sharma added the a: text input Entering text in a text field or keyboard related problems label Jan 22, 2026
@loic-sharma loic-sharma self-requested a review January 22, 2026 19:29
@loic-sharma
Copy link
Member

I think the var _lastInteractionRequiresTraditionalHighlights name is a bit misleading. When it's true the mode is switched to touch and when it's false to traditional. Shouldn't it be the opposite?

Yes it looks like you're correct. I suspect we intended to name that _lastInteractionRequiresTouchHighlights instead. I'll check with @goderbauer and loop back to you!

@loic-sharma
Copy link
Member

loic-sharma commented Jan 23, 2026

I'm ramping up on our software vs physical keyboard APIs. Gemini says:

Virtual Keys vs. Physical Keys
A common point of confusion is why RawKeyboardListener or KeyboardListener often fails to detect virtual keyboard presses.

  • Physical Keyboards: Send RawKeyEvent or KeyEvent via the HardwareKeyboard system. These include scan codes and virtual key codes.
  • Virtual Keyboards: Generally do not trigger KeyEvents. Most IMEs (Input Method Editors) do not generate key events for character keys to avoid interfering with predictive text logic.
  • The Exception: The "Backspace" and "Enter" keys on a soft keyboard sometimes trigger a hardware-like event, but this behavior is inconsistent across different Android manufacturers.

My current understanding

The goal of _HighlightModeManager.handleKeyMessage is to update _HighlightModeManager.highlightMode to FocusHighlightMode.traditional when the user presses a physical key.

Most virtual key presses do not send KeyMessages, thus they don't invoke _HighlightModeManager.handleKeyMessage.

Currently, _HighlightModeManager.handleKeyMessage assumes all KeyMessages are from physical keyboards. However, this is incorrect: software keyboards do sometimes send KeyMessages for the "Backspace" and "Enter" keys.

We need to update _HighlightModeManager.handleKeyMessage to ignore KeyMessages from virtual keyboards. Currently, the PR determines if a KeyMessage is from a virtual keyboard by inspecting the deprecated RawKeyEvent.data.

This approach seems reasonable. However, ideally we'd avoid using deprecated APIs to detect virtual key presses. I'll check with my co-workers to see if they have any ideas there.

@goderbauer
Copy link
Member

Yes it looks like you're correct. I suspect we intended to name that _lastInteractionRequiresTouchHighlights instead. I'll check with @goderbauer and loop back to you!

Yeah, looks like I messed up the last-minute rename when I landed #162417. I think this should be named _lastInteractionRequiresTouchHighlights instead. Sorry for the confusion!

Copy link
Member

@loic-sharma loic-sharma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me, but I left some minor comments to leave ourselves breadcrumbs to make it easier for us to migrate this logic to HardwareKeyboard in the future.

@loic-sharma
Copy link
Member

loic-sharma commented Jan 29, 2026

FYI it looks like you have some test failures too: https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8693133127529602865/+/u/run_test.dart_for_web_canvaskit_tests_shard_and_subshard_4/stdout

12:30 +1393 ~127: test/widgets/focus_manager_test.dart: FocusScopeNode Soft keyboard key events do not change focus highlight mode on Android. (variant: TargetPlatform.android)
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure was thrown running a test:
Expected: FocusHighlightMode:<FocusHighlightMode.touch>
  Actual: FocusHighlightMode:<FocusHighlightMode.traditional>

When the exception was thrown, this was the stack:
../dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 274:3       throw_
../packages/matcher/src/expect/prints_matcher.dart.js 605:22                         fail
../packages/matcher/src/expect/prints_matcher.dart.js 601:12                         _expect
../packages/matcher/src/expect/prints_matcher.dart.js 534:12                         expect$
../packages/flutter_test/src/test_text_input_key_handler.dart.js 14491:12            expect$
focus_manager_test.dart.js 3269:31                                                   <fn>
../dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 623:19               <fn>
../dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 648:23               <fn>
../packages/stack_trace/src/stack_zone_specification.dart.js 245:113                 <fn>
../packages/stack_trace/src/stack_zone_specification.dart.js 292:16                  [_run]
../packages/stack_trace/src/stack_zone_specification.dart.js 245:95                  <fn>
../dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 594:19               <fn>
../dart-sdk/lib/async/zone_root.dart 48:47                                           _rootRunUnary
../dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 118:77  tear
../dart-sdk/lib/async/zone.dart 733:19                                               runUnary
../dart-sdk/lib/async/future_impl.dart 222:18                                        handleValue
../dart-sdk/lib/async/future_impl.dart 948:44                                        handleValueCallback
../dart-sdk/lib/async/future_impl.dart 977:13                                        _propagateToListeners
../dart-sdk/lib/async/future_impl.dart 720:5                                         [_completeWithValue]
../dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 504:7                complete
../dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 571:12               _asyncReturn
../packages/flutter/src/services/text_editing_delta.dart.js 7172:32                  <fn>
../dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 623:19               <fn>
../dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 648:23               <fn>
../packages/stack_trace/src/stack_zone_specification.dart.js 245:113                 <fn>
../packages/stack_trace/src/stack_zone_specification.dart.js 292:16                  [_run]
../packages/stack_trace/src/stack_zone_specification.dart.js 245:95                  <fn>
../dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 594:19               <fn>
../dart-sdk/lib/async/zone_root.dart 48:47                                           _rootRunUnary
../dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 118:77  tear
../dart-sdk/lib/async/zone.dart 733:19                                               runUnary
../dart-sdk/lib/async/future_impl.dart 222:18                                        handleValue
../dart-sdk/lib/async/future_impl.dart 948:44                                        handleValueCallback
../dart-sdk/lib/async/future_impl.dart 977:13                                        _propagateToListeners
../dart-sdk/lib/async/future_impl.dart 720:5                                         [_completeWithValue]
../dart-sdk/lib/async/future_impl.dart 804:7                                         <fn>
../dart-sdk/lib/async/zone_root.dart 35:13                                           _rootRun
../dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 118:77  tear
../dart-sdk/lib/async/zone.dart 726:19                                               run
../dart-sdk/lib/async/zone.dart 625:7                                                runGuarded
../packages/fake_async/fake_async.dart.js 162:18                                     <fn>
../packages/fake_async/fake_async.dart.js 167:40                                     flushMicrotasks
../packages/flutter_test/src/test_text_input_key_handler.dart.js 17010:27            <fn>
../dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 623:19               <fn>
../dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 648:23               <fn>
../packages/stack_trace/src/stack_zone_specification.dart.js 245:113                 <fn>
../packages/stack_trace/src/stack_zone_specification.dart.js 292:16                  [_run]
../packages/stack_trace/src/stack_zone_specification.dart.js 245:95                  <fn>
../dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 542:3                _asyncStartSync
../packages/flutter_test/src/test_text_input_key_handler.dart.js 17037:22            <fn>
../dart-sdk/lib/async/future.dart 287:40                                             <fn>
../packages/stack_trace/src/stack_zone_specification.dart.js 292:16                  [_run]
../packages/stack_trace/src/stack_zone_specification.dart.js 235:71                  <fn>
../dart-sdk/lib/async/zone_root.dart 27:47                                           _rootRun
../dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 118:77  tear
../dart-sdk/lib/async/zone.dart 726:19                                               run
../dart-sdk/lib/async/zone.dart 625:7                                                runGuarded
../dart-sdk/lib/async/zone.dart 666:23                                               <fn>
../packages/stack_trace/src/stack_zone_specification.dart.js 292:16                  [_run]
../packages/stack_trace/src/stack_zone_specification.dart.js 235:71                  <fn>
../dart-sdk/lib/async/zone_root.dart 35:13                                           _rootRun
../dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 118:77  tear
../dart-sdk/lib/async/zone.dart 726:19                                               run
../dart-sdk/lib/async/zone.dart 625:7                                                runGuarded
../dart-sdk/lib/async/zone.dart 666:23                                               <fn>
../dart-sdk/lib/async/schedule_microtask.dart 40:34                                  _microtaskLoop
../dart-sdk/lib/async/schedule_microtask.dart 49:5                                   _startMicrotaskLoop
../dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 118:77  tear
../dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 188:69               <fn>

The test description was:
  Soft keyboard key events do not change focus highlight mode on Android. (variant:
  TargetPlatform.android)
════════════════════════════════════════════════════════════════════════════════════════════════════
12:30 +1393 ~127 -1: test/widgets/focus_manager_test.dart: FocusScopeNode Soft keyboard key events do not change focus highlight mode on Android. (variant: TargetPlatform.android) [E]
  Test failed. See exception logs above.
  The test description was: Soft keyboard key events do not change focus highlight mode on Android. (variant: TargetPlatform.android)

Copy link
Contributor

@Renzo-Olivares Renzo-Olivares left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with some small nits and modulo @loic-sharma comments.

@romaingyh
Copy link
Contributor Author

Hello Flutter team thanks for your review, I'll address comments and update this PR this week

Copy link
Member

@loic-sharma loic-sharma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm heading out on vacation for two weeks, so I'll approve this now even though there are still test failures.

@github-actions github-actions bot removed the a: text input Entering text in a text field or keyboard related problems label Feb 15, 2026
@justinmc
Copy link
Contributor

@romaingyh FYI there are still a few failures here.

@romaingyh
Copy link
Contributor Author

romaingyh commented Mar 2, 2026

@justinmc I think the web tests fail because my fix is based on RawKeyEventDataAndroid which is not created on web :

final RawKeyEventData data;
if (kIsWeb) {
data = dataFromWeb();
} else {
final keymap = message['keymap']! as String;
switch (keymap) {
case 'android':
data = RawKeyEventDataAndroid(
flags: message['flags'] as int? ?? 0,
codePoint: message['codePoint'] as int? ?? 0,
keyCode: message['keyCode'] as int? ?? 0,
plainCodePoint: message['plainCodePoint'] as int? ?? 0,
scanCode: message['scanCode'] as int? ?? 0,
metaState: message['metaState'] as int? ?? 0,
eventSource: message['source'] as int? ?? 0,
vendorId: message['vendorId'] as int? ?? 0,
productId: message['productId'] as int? ?? 0,
deviceId: message['deviceId'] as int? ?? 0,
repeatCount: message['repeatCount'] as int? ?? 0,
);
if (message.containsKey('character')) {
character = message['character'] as String?;
}

As the fix needs RawKeyEventDataAndroid.flags and RawKeyEventDataAndroid.deviceId, would it be acceptable to skip this test if kIsWeb ?

@loic-sharma
Copy link
Member

As the fix needs RawKeyEventDataAndroid.flags and RawKeyEventDataAndroid.deviceId, would it be acceptable to skip this test if kIsWeb ?

@romaingyh Yup that's reasonable. Please make sure to add a comment to explain why we skip the test on web though, something like:

skip: kIsWeb, // [intended] web is incompatible with Android key events.

@loic-sharma loic-sharma added the autosubmit Merge PR when tree becomes green via auto submit App label Mar 3, 2026
@loic-sharma
Copy link
Member

Thanks @romaingyh for the wonderful contribution! 🎉

@auto-submit auto-submit bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Mar 3, 2026
@auto-submit
Copy link
Contributor

auto-submit bot commented Mar 3, 2026

autosubmit label was removed for flutter/flutter/180753, because The base commit of the PR is older than 7 days and can not be merged. Please merge the latest changes from the main into this branch and resubmit the PR.

@loic-sharma loic-sharma added the autosubmit Merge PR when tree becomes green via auto submit App label Mar 4, 2026
@auto-submit auto-submit bot added this pull request to the merge queue Mar 4, 2026
Merged via the queue into flutter:master with commit 7aa0311 Mar 4, 2026
73 of 74 checks passed
@flutter-dashboard flutter-dashboard bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Mar 4, 2026
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 5, 2026
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 5, 2026
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 5, 2026
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 5, 2026
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 6, 2026
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 6, 2026
auto-submit bot pushed a commit to flutter/packages that referenced this pull request Mar 6, 2026
Roll Flutter from d3dd7744e81f to d18214307703 (33 revisions)

flutter/flutter@d3dd774...d182143

2026-03-06 [email protected] Roll Packages from 8d5c5cd to fe3de64 (2 revisions) (flutter/flutter#183308)
2026-03-06 [email protected] Roll Dart SDK from 1b51451cdb99 to 7c7c1e3d024d (2 revisions) (flutter/flutter#183294)
2026-03-06 [email protected] Roll Dart SDK from 9ac06cdd1801 to 1b51451cdb99 (9 revisions) (flutter/flutter#183289)
2026-03-06 [email protected] Add GitHub workflows to assist with release tasks (flutter/flutter#181978)
2026-03-06 [email protected] [Impeller] Fix new convex path shadow generation in perspective (flutter/flutter#183187)
2026-03-06 [email protected] Roll pub packages (flutter/flutter#183178)
2026-03-05 [email protected] fix: use double quotes in settings.gradle.kts template (flutter/flutter#183081)
2026-03-05 [email protected] Add fallbackColor for PredictiveBackPageTransitionBuilder and PredictiveBackFullscreenPageTransitionBuilder (flutter/flutter#182690)
2026-03-05 [email protected] Simplify TesterContextGLES (multithreading logic not needed), and enable some tests that now pass (flutter/flutter#183250)
2026-03-05 [email protected] Roll Skia from a94df1cdabb0 to a69ef43650ee (14 revisions) (flutter/flutter#183280)
2026-03-05 [email protected] Windowing implementation of `showDialog` that uses a native desktop window to display the content  (flutter/flutter#181861)
2026-03-05 [email protected] Build CocoaPod plugin frameworks for Add to App FlutterPluginRegistrant (flutter/flutter#183239)
2026-03-05 [email protected] Extend the Linux web_skwasm_tests_1 timeout to 45 minutes (flutter/flutter#183247)
2026-03-05 [email protected] Update Dart to 3.12 beta 2 (flutter/flutter#183251)
2026-03-05 [email protected] Replace the rest of the references to `flutter/engine` with `flutter/flutter` (flutter/flutter#182938)
2026-03-05 [email protected] chore: convert android_verified_input to pub-workspace (flutter/flutter#183175)
2026-03-05 [email protected] Add await to flutter_test callsites (flutter/flutter#182983)
2026-03-05 [email protected] [iOS] Skip gesture recognizer reset workaround on iOS 26+  (flutter/flutter#183186)
2026-03-05 [email protected] Add warning for plugins not migrated to UIScene (flutter/flutter#182826)
2026-03-05 [email protected] Roll Fuchsia Linux SDK from JJw5EJ87vLGqFVl4h... to 8ay15_eQOEgPHCypm... (flutter/flutter#183255)
2026-03-05 [email protected] Roll Skia from ada0b7628c79 to a94df1cdabb0 (2 revisions) (flutter/flutter#183249)
2026-03-05 [email protected] Roll Packages from 82baf93 to 8d5c5cd (2 revisions) (flutter/flutter#183269)
2026-03-05 [email protected] Add `UnlabaledLeafNodeEvaluation` (flutter/flutter#182872)
2026-03-04 [email protected] Re-specify the ndk version in various test apps, to prevent ndk download (flutter/flutter#183134)
2026-03-04 [email protected] Eliminate rebuilds for Scaffold FAB animation (flutter/flutter#182331)
2026-03-04 [email protected] Add Michal Kucharski to AUTHORS (flutter/flutter#182366)
2026-03-04 [email protected] Merge changelog from 3.41.4 stable. (flutter/flutter#183243)
2026-03-04 [email protected] Allow stylus support on windows (flutter/flutter#165323)
2026-03-04 [email protected] Fix docs on SingletonFlutterWindow.supportsShowingSystemContextMenu (flutter/flutter#183142)
2026-03-04 [email protected] Roll Packages from 9083bc9 to 82baf93 (5 revisions) (flutter/flutter#183240)
2026-03-04 [email protected] Fixes FocusHighlightMode on Android when typing in software keyboard (flutter/flutter#180753)
2026-03-04 [email protected] Make compileShader() retry without sksl if it fails with sksl. (flutter/flutter#183146)
2026-03-04 [email protected] [web] Use pointer-events: auto for non-interactive leaf semantics nodes (flutter/flutter#183077)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
...
xxxOVALxxx pushed a commit to xxxOVALxxx/flutter that referenced this pull request Mar 10, 2026
…lutter#180753)

When typing in a textfield on android mobile, the backspace key produces
a KeyEvent message that switches the FocusHighlightMode from touch to
traditional.

This PR ignores key messages when raw data include the
FLAG_SOFT_KEYBOARD or when the device id is VIRTUAL_KEYBOARD

Fixes flutter#180746 

I know it uses deprecated APIs `RawKeyEvent` and `RawKeyEventData` but I
couldn't find any other solutions and the initial system is also based
on deprecated `KeyMessage`.

Side note: 
I think the var `_lastInteractionRequiresTraditionalHighlights` name is
a bit misleading. When it's true the mode is switched to touch and when
it's false to traditional. Shouldn't it be the opposite?
 
## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Co-authored-by: Justin McCandless <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

f: focus Focus traversal, gaining or losing focus framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Android] Software keyboard backspace makes focus highlightMode invalid

5 participants