Skip to content

Commit 9cc7b8a

Browse files
committed
Revert "Reland: Use dispatchKeyEventPreIme, and handle keys sent to InputConnection.sendKeyEvent on Android (flutter#21979)"
This reverts commit 3cd70f2.
1 parent a82e917 commit 9cc7b8a

File tree

7 files changed

+101
-107
lines changed

7 files changed

+101
-107
lines changed

shell/platform/android/io/flutter/embedding/android/AndroidKeyProcessor.java

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ public AndroidKeyProcessor(
6666
@NonNull TextInputPlugin textInputPlugin) {
6767
this.keyEventChannel = keyEventChannel;
6868
this.textInputPlugin = textInputPlugin;
69-
textInputPlugin.setKeyEventProcessor(this);
70-
this.eventResponder = new EventResponder(view, textInputPlugin);
69+
this.eventResponder = new EventResponder(view);
7170
this.keyEventChannel.setEventResponseHandler(eventResponder);
7271
}
7372

@@ -81,33 +80,53 @@ public void destroy() {
8180
}
8281

8382
/**
84-
* Called when a key event is received by the {@link FlutterView} or the {@link
85-
* InputConnectionAdaptor}.
83+
* Called when a key up event is received by the {@link FlutterView}.
8684
*
8785
* @param keyEvent the Android key event to respond to.
8886
* @return true if the key event should not be propagated to other Android components. Delayed
8987
* synthesis events will return false, so that other components may handle them.
9088
*/
91-
public boolean onKeyEvent(@NonNull KeyEvent keyEvent) {
92-
int action = keyEvent.getAction();
93-
if (action != KeyEvent.ACTION_DOWN && action != KeyEvent.ACTION_UP) {
94-
// There is theoretically a KeyEvent.ACTION_MULTIPLE, but that shouldn't
95-
// be sent anymore anyhow.
89+
public boolean onKeyUp(@NonNull KeyEvent keyEvent) {
90+
if (eventResponder.dispatchingKeyEvent) {
91+
// Don't handle it if it is from our own delayed event synthesis.
9692
return false;
9793
}
94+
95+
Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar());
96+
KeyEventChannel.FlutterKeyEvent flutterEvent =
97+
new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter, eventIdSerial++);
98+
keyEventChannel.keyUp(flutterEvent);
99+
eventResponder.addEvent(flutterEvent.eventId, keyEvent);
100+
return true;
101+
}
102+
103+
/**
104+
* Called when a key down event is received by the {@link FlutterView}.
105+
*
106+
* @param keyEvent the Android key event to respond to.
107+
* @return true if the key event should not be propagated to other Android components. Delayed
108+
* synthesis events will return false, so that other components may handle them.
109+
*/
110+
public boolean onKeyDown(@NonNull KeyEvent keyEvent) {
98111
if (eventResponder.dispatchingKeyEvent) {
99112
// Don't handle it if it is from our own delayed event synthesis.
100113
return false;
101114
}
102115

116+
// If the textInputPlugin is still valid and accepting text, then we'll try
117+
// and send the key event to it, assuming that if the event can be sent,
118+
// that it has been handled.
119+
if (textInputPlugin.getLastInputConnection() != null
120+
&& textInputPlugin.getInputMethodManager().isAcceptingText()) {
121+
if (textInputPlugin.getLastInputConnection().sendKeyEvent(keyEvent)) {
122+
return true;
123+
}
124+
}
125+
103126
Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar());
104127
KeyEventChannel.FlutterKeyEvent flutterEvent =
105128
new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter, eventIdSerial++);
106-
if (action == KeyEvent.ACTION_DOWN) {
107-
keyEventChannel.keyDown(flutterEvent);
108-
} else {
109-
keyEventChannel.keyUp(flutterEvent);
110-
}
129+
keyEventChannel.keyDown(flutterEvent);
111130
eventResponder.addEvent(flutterEvent.eventId, keyEvent);
112131
return true;
113132
}
@@ -177,12 +196,10 @@ private static class EventResponder implements KeyEventChannel.EventResponseHand
177196
private static final long MAX_PENDING_EVENTS = 1000;
178197
final Deque<Entry<Long, KeyEvent>> pendingEvents = new ArrayDeque<Entry<Long, KeyEvent>>();
179198
@NonNull private final View view;
180-
@NonNull private final TextInputPlugin textInputPlugin;
181199
boolean dispatchingKeyEvent = false;
182200

183-
public EventResponder(@NonNull View view, @NonNull TextInputPlugin textInputPlugin) {
201+
public EventResponder(@NonNull View view) {
184202
this.view = view;
185-
this.textInputPlugin = textInputPlugin;
186203
}
187204

188205
/**
@@ -250,26 +267,12 @@ public void addEvent(long id, @NonNull KeyEvent event) {
250267
* @param event the event to be dispatched to the activity.
251268
*/
252269
public void dispatchKeyEvent(KeyEvent event) {
253-
// If the textInputPlugin is still valid and accepting text, then we'll try
254-
// and send the key event to it, assuming that if the event can be sent,
255-
// that it has been handled.
256-
if (textInputPlugin.getLastInputConnection() != null
257-
&& textInputPlugin.getInputMethodManager().isAcceptingText()) {
258-
dispatchingKeyEvent = true;
259-
boolean handled = textInputPlugin.getLastInputConnection().sendKeyEvent(event);
260-
dispatchingKeyEvent = false;
261-
if (handled) {
262-
return;
263-
}
264-
}
265-
266270
// Since the framework didn't handle it, dispatch the key again.
267271
if (view != null) {
268272
// Turn on dispatchingKeyEvent so that we don't dispatch to ourselves and
269273
// send it to the framework again.
270274
dispatchingKeyEvent = true;
271-
272-
view.getRootView().dispatchKeyEventPreIme(event);
275+
view.getRootView().dispatchKeyEvent(event);
273276
dispatchingKeyEvent = false;
274277
}
275278
}

shell/platform/android/io/flutter/embedding/android/FlutterView.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,27 @@ public boolean checkInputConnectionProxy(View view) {
721721
}
722722

723723
/**
724-
* Invoked when a hardware key is pressed or released, before the IME receives the key.
724+
* Invoked when key is released.
725+
*
726+
* <p>This method is typically invoked in response to the release of a physical keyboard key or a
727+
* D-pad button. It is generally not invoked when a virtual software keyboard is used, though a
728+
* software keyboard may choose to invoke this method in some situations.
729+
*
730+
* <p>{@link KeyEvent}s are sent from Android to Flutter. {@link AndroidKeyProcessor} may do some
731+
* additional work with the given {@link KeyEvent}, e.g., combine this {@code keyCode} with the
732+
* previous {@code keyCode} to generate a unicode combined character.
733+
*/
734+
@Override
735+
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
736+
if (!isAttachedToFlutterEngine()) {
737+
return super.onKeyUp(keyCode, event);
738+
}
739+
740+
return androidKeyProcessor.onKeyUp(event) || super.onKeyUp(keyCode, event);
741+
}
742+
743+
/**
744+
* Invoked when key is pressed.
725745
*
726746
* <p>This method is typically invoked in response to the press of a physical keyboard key or a
727747
* D-pad button. It is generally not invoked when a virtual software keyboard is used, though a
@@ -732,13 +752,12 @@ public boolean checkInputConnectionProxy(View view) {
732752
* previous {@code keyCode} to generate a unicode combined character.
733753
*/
734754
@Override
735-
public boolean dispatchKeyEventPreIme(KeyEvent event) {
736-
// If the key processor doesn't handle it, then send it on to the
737-
// superclass. The key processor will typically handle all events except
738-
// those where it has re-dispatched the event after receiving a reply from
739-
// the framework that the framework did not handle it.
740-
return (isAttachedToFlutterEngine() && androidKeyProcessor.onKeyEvent(event))
741-
|| super.dispatchKeyEventPreIme(event);
755+
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
756+
if (!isAttachedToFlutterEngine()) {
757+
return super.onKeyDown(keyCode, event);
758+
}
759+
760+
return androidKeyProcessor.onKeyDown(event) || super.onKeyDown(keyCode, event);
742761
}
743762

744763
/**

shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,13 @@
2727
import android.view.inputmethod.InputMethodManager;
2828
import android.view.inputmethod.InputMethodSubtype;
2929
import io.flutter.Log;
30-
import io.flutter.embedding.android.AndroidKeyProcessor;
3130
import io.flutter.embedding.engine.FlutterJNI;
3231
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
3332

3433
class InputConnectionAdaptor extends BaseInputConnection {
3534
private final View mFlutterView;
3635
private final int mClient;
3736
private final TextInputChannel textInputChannel;
38-
private final AndroidKeyProcessor keyProcessor;
3937
private final Editable mEditable;
4038
private final EditorInfo mEditorInfo;
4139
private int mBatchCount;
@@ -99,7 +97,6 @@ public InputConnectionAdaptor(
9997
View view,
10098
int client,
10199
TextInputChannel textInputChannel,
102-
AndroidKeyProcessor keyProcessor,
103100
Editable editable,
104101
EditorInfo editorInfo,
105102
FlutterJNI flutterJNI) {
@@ -110,7 +107,6 @@ public InputConnectionAdaptor(
110107
mEditable = editable;
111108
mEditorInfo = editorInfo;
112109
mBatchCount = 0;
113-
this.keyProcessor = keyProcessor;
114110
this.flutterTextUtils = new FlutterTextUtils(flutterJNI);
115111
// We create a dummy Layout with max width so that the selection
116112
// shifting acts as if all text were in one line.
@@ -132,10 +128,9 @@ public InputConnectionAdaptor(
132128
View view,
133129
int client,
134130
TextInputChannel textInputChannel,
135-
AndroidKeyProcessor keyProcessor,
136131
Editable editable,
137132
EditorInfo editorInfo) {
138-
this(view, client, textInputChannel, keyProcessor, editable, editorInfo, new FlutterJNI());
133+
this(view, client, textInputChannel, editable, editorInfo, new FlutterJNI());
139134
}
140135

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

329324
@Override
330325
public boolean sendKeyEvent(KeyEvent event) {
331-
// Give the key processor a chance to process this event. It will send it
332-
// to the framework to be handled and return true. If the framework ends up
333-
// not handling it, the processor will re-send the event, this time
334-
// returning false so that it can be processed here.
335-
if (keyProcessor != null && keyProcessor.onKeyEvent(event)) {
336-
return true;
337-
}
338-
339326
markDirty();
340327
if (event.getAction() == KeyEvent.ACTION_DOWN) {
341328
if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {

shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import androidx.annotation.NonNull;
2929
import androidx.annotation.Nullable;
3030
import androidx.annotation.VisibleForTesting;
31-
import io.flutter.embedding.android.AndroidKeyProcessor;
3231
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
3332
import io.flutter.plugin.platform.PlatformViewsController;
3433
import java.util.HashMap;
@@ -49,7 +48,6 @@ public class TextInputPlugin {
4948
@Nullable private Rect lastClientRect;
5049
private final boolean restartAlwaysRequired;
5150
private ImeSyncDeferringInsetsCallback imeSyncCallback;
52-
private AndroidKeyProcessor keyProcessor;
5351

5452
// When true following calls to createInputConnection will return the cached lastInputConnection
5553
// if the input
@@ -174,15 +172,6 @@ ImeSyncDeferringInsetsCallback getImeSyncCallback() {
174172
return imeSyncCallback;
175173
}
176174

177-
@NonNull
178-
public AndroidKeyProcessor getKeyEventProcessor() {
179-
return keyProcessor;
180-
}
181-
182-
public void setKeyEventProcessor(AndroidKeyProcessor processor) {
183-
keyProcessor = processor;
184-
}
185-
186175
/**
187176
* Use the current platform view input connection until unlockPlatformViewInputConnection is
188177
* called.
@@ -324,8 +313,7 @@ public InputConnection createInputConnection(View view, EditorInfo outAttrs) {
324313
outAttrs.imeOptions |= enterAction;
325314

326315
InputConnectionAdaptor connection =
327-
new InputConnectionAdaptor(
328-
view, inputTarget.id, textInputChannel, keyProcessor, mEditable, outAttrs);
316+
new InputConnectionAdaptor(view, inputTarget.id, textInputChannel, mEditable, outAttrs);
329317
outAttrs.initialSelStart = Selection.getSelectionStart(mEditable);
330318
outAttrs.initialSelEnd = Selection.getSelectionEnd(mEditable);
331319

shell/platform/android/io/flutter/view/FlutterView.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,9 +269,19 @@ public DartExecutor getDartExecutor() {
269269
}
270270

271271
@Override
272-
public boolean dispatchKeyEventPreIme(KeyEvent event) {
273-
return (isAttached() && androidKeyProcessor.onKeyEvent(event))
274-
|| super.dispatchKeyEventPreIme(event);
272+
public boolean onKeyUp(int keyCode, KeyEvent event) {
273+
if (!isAttached()) {
274+
return super.onKeyUp(keyCode, event);
275+
}
276+
return androidKeyProcessor.onKeyUp(event) || super.onKeyUp(keyCode, event);
277+
}
278+
279+
@Override
280+
public boolean onKeyDown(int keyCode, KeyEvent event) {
281+
if (!isAttached()) {
282+
return super.onKeyDown(keyCode, event);
283+
}
284+
return androidKeyProcessor.onKeyDown(event) || super.onKeyDown(keyCode, event);
275285
}
276286

277287
public FlutterNativeView getFlutterNativeView() {

shell/platform/android/test/io/flutter/embedding/android/AndroidKeyProcessorTest.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ public void respondsTrueWhenHandlingNewEvents() {
5151
AndroidKeyProcessor processor =
5252
new AndroidKeyProcessor(fakeView, fakeKeyEventChannel, mock(TextInputPlugin.class));
5353

54-
boolean result = processor.onKeyEvent(new FakeKeyEvent(KeyEvent.ACTION_DOWN, 65));
54+
boolean result = processor.onKeyDown(new FakeKeyEvent(KeyEvent.ACTION_DOWN, 65));
5555
assertEquals(true, result);
5656
verify(fakeKeyEventChannel, times(1)).keyDown(any(KeyEventChannel.FlutterKeyEvent.class));
5757
verify(fakeKeyEventChannel, times(0)).keyUp(any(KeyEventChannel.FlutterKeyEvent.class));
58-
verify(fakeView, times(0)).dispatchKeyEventPreIme(any(KeyEvent.class));
58+
verify(fakeView, times(0)).dispatchKeyEvent(any(KeyEvent.class));
5959
}
6060

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

100-
boolean result = processor.onKeyEvent(fakeKeyEvent);
100+
boolean result = processor.onKeyDown(fakeKeyEvent);
101101
assertEquals(true, result);
102102

103103
// Capture the FlutterKeyEvent so we can find out its event ID to use when
104104
// faking our response.
105105
verify(fakeKeyEventChannel, times(1)).keyDown(eventCaptor.capture());
106106
boolean[] dispatchResult = {true};
107-
when(fakeView.dispatchKeyEventPreIme(any(KeyEvent.class)))
107+
when(fakeView.dispatchKeyEvent(any(KeyEvent.class)))
108108
.then(
109109
new Answer<Boolean>() {
110110
@Override
111111
public Boolean answer(InvocationOnMock invocation) throws Throwable {
112112
KeyEvent event = (KeyEvent) invocation.getArguments()[0];
113113
assertEquals(fakeKeyEvent, event);
114-
dispatchResult[0] = processor.onKeyEvent(event);
114+
dispatchResult[0] = processor.onKeyDown(event);
115115
return dispatchResult[0];
116116
}
117117
});
118118

119119
// Fake a response from the framework.
120120
handlerCaptor.getValue().onKeyEventNotHandled(eventCaptor.getValue().eventId);
121-
verify(fakeView, times(1)).dispatchKeyEventPreIme(fakeKeyEvent);
121+
verify(fakeView, times(1)).dispatchKeyEvent(fakeKeyEvent);
122122
assertEquals(false, dispatchResult[0]);
123123
verify(fakeKeyEventChannel, times(0)).keyUp(any(KeyEventChannel.FlutterKeyEvent.class));
124-
verify(fakeRootView, times(1)).dispatchKeyEventPreIme(fakeKeyEvent);
124+
verify(fakeRootView, times(1)).dispatchKeyEvent(fakeKeyEvent);
125125
}
126126

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

150-
boolean result = processor.onKeyEvent(fakeKeyEvent);
150+
boolean result = processor.onKeyUp(fakeKeyEvent);
151151
assertEquals(true, result);
152152

153153
// Capture the FlutterKeyEvent so we can find out its event ID to use when
154154
// faking our response.
155155
verify(fakeKeyEventChannel, times(1)).keyUp(eventCaptor.capture());
156156
boolean[] dispatchResult = {true};
157-
when(fakeView.dispatchKeyEventPreIme(any(KeyEvent.class)))
157+
when(fakeView.dispatchKeyEvent(any(KeyEvent.class)))
158158
.then(
159159
new Answer<Boolean>() {
160160
@Override
161161
public Boolean answer(InvocationOnMock invocation) throws Throwable {
162162
KeyEvent event = (KeyEvent) invocation.getArguments()[0];
163163
assertEquals(fakeKeyEvent, event);
164-
dispatchResult[0] = processor.onKeyEvent(event);
164+
dispatchResult[0] = processor.onKeyUp(event);
165165
return dispatchResult[0];
166166
}
167167
});
168168

169169
// Fake a response from the framework.
170170
handlerCaptor.getValue().onKeyEventNotHandled(eventCaptor.getValue().eventId);
171-
verify(fakeView, times(1)).dispatchKeyEventPreIme(fakeKeyEvent);
171+
verify(fakeView, times(1)).dispatchKeyEvent(fakeKeyEvent);
172172
assertEquals(false, dispatchResult[0]);
173173
verify(fakeKeyEventChannel, times(0)).keyUp(any(KeyEventChannel.FlutterKeyEvent.class));
174-
verify(fakeRootView, times(1)).dispatchKeyEventPreIme(fakeKeyEvent);
174+
verify(fakeRootView, times(1)).dispatchKeyEvent(fakeKeyEvent);
175175
}
176176

177177
@NonNull

0 commit comments

Comments
 (0)