44
55package io .flutter .embedding .android ;
66
7- import android .util .Log ;
87import android .view .KeyCharacterMap ;
98import android .view .KeyEvent ;
10- import android .view .View ;
119import androidx .annotation .NonNull ;
1210import androidx .annotation .Nullable ;
1311import io .flutter .embedding .engine .systemchannels .KeyEventChannel ;
1412import 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- */
3414public 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}
0 commit comments