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

Commit 18200e7

Browse files
authored
Revert "Implement delayed event synthesis key event handling for Android (#19024)" (#19956)
This reverts commit 8825f91 because it breaks flutter_gallery__back_button_memory and a customer test.
1 parent 0318e7e commit 18200e7

File tree

11 files changed

+46
-651
lines changed

11 files changed

+46
-651
lines changed

shell/platform/android/BUILD.gn

-2
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,6 @@ action("robolectric_tests") {
430430
sources = [
431431
"test/io/flutter/FlutterTestSuite.java",
432432
"test/io/flutter/SmokeTest.java",
433-
"test/io/flutter/embedding/android/AndroidKeyProcessorTest.java",
434433
"test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java",
435434
"test/io/flutter/embedding/android/FlutterActivityTest.java",
436435
"test/io/flutter/embedding/android/FlutterAndroidComponentTest.java",
@@ -448,7 +447,6 @@ action("robolectric_tests") {
448447
"test/io/flutter/embedding/engine/dart/DartExecutorTest.java",
449448
"test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java",
450449
"test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java",
451-
"test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java",
452450
"test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java",
453451
"test/io/flutter/external/FlutterLaunchTests.java",
454452
"test/io/flutter/plugin/common/StandardMessageCodecTest.java",

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

+8-182
Original file line numberDiff line numberDiff line change
@@ -4,122 +4,37 @@
44

55
package io.flutter.embedding.android;
66

7-
import android.util.Log;
87
import android.view.KeyCharacterMap;
98
import android.view.KeyEvent;
10-
import android.view.View;
119
import androidx.annotation.NonNull;
1210
import androidx.annotation.Nullable;
1311
import io.flutter.embedding.engine.systemchannels.KeyEventChannel;
1412
import io.flutter.plugin.editing.TextInputPlugin;
15-
import java.util.AbstractMap.SimpleImmutableEntry;
16-
import java.util.ArrayDeque;
17-
import java.util.Deque;
18-
import java.util.Map.Entry;
1913

20-
/**
21-
* A class to process key events from Android, passing them to the framework as messages using
22-
* {@link KeyEventChannel}.
23-
*
24-
* <p>A class that sends Android key events to the framework, and re-dispatches those not handled by
25-
* the framework.
26-
*
27-
* <p>Flutter uses asynchronous event handling to avoid blocking the UI thread, but Android requires
28-
* that events are handled synchronously. So, when a key event is received by Flutter, it tells
29-
* Android synchronously that the key has been handled so that it won't propagate to other
30-
* components. Flutter then uses "delayed event synthesis", where it sends the event to the
31-
* framework, and if the framework responds that it has not handled the event, then this class
32-
* synthesizes a new event to send to Android, without handling it this time.
33-
*/
3414
public class AndroidKeyProcessor {
35-
private static final String TAG = "AndroidKeyProcessor";
36-
private static long eventIdSerial = 0;
37-
3815
@NonNull private final KeyEventChannel keyEventChannel;
3916
@NonNull private final TextInputPlugin textInputPlugin;
4017
private int combiningCharacter;
41-
@NonNull private EventResponder eventResponder;
4218

43-
/**
44-
* Constructor for AndroidKeyProcessor.
45-
*
46-
* <p>The view is used as the destination to send the synthesized key to. This means that the the
47-
* next thing in the focus chain will get the event when the framework returns false from
48-
* onKeyDown/onKeyUp
49-
*
50-
* <p>It is possible that that in the middle of the async round trip, the focus chain could
51-
* change, and instead of the native widget that was "next" when the event was fired getting the
52-
* event, it may be the next widget when the event is synthesized that gets it. In practice, this
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.
55-
*
56-
* @param view takes the activity to use for re-dispatching of events that were not handled by the
57-
* framework.
58-
* @param keyEventChannel the event channel to listen to for new key events.
59-
* @param textInputPlugin a plugin, which, if set, is given key events before the framework is,
60-
* and if it has a valid input connection and is accepting text, then it will handle the event
61-
* and the framework will not receive it.
62-
*/
6319
public AndroidKeyProcessor(
64-
@NonNull View view,
65-
@NonNull KeyEventChannel keyEventChannel,
66-
@NonNull TextInputPlugin textInputPlugin) {
20+
@NonNull KeyEventChannel keyEventChannel, @NonNull TextInputPlugin textInputPlugin) {
6721
this.keyEventChannel = keyEventChannel;
6822
this.textInputPlugin = textInputPlugin;
69-
this.eventResponder = new EventResponder(view);
70-
this.keyEventChannel.setEventResponseHandler(eventResponder);
7123
}
7224

73-
/**
74-
* Called when a key up event is received by the {@link FlutterView}.
75-
*
76-
* @param keyEvent the Android key event to respond to.
77-
* @return true if the key event should not be propagated to other Android components. Delayed
78-
* synthesis events will return false, so that other components may handle them.
79-
*/
80-
public boolean onKeyUp(@NonNull KeyEvent keyEvent) {
81-
if (eventResponder.dispatchingKeyEvent) {
82-
// Don't handle it if it is from our own delayed event synthesis.
83-
return false;
84-
}
85-
25+
public void onKeyUp(@NonNull KeyEvent keyEvent) {
8626
Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar());
87-
KeyEventChannel.FlutterKeyEvent flutterEvent =
88-
new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter, eventIdSerial++);
89-
keyEventChannel.keyUp(flutterEvent);
90-
eventResponder.addEvent(flutterEvent.eventId, keyEvent);
91-
return true;
27+
keyEventChannel.keyUp(new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter));
9228
}
9329

94-
/**
95-
* Called when a key down event is received by the {@link FlutterView}.
96-
*
97-
* @param keyEvent the Android key event to respond to.
98-
* @return true if the key event should not be propagated to other Android components. Delayed
99-
* synthesis events will return false, so that other components may handle them.
100-
*/
101-
public boolean onKeyDown(@NonNull KeyEvent keyEvent) {
102-
if (eventResponder.dispatchingKeyEvent) {
103-
// Don't handle it if it is from our own delayed event synthesis.
104-
return false;
105-
}
106-
107-
// If the textInputPlugin is still valid and accepting text, then we'll try
108-
// and send the key event to it, assuming that if the event can be sent,
109-
// that it has been handled.
30+
public void onKeyDown(@NonNull KeyEvent keyEvent) {
11031
if (textInputPlugin.getLastInputConnection() != null
11132
&& textInputPlugin.getInputMethodManager().isAcceptingText()) {
112-
if (textInputPlugin.getLastInputConnection().sendKeyEvent(keyEvent)) {
113-
return true;
114-
}
33+
textInputPlugin.getLastInputConnection().sendKeyEvent(keyEvent);
11534
}
11635

11736
Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar());
118-
KeyEventChannel.FlutterKeyEvent flutterEvent =
119-
new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter, eventIdSerial++);
120-
keyEventChannel.keyDown(flutterEvent);
121-
eventResponder.addEvent(flutterEvent.eventId, keyEvent);
122-
return true;
37+
keyEventChannel.keyDown(new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter));
12338
}
12439

12540
/**
@@ -155,7 +70,7 @@ private Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoi
15570
return null;
15671
}
15772

158-
char complexCharacter = (char) newCharacterCodePoint;
73+
Character complexCharacter = (char) newCharacterCodePoint;
15974
boolean isNewCodePointACombiningCharacter =
16075
(newCharacterCodePoint & KeyCharacterMap.COMBINING_ACCENT) != 0;
16176
if (isNewCodePointACombiningCharacter) {
@@ -167,8 +82,7 @@ private Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoi
16782
combiningCharacter = plainCodePoint;
16883
}
16984
} else {
170-
// The new character is a regular character. Apply combiningCharacter to it, if
171-
// it exists.
85+
// The new character is a regular character. Apply combiningCharacter to it, if it exists.
17286
if (combiningCharacter != 0) {
17387
int combinedChar = KeyCharacterMap.getDeadChar(combiningCharacter, newCharacterCodePoint);
17488
if (combinedChar > 0) {
@@ -180,92 +94,4 @@ private Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoi
18094

18195
return complexCharacter;
18296
}
183-
184-
private static class EventResponder implements KeyEventChannel.EventResponseHandler {
185-
// The maximum number of pending events that are held before starting to
186-
// complain.
187-
private static final long MAX_PENDING_EVENTS = 1000;
188-
final Deque<Entry<Long, KeyEvent>> pendingEvents = new ArrayDeque<Entry<Long, KeyEvent>>();
189-
@NonNull private final View view;
190-
boolean dispatchingKeyEvent = false;
191-
192-
public EventResponder(@NonNull View view) {
193-
this.view = view;
194-
}
195-
196-
/**
197-
* Removes the pending event with the given id from the cache of pending events.
198-
*
199-
* @param id the id of the event to be removed.
200-
*/
201-
private KeyEvent removePendingEvent(long id) {
202-
if (pendingEvents.getFirst().getKey() != id) {
203-
throw new AssertionError(
204-
"Event response received out of order. Should have seen event "
205-
+ pendingEvents.getFirst().getKey()
206-
+ " first. Instead, received "
207-
+ id);
208-
}
209-
return pendingEvents.removeFirst().getValue();
210-
}
211-
212-
/**
213-
* Called whenever the framework responds that a given key event was handled by the framework.
214-
*
215-
* @param id the event id of the event to be marked as being handled by the framework. Must not
216-
* be null.
217-
*/
218-
@Override
219-
public void onKeyEventHandled(long id) {
220-
removePendingEvent(id);
221-
}
222-
223-
/**
224-
* Called whenever the framework responds that a given key event wasn't handled by the
225-
* framework.
226-
*
227-
* @param id the event id of the event to be marked as not being handled by the framework. Must
228-
* not be null.
229-
*/
230-
@Override
231-
public void onKeyEventNotHandled(long id) {
232-
dispatchKeyEvent(removePendingEvent(id));
233-
}
234-
235-
/** Adds an Android key event with an id to the event responder to wait for a response. */
236-
public void addEvent(long id, @NonNull KeyEvent event) {
237-
if (pendingEvents.size() > 0 && pendingEvents.getFirst().getKey() >= id) {
238-
throw new AssertionError(
239-
"New events must have ids greater than the most recent pending event. New id "
240-
+ id
241-
+ " is less than or equal to the last event id of "
242-
+ pendingEvents.getFirst().getKey());
243-
}
244-
pendingEvents.addLast(new SimpleImmutableEntry<Long, KeyEvent>(id, event));
245-
if (pendingEvents.size() > MAX_PENDING_EVENTS) {
246-
Log.e(
247-
TAG,
248-
"There are "
249-
+ pendingEvents.size()
250-
+ " keyboard events "
251-
+ "that have not yet received a response. Are responses being sent?");
252-
}
253-
}
254-
255-
/**
256-
* Dispatches the event to the activity associated with the context.
257-
*
258-
* @param event the event to be dispatched to the activity.
259-
*/
260-
public void dispatchKeyEvent(KeyEvent event) {
261-
// Since the framework didn't handle it, dispatch the key again.
262-
if (view != null) {
263-
// Turn on dispatchingKeyEvent so that we don't dispatch to ourselves and
264-
// send it to the framework again.
265-
dispatchingKeyEvent = true;
266-
view.dispatchKeyEvent(event);
267-
dispatchingKeyEvent = false;
268-
}
269-
}
270-
}
27197
}

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,8 @@ public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
679679
return super.onKeyUp(keyCode, event);
680680
}
681681

682-
return androidKeyProcessor.onKeyUp(event) || super.onKeyUp(keyCode, event);
682+
androidKeyProcessor.onKeyUp(event);
683+
return super.onKeyUp(keyCode, event);
683684
}
684685

685686
/**
@@ -699,7 +700,8 @@ public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
699700
return super.onKeyDown(keyCode, event);
700701
}
701702

702-
return androidKeyProcessor.onKeyDown(event) || super.onKeyDown(keyCode, event);
703+
androidKeyProcessor.onKeyDown(event);
704+
return super.onKeyDown(keyCode, event);
703705
}
704706

705707
/**
@@ -849,7 +851,7 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
849851
this.flutterEngine.getPlatformViewsController());
850852
localizationPlugin = this.flutterEngine.getLocalizationPlugin();
851853
androidKeyProcessor =
852-
new AndroidKeyProcessor(this, this.flutterEngine.getKeyEventChannel(), textInputPlugin);
854+
new AndroidKeyProcessor(this.flutterEngine.getKeyEventChannel(), textInputPlugin);
853855
androidTouchProcessor =
854856
new AndroidTouchProcessor(this.flutterEngine.getRenderer(), /*trackMotionEvents=*/ false);
855857
accessibilityBridge =

shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java

-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ public void onMessage(
7373
break;
7474
}
7575
}
76-
reply.reply(null);
7776
}
7877
};
7978

0 commit comments

Comments
 (0)