From f5d9376d2c78457c6b3ff6d66c169f3cf3f0310c Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 3 Jun 2021 14:20:15 +0200 Subject: [PATCH 1/7] Android: add support for TextInputType.none TextInputType.none makes it possible to disable the virtual keyboard for certain TextFields, and lays the foundations for custom in-app virtual keyboards (flutter/flutter#76072). Ref: flutter/flutter#83567 --- .../engine/systemchannels/TextInputChannel.java | 3 ++- .../io/flutter/plugin/editing/TextInputPlugin.java | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java index 7715f3a0c0cc2..4c098715d6e4d 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java @@ -628,7 +628,8 @@ public enum TextInputType { MULTILINE("TextInputType.multiline"), EMAIL_ADDRESS("TextInputType.emailAddress"), URL("TextInputType.url"), - VISIBLE_PASSWORD("TextInputType.visiblePassword"); + VISIBLE_PASSWORD("TextInputType.visiblePassword"), + NONE("TextInputType.none"); static TextInputType fromValue(@NonNull String encodedName) throws NoSuchFieldException { for (TextInputType textInputType : TextInputType.values()) { diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index b59f8e325693d..456a9ebf64ab1 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -249,6 +249,8 @@ private static int inputTypeFromTextInputType( return textType; } else if (type.type == TextInputChannel.TextInputType.PHONE) { return InputType.TYPE_CLASS_PHONE; + } else if (type.type == TextInputChannel.TextInputType.NONE) { + return InputType.TYPE_NULL; } int textType = InputType.TYPE_CLASS_TEXT; @@ -363,8 +365,12 @@ public void sendTextInputAppPrivateCommand(String action, Bundle data) { } private void showTextInput(View view) { - view.requestFocus(); - mImm.showSoftInput(view, 0); + if (configuration.inputType.type != TextInputChannel.TextInputType.NONE) { + view.requestFocus(); + mImm.showSoftInput(view, 0); + } else { + hideTextInput(view); + } } private void hideTextInput(View view) { From bf325baed0475365935bd6f18daf154a5422c623 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 16 Jun 2021 09:17:01 +0200 Subject: [PATCH 2/7] No input connection for TextInputType.none --- .../android/io/flutter/plugin/editing/TextInputPlugin.java | 6 +++++- 1 file changed, 5 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 456a9ebf64ab1..6fb1130926f7d 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -388,7 +388,11 @@ private void hideTextInput(View view) { void setTextInputClient(int client, TextInputChannel.Configuration configuration) { // Call notifyViewExited on the previous field. notifyViewExited(); - inputTarget = new InputTarget(InputTarget.Type.FRAMEWORK_CLIENT, client); + if (configuration.inputType.type != TextInputChannel.TextInputType.NONE) { + inputTarget = new InputTarget(InputTarget.Type.FRAMEWORK_CLIENT, client); + } else { + inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, client); + } if (mEditable != null) { mEditable.removeEditingStateListener(this); From 876274d25b9dc7250c30cb9e6baf3f2c1c94d6df Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 16 Jun 2021 17:43:39 +0200 Subject: [PATCH 3/7] Fix TextInputPluginTest --- .../io/flutter/plugin/editing/TextInputPlugin.java | 11 +++++++++-- 1 file changed, 9 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 6fb1130926f7d..2530b519ffd4e 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -364,8 +364,15 @@ public void sendTextInputAppPrivateCommand(String action, Bundle data) { mImm.sendAppPrivateCommand(mView, action, data); } + private boolean canShowTextInput() { + if (configuration == null || configuration.inputType == null) { + return true; + } + return configuration.inputType.type != TextInputChannel.TextInputType.NONE; + } + private void showTextInput(View view) { - if (configuration.inputType.type != TextInputChannel.TextInputType.NONE) { + if (canShowTextInput()) { view.requestFocus(); mImm.showSoftInput(view, 0); } else { @@ -388,7 +395,7 @@ private void hideTextInput(View view) { void setTextInputClient(int client, TextInputChannel.Configuration configuration) { // Call notifyViewExited on the previous field. notifyViewExited(); - if (configuration.inputType.type != TextInputChannel.TextInputType.NONE) { + if (canShowTextInput()) { inputTarget = new InputTarget(InputTarget.Type.FRAMEWORK_CLIENT, client); } else { inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, client); From 7220e97a3914f792e7099a584d3032800b900a55 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Tue, 22 Jun 2021 23:56:09 +0200 Subject: [PATCH 4/7] Fix setTextInputClient() & add test --- .../plugin/editing/TextInputPlugin.java | 2 +- .../plugin/editing/TextInputPluginTest.java | 28 +++++++++++++++++++ 2 files changed, 29 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 2530b519ffd4e..2d20d712833f7 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -395,6 +395,7 @@ private void hideTextInput(View view) { 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 { @@ -407,7 +408,6 @@ void setTextInputClient(int client, TextInputChannel.Configuration configuration mEditable = new ListenableEditingState( configuration.autofill != null ? configuration.autofill.editState : null, mView); - this.configuration = configuration; updateAutofillConfigurationIfNeeded(configuration); // setTextInputClient will be followed by a call to setTextInputEditingState. 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 7e8436418ae96..f9c96a6863921 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java @@ -596,6 +596,34 @@ public void inputConnection_finishComposingTextUpdatesIMM() throws JSONException assertEquals(0, testImm.getLastCursorAnchorInfo().getComposingText().length()); } + @Test + public void inputConnection_textInputTypeNone() { + FlutterJNI mockFlutterJni = mock(FlutterJNI.class); + View testView = new View(RuntimeEnvironment.application); + 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, + TextInputChannel.TextCapitalization.NONE, + new TextInputChannel.InputType(TextInputChannel.TextInputType.NONE, false, false), + 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)); + + InputConnection connection = textInputPlugin.createInputConnection(testView, new EditorInfo()); + assertEquals(connection, null); + } + // -------- Start: Autofill Tests ------- @Test public void autofill_onProvideVirtualViewStructure() { From 9afec702aef9cc1c5dab327ff28a583bf3af7572 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 23 Jun 2021 00:31:02 +0200 Subject: [PATCH 5/7] Cleanup inputConnection_textInputTypeNone() --- .../test/io/flutter/plugin/editing/TextInputPluginTest.java | 6 +----- 1 file changed, 1 insertion(+), 5 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 f9c96a6863921..d1cb8a40357a8 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java @@ -598,9 +598,8 @@ public void inputConnection_finishComposingTextUpdatesIMM() throws JSONException @Test public void inputConnection_textInputTypeNone() { - FlutterJNI mockFlutterJni = mock(FlutterJNI.class); View testView = new View(RuntimeEnvironment.application); - DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJni, mock(AssetManager.class))); + DartExecutor dartExecutor = mock(DartExecutor.class); TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); TextInputPlugin textInputPlugin = new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class)); @@ -616,9 +615,6 @@ public void inputConnection_textInputTypeNone() { 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)); InputConnection connection = textInputPlugin.createInputConnection(testView, new EditorInfo()); assertEquals(connection, null); From 5df062aea9057ecae06f596bbb76b0997c592e2d Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Wed, 23 Jun 2021 01:23:03 +0200 Subject: [PATCH 6/7] Test that TextInputType.NONE is actually honored --- .../plugin/editing/TextInputPlugin.java | 3 ++- .../plugin/editing/TextInputPluginTest.java | 27 +++++++++++++++++++ 2 files changed, 29 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 2d20d712833f7..c786c1c4a271f 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -371,7 +371,8 @@ private boolean canShowTextInput() { return configuration.inputType.type != TextInputChannel.TextInputType.NONE; } - private void showTextInput(View view) { + @VisibleForTesting + void showTextInput(View view) { if (canShowTextInput()) { view.requestFocus(); mImm.showSoftInput(view, 0); 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 d1cb8a40357a8..23964b509fba3 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java @@ -620,6 +620,33 @@ public void inputConnection_textInputTypeNone() { assertEquals(connection, null); } + @Test + public void showTextInput_textInputTypeNone() { + TestImm testImm = + Shadow.extract( + RuntimeEnvironment.application.getSystemService(Context.INPUT_METHOD_SERVICE)); + View testView = new View(RuntimeEnvironment.application); + DartExecutor dartExecutor = mock(DartExecutor.class); + TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); + TextInputPlugin textInputPlugin = + new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class)); + textInputPlugin.setTextInputClient( + 0, + new TextInputChannel.Configuration( + false, + false, + true, + TextInputChannel.TextCapitalization.NONE, + new TextInputChannel.InputType(TextInputChannel.TextInputType.NONE, false, false), + null, + null, + null, + null)); + + textInputPlugin.showTextInput(testView); + assertEquals(testImm.isSoftInputVisible(), false); + } + // -------- Start: Autofill Tests ------- @Test public void autofill_onProvideVirtualViewStructure() { From 0dbfa4ea3debc9be8337ac30bdba7a6d331a1349 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 24 Jun 2021 22:59:02 +0200 Subject: [PATCH 7/7] Adapt to API change --- .../test/io/flutter/plugin/editing/TextInputPluginTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 9eea98b5ca9f0..acf1d4fe7c790 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java @@ -624,7 +624,9 @@ public void inputConnection_textInputTypeNone() { null, null)); - InputConnection connection = textInputPlugin.createInputConnection(testView, new EditorInfo()); + InputConnection connection = + textInputPlugin.createInputConnection( + testView, mock(KeyboardManager.class), new EditorInfo()); assertEquals(connection, null); }