From d925ac1a532eed35376cf5114551f0e80cb119cd Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Thu, 18 Jan 2024 07:38:34 +0100 Subject: [PATCH] [Android] Reset IME state in TextInputPlugin.clearTextInputClient (flutter/engine#49829) ## Description This PR calls Android API `InputMethodManager.restartInput` to reset IMEs internal states. Otherwise some IMEs (Gboard for instance) keep reacting based on the previous input configuration until a new configuration is set. - On Android native, `restartInput` is called in several places, for instance in https://github.com/AndroidSDKSources/android-sdk-sources-for-api-level-34/blob/f2197987748faef78e869662ae1fd039daa22a63/android/widget/TextView.java#L2458. - On Compose, https://github.com/flutter/flutter/issues/70546#issuecomment-1088345561 pointed out where it is called. - On Flutter, it is called at some point but mainly when another `TextField` is focused (it is mainly called in `setTextInputEditingState`). ## Related Issue Fixes https://github.com/flutter/flutter/issues/70546. ## Tests Adds 1 test. --- .../plugin/editing/TextInputPlugin.java | 4 +++ .../plugin/editing/TextInputPluginTest.java | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+) 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 4897eaf2f1..b4ce4726e8 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 @@ -569,6 +569,10 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); unlockPlatformViewInputConnection(); lastClientRect = null; + + // Call restartInput to reset IME internal states. Otherwise some IMEs (Gboard for instance) + // keep reacting based on the previous input configuration until a new configuration is set. + mImm.restartInput(mView); } private static class InputTarget { 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 c4eebd33b9..13f5506838 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 @@ -1128,6 +1128,41 @@ public class TextInputPluginTest { assertEquals(1, testImm.getRestartCount(testView)); } + @Test + public void clearTextInputClient_alwaysRestartsImm() { + // Initialize a general TextInputPlugin. + InputMethodSubtype inputMethodSubtype = mock(InputMethodSubtype.class); + TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE)); + testImm.setCurrentInputMethodSubtype(inputMethodSubtype); + View testView = new View(ctx); + TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class)); + TextInputPlugin textInputPlugin = + new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class)); + textInputPlugin.setTextInputClient( + 0, + new TextInputChannel.Configuration( + false, + false, + true, + true, + false, + TextInputChannel.TextCapitalization.NONE, + null, + 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)); + assertEquals(1, testImm.getRestartCount(testView)); + + // A restart is always forced when calling clearTextInputClient(). + textInputPlugin.clearTextInputClient(); + assertEquals(2, testImm.getRestartCount(testView)); + } + @Test public void destroy_clearTextInputMethodHandler() { View testView = new View(ctx);