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

Commit d4b9518

Browse files
WIP
1 parent 9c09042 commit d4b9518

File tree

19 files changed

+1068
-795
lines changed

19 files changed

+1068
-795
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,8 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/Flutt
759759
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java
760760
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterTextureView.java
761761
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java
762+
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyboardManager.java
763+
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/KeyChannelResponder.java
762764
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/MotionEventTracker.java
763765
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/RenderMode.java
764766
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/SplashScreen.java

shell/platform/android/BUILD.gn

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ android_java_sources = [
143143
"io/flutter/embedding/android/FlutterSurfaceView.java",
144144
"io/flutter/embedding/android/FlutterTextureView.java",
145145
"io/flutter/embedding/android/FlutterView.java",
146+
"io/flutter/embedding/android/KeyboardManager.java",
147+
"io/flutter/embedding/android/KeyChannelResponder.java",
146148
"io/flutter/embedding/android/MotionEventTracker.java",
147149
"io/flutter/embedding/android/RenderMode.java",
148150
"io/flutter/embedding/android/SplashScreen.java",
@@ -463,6 +465,8 @@ action("robolectric_tests") {
463465
"test/io/flutter/embedding/android/FlutterFragmentActivityTest.java",
464466
"test/io/flutter/embedding/android/FlutterFragmentTest.java",
465467
"test/io/flutter/embedding/android/FlutterViewTest.java",
468+
"test/io/flutter/embedding/android/KeyboardManagerTest.java",
469+
"test/io/flutter/embedding/android/KeyChannelResponderTest.java",
466470
"test/io/flutter/embedding/android/RobolectricFlutterActivity.java",
467471
"test/io/flutter/embedding/engine/FlutterEngineCacheTest.java",
468472
"test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java",

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

Lines changed: 2 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,7 @@
55
package io.flutter.embedding.android;
66

77
import android.view.KeyCharacterMap;
8-
import android.view.KeyEvent;
9-
import android.view.View;
10-
import androidx.annotation.NonNull;
118
import androidx.annotation.Nullable;
12-
import io.flutter.Log;
13-
import io.flutter.embedding.engine.systemchannels.KeyEventChannel;
14-
import io.flutter.plugin.editing.TextInputPlugin;
15-
import java.util.ArrayDeque;
16-
import java.util.Deque;
17-
import java.util.Iterator;
189

1910
/**
2011
* A class to process key events from Android, passing them to the framework as messages using
@@ -30,13 +21,8 @@
3021
* framework, and if the framework responds that it has not handled the event, then this class
3122
* synthesizes a new event to send to Android, without handling it this time.
3223
*/
33-
public class AndroidKeyProcessor {
34-
private static final String TAG = "AndroidKeyProcessor";
35-
36-
@NonNull private final KeyEventChannel keyEventChannel;
37-
@NonNull private final TextInputPlugin textInputPlugin;
24+
class AndroidKeyProcessor {
3825
private int combiningCharacter;
39-
@NonNull private EventResponder eventResponder;
4026

4127
/**
4228
* Constructor for AndroidKeyProcessor.
@@ -58,74 +44,6 @@ public class AndroidKeyProcessor {
5844
* and if it has a valid input connection and is accepting text, then it will handle the event
5945
* and the framework will not receive it.
6046
*/
61-
public AndroidKeyProcessor(
62-
@NonNull View view,
63-
@NonNull KeyEventChannel keyEventChannel,
64-
@NonNull TextInputPlugin textInputPlugin) {
65-
this.keyEventChannel = keyEventChannel;
66-
this.textInputPlugin = textInputPlugin;
67-
textInputPlugin.setKeyEventProcessor(this);
68-
this.eventResponder = new EventResponder(view, textInputPlugin);
69-
this.keyEventChannel.setEventResponseHandler(eventResponder);
70-
}
71-
72-
/**
73-
* Detaches the key processor from the Flutter engine.
74-
*
75-
* <p>The AndroidKeyProcessor instance should not be used after calling this.
76-
*/
77-
public void destroy() {
78-
keyEventChannel.setEventResponseHandler(null);
79-
}
80-
81-
/**
82-
* Called when a key event is received by the {@link FlutterView} or the {@link
83-
* InputConnectionAdaptor}.
84-
*
85-
* @param keyEvent the Android key event to respond to.
86-
* @return true if the key event should not be propagated to other Android components. Delayed
87-
* synthesis events will return false, so that other components may handle them.
88-
*/
89-
public boolean onKeyEvent(@NonNull KeyEvent keyEvent) {
90-
int action = keyEvent.getAction();
91-
if (action != KeyEvent.ACTION_DOWN && action != KeyEvent.ACTION_UP) {
92-
// There is theoretically a KeyEvent.ACTION_MULTIPLE, but theoretically
93-
// that isn't sent by Android anymore, so this is just for protection in
94-
// case the theory is wrong.
95-
return false;
96-
}
97-
if (isPendingEvent(keyEvent)) {
98-
// If the keyEvent is in the queue of pending events we've seen, and has
99-
// the same id, then we know that this is a re-dispatched keyEvent, and we
100-
// shouldn't respond to it, but we should remove it from tracking now.
101-
eventResponder.removePendingEvent(keyEvent);
102-
return false;
103-
}
104-
105-
Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar());
106-
KeyEventChannel.FlutterKeyEvent flutterEvent =
107-
new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter);
108-
109-
eventResponder.addEvent(keyEvent);
110-
if (action == KeyEvent.ACTION_DOWN) {
111-
keyEventChannel.keyDown(flutterEvent);
112-
} else {
113-
keyEventChannel.keyUp(flutterEvent);
114-
}
115-
return true;
116-
}
117-
118-
/**
119-
* Returns whether or not the given event is currently being processed by this key processor. This
120-
* is used to determine if a new key event sent to the {@link InputConnectionAdaptor} originates
121-
* from a hardware key event, or a soft keyboard editing event.
122-
*
123-
* @param event the event to check for being the current event.
124-
* @return
125-
*/
126-
public boolean isPendingEvent(@NonNull KeyEvent event) {
127-
return eventResponder.findPendingEvent(event) != null;
128-
}
12947

13048
/**
13149
* Applies the given Unicode character in {@code newCharacterCodePoint} to a previously entered
@@ -155,11 +73,7 @@ public boolean isPendingEvent(@NonNull KeyEvent event) {
15573
* https://en.wikipedia.org/wiki/Combining_character
15674
*/
15775
@Nullable
158-
private Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoint) {
159-
if (newCharacterCodePoint == 0) {
160-
return null;
161-
}
162-
76+
Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoint) {
16377
char complexCharacter = (char) newCharacterCodePoint;
16478
boolean isNewCodePointACombiningCharacter =
16579
(newCharacterCodePoint & KeyCharacterMap.COMBINING_ACCENT) != 0;
@@ -185,91 +99,4 @@ private Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoi
18599

186100
return complexCharacter;
187101
}
188-
189-
private static class EventResponder implements KeyEventChannel.EventResponseHandler {
190-
// The maximum number of pending events that are held before starting to
191-
// complain.
192-
private static final long MAX_PENDING_EVENTS = 1000;
193-
final Deque<KeyEvent> pendingEvents = new ArrayDeque<KeyEvent>();
194-
@NonNull private final View view;
195-
@NonNull private final TextInputPlugin textInputPlugin;
196-
197-
public EventResponder(@NonNull View view, @NonNull TextInputPlugin textInputPlugin) {
198-
this.view = view;
199-
this.textInputPlugin = textInputPlugin;
200-
}
201-
202-
/** Removes the first pending event from the cache of pending events. */
203-
private void removePendingEvent(KeyEvent event) {
204-
pendingEvents.remove(event);
205-
}
206-
207-
private KeyEvent findPendingEvent(KeyEvent event) {
208-
Iterator<KeyEvent> iter = pendingEvents.iterator();
209-
while (iter.hasNext()) {
210-
KeyEvent item = iter.next();
211-
if (item == event) {
212-
return item;
213-
}
214-
}
215-
return null;
216-
}
217-
218-
/**
219-
* Called whenever the framework responds that a given key event was handled by the framework.
220-
*
221-
* @param event the event to be marked as being handled by the framework. Must not be null.
222-
*/
223-
@Override
224-
public void onKeyEventHandled(KeyEvent event) {
225-
removePendingEvent(event);
226-
}
227-
228-
/**
229-
* Called whenever the framework responds that a given key event wasn't handled by the
230-
* framework.
231-
*
232-
* @param event the event to be marked as not being handled by the framework. Must not be null.
233-
*/
234-
@Override
235-
public void onKeyEventNotHandled(KeyEvent event) {
236-
redispatchKeyEvent(findPendingEvent(event));
237-
}
238-
239-
/** Adds an Android key event to the event responder to wait for a response. */
240-
public void addEvent(@NonNull KeyEvent event) {
241-
pendingEvents.addLast(event);
242-
if (pendingEvents.size() > MAX_PENDING_EVENTS) {
243-
Log.e(
244-
TAG,
245-
"There are "
246-
+ pendingEvents.size()
247-
+ " keyboard events that have not yet received a response. Are responses being "
248-
+ "sent?");
249-
}
250-
}
251-
252-
/**
253-
* Dispatches the event to the activity associated with the context.
254-
*
255-
* @param event the event to be dispatched to the activity.
256-
*/
257-
private void redispatchKeyEvent(KeyEvent event) {
258-
// If the textInputPlugin is still valid and accepting text, then we'll try
259-
// and send the key event to it, assuming that if the event can be sent,
260-
// that it has been handled.
261-
if (textInputPlugin.getInputMethodManager().isAcceptingText()
262-
&& textInputPlugin.getLastInputConnection() != null
263-
&& textInputPlugin.getLastInputConnection().sendKeyEvent(event)) {
264-
// The event was handled, so we can remove it from the queue.
265-
removePendingEvent(event);
266-
return;
267-
}
268-
269-
// Since the framework didn't handle it, dispatch the event again.
270-
if (view != null) {
271-
view.getRootView().dispatchKeyEvent(event);
272-
}
273-
}
274-
}
275102
}

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC
101101
@Nullable private MouseCursorPlugin mouseCursorPlugin;
102102
@Nullable private TextInputPlugin textInputPlugin;
103103
@Nullable private LocalizationPlugin localizationPlugin;
104-
@Nullable private AndroidKeyProcessor androidKeyProcessor;
104+
@Nullable private KeyboardManager keyboardManager;
105105
@Nullable private AndroidTouchProcessor androidTouchProcessor;
106106
@Nullable private AccessibilityBridge accessibilityBridge;
107107

@@ -744,7 +744,7 @@ public boolean dispatchKeyEvent(KeyEvent event) {
744744
// superclass. The key processor will typically handle all events except
745745
// those where it has re-dispatched the event after receiving a reply from
746746
// the framework that the framework did not handle it.
747-
return (isAttachedToFlutterEngine() && androidKeyProcessor.onKeyEvent(event))
747+
return (isAttachedToFlutterEngine() && keyboardManager.handleEvent(event))
748748
|| super.dispatchKeyEvent(event);
749749
}
750750

@@ -894,8 +894,9 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
894894
this.flutterEngine.getTextInputChannel(),
895895
this.flutterEngine.getPlatformViewsController());
896896
localizationPlugin = this.flutterEngine.getLocalizationPlugin();
897-
androidKeyProcessor =
898-
new AndroidKeyProcessor(this, this.flutterEngine.getKeyEventChannel(), textInputPlugin);
897+
898+
keyboardManager =
899+
new KeyboardManager(this, textInputPlugin, flutterEngine.getKeyEventChannel());
899900
androidTouchProcessor =
900901
new AndroidTouchProcessor(this.flutterEngine.getRenderer(), /*trackMotionEvents=*/ false);
901902
accessibilityBridge =
@@ -979,8 +980,7 @@ public void detachFromFlutterEngine() {
979980
// TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin
980981
textInputPlugin.getInputMethodManager().restartInput(this);
981982
textInputPlugin.destroy();
982-
983-
androidKeyProcessor.destroy();
983+
keyboardManager.destroy();
984984

985985
if (mouseCursorPlugin != null) {
986986
mouseCursorPlugin.destroy();
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package io.flutter.embedding.android;
2+
3+
import android.view.KeyEvent;
4+
import androidx.annotation.NonNull;
5+
import io.flutter.embedding.engine.systemchannels.KeyEventChannel;
6+
7+
/**
8+
* A light wrapper around a {@link KeyEventChannel} that turns it into a {@link PrimaryResponder}.
9+
*/
10+
class KeyChannelResponder implements KeyboardManager.PrimaryResponder {
11+
private static final String TAG = "KeyChannelResponder";
12+
13+
@NonNull private final KeyEventChannel keyEventChannel;
14+
private final AndroidKeyProcessor keyProcessor = new AndroidKeyProcessor();
15+
16+
KeyChannelResponder(@NonNull KeyEventChannel keyEventChannel) {
17+
this.keyEventChannel = keyEventChannel;
18+
}
19+
20+
@Override
21+
public void handleEvent(
22+
@NonNull KeyEvent keyEvent, @NonNull OnKeyEventHandledCallback onKeyEventHandledCallback) {
23+
final int action = keyEvent.getAction();
24+
if (action != KeyEvent.ACTION_DOWN && action != KeyEvent.ACTION_UP) {
25+
// There is theoretically a KeyEvent.ACTION_MULTIPLE, but theoretically
26+
// that isn't sent by Android anymore, so this is just for protection in
27+
// case the theory is wrong.
28+
onKeyEventHandledCallback.onKeyEventHandled(false);
29+
return;
30+
}
31+
32+
final Character complexCharacter =
33+
keyProcessor.applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar());
34+
KeyEventChannel.FlutterKeyEvent flutterEvent =
35+
new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter);
36+
37+
final boolean isKeyUp = action != KeyEvent.ACTION_DOWN;
38+
keyEventChannel.sendFlutterKeyEvent(
39+
flutterEvent,
40+
isKeyUp,
41+
(isEventHandled) -> onKeyEventHandledCallback.onKeyEventHandled(isEventHandled));
42+
return;
43+
}
44+
}

0 commit comments

Comments
 (0)