Skip to content

Commit

Permalink
Revert "Use dispatchKeyEventPreIme, and handle keys sent to InputConn…
Browse files Browse the repository at this point in the history
…ection.sendKeyEvent on Android (flutter#21163)" (flutter#21513)

This reverts commit 539034a.
  • Loading branch information
renyou authored Sep 30, 2020
1 parent 4060002 commit 83b9df9
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ public AndroidKeyProcessor(
@NonNull TextInputPlugin textInputPlugin) {
this.keyEventChannel = keyEventChannel;
this.textInputPlugin = textInputPlugin;
textInputPlugin.setKeyEventProcessor(this);
this.eventResponder = new EventResponder(view, textInputPlugin);
this.eventResponder = new EventResponder(view);
this.keyEventChannel.setEventResponseHandler(eventResponder);
}

Expand All @@ -81,33 +80,53 @@ public void destroy() {
}

/**
* Called when a key event is received by the {@link FlutterView} or the {@link
* InputConnectionAdaptor}.
* Called when a key up 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 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.
public boolean onKeyUp(@NonNull KeyEvent keyEvent) {
if (eventResponder.dispatchingKeyEvent) {
// Don't handle it if it is from our own delayed event synthesis.
return false;
}

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 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++);
if (action == KeyEvent.ACTION_DOWN) {
keyEventChannel.keyDown(flutterEvent);
} else {
keyEventChannel.keyUp(flutterEvent);
}
keyEventChannel.keyDown(flutterEvent);
eventResponder.addEvent(flutterEvent.eventId, keyEvent);
return true;
}
Expand Down Expand Up @@ -177,12 +196,10 @@ private static class EventResponder implements KeyEventChannel.EventResponseHand
private static final long MAX_PENDING_EVENTS = 1000;
final Deque<Entry<Long, KeyEvent>> pendingEvents = new ArrayDeque<Entry<Long, KeyEvent>>();
@NonNull private final View view;
@NonNull private final TextInputPlugin textInputPlugin;
boolean dispatchingKeyEvent = false;

public EventResponder(@NonNull View view, @NonNull TextInputPlugin textInputPlugin) {
public EventResponder(@NonNull View view) {
this.view = view;
this.textInputPlugin = textInputPlugin;
}

/**
Expand Down Expand Up @@ -250,26 +267,12 @@ public void addEvent(long id, @NonNull KeyEvent event) {
* @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().dispatchKeyEventPreIme(event);
view.getRootView().dispatchKeyEvent(event);
dispatchingKeyEvent = false;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,27 @@ public boolean checkInputConnectionProxy(View view) {
}

/**
* Invoked when a hardware key is pressed or released, before the IME receives the key.
* Invoked when key is released.
*
* <p>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.
*
* <p>{@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.
*
* <p>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
Expand All @@ -732,13 +752,12 @@ public boolean checkInputConnectionProxy(View view) {
* previous {@code keyCode} to generate a unicode combined character.
*/
@Override
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);
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
if (!isAttachedToFlutterEngine()) {
return super.onKeyDown(keyCode, event);
}

return androidKeyProcessor.onKeyDown(event) || super.onKeyDown(keyCode, event);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,13 @@
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;

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;
Expand Down Expand Up @@ -99,7 +97,6 @@ public InputConnectionAdaptor(
View view,
int client,
TextInputChannel textInputChannel,
AndroidKeyProcessor keyProcessor,
Editable editable,
EditorInfo editorInfo,
FlutterJNI flutterJNI) {
Expand All @@ -110,7 +107,6 @@ public InputConnectionAdaptor(
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.
Expand All @@ -132,10 +128,9 @@ public InputConnectionAdaptor(
View view,
int client,
TextInputChannel textInputChannel,
AndroidKeyProcessor keyProcessor,
Editable editable,
EditorInfo editorInfo) {
this(view, client, textInputChannel, keyProcessor, editable, editorInfo, new FlutterJNI());
this(view, client, textInputChannel, editable, editorInfo, new FlutterJNI());
}

// Send the current state of the editable to Flutter.
Expand Down Expand Up @@ -328,14 +323,6 @@ private static int clampIndexToEditable(int index, Editable editable) {

@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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
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;
Expand All @@ -55,7 +54,6 @@ 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
Expand Down Expand Up @@ -329,15 +327,6 @@ ImeSyncDeferringInsetsCallback getImeSyncCallback() {
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.
Expand Down Expand Up @@ -480,8 +469,7 @@ public InputConnection createInputConnection(View view, EditorInfo outAttrs) {
outAttrs.imeOptions |= enterAction;

InputConnectionAdaptor connection =
new InputConnectionAdaptor(
view, inputTarget.id, textInputChannel, keyProcessor, mEditable, outAttrs);
new InputConnectionAdaptor(view, inputTarget.id, textInputChannel, mEditable, outAttrs);
outAttrs.initialSelStart = Selection.getSelectionStart(mEditable);
outAttrs.initialSelEnd = Selection.getSelectionEnd(mEditable);

Expand Down
16 changes: 13 additions & 3 deletions shell/platform/android/io/flutter/view/FlutterView.java
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,19 @@ public DartExecutor getDartExecutor() {
}

@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
return (isAttached() && androidKeyProcessor.onKeyEvent(event))
|| super.dispatchKeyEventPreIme(event);
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 FlutterNativeView getFlutterNativeView() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ public void respondsTrueWhenHandlingNewEvents() {
AndroidKeyProcessor processor =
new AndroidKeyProcessor(fakeView, fakeKeyEventChannel, mock(TextInputPlugin.class));

boolean result = processor.onKeyEvent(new FakeKeyEvent(KeyEvent.ACTION_DOWN, 65));
boolean result = processor.onKeyDown(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)).dispatchKeyEventPreIme(any(KeyEvent.class));
verify(fakeView, times(0)).dispatchKeyEvent(any(KeyEvent.class));
}

@Test
Expand Down Expand Up @@ -97,31 +97,31 @@ public View answer(InvocationOnMock invocation) throws Throwable {
ArgumentCaptor.forClass(KeyEventChannel.FlutterKeyEvent.class);
FakeKeyEvent fakeKeyEvent = new FakeKeyEvent(KeyEvent.ACTION_DOWN, 65);

boolean result = processor.onKeyEvent(fakeKeyEvent);
boolean result = processor.onKeyDown(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.dispatchKeyEventPreIme(any(KeyEvent.class)))
when(fakeView.dispatchKeyEvent(any(KeyEvent.class)))
.then(
new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
KeyEvent event = (KeyEvent) invocation.getArguments()[0];
assertEquals(fakeKeyEvent, event);
dispatchResult[0] = processor.onKeyEvent(event);
dispatchResult[0] = processor.onKeyDown(event);
return dispatchResult[0];
}
});

// Fake a response from the framework.
handlerCaptor.getValue().onKeyEventNotHandled(eventCaptor.getValue().eventId);
verify(fakeView, times(1)).dispatchKeyEventPreIme(fakeKeyEvent);
verify(fakeView, times(1)).dispatchKeyEvent(fakeKeyEvent);
assertEquals(false, dispatchResult[0]);
verify(fakeKeyEventChannel, times(0)).keyUp(any(KeyEventChannel.FlutterKeyEvent.class));
verify(fakeRootView, times(1)).dispatchKeyEventPreIme(fakeKeyEvent);
verify(fakeRootView, times(1)).dispatchKeyEvent(fakeKeyEvent);
}

public void synthesizesEventsWhenKeyUpNotHandled() {
Expand All @@ -147,31 +147,31 @@ public View answer(InvocationOnMock invocation) throws Throwable {
ArgumentCaptor.forClass(KeyEventChannel.FlutterKeyEvent.class);
FakeKeyEvent fakeKeyEvent = new FakeKeyEvent(KeyEvent.ACTION_UP, 65);

boolean result = processor.onKeyEvent(fakeKeyEvent);
boolean result = processor.onKeyUp(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.dispatchKeyEventPreIme(any(KeyEvent.class)))
when(fakeView.dispatchKeyEvent(any(KeyEvent.class)))
.then(
new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
KeyEvent event = (KeyEvent) invocation.getArguments()[0];
assertEquals(fakeKeyEvent, event);
dispatchResult[0] = processor.onKeyEvent(event);
dispatchResult[0] = processor.onKeyUp(event);
return dispatchResult[0];
}
});

// Fake a response from the framework.
handlerCaptor.getValue().onKeyEventNotHandled(eventCaptor.getValue().eventId);
verify(fakeView, times(1)).dispatchKeyEventPreIme(fakeKeyEvent);
verify(fakeView, times(1)).dispatchKeyEvent(fakeKeyEvent);
assertEquals(false, dispatchResult[0]);
verify(fakeKeyEventChannel, times(0)).keyUp(any(KeyEventChannel.FlutterKeyEvent.class));
verify(fakeRootView, times(1)).dispatchKeyEventPreIme(fakeKeyEvent);
verify(fakeRootView, times(1)).dispatchKeyEvent(fakeKeyEvent);
}

@NonNull
Expand Down
Loading

0 comments on commit 83b9df9

Please sign in to comment.