1111import androidx .annotation .Nullable ;
1212import io .flutter .Log ;
1313import io .flutter .embedding .engine .systemchannels .KeyEventChannel ;
14+ import io .flutter .embedding .engine .systemchannels .KeyEventChannel .FlutterKeyEvent ;
1415import io .flutter .plugin .editing .TextInputPlugin ;
1516import java .util .AbstractMap .SimpleImmutableEntry ;
1617import java .util .ArrayDeque ;
3334 */
3435public class AndroidKeyProcessor {
3536 private static final String TAG = "AndroidKeyProcessor" ;
36- private static long eventIdSerial = 0 ;
3737
3838 @ NonNull private final KeyEventChannel keyEventChannel ;
3939 @ NonNull private final TextInputPlugin textInputPlugin ;
@@ -50,8 +50,8 @@ public class AndroidKeyProcessor {
5050 * <p>It is possible that that in the middle of the async round trip, the focus chain could
5151 * change, and instead of the native widget that was "next" when the event was fired getting the
5252 * 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.
53+ * shouldn't be a huge problem, as this is an unlikely occurrence to happen without user input,
54+ * and it may actually be desired behavior, but it is possible.
5555 *
5656 * @param view takes the activity to use for re-dispatching of events that were not handled by the
5757 * framework.
@@ -88,31 +88,49 @@ public void destroy() {
8888 * @return true if the key event should not be propagated to other Android components. Delayed
8989 * synthesis events will return false, so that other components may handle them.
9090 */
91- public boolean onKeyEvent (@ NonNull KeyEvent keyEvent ) {
92- int action = keyEvent .getAction ();
93- if (action != KeyEvent .ACTION_DOWN && action != KeyEvent .ACTION_UP ) {
91+ public boolean onKeyEvent (@ NonNull KeyEvent event ) {
92+ int action = event .getAction ();
93+ if (action != event .ACTION_DOWN && action != event .ACTION_UP ) {
9494 // There is theoretically a KeyEvent.ACTION_MULTIPLE, but theoretically
9595 // that isn't sent by Android anymore, so this is just for protection in
9696 // case the theory is wrong.
9797 return false ;
9898 }
99- if (eventResponder .dispatchingKeyEvent ) {
100- // Don't handle it if it is from our own delayed event dispatch.
99+ long eventId = FlutterKeyEvent .computeEventId (event );
100+ if (eventResponder .isHeadEvent (eventId )) {
101+ // If the event is at the head of the queue of pending events we've seen,
102+ // and has the same id, then we know that this is a re-dispatched event, and
103+ // we shouldn't respond to it, but we should remove it from tracking now.
104+ eventResponder .removePendingEvent (eventId );
101105 return false ;
102106 }
103107
104- Character complexCharacter = applyCombiningCharacterToBaseCharacter (keyEvent .getUnicodeChar ());
108+ Character complexCharacter = applyCombiningCharacterToBaseCharacter (event .getUnicodeChar ());
105109 KeyEventChannel .FlutterKeyEvent flutterEvent =
106- new KeyEventChannel .FlutterKeyEvent (keyEvent , complexCharacter , eventIdSerial ++);
110+ new KeyEventChannel .FlutterKeyEvent (event , complexCharacter );
111+
112+ eventResponder .addEvent (flutterEvent .eventId , event );
107113 if (action == KeyEvent .ACTION_DOWN ) {
108114 keyEventChannel .keyDown (flutterEvent );
109115 } else {
110116 keyEventChannel .keyUp (flutterEvent );
111117 }
112- eventResponder .addEvent (flutterEvent .eventId , keyEvent );
113118 return true ;
114119 }
115120
121+ /**
122+ * Returns whether or not the given event is currently being processed by this key processor. This
123+ * is used to determine if a new key event sent to the {@link InputConnectionAdaptor} originates
124+ * from a hardware key event, or a soft keyboard editing event.
125+ *
126+ * @param event the event to check for being the current event.
127+ * @return
128+ */
129+ public boolean isCurrentEvent (@ NonNull KeyEvent event ) {
130+ long id = FlutterKeyEvent .computeEventId (event );
131+ return eventResponder .isHeadEvent (id );
132+ }
133+
116134 /**
117135 * Applies the given Unicode character in {@code newCharacterCodePoint} to a previously entered
118136 * Unicode combining character and returns the combination of these characters if a combination
@@ -179,7 +197,6 @@ private static class EventResponder implements KeyEventChannel.EventResponseHand
179197 final Deque <Entry <Long , KeyEvent >> pendingEvents = new ArrayDeque <Entry <Long , KeyEvent >>();
180198 @ NonNull private final View view ;
181199 @ NonNull private final TextInputPlugin textInputPlugin ;
182- boolean dispatchingKeyEvent = false ;
183200
184201 public EventResponder (@ NonNull View view , @ NonNull TextInputPlugin textInputPlugin ) {
185202 this .view = view ;
@@ -202,6 +219,25 @@ private KeyEvent removePendingEvent(long id) {
202219 return pendingEvents .removeFirst ().getValue ();
203220 }
204221
222+ private KeyEvent findPendingEvent (long id ) {
223+ if (pendingEvents .size () == 0 ) {
224+ throw new AssertionError (
225+ "Event response received when no events are in the queue. Received id " + id );
226+ }
227+ if (pendingEvents .getFirst ().getKey () != id ) {
228+ throw new AssertionError (
229+ "Event response received out of order. Should have seen event "
230+ + pendingEvents .getFirst ().getKey ()
231+ + " first. Instead, received "
232+ + id );
233+ }
234+ return pendingEvents .getFirst ().getValue ();
235+ }
236+
237+ private boolean isHeadEvent (long id ) {
238+ return pendingEvents .size () > 0 && pendingEvents .getFirst ().getKey () == id ;
239+ }
240+
205241 /**
206242 * Called whenever the framework responds that a given key event was handled by the framework.
207243 *
@@ -222,18 +258,11 @@ public void onKeyEventHandled(long id) {
222258 */
223259 @ Override
224260 public void onKeyEventNotHandled (long id ) {
225- dispatchKeyEvent (removePendingEvent (id ));
261+ dispatchKeyEvent (findPendingEvent (id ), id );
226262 }
227263
228264 /** Adds an Android key event with an id to the event responder to wait for a response. */
229265 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- }
237266 pendingEvents .addLast (new SimpleImmutableEntry <Long , KeyEvent >(id , event ));
238267 if (pendingEvents .size () > MAX_PENDING_EVENTS ) {
239268 Log .e (
@@ -250,27 +279,21 @@ public void addEvent(long id, @NonNull KeyEvent event) {
250279 *
251280 * @param event the event to be dispatched to the activity.
252281 */
253- public void dispatchKeyEvent (KeyEvent event ) {
282+ public void dispatchKeyEvent (KeyEvent event , long id ) {
254283 // If the textInputPlugin is still valid and accepting text, then we'll try
255284 // and send the key event to it, assuming that if the event can be sent,
256285 // that it has been handled.
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- }
286+ if (textInputPlugin .getInputMethodManager ().isAcceptingText ()
287+ && textInputPlugin .getLastInputConnection () != null
288+ && textInputPlugin .getLastInputConnection ().sendKeyEvent (event )) {
289+ // The event was handled, so we can remove it from the queue.
290+ removePendingEvent (id );
291+ return ;
265292 }
266293
267294 // Since the framework didn't handle it, dispatch the event again.
268295 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 ;
272296 view .getRootView ().dispatchKeyEvent (event );
273- dispatchingKeyEvent = false ;
274297 }
275298 }
276299 }
0 commit comments