Skip to content

Commit 17227c1

Browse files
authored
[Android] Return keyboard pressed state (flutter#41695)
## Description This PR updates the Android engine in order to answer to keyboard pressed state queries from the framework (as implemented in flutter#122885). ## Related Issue Fixes flutter#122441 Android engine implementation for flutter#87391 ## Tests Adds 2 tests.
1 parent c7c679d commit 17227c1

13 files changed

Lines changed: 257 additions & 1 deletion

File tree

ci/licenses_golden/licenses_flutter

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2338,6 +2338,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/rend
23382338
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java + ../../../flutter/LICENSE
23392339
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/DeferredComponentChannel.java + ../../../flutter/LICENSE
23402340
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java + ../../../flutter/LICENSE
2341+
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java + ../../../flutter/LICENSE
23412342
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LifecycleChannel.java + ../../../flutter/LICENSE
23422343
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java + ../../../flutter/LICENSE
23432344
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/MouseCursorChannel.java + ../../../flutter/LICENSE
@@ -2371,6 +2372,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/Listen
23712372
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java + ../../../flutter/LICENSE
23722373
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextEditingDelta.java + ../../../flutter/LICENSE
23732374
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java + ../../../flutter/LICENSE
2375+
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/keyboard/KeyboardPlugin.java + ../../../flutter/LICENSE
23742376
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/localization/LocalizationPlugin.java + ../../../flutter/LICENSE
23752377
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/mouse/MouseCursorPlugin.java + ../../../flutter/LICENSE
23762378
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/AccessibilityEventsDelegate.java + ../../../flutter/LICENSE
@@ -4963,6 +4965,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/render
49634965
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java
49644966
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/DeferredComponentChannel.java
49654967
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java
4968+
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java
49664969
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LifecycleChannel.java
49674970
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java
49684971
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/MouseCursorChannel.java
@@ -4999,6 +5002,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/Listenab
49995002
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java
50005003
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextEditingDelta.java
50015004
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java
5005+
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/keyboard/KeyboardPlugin.java
50025006
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/localization/LocalizationPlugin.java
50035007
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/mouse/MouseCursorPlugin.java
50045008
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/AccessibilityEventsDelegate.java

shell/platform/android/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ android_java_sources = [
249249
"io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java",
250250
"io/flutter/embedding/engine/systemchannels/DeferredComponentChannel.java",
251251
"io/flutter/embedding/engine/systemchannels/KeyEventChannel.java",
252+
"io/flutter/embedding/engine/systemchannels/KeyboardChannel.java",
252253
"io/flutter/embedding/engine/systemchannels/LifecycleChannel.java",
253254
"io/flutter/embedding/engine/systemchannels/LocalizationChannel.java",
254255
"io/flutter/embedding/engine/systemchannels/MouseCursorChannel.java",
@@ -285,6 +286,7 @@ android_java_sources = [
285286
"io/flutter/plugin/editing/SpellCheckPlugin.java",
286287
"io/flutter/plugin/editing/TextEditingDelta.java",
287288
"io/flutter/plugin/editing/TextInputPlugin.java",
289+
"io/flutter/plugin/keyboard/KeyboardPlugin.java",
288290
"io/flutter/plugin/localization/LocalizationPlugin.java",
289291
"io/flutter/plugin/mouse/MouseCursorPlugin.java",
290292
"io/flutter/plugin/platform/AccessibilityEventsDelegate.java",

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
import io.flutter.plugin.common.BinaryMessenger;
6363
import io.flutter.plugin.editing.SpellCheckPlugin;
6464
import io.flutter.plugin.editing.TextInputPlugin;
65+
import io.flutter.plugin.keyboard.KeyboardPlugin;
6566
import io.flutter.plugin.localization.LocalizationPlugin;
6667
import io.flutter.plugin.mouse.MouseCursorPlugin;
6768
import io.flutter.plugin.platform.PlatformViewsController;
@@ -130,6 +131,7 @@ public class FlutterView extends FrameLayout
130131
@Nullable private MouseCursorPlugin mouseCursorPlugin;
131132
@Nullable private TextInputPlugin textInputPlugin;
132133
@Nullable private SpellCheckPlugin spellCheckPlugin;
134+
@Nullable private KeyboardPlugin keyboardPlugin;
133135
@Nullable private LocalizationPlugin localizationPlugin;
134136
@Nullable private KeyboardManager keyboardManager;
135137
@Nullable private AndroidTouchProcessor androidTouchProcessor;
@@ -1177,6 +1179,8 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
11771179
localizationPlugin = this.flutterEngine.getLocalizationPlugin();
11781180

11791181
keyboardManager = new KeyboardManager(this);
1182+
keyboardPlugin = new KeyboardPlugin(keyboardManager, this.flutterEngine.getKeyboardChannel());
1183+
11801184
androidTouchProcessor =
11811185
new AndroidTouchProcessor(this.flutterEngine.getRenderer(), /*trackMotionEvents=*/ false);
11821186
accessibilityBridge =
@@ -1273,10 +1277,12 @@ public void detachFromFlutterEngine() {
12731277
if (spellCheckPlugin != null) {
12741278
spellCheckPlugin.destroy();
12751279
}
1276-
12771280
if (mouseCursorPlugin != null) {
12781281
mouseCursorPlugin.destroy();
12791282
}
1283+
if (keyboardPlugin != null) {
1284+
keyboardPlugin.destroy();
1285+
}
12801286

12811287
// Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface.
12821288
FlutterRenderer flutterRenderer = flutterEngine.getRenderer();

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
import io.flutter.embedding.android.KeyboardMap.TogglingGoal;
1212
import io.flutter.plugin.common.BinaryMessenger;
1313
import java.util.ArrayList;
14+
import java.util.Collections;
1415
import java.util.HashMap;
16+
import java.util.Map;
1517

1618
/**
1719
* A {@link KeyboardManager.Responder} of {@link KeyboardManager} that handles events by sending
@@ -405,4 +407,14 @@ public void handleEvent(
405407
onKeyEventHandledCallback.onKeyEventHandled(true);
406408
}
407409
}
410+
411+
/**
412+
* Returns an unmodifiable view of the pressed state.
413+
*
414+
* @return A map whose keys are physical keyboard key IDs and values are the corresponding logical
415+
* keyboard key IDs.
416+
*/
417+
public Map<Long, Long> getPressedState() {
418+
return Collections.unmodifiableMap(pressingRecords);
419+
}
408420
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import io.flutter.plugin.editing.InputConnectionAdaptor;
1414
import io.flutter.plugin.editing.TextInputPlugin;
1515
import java.util.HashSet;
16+
import java.util.Map;
1617

1718
/**
1819
* Processes keyboard events and cooperate with {@link TextInputPlugin}.
@@ -252,4 +253,15 @@ private void onUnhandled(@NonNull KeyEvent keyEvent) {
252253
Log.w(TAG, "A redispatched key event was consumed before reaching KeyboardManager");
253254
}
254255
}
256+
257+
/**
258+
* Returns an unmodifiable view of the pressed state.
259+
*
260+
* @return A map whose keys are physical keyboard key IDs and values are the corresponding logical
261+
* keyboard key IDs.
262+
*/
263+
public Map<Long, Long> getPressedState() {
264+
KeyEmbedderResponder embedderResponder = (KeyEmbedderResponder) responders[0];
265+
return embedderResponder.getPressedState();
266+
}
255267
}

shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import io.flutter.embedding.engine.renderer.RenderSurface;
2727
import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
2828
import io.flutter.embedding.engine.systemchannels.DeferredComponentChannel;
29+
import io.flutter.embedding.engine.systemchannels.KeyboardChannel;
2930
import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
3031
import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
3132
import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
@@ -89,6 +90,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
8990
// System channels.
9091
@NonNull private final AccessibilityChannel accessibilityChannel;
9192
@NonNull private final DeferredComponentChannel deferredComponentChannel;
93+
@NonNull private final KeyboardChannel keyboardChannel;
9294
@NonNull private final LifecycleChannel lifecycleChannel;
9395
@NonNull private final LocalizationChannel localizationChannel;
9496
@NonNull private final MouseCursorChannel mouseCursorChannel;
@@ -324,6 +326,7 @@ public FlutterEngine(
324326

325327
accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI);
326328
deferredComponentChannel = new DeferredComponentChannel(dartExecutor);
329+
keyboardChannel = new KeyboardChannel(dartExecutor);
327330
lifecycleChannel = new LifecycleChannel(dartExecutor);
328331
localizationChannel = new LocalizationChannel(dartExecutor);
329332
mouseCursorChannel = new MouseCursorChannel(dartExecutor);
@@ -516,6 +519,12 @@ public AccessibilityChannel getAccessibilityChannel() {
516519
return accessibilityChannel;
517520
}
518521

522+
/** System channel that allows querying the keyboard pressed state. */
523+
@NonNull
524+
public KeyboardChannel getKeyboardChannel() {
525+
return keyboardChannel;
526+
}
527+
519528
/** System channel that sends Android lifecycle events to Flutter. */
520529
@NonNull
521530
public LifecycleChannel getLifecycleChannel() {
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.embedding.engine.systemchannels;
6+
7+
import androidx.annotation.NonNull;
8+
import androidx.annotation.Nullable;
9+
import io.flutter.embedding.engine.dart.DartExecutor;
10+
import io.flutter.plugin.common.MethodCall;
11+
import io.flutter.plugin.common.MethodChannel;
12+
import io.flutter.plugin.common.StandardMethodCodec;
13+
import java.util.HashMap;
14+
import java.util.Map;
15+
16+
/**
17+
* Event message channel for keyboard events to/from the Flutter framework.
18+
*
19+
* <p>Receives asynchronous messages from the framework to query the engine known pressed state.
20+
*/
21+
public class KeyboardChannel {
22+
private static final String TAG = "KeyboardChannel";
23+
24+
public final MethodChannel channel;
25+
private KeyboardMethodHandler keyboardMethodHandler;
26+
27+
@NonNull
28+
public final MethodChannel.MethodCallHandler parsingMethodHandler =
29+
new MethodChannel.MethodCallHandler() {
30+
@Override
31+
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
32+
if (keyboardMethodHandler == null) {
33+
return;
34+
}
35+
switch (call.method) {
36+
case "getKeyboardState":
37+
Map<Long, Long> pressedState = new HashMap<>();
38+
try {
39+
pressedState = keyboardMethodHandler.getKeyboardState();
40+
} catch (IllegalStateException exception) {
41+
result.error("error", exception.getMessage(), null);
42+
}
43+
result.success(pressedState);
44+
break;
45+
default:
46+
result.notImplemented();
47+
break;
48+
}
49+
}
50+
};
51+
52+
public KeyboardChannel(@NonNull DartExecutor dartExecutor) {
53+
channel = new MethodChannel(dartExecutor, "flutter/keyboard", StandardMethodCodec.INSTANCE);
54+
channel.setMethodCallHandler(parsingMethodHandler);
55+
}
56+
57+
/**
58+
* Sets the {@link KeyboardMethodHandler} which receives all requests to query the keyboard state.
59+
*/
60+
public void setKeyboardMethodHandler(@Nullable KeyboardMethodHandler keyboardMethodHandler) {
61+
this.keyboardMethodHandler = keyboardMethodHandler;
62+
}
63+
64+
public interface KeyboardMethodHandler {
65+
/**
66+
* Returns the keyboard pressed states.
67+
*
68+
* @return A map whose keys are physical keyboard key IDs and values are the corresponding
69+
* logical keyboard key IDs.
70+
*/
71+
Map<Long, Long> getKeyboardState();
72+
}
73+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.plugin.keyboard;
6+
7+
import androidx.annotation.NonNull;
8+
import androidx.annotation.VisibleForTesting;
9+
import io.flutter.embedding.android.KeyboardManager;
10+
import io.flutter.embedding.engine.systemchannels.KeyboardChannel;
11+
import io.flutter.plugin.common.MethodChannel;
12+
import java.util.Map;
13+
14+
/**
15+
* {@link KeyboardPlugin} is the implementation of all functionalities needed for querying keyboard
16+
* pressed state.
17+
*
18+
* <p>The plugin handles requests for querying keyboard pressed states by the {@link
19+
* io.flutter.embedding.engine.systemchannels.KeyboardChannel} via returning the {@link
20+
* io.flutter.embedding.android.KeyEmbedderResponder} pressed keys.
21+
*/
22+
public class KeyboardPlugin implements KeyboardChannel.KeyboardMethodHandler {
23+
24+
private final KeyboardChannel mKeyboardChannel;
25+
private final KeyboardManager mKeyboardManager;
26+
27+
@VisibleForTesting MethodChannel.Result pendingResult;
28+
29+
public KeyboardPlugin(
30+
@NonNull KeyboardManager keyboardManager, @NonNull KeyboardChannel keyboardChannel) {
31+
mKeyboardManager = keyboardManager;
32+
mKeyboardChannel = keyboardChannel;
33+
34+
mKeyboardChannel.setKeyboardMethodHandler(this);
35+
}
36+
37+
/**
38+
* Unregisters this {@code KeyboardPlugin} as the {@code KeyboardChannel.KeyboardMethodHandler},
39+
* for the {@link io.flutter.embedding.engine.systemchannels.KeyboardChannel}.
40+
*
41+
* <p>Do not invoke any methods on a {@code KeyboardPlugin} after invoking this method.
42+
*/
43+
public void destroy() {
44+
mKeyboardChannel.setKeyboardMethodHandler(null);
45+
}
46+
47+
/**
48+
* Returns the keyboard pressed state.
49+
*
50+
* @return A map whose keys are physical keyboard key IDs and values are the corresponding logical
51+
* keyboard key IDs.
52+
*/
53+
@Override
54+
public Map<Long, Long> getKeyboardState() {
55+
return mKeyboardManager.getPressedState();
56+
}
57+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import io.flutter.embedding.engine.renderer.FlutterRenderer;
4949
import io.flutter.embedding.engine.renderer.SurfaceTextureWrapper;
5050
import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
51+
import io.flutter.embedding.engine.systemchannels.KeyboardChannel;
5152
import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
5253
import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
5354
import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
@@ -59,6 +60,7 @@
5960
import io.flutter.plugin.common.ActivityLifecycleListener;
6061
import io.flutter.plugin.common.BinaryMessenger;
6162
import io.flutter.plugin.editing.TextInputPlugin;
63+
import io.flutter.plugin.keyboard.KeyboardPlugin;
6264
import io.flutter.plugin.localization.LocalizationPlugin;
6365
import io.flutter.plugin.mouse.MouseCursorPlugin;
6466
import io.flutter.plugin.platform.PlatformPlugin;
@@ -124,12 +126,14 @@ static final class ViewportMetrics {
124126
private final FlutterRenderer flutterRenderer;
125127
private final NavigationChannel navigationChannel;
126128
private final LifecycleChannel lifecycleChannel;
129+
private final KeyboardChannel keyboardChannel;
127130
private final LocalizationChannel localizationChannel;
128131
private final PlatformChannel platformChannel;
129132
private final SettingsChannel settingsChannel;
130133
private final SystemChannel systemChannel;
131134
private final InputMethodManager mImm;
132135
private final TextInputPlugin mTextInputPlugin;
136+
private final KeyboardPlugin mKeyboardPlugin;
133137
private final LocalizationPlugin mLocalizationPlugin;
134138
private final MouseCursorPlugin mMouseCursorPlugin;
135139
private final KeyboardManager mKeyboardManager;
@@ -213,6 +217,7 @@ public void surfaceDestroyed(SurfaceHolder holder) {
213217

214218
// Create all platform channels
215219
navigationChannel = new NavigationChannel(dartExecutor);
220+
keyboardChannel = new KeyboardChannel(dartExecutor);
216221
lifecycleChannel = new LifecycleChannel(dartExecutor);
217222
localizationChannel = new LocalizationChannel(dartExecutor);
218223
platformChannel = new PlatformChannel(dartExecutor);
@@ -233,7 +238,9 @@ public void onPostResume() {
233238
mNativeView.getPluginRegistry().getPlatformViewsController();
234239
mTextInputPlugin =
235240
new TextInputPlugin(this, new TextInputChannel(dartExecutor), platformViewsController);
241+
236242
mKeyboardManager = new KeyboardManager(this);
243+
mKeyboardPlugin = new KeyboardPlugin(mKeyboardManager, keyboardChannel);
237244

238245
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
239246
mMouseCursorPlugin = new MouseCursorPlugin(this, new MouseCursorChannel(dartExecutor));

shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import io.flutter.embedding.engine.renderer.FlutterRenderer;
4040
import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
4141
import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
42+
import io.flutter.embedding.engine.systemchannels.KeyboardChannel;
4243
import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
4344
import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
4445
import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
@@ -1293,6 +1294,7 @@ private FlutterEngine mockFlutterEngine() {
12931294
when(engine.getAccessibilityChannel()).thenReturn(mock(AccessibilityChannel.class));
12941295
when(engine.getActivityControlSurface()).thenReturn(mock(ActivityControlSurface.class));
12951296
when(engine.getDartExecutor()).thenReturn(mock(DartExecutor.class));
1297+
when(engine.getKeyboardChannel()).thenReturn(mock(KeyboardChannel.class));
12961298
when(engine.getLifecycleChannel()).thenReturn(mock(LifecycleChannel.class));
12971299
when(engine.getLocalizationChannel()).thenReturn(mock(LocalizationChannel.class));
12981300
when(engine.getLocalizationPlugin()).thenReturn(mock(LocalizationPlugin.class));

0 commit comments

Comments
 (0)