From 309546373cc724c9ba18e57e25e4db00abae2335 Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Tue, 21 Jun 2022 16:16:02 -0700 Subject: [PATCH 1/3] Clean up text input configuration in clearTextInputClient --- .../plugin/editing/TextInputPlugin.java | 24 +++++----- .../plugin/editing/TextInputPluginTest.java | 48 +++++++++++++++++++ 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 70658ee934493..1a992b4306003 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -43,7 +43,7 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch @NonNull private final TextInputChannel textInputChannel; @NonNull private InputTarget inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); @Nullable private TextInputChannel.Configuration configuration; - @Nullable private SparseArray mAutofillConfigurations; + @Nullable private SparseArray autofillConfiguration; @NonNull private ListenableEditingState mEditable; private boolean mRestartInputPending; @Nullable private InputConnection lastInputConnection; @@ -477,6 +477,7 @@ public void inspect(double x, double y) { void clearTextInputClient() { mEditable.removeEditingStateListener(this); notifyViewExited(); + configuration = null; updateAutofillConfigurationIfNeeded(null); inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); lastClientRect = null; @@ -596,7 +597,7 @@ public void didChangeEditingState( // have changed. However if the value of an unfocused EditableText is changed in the framework, // such change will not be sent to the text input plugin until the next TextInput.attach call. private boolean needsAutofill() { - return mAutofillConfigurations != null; + return autofillConfiguration != null; } private void notifyViewEntered() { @@ -641,21 +642,20 @@ private void updateAutofillConfigurationIfNeeded(TextInputChannel.Configuration if (configuration == null || configuration.autofill == null) { // Disables autofill if the configuration doesn't have an autofill field. - mAutofillConfigurations = null; + autofillConfiguration = null; return; } final TextInputChannel.Configuration[] configurations = configuration.fields; - mAutofillConfigurations = new SparseArray<>(); + autofillConfiguration = new SparseArray<>(); if (configurations == null) { - mAutofillConfigurations.put( - configuration.autofill.uniqueIdentifier.hashCode(), configuration); + autofillConfiguration.put(configuration.autofill.uniqueIdentifier.hashCode(), configuration); } else { for (TextInputChannel.Configuration config : configurations) { TextInputChannel.Configuration.Autofill autofill = config.autofill; if (autofill != null) { - mAutofillConfigurations.put(autofill.uniqueIdentifier.hashCode(), config); + autofillConfiguration.put(autofill.uniqueIdentifier.hashCode(), config); afm.notifyValueChanged( mView, autofill.uniqueIdentifier.hashCode(), @@ -672,9 +672,9 @@ public void onProvideAutofillVirtualStructure(@NonNull ViewStructure structure, final String triggerIdentifier = configuration.autofill.uniqueIdentifier; final AutofillId parentId = structure.getAutofillId(); - for (int i = 0; i < mAutofillConfigurations.size(); i++) { - final int autofillId = mAutofillConfigurations.keyAt(i); - final TextInputChannel.Configuration config = mAutofillConfigurations.valueAt(i); + for (int i = 0; i < autofillConfiguration.size(); i++) { + final int autofillId = autofillConfiguration.keyAt(i); + final TextInputChannel.Configuration config = autofillConfiguration.valueAt(i); final TextInputChannel.Configuration.Autofill autofill = config.autofill; if (autofill == null) { continue; @@ -715,7 +715,7 @@ public void autofill(@NonNull SparseArray values) { } final TextInputChannel.Configuration.Autofill currentAutofill = configuration.autofill; - if (currentAutofill == null) { + if (currentAutofill == null || autofillConfiguration == null) { return; } @@ -723,7 +723,7 @@ public void autofill(@NonNull SparseArray values) { for (int i = 0; i < values.size(); i++) { int virtualId = values.keyAt(i); - final TextInputChannel.Configuration config = mAutofillConfigurations.get(virtualId); + final TextInputChannel.Configuration config = autofillConfiguration.get(virtualId); if (config == null || config.autofill == null) { continue; } 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 8ec751164d7f9..c3d16eeadb3d2 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java @@ -12,6 +12,7 @@ import static org.mockito.Mockito.isNotNull; import static org.mockito.Mockito.isNull; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -1779,6 +1780,53 @@ public void autofill_testAutofillUpdatesTheFramework() { assertEquals(editState.text, "unfocused field"); } + @Test + public void autofill_doesNotCrashAfterClearClientCall() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return; + } + FlutterView testView = new FlutterView(ctx); + TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class)); + TextInputPlugin textInputPlugin = + new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class)); + // Set up an autofill scenario with 2 fields. + final TextInputChannel.Configuration.Autofill autofillConfig = + new TextInputChannel.Configuration.Autofill( + "1", + new String[] {"HINT1"}, + "placeholder1", + new TextInputChannel.TextEditState("", 0, 0, -1, -1)); + final TextInputChannel.Configuration config = + new TextInputChannel.Configuration( + false, + false, + true, + true, + false, + TextInputChannel.TextCapitalization.NONE, + null, + null, + null, + autofillConfig, + null); + + textInputPlugin.setTextInputClient(0, config); + textInputPlugin.setTextInputEditingState( + testView, new TextInputChannel.TextEditState("", 0, 0, -1, -1)); + textInputPlugin.clearTextInputClient(); + + final SparseArray autofillValues = new SparseArray(); + autofillValues.append("1".hashCode(), AutofillValue.forText("focused field")); + autofillValues.append("2".hashCode(), AutofillValue.forText("unfocused field")); + + // Autofill both fields. + textInputPlugin.autofill(autofillValues); + + verify(textInputChannel, never()).updateEditingStateWithTag(anyInt(), any()); + verify(textInputChannel, never()) + .updateEditingState(anyInt(), any(), anyInt(), anyInt(), anyInt(), anyInt()); + } + @Test public void autofill_testSetTextIpnutClientUpdatesSideFields() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { From c752816eb78a1bceb05c03bcba5d14cf2dc1a4ba Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Tue, 21 Jun 2022 18:18:07 -0700 Subject: [PATCH 2/3] fix NPE --- .../android/io/flutter/plugin/editing/TextInputPlugin.java | 4 ++-- 1 file changed, 2 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 1a992b4306003..c90f26debf19f 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -714,11 +714,11 @@ public void autofill(@NonNull SparseArray values) { return; } - final TextInputChannel.Configuration.Autofill currentAutofill = configuration.autofill; - if (currentAutofill == null || autofillConfiguration == null) { + if (configuration == null || autofillConfiguration == null || configuration.autofill == null) { return; } + final TextInputChannel.Configuration.Autofill currentAutofill = configuration.autofill; final HashMap editingValues = new HashMap<>(); for (int i = 0; i < values.size(); i++) { int virtualId = values.keyAt(i); From 9a9b1dbb51a8eb2c950e1e6597cd0be0ec2c1d03 Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Wed, 22 Jun 2022 08:22:12 -0700 Subject: [PATCH 3/3] whoops --- .../test/io/flutter/plugin/editing/TextInputPluginTest.java | 2 +- 1 file changed, 1 insertion(+), 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 c3d16eeadb3d2..f0c8c9004d7bf 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java @@ -1786,7 +1786,7 @@ public void autofill_doesNotCrashAfterClearClientCall() { return; } FlutterView testView = new FlutterView(ctx); - TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class)); + TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class))); TextInputPlugin textInputPlugin = new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class)); // Set up an autofill scenario with 2 fields.