From e48f2f98d982d146f6419f351fde6665c21686ae Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Wed, 12 Feb 2020 13:04:41 -0800 Subject: [PATCH 1/6] This fixes the Chinese newline, does hardware enter still work? --- .../io/flutter/plugin/editing/InputConnectionAdaptor.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java index 64e41df5f0047..bb1a6286095d0 100644 --- a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java +++ b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java @@ -221,12 +221,12 @@ public boolean sendKeyEvent(KeyEvent event) { int newSel = Math.min(selStart + 1, mEditable.length()); setSelection(newSel, newSel); return true; - } else if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER - || event.getKeyCode() == KeyEvent.KEYCODE_NUMPAD_ENTER) { - performEditorAction(mEditorInfo.imeOptions & EditorInfo.IME_MASK_ACTION); - return true; } else { // Enter a character. + if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER + || event.getKeyCode() == KeyEvent.KEYCODE_NUMPAD_ENTER) { + performEditorAction(mEditorInfo.imeOptions & EditorInfo.IME_MASK_ACTION); + } int character = event.getUnicodeChar(); if (character != 0) { int selStart = Math.max(0, Selection.getSelectionStart(mEditable)); From b595146175760aaa911966acd676d4dd28920843 Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Fri, 14 Feb 2020 11:10:21 -0800 Subject: [PATCH 2/6] Test that enter key calls insert --- shell/platform/android/BUILD.gn | 2 + .../test/io/flutter/FlutterTestSuite.java | 4 + .../editing/InputConnectionAdaptorTest.java | 78 +++++++++++++++++++ .../test/io/flutter/util/FakeKeyEvent.java | 15 ++++ 4 files changed, 99 insertions(+) create mode 100644 shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java create mode 100644 shell/platform/android/test/io/flutter/util/FakeKeyEvent.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 92130115f13e0..2b940b214bb2c 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -433,10 +433,12 @@ action("robolectric_tests") { "test/io/flutter/embedding/engine/systemchannels/PlatformChannelTest.java", "test/io/flutter/external/FlutterLaunchTests.java", "test/io/flutter/plugin/common/StandardMessageCodecTest.java", + "test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java", "test/io/flutter/plugin/editing/TextInputPluginTest.java", "test/io/flutter/plugin/platform/PlatformPluginTest.java", "test/io/flutter/plugin/platform/SingleViewPresentationTest.java", "test/io/flutter/plugins/GeneratedPluginRegistrant.java", + "test/io/flutter/util/FakeKeyEvent.java", "test/io/flutter/util/PreconditionsTest.java", ] diff --git a/shell/platform/android/test/io/flutter/FlutterTestSuite.java b/shell/platform/android/test/io/flutter/FlutterTestSuite.java index b72cf077ca865..58b2bb8fc46f9 100644 --- a/shell/platform/android/test/io/flutter/FlutterTestSuite.java +++ b/shell/platform/android/test/io/flutter/FlutterTestSuite.java @@ -17,9 +17,11 @@ import io.flutter.embedding.engine.systemchannels.PlatformChannelTest; import io.flutter.external.FlutterLaunchTests; import io.flutter.plugin.common.StandardMessageCodecTest; +import io.flutter.plugin.editing.InputConnectionAdaptorTest; import io.flutter.plugin.editing.TextInputPluginTest; import io.flutter.plugin.platform.PlatformPluginTest; import io.flutter.plugin.platform.SingleViewPresentationTest; +import io.flutter.util.FakeKeyEvent; import io.flutter.util.PreconditionsTest; import org.junit.runner.RunWith; import org.junit.runners.Suite; @@ -33,6 +35,7 @@ @SuiteClasses({ // FlutterActivityAndFragmentDelegateTest.class, //TODO(mklim): Fix and re-enable this DartExecutorTest.class, + FakeKeyEvent.class, FlutterActivityTest.class, FlutterAndroidComponentTest.class, FlutterEngineCacheTest.class, @@ -44,6 +47,7 @@ FlutterShellArgsTest.class, FlutterRendererTest.class, FlutterViewTest.class, + InputConnectionAdaptorTest.class, PlatformChannelTest.class, PlatformPluginTest.class, PluginComponentTest.class, diff --git a/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java b/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java new file mode 100644 index 0000000000000..a8aef2adc9976 --- /dev/null +++ b/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java @@ -0,0 +1,78 @@ +package io.flutter.plugin.editing; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.AssetManager; +import android.os.Build; +import android.provider.Settings; +import android.text.Editable; +import android.util.SparseIntArray; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.CursorAnchorInfo; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; +import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.systemchannels.TextInputChannel; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.JSONMethodCodec; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.platform.PlatformViewsController; +import io.flutter.util.FakeKeyEvent; +import java.lang.NullPointerException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.json.JSONArray; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Config(manifest = Config.NONE, sdk = 27) +@RunWith(RobolectricTestRunner.class) +public class InputConnectionAdaptorTest { + @Test + public void inputConnectionAdaptor_ReceivesEnter() throws NullPointerException { + View testView = new View(RuntimeEnvironment.application); + FlutterJNI mockFlutterJni = mock(FlutterJNI.class); + DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJni, mock(AssetManager.class))); + int inputTargetId = 0; + TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); + Editable mEditable = Editable.Factory.getInstance().newEditable(""); + Editable spyEditable = spy(mEditable); + EditorInfo outAttrs = new EditorInfo(); + + InputConnectionAdaptor inputConnectionAdaptor = new InputConnectionAdaptor( + testView, + inputTargetId, + textInputChannel, + spyEditable, + outAttrs + ); + + // Send an enter key and make sure the Editable received it. + FakeKeyEvent keyEvent = new FakeKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER); + inputConnectionAdaptor.sendKeyEvent(keyEvent); + verify(spyEditable, times(1)).insert(eq(0), anyString()); + } +} diff --git a/shell/platform/android/test/io/flutter/util/FakeKeyEvent.java b/shell/platform/android/test/io/flutter/util/FakeKeyEvent.java new file mode 100644 index 0000000000000..2c9b9ceb1c3b8 --- /dev/null +++ b/shell/platform/android/test/io/flutter/util/FakeKeyEvent.java @@ -0,0 +1,15 @@ +package io.flutter.util; + +import android.view.KeyEvent; + +// In the test environment, keyEvent.getUnicodeChar throws an exception. This +// class works around the exception by hardcoding the returned value. +public class FakeKeyEvent extends KeyEvent { + public FakeKeyEvent(int action, int keyCode) { + super(action, keyCode); + } + + public final int getUnicodeChar() { + return 1; + } +} From b63b02d6b2123c3fa9406ad3f4b8a3c442459eaf Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Wed, 19 Feb 2020 10:43:27 -0800 Subject: [PATCH 3/6] Only call when multiline, and update test to send correct inputtype --- .../plugin/editing/InputConnectionAdaptor.java | 12 ++++++++---- .../plugin/editing/InputConnectionAdaptorTest.java | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java index bb1a6286095d0..493de73bbb378 100644 --- a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java +++ b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java @@ -8,6 +8,7 @@ import android.os.Build; import android.text.DynamicLayout; import android.text.Editable; +import android.text.InputType; import android.text.Layout; import android.text.Selection; import android.text.TextPaint; @@ -221,12 +222,15 @@ public boolean sendKeyEvent(KeyEvent event) { int newSel = Math.min(selStart + 1, mEditable.length()); setSelection(newSel, newSel); return true; + // When the enter key is pressed on a non-multiline field, consider it a + // submit instead of a newline. + } else if ((event.getKeyCode() == KeyEvent.KEYCODE_ENTER + || event.getKeyCode() == KeyEvent.KEYCODE_NUMPAD_ENTER) + && mEditorInfo.inputType != (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE)) { + performEditorAction(mEditorInfo.imeOptions & EditorInfo.IME_MASK_ACTION); + return true; } else { // Enter a character. - if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER - || event.getKeyCode() == KeyEvent.KEYCODE_NUMPAD_ENTER) { - performEditorAction(mEditorInfo.imeOptions & EditorInfo.IME_MASK_ACTION); - } int character = event.getUnicodeChar(); if (character != 0) { int selStart = Math.max(0, Selection.getSelectionStart(mEditable)); diff --git a/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java b/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java index a8aef2adc9976..873b1ee0a834b 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java @@ -17,6 +17,7 @@ import android.os.Build; import android.provider.Settings; import android.text.Editable; +import android.text.InputType; import android.util.SparseIntArray; import android.view.KeyEvent; import android.view.View; @@ -61,6 +62,7 @@ public void inputConnectionAdaptor_ReceivesEnter() throws NullPointerException { Editable mEditable = Editable.Factory.getInstance().newEditable(""); Editable spyEditable = spy(mEditable); EditorInfo outAttrs = new EditorInfo(); + outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE; InputConnectionAdaptor inputConnectionAdaptor = new InputConnectionAdaptor( testView, From b99700c18c8eb6eed116c30d3bcf85fea0f1101b Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Wed, 19 Feb 2020 11:03:05 -0800 Subject: [PATCH 4/6] Formatting --- .../editing/InputConnectionAdaptor.java | 13 +++---- .../editing/InputConnectionAdaptorTest.java | 36 ++----------------- 2 files changed, 10 insertions(+), 39 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java index 493de73bbb378..035049e0b03b5 100644 --- a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java +++ b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java @@ -222,13 +222,14 @@ public boolean sendKeyEvent(KeyEvent event) { int newSel = Math.min(selStart + 1, mEditable.length()); setSelection(newSel, newSel); return true; - // When the enter key is pressed on a non-multiline field, consider it a - // submit instead of a newline. + // When the enter key is pressed on a non-multiline field, consider it a + // submit instead of a newline. } else if ((event.getKeyCode() == KeyEvent.KEYCODE_ENTER - || event.getKeyCode() == KeyEvent.KEYCODE_NUMPAD_ENTER) - && mEditorInfo.inputType != (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE)) { - performEditorAction(mEditorInfo.imeOptions & EditorInfo.IME_MASK_ACTION); - return true; + || event.getKeyCode() == KeyEvent.KEYCODE_NUMPAD_ENTER) + && mEditorInfo.inputType + != (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE)) { + performEditorAction(mEditorInfo.imeOptions & EditorInfo.IME_MASK_ACTION); + return true; } else { // Enter a character. int character = event.getUnicodeChar(); diff --git a/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java b/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java index 873b1ee0a834b..b366efb110191 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java @@ -1,53 +1,27 @@ package io.flutter.plugin.editing; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import android.content.Context; import android.content.res.AssetManager; -import android.os.Build; -import android.provider.Settings; import android.text.Editable; import android.text.InputType; -import android.util.SparseIntArray; import android.view.KeyEvent; import android.view.View; -import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputMethodSubtype; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.systemchannels.TextInputChannel; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.JSONMethodCodec; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.platform.PlatformViewsController; import io.flutter.util.FakeKeyEvent; -import java.lang.NullPointerException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import org.json.JSONArray; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; @Config(manifest = Config.NONE, sdk = 27) @RunWith(RobolectricTestRunner.class) @@ -64,13 +38,9 @@ public void inputConnectionAdaptor_ReceivesEnter() throws NullPointerException { EditorInfo outAttrs = new EditorInfo(); outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE; - InputConnectionAdaptor inputConnectionAdaptor = new InputConnectionAdaptor( - testView, - inputTargetId, - textInputChannel, - spyEditable, - outAttrs - ); + InputConnectionAdaptor inputConnectionAdaptor = + new InputConnectionAdaptor( + testView, inputTargetId, textInputChannel, spyEditable, outAttrs); // Send an enter key and make sure the Editable received it. FakeKeyEvent keyEvent = new FakeKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER); From 6391eea41e753a7984c4d6d33a07659051f55414 Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Wed, 19 Feb 2020 12:39:43 -0800 Subject: [PATCH 5/6] Fix behavior in app by using the same way to detect multiline as elsewhere --- .../io/flutter/plugin/editing/InputConnectionAdaptor.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java index 035049e0b03b5..7f2cd28f5b699 100644 --- a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java +++ b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java @@ -226,8 +226,7 @@ public boolean sendKeyEvent(KeyEvent event) { // submit instead of a newline. } else if ((event.getKeyCode() == KeyEvent.KEYCODE_ENTER || event.getKeyCode() == KeyEvent.KEYCODE_NUMPAD_ENTER) - && mEditorInfo.inputType - != (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE)) { + && (InputType.TYPE_TEXT_FLAG_MULTI_LINE & mEditorInfo.inputType) == 0) { performEditorAction(mEditorInfo.imeOptions & EditorInfo.IME_MASK_ACTION); return true; } else { From 16cc88b4e756a16c68e68f3583fef934e868afd7 Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Wed, 19 Feb 2020 12:59:19 -0800 Subject: [PATCH 6/6] FakeKey is not a test, so remove it from suite --- shell/platform/android/test/io/flutter/FlutterTestSuite.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/shell/platform/android/test/io/flutter/FlutterTestSuite.java b/shell/platform/android/test/io/flutter/FlutterTestSuite.java index 58b2bb8fc46f9..480998ac362d4 100644 --- a/shell/platform/android/test/io/flutter/FlutterTestSuite.java +++ b/shell/platform/android/test/io/flutter/FlutterTestSuite.java @@ -21,7 +21,6 @@ import io.flutter.plugin.editing.TextInputPluginTest; import io.flutter.plugin.platform.PlatformPluginTest; import io.flutter.plugin.platform.SingleViewPresentationTest; -import io.flutter.util.FakeKeyEvent; import io.flutter.util.PreconditionsTest; import org.junit.runner.RunWith; import org.junit.runners.Suite; @@ -35,7 +34,6 @@ @SuiteClasses({ // FlutterActivityAndFragmentDelegateTest.class, //TODO(mklim): Fix and re-enable this DartExecutorTest.class, - FakeKeyEvent.class, FlutterActivityTest.class, FlutterAndroidComponentTest.class, FlutterEngineCacheTest.class,