From ca0ef9173f77468ec097f2b9f92c3d43503bb394 Mon Sep 17 00:00:00 2001 From: Bart Cone Date: Tue, 23 Jan 2024 15:57:59 -0500 Subject: [PATCH 1/5] [Android] Fix TextInputType.none for devices with physical keyboard --- .../flutter/plugin/editing/TextInputPlugin.java | 15 ++------------- .../plugin/editing/TextInputPluginTest.java | 3 ++- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index b4ce4726e8c51..c016b8dac2be5 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -387,16 +387,9 @@ public void sendTextInputAppPrivateCommand(@NonNull String action, @NonNull Bund 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.inputType.type != TextInputChannel.TextInputType.NONE) { view.requestFocus(); mImm.showSoftInput(view, 0); } else { @@ -420,11 +413,7 @@ void setTextInputClient(int client, TextInputChannel.Configuration configuration // 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/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java index 13f5506838b02..3b603267d0d40 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java +++ b/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; @@ -1310,7 +1311,7 @@ public void inputConnection_textInputTypeNone() { InputConnection connection = textInputPlugin.createInputConnection( testView, mock(KeyboardManager.class), new EditorInfo()); - assertEquals(connection, null); + assertNotNull(connection); } @Test From 64c275ff6e43c177be2321f889f5da4e9113498a Mon Sep 17 00:00:00 2001 From: Bart Cone Date: Fri, 26 Jan 2024 17:34:59 -0500 Subject: [PATCH 2/5] Added additional test case for key events and input type none --- .../plugin/editing/TextInputPlugin.java | 3 +- .../plugin/editing/TextInputPluginTest.java | 58 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index c016b8dac2be5..e07b01fe8285e 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -389,7 +389,8 @@ public void sendTextInputAppPrivateCommand(@NonNull String action, @NonNull Bund @VisibleForTesting void showTextInput(View view) { - if (configuration.inputType.type != TextInputChannel.TextInputType.NONE) { + if (configuration == null || configuration.inputType == null + || configuration.inputType.type != TextInputChannel.TextInputType.NONE) { view.requestFocus(); mImm.showSoftInput(view, 0); } else { diff --git a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java index 3b603267d0d40..485a6ecc8ea1e 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java @@ -1233,6 +1233,64 @@ public void inputConnection_createsActionFromEnter() throws JSONException { new String[] {"0", "TextInputAction.done"}); } + @SuppressWarnings("deprecation") + // DartExecutor.send is deprecated. + @Test + public void inputConnection_respondsToKeyEvents_textInputTypeNone() throws JSONException { + TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE)); + FlutterJNI mockFlutterJni = mock(FlutterJNI.class); + View testView = new View(ctx); + DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJni, mock(AssetManager.class))); + TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); + TextInputPlugin textInputPlugin = + new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class)); + textInputPlugin.setTextInputClient( + 0, + new TextInputChannel.Configuration( + false, + false, + true, + true, + false, + TextInputChannel.TextCapitalization.NONE, + new TextInputChannel.InputType(TextInputChannel.TextInputType.NONE, false, false), + null, + null, + null, + null, + null)); + // There's a pending restart since we initialized the text input client. Flush that now. + textInputPlugin.setTextInputEditingState( + testView, new TextInputChannel.TextEditState("", 0, 0, -1, -1)); + + ArgumentCaptor channelCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class); + verify(dartExecutor, times(1)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull()); + assertEquals("flutter/textinput", channelCaptor.getValue()); + verifyMethodCall(bufferCaptor.getValue(), "TextInputClient.requestExistingInputState", null); + InputConnectionAdaptor connection = + (InputConnectionAdaptor) + textInputPlugin.createInputConnection( + testView, mock(KeyboardManager.class), new EditorInfo()); + + connection.handleKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER)); + verify(dartExecutor, times(2)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull()); + assertEquals("flutter/textinput", channelCaptor.getValue()); + verifyMethodCall( + bufferCaptor.getValue(), + "TextInputClient.performAction", + new String[] {"0", "TextInputAction.done"}); + connection.handleKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER)); + + connection.handleKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_NUMPAD_ENTER)); + verify(dartExecutor, times(3)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull()); + assertEquals("flutter/textinput", channelCaptor.getValue()); + verifyMethodCall( + bufferCaptor.getValue(), + "TextInputClient.performAction", + new String[] {"0", "TextInputAction.done"}); + } + @SuppressWarnings("deprecation") // InputMethodSubtype @Test public void inputConnection_finishComposingTextUpdatesIMM() throws JSONException { From cfd1d3e7f20bcb671f36c07ede257645e3d80061 Mon Sep 17 00:00:00 2001 From: Bart Cone Date: Fri, 26 Jan 2024 21:57:23 -0500 Subject: [PATCH 3/5] Addressed formatting issues --- .../android/io/flutter/plugin/editing/TextInputPlugin.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index e07b01fe8285e..43ca8479cb06f 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -389,8 +389,9 @@ public void sendTextInputAppPrivateCommand(@NonNull String action, @NonNull Bund @VisibleForTesting void showTextInput(View view) { - if (configuration == null || configuration.inputType == null - || configuration.inputType.type != TextInputChannel.TextInputType.NONE) { + if (configuration == null + || configuration.inputType == null + || configuration.inputType.type != TextInputChannel.TextInputType.NONE) { view.requestFocus(); mImm.showSoftInput(view, 0); } else { From a8781696b8c3d68ea48a33f7cf7a9944fb822cf2 Mon Sep 17 00:00:00 2001 From: Bart Cone Date: Wed, 31 Jan 2024 11:30:35 -0500 Subject: [PATCH 4/5] Refactored test cases --- .../plugin/editing/TextInputPluginTest.java | 66 +++---------------- 1 file changed, 9 insertions(+), 57 deletions(-) diff --git a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java index 6eae5b3f06c11..697aea6ddccbd 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java @@ -1177,8 +1177,8 @@ public void destroy_clearTextInputMethodHandler() { @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); @@ -1195,7 +1195,7 @@ public void inputConnection_createsActionFromEnter() throws JSONException { true, false, TextInputChannel.TextCapitalization.NONE, - new TextInputChannel.InputType(TextInputChannel.TextInputType.TEXT, false, false), + new TextInputChannel.InputType(textInputType, false, false), null, null, null, @@ -1232,63 +1232,15 @@ public void inputConnection_createsActionFromEnter() throws JSONException { "TextInputClient.performAction", new String[] {"0", "TextInputAction.done"}); } + + @Test + public void inputConnection_createsActionFromEnter() throws JSONException { + verifyInputConnection(TextInputChannel.TextInputType.TEXT); + } - @SuppressWarnings("deprecation") - // DartExecutor.send is deprecated. @Test public void inputConnection_respondsToKeyEvents_textInputTypeNone() throws JSONException { - TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE)); - FlutterJNI mockFlutterJni = mock(FlutterJNI.class); - View testView = new View(ctx); - DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJni, mock(AssetManager.class))); - TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); - TextInputPlugin textInputPlugin = - new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class)); - textInputPlugin.setTextInputClient( - 0, - new TextInputChannel.Configuration( - false, - false, - true, - true, - false, - TextInputChannel.TextCapitalization.NONE, - new TextInputChannel.InputType(TextInputChannel.TextInputType.NONE, false, false), - null, - null, - null, - null, - null)); - // There's a pending restart since we initialized the text input client. Flush that now. - textInputPlugin.setTextInputEditingState( - testView, new TextInputChannel.TextEditState("", 0, 0, -1, -1)); - - ArgumentCaptor channelCaptor = ArgumentCaptor.forClass(String.class); - ArgumentCaptor bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class); - verify(dartExecutor, times(1)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull()); - assertEquals("flutter/textinput", channelCaptor.getValue()); - verifyMethodCall(bufferCaptor.getValue(), "TextInputClient.requestExistingInputState", null); - InputConnectionAdaptor connection = - (InputConnectionAdaptor) - textInputPlugin.createInputConnection( - testView, mock(KeyboardManager.class), new EditorInfo()); - - connection.handleKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER)); - verify(dartExecutor, times(2)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull()); - assertEquals("flutter/textinput", channelCaptor.getValue()); - verifyMethodCall( - bufferCaptor.getValue(), - "TextInputClient.performAction", - new String[] {"0", "TextInputAction.done"}); - connection.handleKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER)); - - connection.handleKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_NUMPAD_ENTER)); - verify(dartExecutor, times(3)).send(channelCaptor.capture(), bufferCaptor.capture(), isNull()); - assertEquals("flutter/textinput", channelCaptor.getValue()); - verifyMethodCall( - bufferCaptor.getValue(), - "TextInputClient.performAction", - new String[] {"0", "TextInputAction.done"}); + verifyInputConnection(TextInputChannel.TextInputType.NONE); } @SuppressWarnings("deprecation") // InputMethodSubtype From 820865e72053b77247929ae321ff256304a0800b Mon Sep 17 00:00:00 2001 From: Bart Cone Date: Thu, 1 Feb 2024 16:09:12 -0500 Subject: [PATCH 5/5] Removed trailing whitespace --- .../test/io/flutter/plugin/editing/TextInputPluginTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java index 697aea6ddccbd..3bb087916080e 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java @@ -1177,7 +1177,7 @@ public void destroy_clearTextInputMethodHandler() { @SuppressWarnings("deprecation") // DartExecutor.send is deprecated. - private void verifyInputConnection(TextInputChannel.TextInputType textInputType) + private void verifyInputConnection(TextInputChannel.TextInputType textInputType) throws JSONException { TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE)); FlutterJNI mockFlutterJni = mock(FlutterJNI.class); @@ -1232,7 +1232,7 @@ private void verifyInputConnection(TextInputChannel.TextInputType textInputType) "TextInputClient.performAction", new String[] {"0", "TextInputAction.done"}); } - + @Test public void inputConnection_createsActionFromEnter() throws JSONException { verifyInputConnection(TextInputChannel.TextInputType.TEXT);