From afeb9b27f0191997cc45682ab756c0be4d3e155c Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Tue, 29 Sep 2020 12:46:29 -0700 Subject: [PATCH] Use dispatchKeyEventPreIme, and handle keys sent to InputConnection.sendKeyEvent on Android (flutter/engine#21163) This switches from using dispatchKeyEvent to using dispatchKeyEventPreIme so that keys can be intercepted before they reach the IME and be handled by the framework. It also now intercepts key events sent to InputConnection.sendKeyEvent, as some IMEs do (e.g. the Hacker's Keyboard), and sends the to Flutter before sending them to the IME (which it now only does if they are not handled by the framework). This fixes the problem where pressing TAB on a hardware keyboard sends the tab to both the text field and to the focus traversal system. Note that we still can't intercept all keystrokes given to a soft keyboard, only those which the soft keyboard decides to send to InputConnection.sendKeyEvent. --- .../android/AndroidKeyProcessor.java | 69 +++++++++---------- .../embedding/android/FlutterView.java | 35 +++------- .../editing/InputConnectionAdaptor.java | 15 +++- .../plugin/editing/TextInputPlugin.java | 14 +++- .../android/io/flutter/view/FlutterView.java | 16 +---- .../android/AndroidKeyProcessorTest.java | 24 +++---- .../editing/InputConnectionAdaptorTest.java | 37 ++++++---- 7 files changed, 108 insertions(+), 102 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidKeyProcessor.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidKeyProcessor.java index 14d7effe56c..c2c55495965 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidKeyProcessor.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/AndroidKeyProcessor.java @@ -66,7 +66,8 @@ public class AndroidKeyProcessor { @NonNull TextInputPlugin textInputPlugin) { this.keyEventChannel = keyEventChannel; this.textInputPlugin = textInputPlugin; - this.eventResponder = new EventResponder(view); + textInputPlugin.setKeyEventProcessor(this); + this.eventResponder = new EventResponder(view, textInputPlugin); this.keyEventChannel.setEventResponseHandler(eventResponder); } @@ -80,13 +81,20 @@ public class AndroidKeyProcessor { } /** - * Called when a key up event is received by the {@link FlutterView}. + * Called when a key event is received by the {@link FlutterView} or the {@link + * InputConnectionAdaptor}. * * @param keyEvent the Android key event to respond to. * @return true if the key event should not be propagated to other Android components. Delayed * synthesis events will return false, so that other components may handle them. */ - public boolean onKeyUp(@NonNull KeyEvent keyEvent) { + public boolean onKeyEvent(@NonNull KeyEvent keyEvent) { + int action = keyEvent.getAction(); + if (action != KeyEvent.ACTION_DOWN && action != KeyEvent.ACTION_UP) { + // There is theoretically a KeyEvent.ACTION_MULTIPLE, but that shouldn't + // be sent anymore anyhow. + return false; + } if (eventResponder.dispatchingKeyEvent) { // Don't handle it if it is from our own delayed event synthesis. return false; @@ -95,38 +103,11 @@ public class AndroidKeyProcessor { Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar()); KeyEventChannel.FlutterKeyEvent flutterEvent = new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter, eventIdSerial++); - keyEventChannel.keyUp(flutterEvent); - eventResponder.addEvent(flutterEvent.eventId, keyEvent); - return true; - } - - /** - * Called when a key down event is received by the {@link FlutterView}. - * - * @param keyEvent the Android key event to respond to. - * @return true if the key event should not be propagated to other Android components. Delayed - * synthesis events will return false, so that other components may handle them. - */ - public boolean onKeyDown(@NonNull KeyEvent keyEvent) { - if (eventResponder.dispatchingKeyEvent) { - // Don't handle it if it is from our own delayed event synthesis. - return false; + if (action == KeyEvent.ACTION_DOWN) { + keyEventChannel.keyDown(flutterEvent); + } else { + keyEventChannel.keyUp(flutterEvent); } - - // If the textInputPlugin is still valid and accepting text, then we'll try - // and send the key event to it, assuming that if the event can be sent, - // that it has been handled. - if (textInputPlugin.getLastInputConnection() != null - && textInputPlugin.getInputMethodManager().isAcceptingText()) { - if (textInputPlugin.getLastInputConnection().sendKeyEvent(keyEvent)) { - return true; - } - } - - Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar()); - KeyEventChannel.FlutterKeyEvent flutterEvent = - new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter, eventIdSerial++); - keyEventChannel.keyDown(flutterEvent); eventResponder.addEvent(flutterEvent.eventId, keyEvent); return true; } @@ -196,10 +177,12 @@ public class AndroidKeyProcessor { private static final long MAX_PENDING_EVENTS = 1000; final Deque> pendingEvents = new ArrayDeque>(); @NonNull private final View view; + @NonNull private final TextInputPlugin textInputPlugin; boolean dispatchingKeyEvent = false; - public EventResponder(@NonNull View view) { + public EventResponder(@NonNull View view, @NonNull TextInputPlugin textInputPlugin) { this.view = view; + this.textInputPlugin = textInputPlugin; } /** @@ -267,12 +250,26 @@ public class AndroidKeyProcessor { * @param event the event to be dispatched to the activity. */ public void dispatchKeyEvent(KeyEvent event) { + // If the textInputPlugin is still valid and accepting text, then we'll try + // and send the key event to it, assuming that if the event can be sent, + // that it has been handled. + if (textInputPlugin.getLastInputConnection() != null + && textInputPlugin.getInputMethodManager().isAcceptingText()) { + dispatchingKeyEvent = true; + boolean handled = textInputPlugin.getLastInputConnection().sendKeyEvent(event); + dispatchingKeyEvent = false; + if (handled) { + return; + } + } + // Since the framework didn't handle it, dispatch the key again. if (view != null) { // Turn on dispatchingKeyEvent so that we don't dispatch to ourselves and // send it to the framework again. dispatchingKeyEvent = true; - view.getRootView().dispatchKeyEvent(event); + + view.getRootView().dispatchKeyEventPreIme(event); dispatchingKeyEvent = false; } } diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 48d9243cf86..78cc4e7e716 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -721,27 +721,7 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC } /** - * Invoked when key is released. - * - *

This method is typically invoked in response to the release of a physical keyboard key or a - * D-pad button. It is generally not invoked when a virtual software keyboard is used, though a - * software keyboard may choose to invoke this method in some situations. - * - *

{@link KeyEvent}s are sent from Android to Flutter. {@link AndroidKeyProcessor} may do some - * additional work with the given {@link KeyEvent}, e.g., combine this {@code keyCode} with the - * previous {@code keyCode} to generate a unicode combined character. - */ - @Override - public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) { - if (!isAttachedToFlutterEngine()) { - return super.onKeyUp(keyCode, event); - } - - return androidKeyProcessor.onKeyUp(event) || super.onKeyUp(keyCode, event); - } - - /** - * Invoked when key is pressed. + * Invoked when a hardware key is pressed or released, before the IME receives the key. * *

This method is typically invoked in response to the press of a physical keyboard key or a * D-pad button. It is generally not invoked when a virtual software keyboard is used, though a @@ -752,12 +732,13 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC * previous {@code keyCode} to generate a unicode combined character. */ @Override - public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { - if (!isAttachedToFlutterEngine()) { - return super.onKeyDown(keyCode, event); - } - - return androidKeyProcessor.onKeyDown(event) || super.onKeyDown(keyCode, event); + public boolean dispatchKeyEventPreIme(KeyEvent event) { + // If the key processor doesn't handle it, then send it on to the + // superclass. The key processor will typically handle all events except + // those where it has re-dispatched the event after receiving a reply from + // the framework that the framework did not handle it. + return (isAttachedToFlutterEngine() && androidKeyProcessor.onKeyEvent(event)) + || super.dispatchKeyEventPreIme(event); } /** diff --git a/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java b/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java index d702e2b7855..26cb5d9d984 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java @@ -27,6 +27,7 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; import io.flutter.Log; +import io.flutter.embedding.android.AndroidKeyProcessor; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.systemchannels.TextInputChannel; @@ -34,6 +35,7 @@ class InputConnectionAdaptor extends BaseInputConnection { private final View mFlutterView; private final int mClient; private final TextInputChannel textInputChannel; + private final AndroidKeyProcessor keyProcessor; private final Editable mEditable; private final EditorInfo mEditorInfo; private int mBatchCount; @@ -97,6 +99,7 @@ class InputConnectionAdaptor extends BaseInputConnection { View view, int client, TextInputChannel textInputChannel, + AndroidKeyProcessor keyProcessor, Editable editable, EditorInfo editorInfo, FlutterJNI flutterJNI) { @@ -107,6 +110,7 @@ class InputConnectionAdaptor extends BaseInputConnection { mEditable = editable; mEditorInfo = editorInfo; mBatchCount = 0; + this.keyProcessor = keyProcessor; this.flutterTextUtils = new FlutterTextUtils(flutterJNI); // We create a dummy Layout with max width so that the selection // shifting acts as if all text were in one line. @@ -128,9 +132,10 @@ class InputConnectionAdaptor extends BaseInputConnection { View view, int client, TextInputChannel textInputChannel, + AndroidKeyProcessor keyProcessor, Editable editable, EditorInfo editorInfo) { - this(view, client, textInputChannel, editable, editorInfo, new FlutterJNI()); + this(view, client, textInputChannel, keyProcessor, editable, editorInfo, new FlutterJNI()); } // Send the current state of the editable to Flutter. @@ -323,6 +328,14 @@ class InputConnectionAdaptor extends BaseInputConnection { @Override public boolean sendKeyEvent(KeyEvent event) { + // Give the key processor a chance to process this event. It will send it + // to the framework to be handled and return true. If the framework ends up + // not handling it, the processor will re-send the event, this time + // returning false so that it can be processed here. + if (keyProcessor != null && keyProcessor.onKeyEvent(event)) { + return true; + } + markDirty(); if (event.getAction() == KeyEvent.ACTION_DOWN) { if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { 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 3090c6fba81..abbece54394 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 @@ -33,6 +33,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; +import io.flutter.embedding.android.AndroidKeyProcessor; import io.flutter.embedding.engine.systemchannels.TextInputChannel; import io.flutter.plugin.platform.PlatformViewsController; import java.util.HashMap; @@ -54,6 +55,7 @@ public class TextInputPlugin { @Nullable private Rect lastClientRect; private final boolean restartAlwaysRequired; private ImeSyncDeferringInsetsCallback imeSyncCallback; + private AndroidKeyProcessor keyProcessor; // When true following calls to createInputConnection will return the cached lastInputConnection // if the input @@ -327,6 +329,15 @@ public class TextInputPlugin { return imeSyncCallback; } + @NonNull + public AndroidKeyProcessor getKeyEventProcessor() { + return keyProcessor; + } + + public void setKeyEventProcessor(AndroidKeyProcessor processor) { + keyProcessor = processor; + } + /** * Use the current platform view input connection until unlockPlatformViewInputConnection is * called. @@ -469,7 +480,8 @@ public class TextInputPlugin { outAttrs.imeOptions |= enterAction; InputConnectionAdaptor connection = - new InputConnectionAdaptor(view, inputTarget.id, textInputChannel, mEditable, outAttrs); + new InputConnectionAdaptor( + view, inputTarget.id, textInputChannel, keyProcessor, mEditable, outAttrs); outAttrs.initialSelStart = Selection.getSelectionStart(mEditable); outAttrs.initialSelEnd = Selection.getSelectionEnd(mEditable); diff --git a/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterView.java b/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterView.java index 9899e1714b2..172c4492d66 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterView.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/view/FlutterView.java @@ -268,19 +268,9 @@ public class FlutterView extends SurfaceView } @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (!isAttached()) { - return super.onKeyUp(keyCode, event); - } - return androidKeyProcessor.onKeyUp(event) || super.onKeyUp(keyCode, event); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (!isAttached()) { - return super.onKeyDown(keyCode, event); - } - return androidKeyProcessor.onKeyDown(event) || super.onKeyDown(keyCode, event); + public boolean dispatchKeyEventPreIme(KeyEvent event) { + return (isAttached() && androidKeyProcessor.onKeyEvent(event)) + || super.dispatchKeyEventPreIme(event); } public FlutterNativeView getFlutterNativeView() { diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/AndroidKeyProcessorTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/AndroidKeyProcessorTest.java index 8eddb009dc8..74feb6c8046 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/AndroidKeyProcessorTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/android/AndroidKeyProcessorTest.java @@ -51,11 +51,11 @@ public class AndroidKeyProcessorTest { AndroidKeyProcessor processor = new AndroidKeyProcessor(fakeView, fakeKeyEventChannel, mock(TextInputPlugin.class)); - boolean result = processor.onKeyDown(new FakeKeyEvent(KeyEvent.ACTION_DOWN, 65)); + boolean result = processor.onKeyEvent(new FakeKeyEvent(KeyEvent.ACTION_DOWN, 65)); assertEquals(true, result); verify(fakeKeyEventChannel, times(1)).keyDown(any(KeyEventChannel.FlutterKeyEvent.class)); verify(fakeKeyEventChannel, times(0)).keyUp(any(KeyEventChannel.FlutterKeyEvent.class)); - verify(fakeView, times(0)).dispatchKeyEvent(any(KeyEvent.class)); + verify(fakeView, times(0)).dispatchKeyEventPreIme(any(KeyEvent.class)); } @Test @@ -97,31 +97,31 @@ public class AndroidKeyProcessorTest { ArgumentCaptor.forClass(KeyEventChannel.FlutterKeyEvent.class); FakeKeyEvent fakeKeyEvent = new FakeKeyEvent(KeyEvent.ACTION_DOWN, 65); - boolean result = processor.onKeyDown(fakeKeyEvent); + boolean result = processor.onKeyEvent(fakeKeyEvent); assertEquals(true, result); // Capture the FlutterKeyEvent so we can find out its event ID to use when // faking our response. verify(fakeKeyEventChannel, times(1)).keyDown(eventCaptor.capture()); boolean[] dispatchResult = {true}; - when(fakeView.dispatchKeyEvent(any(KeyEvent.class))) + when(fakeView.dispatchKeyEventPreIme(any(KeyEvent.class))) .then( new Answer() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { KeyEvent event = (KeyEvent) invocation.getArguments()[0]; assertEquals(fakeKeyEvent, event); - dispatchResult[0] = processor.onKeyDown(event); + dispatchResult[0] = processor.onKeyEvent(event); return dispatchResult[0]; } }); // Fake a response from the framework. handlerCaptor.getValue().onKeyEventNotHandled(eventCaptor.getValue().eventId); - verify(fakeView, times(1)).dispatchKeyEvent(fakeKeyEvent); + verify(fakeView, times(1)).dispatchKeyEventPreIme(fakeKeyEvent); assertEquals(false, dispatchResult[0]); verify(fakeKeyEventChannel, times(0)).keyUp(any(KeyEventChannel.FlutterKeyEvent.class)); - verify(fakeRootView, times(1)).dispatchKeyEvent(fakeKeyEvent); + verify(fakeRootView, times(1)).dispatchKeyEventPreIme(fakeKeyEvent); } public void synthesizesEventsWhenKeyUpNotHandled() { @@ -147,31 +147,31 @@ public class AndroidKeyProcessorTest { ArgumentCaptor.forClass(KeyEventChannel.FlutterKeyEvent.class); FakeKeyEvent fakeKeyEvent = new FakeKeyEvent(KeyEvent.ACTION_UP, 65); - boolean result = processor.onKeyUp(fakeKeyEvent); + boolean result = processor.onKeyEvent(fakeKeyEvent); assertEquals(true, result); // Capture the FlutterKeyEvent so we can find out its event ID to use when // faking our response. verify(fakeKeyEventChannel, times(1)).keyUp(eventCaptor.capture()); boolean[] dispatchResult = {true}; - when(fakeView.dispatchKeyEvent(any(KeyEvent.class))) + when(fakeView.dispatchKeyEventPreIme(any(KeyEvent.class))) .then( new Answer() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { KeyEvent event = (KeyEvent) invocation.getArguments()[0]; assertEquals(fakeKeyEvent, event); - dispatchResult[0] = processor.onKeyUp(event); + dispatchResult[0] = processor.onKeyEvent(event); return dispatchResult[0]; } }); // Fake a response from the framework. handlerCaptor.getValue().onKeyEventNotHandled(eventCaptor.getValue().eventId); - verify(fakeView, times(1)).dispatchKeyEvent(fakeKeyEvent); + verify(fakeView, times(1)).dispatchKeyEventPreIme(fakeKeyEvent); assertEquals(false, dispatchResult[0]); verify(fakeKeyEventChannel, times(0)).keyUp(any(KeyEventChannel.FlutterKeyEvent.class)); - verify(fakeRootView, times(1)).dispatchKeyEvent(fakeKeyEvent); + verify(fakeRootView, times(1)).dispatchKeyEventPreIme(fakeKeyEvent); } @NonNull diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java index 2052a47934c..a2d8eb963a4 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java @@ -25,6 +25,7 @@ import android.view.KeyEvent; import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; +import io.flutter.embedding.android.AndroidKeyProcessor; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.systemchannels.TextInputChannel; @@ -68,6 +69,7 @@ public class InputConnectionAdaptorTest { DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJni, mock(AssetManager.class))); int inputTargetId = 0; TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); + AndroidKeyProcessor mockKeyProcessor = mock(AndroidKeyProcessor.class); Editable mEditable = Editable.Factory.getInstance().newEditable(""); Editable spyEditable = spy(mEditable); EditorInfo outAttrs = new EditorInfo(); @@ -75,7 +77,7 @@ public class InputConnectionAdaptorTest { InputConnectionAdaptor inputConnectionAdaptor = new InputConnectionAdaptor( - testView, inputTargetId, textInputChannel, spyEditable, outAttrs); + testView, inputTargetId, textInputChannel, mockKeyProcessor, spyEditable, outAttrs); // Send an enter key and make sure the Editable received it. FakeKeyEvent keyEvent = new FakeKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER); @@ -156,10 +158,11 @@ public class InputConnectionAdaptorTest { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); + AndroidKeyProcessor mockKeyProcessor = mock(AndroidKeyProcessor.class); Editable editable = sampleEditable(0, 0); InputConnectionAdaptor adaptor = new InputConnectionAdaptor( - testView, client, textInputChannel, editable, null, mockFlutterJNI); + testView, client, textInputChannel, mockKeyProcessor, editable, null, mockFlutterJNI); adaptor.performPrivateCommand("actionCommand", null); ArgumentCaptor channelCaptor = ArgumentCaptor.forClass(String.class); @@ -183,10 +186,11 @@ public class InputConnectionAdaptorTest { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); + AndroidKeyProcessor mockKeyProcessor = mock(AndroidKeyProcessor.class); Editable editable = sampleEditable(0, 0); InputConnectionAdaptor adaptor = new InputConnectionAdaptor( - testView, client, textInputChannel, editable, null, mockFlutterJNI); + testView, client, textInputChannel, mockKeyProcessor, editable, null, mockFlutterJNI); Bundle bundle = new Bundle(); byte[] buffer = new byte[] {'a', 'b', 'c', 'd'}; @@ -216,10 +220,11 @@ public class InputConnectionAdaptorTest { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); + AndroidKeyProcessor mockKeyProcessor = mock(AndroidKeyProcessor.class); Editable editable = sampleEditable(0, 0); InputConnectionAdaptor adaptor = new InputConnectionAdaptor( - testView, client, textInputChannel, editable, null, mockFlutterJNI); + testView, client, textInputChannel, mockKeyProcessor, editable, null, mockFlutterJNI); Bundle bundle = new Bundle(); byte b = 3; @@ -247,10 +252,11 @@ public class InputConnectionAdaptorTest { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); + AndroidKeyProcessor mockKeyProcessor = mock(AndroidKeyProcessor.class); Editable editable = sampleEditable(0, 0); InputConnectionAdaptor adaptor = new InputConnectionAdaptor( - testView, client, textInputChannel, editable, null, mockFlutterJNI); + testView, client, textInputChannel, mockKeyProcessor, editable, null, mockFlutterJNI); Bundle bundle = new Bundle(); char[] buffer = new char[] {'a', 'b', 'c', 'd'}; @@ -281,10 +287,11 @@ public class InputConnectionAdaptorTest { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); + AndroidKeyProcessor mockKeyProcessor = mock(AndroidKeyProcessor.class); Editable editable = sampleEditable(0, 0); InputConnectionAdaptor adaptor = new InputConnectionAdaptor( - testView, client, textInputChannel, editable, null, mockFlutterJNI); + testView, client, textInputChannel, mockKeyProcessor, editable, null, mockFlutterJNI); Bundle bundle = new Bundle(); char b = 'a'; @@ -312,10 +319,11 @@ public class InputConnectionAdaptorTest { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); + AndroidKeyProcessor mockKeyProcessor = mock(AndroidKeyProcessor.class); Editable editable = sampleEditable(0, 0); InputConnectionAdaptor adaptor = new InputConnectionAdaptor( - testView, client, textInputChannel, editable, null, mockFlutterJNI); + testView, client, textInputChannel, mockKeyProcessor, editable, null, mockFlutterJNI); Bundle bundle = new Bundle(); CharSequence charSequence1 = new StringBuffer("abc"); @@ -347,10 +355,11 @@ public class InputConnectionAdaptorTest { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); + AndroidKeyProcessor mockKeyProcessor = mock(AndroidKeyProcessor.class); Editable editable = sampleEditable(0, 0); InputConnectionAdaptor adaptor = new InputConnectionAdaptor( - testView, client, textInputChannel, editable, null, mockFlutterJNI); + testView, client, textInputChannel, mockKeyProcessor, editable, null, mockFlutterJNI); Bundle bundle = new Bundle(); CharSequence charSequence = new StringBuffer("abc"); @@ -380,10 +389,11 @@ public class InputConnectionAdaptorTest { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); + AndroidKeyProcessor mockKeyProcessor = mock(AndroidKeyProcessor.class); Editable editable = sampleEditable(0, 0); InputConnectionAdaptor adaptor = new InputConnectionAdaptor( - testView, client, textInputChannel, editable, null, mockFlutterJNI); + testView, client, textInputChannel, mockKeyProcessor, editable, null, mockFlutterJNI); Bundle bundle = new Bundle(); float value = 0.5f; @@ -411,10 +421,11 @@ public class InputConnectionAdaptorTest { FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); TextInputChannel textInputChannel = new TextInputChannel(dartExecutor); + AndroidKeyProcessor mockKeyProcessor = mock(AndroidKeyProcessor.class); Editable editable = sampleEditable(0, 0); InputConnectionAdaptor adaptor = new InputConnectionAdaptor( - testView, client, textInputChannel, editable, null, mockFlutterJNI); + testView, client, textInputChannel, mockKeyProcessor, editable, null, mockFlutterJNI); Bundle bundle = new Bundle(); float[] value = {0.5f, 0.6f}; @@ -907,6 +918,7 @@ public class InputConnectionAdaptorTest { DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJni, mock(AssetManager.class))); int inputTargetId = 0; TestTextInputChannel textInputChannel = new TestTextInputChannel(dartExecutor); + AndroidKeyProcessor mockKeyProcessor = mock(AndroidKeyProcessor.class); Editable mEditable = Editable.Factory.getInstance().newEditable(""); Editable spyEditable = spy(mEditable); EditorInfo outAttrs = new EditorInfo(); @@ -914,7 +926,7 @@ public class InputConnectionAdaptorTest { InputConnectionAdaptor inputConnectionAdaptor = new InputConnectionAdaptor( - testView, inputTargetId, textInputChannel, spyEditable, outAttrs); + testView, inputTargetId, textInputChannel, mockKeyProcessor, spyEditable, outAttrs); inputConnectionAdaptor.beginBatchEdit(); assertEquals(textInputChannel.updateEditingStateInvocations, 0); @@ -1159,6 +1171,7 @@ public class InputConnectionAdaptorTest { int client = 0; TextInputChannel textInputChannel = mock(TextInputChannel.class); FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); + AndroidKeyProcessor mockKeyProcessor = mock(AndroidKeyProcessor.class); when(mockFlutterJNI.nativeFlutterTextUtilsIsEmoji(anyInt())) .thenAnswer((invocation) -> Emoji.isEmoji((int) invocation.getArguments()[0])); when(mockFlutterJNI.nativeFlutterTextUtilsIsEmojiModifier(anyInt())) @@ -1175,7 +1188,7 @@ public class InputConnectionAdaptorTest { .thenAnswer( (invocation) -> Emoji.isRegionalIndicatorSymbol((int) invocation.getArguments()[0])); return new InputConnectionAdaptor( - testView, client, textInputChannel, editable, null, mockFlutterJNI); + testView, client, textInputChannel, mockKeyProcessor, editable, null, mockFlutterJNI); } private class TestTextInputChannel extends TextInputChannel {