Skip to content

Commit

Permalink
[Android] Return keyboard pressed state
Browse files Browse the repository at this point in the history
  • Loading branch information
bleroux committed May 4, 2023
1 parent da45a21 commit e04745d
Show file tree
Hide file tree
Showing 13 changed files with 261 additions and 1 deletion.
4 changes: 4 additions & 0 deletions ci/licenses_golden/licenses_flutter
Expand Up @@ -2312,6 +2312,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/rend
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/DeferredComponentChannel.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LifecycleChannel.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/MouseCursorChannel.java + ../../../flutter/LICENSE
Expand Down Expand Up @@ -2345,6 +2346,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/Listen
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextEditingDelta.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/keyboard/KeyboardPlugin.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/localization/LocalizationPlugin.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/mouse/MouseCursorPlugin.java + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/AccessibilityEventsDelegate.java + ../../../flutter/LICENSE
Expand Down Expand Up @@ -4912,6 +4914,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/render
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/DeferredComponentChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LifecycleChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/LocalizationChannel.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/MouseCursorChannel.java
Expand Down Expand Up @@ -4948,6 +4951,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/Listenab
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextEditingDelta.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/keyboard/KeyboardPlugin.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/localization/LocalizationPlugin.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/mouse/MouseCursorPlugin.java
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/AccessibilityEventsDelegate.java
Expand Down
2 changes: 2 additions & 0 deletions shell/platform/android/BUILD.gn
Expand Up @@ -247,6 +247,7 @@ android_java_sources = [
"io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java",
"io/flutter/embedding/engine/systemchannels/DeferredComponentChannel.java",
"io/flutter/embedding/engine/systemchannels/KeyEventChannel.java",
"io/flutter/embedding/engine/systemchannels/KeyboardChannel.java",
"io/flutter/embedding/engine/systemchannels/LifecycleChannel.java",
"io/flutter/embedding/engine/systemchannels/LocalizationChannel.java",
"io/flutter/embedding/engine/systemchannels/MouseCursorChannel.java",
Expand Down Expand Up @@ -283,6 +284,7 @@ android_java_sources = [
"io/flutter/plugin/editing/SpellCheckPlugin.java",
"io/flutter/plugin/editing/TextEditingDelta.java",
"io/flutter/plugin/editing/TextInputPlugin.java",
"io/flutter/plugin/keyboard/KeyboardPlugin.java",
"io/flutter/plugin/localization/LocalizationPlugin.java",
"io/flutter/plugin/mouse/MouseCursorPlugin.java",
"io/flutter/plugin/platform/AccessibilityEventsDelegate.java",
Expand Down
Expand Up @@ -62,6 +62,7 @@
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.editing.SpellCheckPlugin;
import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.plugin.keyboard.KeyboardPlugin;
import io.flutter.plugin.localization.LocalizationPlugin;
import io.flutter.plugin.mouse.MouseCursorPlugin;
import io.flutter.plugin.platform.PlatformViewsController;
Expand Down Expand Up @@ -130,6 +131,7 @@ public class FlutterView extends FrameLayout
@Nullable private MouseCursorPlugin mouseCursorPlugin;
@Nullable private TextInputPlugin textInputPlugin;
@Nullable private SpellCheckPlugin spellCheckPlugin;
@Nullable private KeyboardPlugin keyboardPlugin;
@Nullable private LocalizationPlugin localizationPlugin;
@Nullable private KeyboardManager keyboardManager;
@Nullable private AndroidTouchProcessor androidTouchProcessor;
Expand Down Expand Up @@ -1175,6 +1177,8 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
localizationPlugin = this.flutterEngine.getLocalizationPlugin();

keyboardManager = new KeyboardManager(this);
keyboardPlugin = new KeyboardPlugin(keyboardManager, this.flutterEngine.getKeyboardChannel());

androidTouchProcessor =
new AndroidTouchProcessor(this.flutterEngine.getRenderer(), /*trackMotionEvents=*/ false);
accessibilityBridge =
Expand Down Expand Up @@ -1271,10 +1275,12 @@ public void detachFromFlutterEngine() {
if (spellCheckPlugin != null) {
spellCheckPlugin.destroy();
}

if (mouseCursorPlugin != null) {
mouseCursorPlugin.destroy();
}
if (keyboardPlugin != null) {
keyboardPlugin.destroy();
}

// Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface.
FlutterRenderer flutterRenderer = flutterEngine.getRenderer();
Expand Down
Expand Up @@ -11,7 +11,9 @@
import io.flutter.embedding.android.KeyboardMap.TogglingGoal;
import io.flutter.plugin.common.BinaryMessenger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
* A {@link KeyboardManager.Responder} of {@link KeyboardManager} that handles events by sending
Expand Down Expand Up @@ -405,4 +407,14 @@ public void handleEvent(
onKeyEventHandledCallback.onKeyEventHandled(true);
}
}

/**
* Returns an unmodifiable view of the pressed state.
*
* @return A map which keys are physical keyboard key ID and values the corresponding logical
* keyboard key ID.
*/
public Map<Long, Long> getPressedState() {
return Collections.unmodifiableMap(pressingRecords);
}
}
Expand Up @@ -13,6 +13,7 @@
import io.flutter.plugin.editing.InputConnectionAdaptor;
import io.flutter.plugin.editing.TextInputPlugin;
import java.util.HashSet;
import java.util.Map;

/**
* Processes keyboard events and cooperate with {@link TextInputPlugin}.
Expand Down Expand Up @@ -252,4 +253,15 @@ private void onUnhandled(@NonNull KeyEvent keyEvent) {
Log.w(TAG, "A redispatched key event was consumed before reaching KeyboardManager");
}
}

/**
* Returns an unmodifiable view of the pressed state.
*
* @return A map which keys are physical keyboard key ID and values the corresponding logical
* keyboard key ID.
*/
public Map<Long, Long> getPressedState() {
KeyEmbedderResponder embedderResponder = (KeyEmbedderResponder) responders[0];
return embedderResponder.getPressedState();
}
}
Expand Up @@ -26,6 +26,7 @@
import io.flutter.embedding.engine.renderer.RenderSurface;
import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
import io.flutter.embedding.engine.systemchannels.DeferredComponentChannel;
import io.flutter.embedding.engine.systemchannels.KeyboardChannel;
import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
Expand Down Expand Up @@ -88,6 +89,7 @@ public class FlutterEngine {
// System channels.
@NonNull private final AccessibilityChannel accessibilityChannel;
@NonNull private final DeferredComponentChannel deferredComponentChannel;
@NonNull private final KeyboardChannel keyboardChannel;
@NonNull private final LifecycleChannel lifecycleChannel;
@NonNull private final LocalizationChannel localizationChannel;
@NonNull private final MouseCursorChannel mouseCursorChannel;
Expand Down Expand Up @@ -323,6 +325,7 @@ public FlutterEngine(

accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI);
deferredComponentChannel = new DeferredComponentChannel(dartExecutor);
keyboardChannel = new KeyboardChannel(dartExecutor);
lifecycleChannel = new LifecycleChannel(dartExecutor);
localizationChannel = new LocalizationChannel(dartExecutor);
mouseCursorChannel = new MouseCursorChannel(dartExecutor);
Expand Down Expand Up @@ -513,6 +516,12 @@ public AccessibilityChannel getAccessibilityChannel() {
return accessibilityChannel;
}

/** System channel that allows querying the keyboard pressed state. */
@NonNull
public KeyboardChannel getKeyboardChannel() {
return keyboardChannel;
}

/** System channel that sends Android lifecycle events to Flutter. */
@NonNull
public LifecycleChannel getLifecycleChannel() {
Expand Down
@@ -0,0 +1,77 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.embedding.engine.systemchannels;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.Log;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.StandardMethodCodec;
import java.util.HashMap;
import java.util.Map;

/**
* Event message channel for keyboard events to/from the Flutter framework.
*
* <p>Receives asynchronous messages from the framework to query the engine known pressed state.
*/
public class KeyboardChannel {
private static final String TAG = "KeyboardChannel";

public final MethodChannel channel;
private KeyboardMethodHandler keyboardMethodHandler;

@NonNull
public final MethodChannel.MethodCallHandler parsingMethodHandler =
new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
if (keyboardMethodHandler == null) {
Log.v(TAG, "No KeyboardMethodHandler registered, call not forwarded to keyboard API.");
return;
}
String method = call.method;
Log.v(TAG, "Received '" + method + "' message.");
switch (method) {
case "getKeyboardState":
Map<Long, Long> pressedState = new HashMap<>();
try {
pressedState = keyboardMethodHandler.getKeyboardState();
} catch (IllegalStateException exception) {
result.error("error", exception.getMessage(), null);
}
result.success(pressedState);
break;
default:
result.notImplemented();
break;
}
}
};

public KeyboardChannel(@NonNull DartExecutor dartExecutor) {
channel = new MethodChannel(dartExecutor, "flutter/keyboard", StandardMethodCodec.INSTANCE);
channel.setMethodCallHandler(parsingMethodHandler);
}

/**
* Sets the {@link KeyboardMethodHandler} which receives all requests to query the keyboard state.
*/
public void setKeyboardMethodHandler(@Nullable KeyboardMethodHandler keyboardMethodHandler) {
this.keyboardMethodHandler = keyboardMethodHandler;
}

public interface KeyboardMethodHandler {
/**
* Returns the keyboard pressed states.
*
* @return A map which keys are physical keyboard key ID and values the corresponding logical
* keyboard key ID.
*/
Map<Long, Long> getKeyboardState();
}
}
@@ -0,0 +1,57 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugin.keyboard;

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import io.flutter.embedding.android.KeyboardManager;
import io.flutter.embedding.engine.systemchannels.KeyboardChannel;
import io.flutter.plugin.common.MethodChannel;
import java.util.Map;

/**
* {@link KeyboardPlugin} is the implementation of all functionalities needed for querying keyboard
* pressed state.
*
* <p>The plugin handles requests for querying keyboard pressed states by the {@link
* io.flutter.embedding.engine.systemchannels.KeyboardChannel} via returning the {@link
* io.flutter.embedding.android.KeyEmbedderResponder} pressed keys.
*/
public class KeyboardPlugin implements KeyboardChannel.KeyboardMethodHandler {

private final KeyboardChannel mKeyboardChannel;
private final KeyboardManager mKeyboardManager;

@VisibleForTesting MethodChannel.Result pendingResult;

public KeyboardPlugin(
@NonNull KeyboardManager keyboardManager, @NonNull KeyboardChannel keyboardChannel) {
mKeyboardManager = keyboardManager;
mKeyboardChannel = keyboardChannel;

mKeyboardChannel.setKeyboardMethodHandler(this);
}

/**
* Unregisters this {@code KeyboardPlugin} as the {@code KeyboardChannel.KeyboardMethodHandler},
* for the {@link io.flutter.embedding.engine.systemchannels.KeyboardChannel}.
*
* <p>Do not invoke any methods on a {@code KeyboardPlugin} after invoking this method.
*/
public void destroy() {
mKeyboardChannel.setKeyboardMethodHandler(null);
}

/**
* Returns the keyboard pressed state.
*
* @return A map which keys are physical keyboard key ID and values the corresponding logical
* keyboard key ID.
*/
@Override
public Map<Long, Long> getKeyboardState() {
return mKeyboardManager.getPressedState();
}
}
7 changes: 7 additions & 0 deletions shell/platform/android/io/flutter/view/FlutterView.java
Expand Up @@ -48,6 +48,7 @@
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.SurfaceTextureWrapper;
import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
import io.flutter.embedding.engine.systemchannels.KeyboardChannel;
import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
Expand All @@ -59,6 +60,7 @@
import io.flutter.plugin.common.ActivityLifecycleListener;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.plugin.keyboard.KeyboardPlugin;
import io.flutter.plugin.localization.LocalizationPlugin;
import io.flutter.plugin.mouse.MouseCursorPlugin;
import io.flutter.plugin.platform.PlatformPlugin;
Expand Down Expand Up @@ -124,12 +126,14 @@ static final class ViewportMetrics {
private final FlutterRenderer flutterRenderer;
private final NavigationChannel navigationChannel;
private final LifecycleChannel lifecycleChannel;
private final KeyboardChannel keyboardChannel;
private final LocalizationChannel localizationChannel;
private final PlatformChannel platformChannel;
private final SettingsChannel settingsChannel;
private final SystemChannel systemChannel;
private final InputMethodManager mImm;
private final TextInputPlugin mTextInputPlugin;
private final KeyboardPlugin mKeyboardPlugin;
private final LocalizationPlugin mLocalizationPlugin;
private final MouseCursorPlugin mMouseCursorPlugin;
private final KeyboardManager mKeyboardManager;
Expand Down Expand Up @@ -213,6 +217,7 @@ public void surfaceDestroyed(SurfaceHolder holder) {

// Create all platform channels
navigationChannel = new NavigationChannel(dartExecutor);
keyboardChannel = new KeyboardChannel(dartExecutor);
lifecycleChannel = new LifecycleChannel(dartExecutor);
localizationChannel = new LocalizationChannel(dartExecutor);
platformChannel = new PlatformChannel(dartExecutor);
Expand All @@ -233,7 +238,9 @@ public void onPostResume() {
mNativeView.getPluginRegistry().getPlatformViewsController();
mTextInputPlugin =
new TextInputPlugin(this, new TextInputChannel(dartExecutor), platformViewsController);

mKeyboardManager = new KeyboardManager(this);
mKeyboardPlugin = new KeyboardPlugin(mKeyboardManager, keyboardChannel);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
mMouseCursorPlugin = new MouseCursorPlugin(this, new MouseCursorChannel(dartExecutor));
Expand Down
Expand Up @@ -39,6 +39,7 @@
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
import io.flutter.embedding.engine.systemchannels.KeyboardChannel;
import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
Expand Down Expand Up @@ -1293,6 +1294,7 @@ private FlutterEngine mockFlutterEngine() {
when(engine.getAccessibilityChannel()).thenReturn(mock(AccessibilityChannel.class));
when(engine.getActivityControlSurface()).thenReturn(mock(ActivityControlSurface.class));
when(engine.getDartExecutor()).thenReturn(mock(DartExecutor.class));
when(engine.getKeyboardChannel()).thenReturn(mock(KeyboardChannel.class));
when(engine.getLifecycleChannel()).thenReturn(mock(LifecycleChannel.class));
when(engine.getLocalizationChannel()).thenReturn(mock(LocalizationChannel.class));
when(engine.getLocalizationPlugin()).thenReturn(mock(LocalizationPlugin.class));
Expand Down

0 comments on commit e04745d

Please sign in to comment.