From 1b95fed6c0cb967b45f163d808f0bc3fdfbdc41c Mon Sep 17 00:00:00 2001 From: Bart Cone Date: Fri, 2 Feb 2024 02:24:39 -0500 Subject: [PATCH] [Android] Fix TextInputType.none for devices with physical keyboard (flutter/engine#49980) ## Description This PR fixes an issue where keystrokes aren't received on Android devices with physical keyboards (e.g. rugged Zebra devices) when `keyboardType` is set to `TextInputType.none` on a `TextField`. The logic in `setTextInputClient` and `canShowTextInput` created an `inputTarget` with `InputTarget.Type.NO_TARGET` which caused the [input connection to short circuit](https://github.com/flutter/engine/blob/main/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java#L296) and not be established. Bug introduction PR: https://github.com/flutter/engine/pull/26585 ## Related Issue https://github.com/flutter/flutter/issues/89983 ## Unit Test Notes - The existing `showTextInput_textInputTypeNone()` stays green after update. - `inputConnection_textInputTypeNone()` updated to `assertNotNull`. I would make this more specific, but this is my first venture into the Flutter engine and don't know enough about those connection attributes. ## Demo Video below with Zebra MC9300 device. This issue can also be reproduced in a standard android emulator. Simply add a `TextField`, configure `keyboardType` to be `TextInputType.none` and attempt to enter text after running and giving focus to textfield. Before https://github.com/flutter/engine/assets/1988098/348ca061-b8b9-4483-956e-0732c1238207 After https://github.com/flutter/engine/assets/1988098/b65c7251-59b4-4c73-9b85-7ac03f47a7e4 ## 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] and the [C++, Objective-C, Java style guides]. - [x] I listed at least one issue that this PR fixes in the description above. - [ ] I added new tests to check the change I am making or feature I am adding, or the PR is [test-exempt]. See [testing the engine] for instructions on writing and running engine tests. - [ ] I updated/added relevant documentation (doc comments with `///`). - [x] I signed the [CLA]. - [x] All existing and new tests are passing. --- .../plugin/editing/TextInputPlugin.java | 17 ++++------------- .../plugin/editing/TextInputPluginTest.java | 19 +++++++++++++++---- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 8bd35f62fd..91644f3514 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -376,16 +376,11 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch mImm.sendAppPrivateCommand(mView, action, data); } - private boolean canShowTextInput() { - if (configuration == null || configuration.inputType == null) { - return true; - } - return configuration.inputType.type != TextInputChannel.TextInputType.NONE; - } - @VisibleForTesting void showTextInput(View view) { - if (canShowTextInput()) { + if (configuration == null + || configuration.inputType == null + || configuration.inputType.type != TextInputChannel.TextInputType.NONE) { view.requestFocus(); mImm.showSoftInput(view, 0); } else { @@ -409,11 +404,7 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch // Call notifyViewExited on the previous field. notifyViewExited(); this.configuration = configuration; - if (canShowTextInput()) { - inputTarget = new InputTarget(InputTarget.Type.FRAMEWORK_CLIENT, client); - } else { - inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, client); - } + inputTarget = new InputTarget(InputTarget.Type.FRAMEWORK_CLIENT, client); mEditable.removeEditingStateListener(this); mEditable = diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java index f73a74f6c9..3bb0879160 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java @@ -1,6 +1,7 @@ package io.flutter.plugin.editing; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.AdditionalMatchers.aryEq; @@ -1176,8 +1177,8 @@ public class TextInputPluginTest { @SuppressWarnings("deprecation") // DartExecutor.send is deprecated. - @Test - public void inputConnection_createsActionFromEnter() throws JSONException { + private void verifyInputConnection(TextInputChannel.TextInputType textInputType) + throws JSONException { TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE)); FlutterJNI mockFlutterJni = mock(FlutterJNI.class); View testView = new View(ctx); @@ -1194,7 +1195,7 @@ public class TextInputPluginTest { true, false, TextInputChannel.TextCapitalization.NONE, - new TextInputChannel.InputType(TextInputChannel.TextInputType.TEXT, false, false), + new TextInputChannel.InputType(textInputType, false, false), null, null, null, @@ -1232,6 +1233,16 @@ public class TextInputPluginTest { new String[] {"0", "TextInputAction.done"}); } + @Test + public void inputConnection_createsActionFromEnter() throws JSONException { + verifyInputConnection(TextInputChannel.TextInputType.TEXT); + } + + @Test + public void inputConnection_respondsToKeyEvents_textInputTypeNone() throws JSONException { + verifyInputConnection(TextInputChannel.TextInputType.NONE); + } + @SuppressWarnings("deprecation") // InputMethodSubtype @Test public void inputConnection_finishComposingTextUpdatesIMM() throws JSONException { @@ -1310,7 +1321,7 @@ public class TextInputPluginTest { InputConnection connection = textInputPlugin.createInputConnection( testView, mock(KeyboardManager.class), new EditorInfo()); - assertEquals(connection, null); + assertNotNull(connection); } @Test