Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 6c8342f

Browse files
authored
Revert "Fix race condition in key event handling on Android (#22658)" (#22823)
This reverts commit 40fa345 (#22658) because it breaks some Google tests. Will investigate and re-land.
1 parent 34f49a1 commit 6c8342f

File tree

8 files changed

+254
-169
lines changed

8 files changed

+254
-169
lines changed

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

Lines changed: 52 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
import io.flutter.Log;
1313
import io.flutter.embedding.engine.systemchannels.KeyEventChannel;
1414
import io.flutter.plugin.editing.TextInputPlugin;
15+
import java.util.AbstractMap.SimpleImmutableEntry;
1516
import java.util.ArrayDeque;
1617
import java.util.Deque;
18+
import java.util.Map.Entry;
1719

1820
/**
1921
* A class to process key events from Android, passing them to the framework as messages using
@@ -31,6 +33,7 @@
3133
*/
3234
public class AndroidKeyProcessor {
3335
private static final String TAG = "AndroidKeyProcessor";
36+
private static long eventIdSerial = 0;
3437

3538
@NonNull private final KeyEventChannel keyEventChannel;
3639
@NonNull private final TextInputPlugin textInputPlugin;
@@ -47,8 +50,8 @@ public class AndroidKeyProcessor {
4750
* <p>It is possible that that in the middle of the async round trip, the focus chain could
4851
* change, and instead of the native widget that was "next" when the event was fired getting the
4952
* event, it may be the next widget when the event is synthesized that gets it. In practice, this
50-
* shouldn't be a huge problem, as this is an unlikely occurrence to happen without user input,
51-
* and it may actually be desired behavior, but it is possible.
53+
* shouldn't be a huge problem, as this is an unlikely occurance to happen without user input, and
54+
* it may actually be desired behavior, but it is possible.
5255
*
5356
* @param view takes the activity to use for re-dispatching of events that were not handled by the
5457
* framework.
@@ -93,39 +96,23 @@ public boolean onKeyEvent(@NonNull KeyEvent keyEvent) {
9396
// case the theory is wrong.
9497
return false;
9598
}
96-
if (eventResponder.isHeadEvent(keyEvent)) {
97-
// If the keyEvent is at the head of the queue of pending events we've seen,
98-
// and has the same id, then we know that this is a re-dispatched keyEvent, and
99-
// we shouldn't respond to it, but we should remove it from tracking now.
100-
eventResponder.removeHeadEvent();
99+
if (eventResponder.dispatchingKeyEvent) {
100+
// Don't handle it if it is from our own delayed event dispatch.
101101
return false;
102102
}
103103

104104
Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar());
105105
KeyEventChannel.FlutterKeyEvent flutterEvent =
106-
new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter);
107-
108-
eventResponder.addEvent(keyEvent);
106+
new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter, eventIdSerial++);
109107
if (action == KeyEvent.ACTION_DOWN) {
110108
keyEventChannel.keyDown(flutterEvent);
111109
} else {
112110
keyEventChannel.keyUp(flutterEvent);
113111
}
112+
eventResponder.addEvent(flutterEvent.eventId, keyEvent);
114113
return true;
115114
}
116115

117-
/**
118-
* Returns whether or not the given event is currently being processed by this key processor. This
119-
* is used to determine if a new key event sent to the {@link InputConnectionAdaptor} originates
120-
* from a hardware key event, or a soft keyboard editing event.
121-
*
122-
* @param event the event to check for being the current event.
123-
* @return
124-
*/
125-
public boolean isCurrentEvent(@NonNull KeyEvent event) {
126-
return eventResponder.isHeadEvent(event);
127-
}
128-
129116
/**
130117
* Applies the given Unicode character in {@code newCharacterCodePoint} to a previously entered
131118
* Unicode combining character and returns the combination of these characters if a combination
@@ -189,63 +176,65 @@ private static class EventResponder implements KeyEventChannel.EventResponseHand
189176
// The maximum number of pending events that are held before starting to
190177
// complain.
191178
private static final long MAX_PENDING_EVENTS = 1000;
192-
final Deque<KeyEvent> pendingEvents = new ArrayDeque<KeyEvent>();
179+
final Deque<Entry<Long, KeyEvent>> pendingEvents = new ArrayDeque<Entry<Long, KeyEvent>>();
193180
@NonNull private final View view;
194181
@NonNull private final TextInputPlugin textInputPlugin;
182+
boolean dispatchingKeyEvent = false;
195183

196184
public EventResponder(@NonNull View view, @NonNull TextInputPlugin textInputPlugin) {
197185
this.view = view;
198186
this.textInputPlugin = textInputPlugin;
199187
}
200188

201-
/** Removes the first pending event from the cache of pending events. */
202-
private KeyEvent removeHeadEvent() {
203-
return pendingEvents.removeFirst();
204-
}
205-
206-
private KeyEvent checkIsHeadEvent(KeyEvent event) {
207-
if (pendingEvents.size() == 0) {
208-
throw new AssertionError(
209-
"Event response received when no events are in the queue. Received event " + event);
210-
}
211-
if (pendingEvents.getFirst() != event) {
189+
/**
190+
* Removes the pending event with the given id from the cache of pending events.
191+
*
192+
* @param id the id of the event to be removed.
193+
*/
194+
private KeyEvent removePendingEvent(long id) {
195+
if (pendingEvents.getFirst().getKey() != id) {
212196
throw new AssertionError(
213197
"Event response received out of order. Should have seen event "
214-
+ pendingEvents.getFirst()
198+
+ pendingEvents.getFirst().getKey()
215199
+ " first. Instead, received "
216-
+ event);
200+
+ id);
217201
}
218-
return pendingEvents.getFirst();
219-
}
220-
221-
private boolean isHeadEvent(KeyEvent event) {
222-
return pendingEvents.size() > 0 && pendingEvents.getFirst() == event;
202+
return pendingEvents.removeFirst().getValue();
223203
}
224204

225205
/**
226206
* Called whenever the framework responds that a given key event was handled by the framework.
227207
*
228-
* @param event the event to be marked as being handled by the framework. Must not be null.
208+
* @param id the event id of the event to be marked as being handled by the framework. Must not
209+
* be null.
229210
*/
230211
@Override
231-
public void onKeyEventHandled(KeyEvent event) {
232-
removeHeadEvent();
212+
public void onKeyEventHandled(long id) {
213+
removePendingEvent(id);
233214
}
234215

235216
/**
236217
* Called whenever the framework responds that a given key event wasn't handled by the
237218
* framework.
238219
*
239-
* @param event the event to be marked as not being handled by the framework. Must not be null.
220+
* @param id the event id of the event to be marked as not being handled by the framework. Must
221+
* not be null.
240222
*/
241223
@Override
242-
public void onKeyEventNotHandled(KeyEvent event) {
243-
redispatchKeyEvent(checkIsHeadEvent(event));
224+
public void onKeyEventNotHandled(long id) {
225+
dispatchKeyEvent(removePendingEvent(id));
244226
}
245227

246-
/** Adds an Android key event to the event responder to wait for a response. */
247-
public void addEvent(@NonNull KeyEvent event) {
248-
pendingEvents.addLast(event);
228+
/** Adds an Android key event with an id to the event responder to wait for a response. */
229+
public void addEvent(long id, @NonNull KeyEvent event) {
230+
if (pendingEvents.size() > 0 && pendingEvents.getFirst().getKey() >= id) {
231+
throw new AssertionError(
232+
"New events must have ids greater than the most recent pending event. New id "
233+
+ id
234+
+ " is less than or equal to the last event id of "
235+
+ pendingEvents.getFirst().getKey());
236+
}
237+
pendingEvents.addLast(new SimpleImmutableEntry<Long, KeyEvent>(id, event));
249238
if (pendingEvents.size() > MAX_PENDING_EVENTS) {
250239
Log.e(
251240
TAG,
@@ -261,21 +250,27 @@ public void addEvent(@NonNull KeyEvent event) {
261250
*
262251
* @param event the event to be dispatched to the activity.
263252
*/
264-
private void redispatchKeyEvent(KeyEvent event) {
253+
public void dispatchKeyEvent(KeyEvent event) {
265254
// If the textInputPlugin is still valid and accepting text, then we'll try
266255
// and send the key event to it, assuming that if the event can be sent,
267256
// that it has been handled.
268-
if (textInputPlugin.getInputMethodManager().isAcceptingText()
269-
&& textInputPlugin.getLastInputConnection() != null
270-
&& textInputPlugin.getLastInputConnection().sendKeyEvent(event)) {
271-
// The event was handled, so we can remove it from the queue.
272-
removeHeadEvent();
273-
return;
257+
if (textInputPlugin.getLastInputConnection() != null
258+
&& textInputPlugin.getInputMethodManager().isAcceptingText()) {
259+
dispatchingKeyEvent = true;
260+
boolean handled = textInputPlugin.getLastInputConnection().sendKeyEvent(event);
261+
dispatchingKeyEvent = false;
262+
if (handled) {
263+
return;
264+
}
274265
}
275266

276267
// Since the framework didn't handle it, dispatch the event again.
277268
if (view != null) {
269+
// Turn on dispatchingKeyEvent so that we don't dispatch to ourselves and
270+
// send it to the framework again.
271+
dispatchingKeyEvent = true;
278272
view.getRootView().dispatchKeyEvent(event);
273+
dispatchingKeyEvent = false;
279274
}
280275
}
281276
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,11 @@ public boolean dispatchKeyEvent(KeyEvent event) {
739739
} else if (event.getAction() == KeyEvent.ACTION_UP) {
740740
// Stop tracking the event.
741741
getKeyDispatcherState().handleUpEvent(event);
742+
if (!event.isTracking() || event.isCanceled()) {
743+
// Don't send the event to the key processor if it was canceled, or no
744+
// longer being tracked.
745+
return super.dispatchKeyEvent(event);
746+
}
742747
}
743748
// If the key processor doesn't handle it, then send it on to the
744749
// superclass. The key processor will typically handle all events except

0 commit comments

Comments
 (0)