4
4
5
5
package io .flutter .embedding .android ;
6
6
7
- import android .util .Log ;
8
7
import android .view .KeyCharacterMap ;
9
8
import android .view .KeyEvent ;
10
- import android .view .View ;
11
9
import androidx .annotation .NonNull ;
12
10
import androidx .annotation .Nullable ;
13
11
import io .flutter .embedding .engine .systemchannels .KeyEventChannel ;
14
12
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 ;
19
13
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
- */
34
14
public class AndroidKeyProcessor {
35
- private static final String TAG = "AndroidKeyProcessor" ;
36
- private static long eventIdSerial = 0 ;
37
-
38
15
@ NonNull private final KeyEventChannel keyEventChannel ;
39
16
@ NonNull private final TextInputPlugin textInputPlugin ;
40
17
private int combiningCharacter ;
41
- @ NonNull private EventResponder eventResponder ;
42
18
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
- */
63
19
public AndroidKeyProcessor (
64
- @ NonNull View view ,
65
- @ NonNull KeyEventChannel keyEventChannel ,
66
- @ NonNull TextInputPlugin textInputPlugin ) {
20
+ @ NonNull KeyEventChannel keyEventChannel , @ NonNull TextInputPlugin textInputPlugin ) {
67
21
this .keyEventChannel = keyEventChannel ;
68
22
this .textInputPlugin = textInputPlugin ;
69
- this .eventResponder = new EventResponder (view );
70
- this .keyEventChannel .setEventResponseHandler (eventResponder );
71
23
}
72
24
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 ) {
86
26
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 ));
92
28
}
93
29
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 ) {
110
31
if (textInputPlugin .getLastInputConnection () != null
111
32
&& textInputPlugin .getInputMethodManager ().isAcceptingText ()) {
112
- if (textInputPlugin .getLastInputConnection ().sendKeyEvent (keyEvent )) {
113
- return true ;
114
- }
33
+ textInputPlugin .getLastInputConnection ().sendKeyEvent (keyEvent );
115
34
}
116
35
117
36
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 ));
123
38
}
124
39
125
40
/**
@@ -155,7 +70,7 @@ private Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoi
155
70
return null ;
156
71
}
157
72
158
- char complexCharacter = (char ) newCharacterCodePoint ;
73
+ Character complexCharacter = (char ) newCharacterCodePoint ;
159
74
boolean isNewCodePointACombiningCharacter =
160
75
(newCharacterCodePoint & KeyCharacterMap .COMBINING_ACCENT ) != 0 ;
161
76
if (isNewCodePointACombiningCharacter ) {
@@ -167,8 +82,7 @@ private Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoi
167
82
combiningCharacter = plainCodePoint ;
168
83
}
169
84
} 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.
172
86
if (combiningCharacter != 0 ) {
173
87
int combinedChar = KeyCharacterMap .getDeadChar (combiningCharacter , newCharacterCodePoint );
174
88
if (combinedChar > 0 ) {
@@ -180,92 +94,4 @@ private Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoi
180
94
181
95
return complexCharacter ;
182
96
}
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
- }
271
97
}
0 commit comments