From e04745ddf7eaabb2314367228611e7db2fa73b63 Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Thu, 4 May 2023 09:50:59 +0200 Subject: [PATCH 1/3] [Android] Return keyboard pressed state --- ci/licenses_golden/licenses_flutter | 4 + shell/platform/android/BUILD.gn | 2 + .../embedding/android/FlutterView.java | 8 +- .../android/KeyEmbedderResponder.java | 12 +++ .../embedding/android/KeyboardManager.java | 12 +++ .../embedding/engine/FlutterEngine.java | 9 +++ .../systemchannels/KeyboardChannel.java | 77 +++++++++++++++++++ .../plugin/keyboard/KeyboardPlugin.java | 57 ++++++++++++++ .../android/io/flutter/view/FlutterView.java | 7 ++ ...lutterActivityAndFragmentDelegateTest.java | 2 + .../android/KeyboardManagerTest.java | 19 +++++ .../plugin/keyboard/KeyboardPluginTest.java | 51 ++++++++++++ .../platform/PlatformViewsControllerTest.java | 2 + 13 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java create mode 100644 shell/platform/android/io/flutter/plugin/keyboard/KeyboardPlugin.java create mode 100644 shell/platform/android/test/io/flutter/plugin/keyboard/KeyboardPluginTest.java diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ffcb36dbe7f3f..f7e4898830742 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 37ad46e02e680..26b5781ecafa0 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -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", @@ -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", diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 9bd0022fe356f..a32a2d0efe4e8 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -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; @@ -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; @@ -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 = @@ -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(); diff --git a/shell/platform/android/io/flutter/embedding/android/KeyEmbedderResponder.java b/shell/platform/android/io/flutter/embedding/android/KeyEmbedderResponder.java index 2a5d5b1a87400..03efae3c1b9e3 100644 --- a/shell/platform/android/io/flutter/embedding/android/KeyEmbedderResponder.java +++ b/shell/platform/android/io/flutter/embedding/android/KeyEmbedderResponder.java @@ -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 @@ -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 getPressedState() { + return Collections.unmodifiableMap(pressingRecords); + } } diff --git a/shell/platform/android/io/flutter/embedding/android/KeyboardManager.java b/shell/platform/android/io/flutter/embedding/android/KeyboardManager.java index 76e344a4e5d26..1d953499d6f32 100644 --- a/shell/platform/android/io/flutter/embedding/android/KeyboardManager.java +++ b/shell/platform/android/io/flutter/embedding/android/KeyboardManager.java @@ -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}. @@ -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 getPressedState() { + KeyEmbedderResponder embedderResponder = (KeyEmbedderResponder) responders[0]; + return embedderResponder.getPressedState(); + } } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index bc2902c0281a9..72d93f1d4b511 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -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; @@ -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; @@ -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); @@ -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() { diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java new file mode 100644 index 0000000000000..25861caa3b78c --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java @@ -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. + * + *

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 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 getKeyboardState(); + } +} diff --git a/shell/platform/android/io/flutter/plugin/keyboard/KeyboardPlugin.java b/shell/platform/android/io/flutter/plugin/keyboard/KeyboardPlugin.java new file mode 100644 index 0000000000000..d9f2a61ae46c1 --- /dev/null +++ b/shell/platform/android/io/flutter/plugin/keyboard/KeyboardPlugin.java @@ -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. + * + *

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}. + * + *

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 getKeyboardState() { + return mKeyboardManager.getPressedState(); + } +} diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index eb498a2335e96..4f20e75250c64 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -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; @@ -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; @@ -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; @@ -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); @@ -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)); diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java index d5daa43f269a2..8c459861cd900 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -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; @@ -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)); diff --git a/shell/platform/android/test/io/flutter/embedding/android/KeyboardManagerTest.java b/shell/platform/android/test/io/flutter/embedding/android/KeyboardManagerTest.java index 799ae861b2352..6fddf593202d6 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/KeyboardManagerTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/KeyboardManagerTest.java @@ -25,6 +25,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -1564,4 +1565,22 @@ public void synchronizeCapsLock() { calls.get(0).keyData, Type.kUp, PHYSICAL_CAPS_LOCK, LOGICAL_CAPS_LOCK, null, false); calls.clear(); } + + @Test + public void getPressedState() { + final KeyboardTester tester = new KeyboardTester(); + + tester.respondToTextInputWith(true); // Suppress redispatching. + + // Initial pressed state is empty. + assertEquals(tester.keyboardManager.getPressedState(), Map.of()); + + tester.keyboardManager.handleEvent( + new FakeKeyEvent(ACTION_DOWN, SCAN_KEY_A, KEYCODE_A, 1, 'a', 0)); + assertEquals(tester.keyboardManager.getPressedState(), Map.of(PHYSICAL_KEY_A, LOGICAL_KEY_A)); + + tester.keyboardManager.handleEvent( + new FakeKeyEvent(ACTION_UP, SCAN_KEY_A, KEYCODE_A, 0, 'a', 0)); + assertEquals(tester.keyboardManager.getPressedState(), Map.of()); + } } diff --git a/shell/platform/android/test/io/flutter/plugin/keyboard/KeyboardPluginTest.java b/shell/platform/android/test/io/flutter/plugin/keyboard/KeyboardPluginTest.java new file mode 100644 index 0000000000000..0e3ecb1385d9d --- /dev/null +++ b/shell/platform/android/test/io/flutter/plugin/keyboard/KeyboardPluginTest.java @@ -0,0 +1,51 @@ +package io.flutter.plugin.keyboard; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.systemchannels.KeyboardChannel; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.StandardMethodCodec; +import java.nio.ByteBuffer; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +@RunWith(AndroidJUnit4.class) +public class KeyboardPluginTest { + + private static void sendToBinaryMessageHandler( + BinaryMessenger.BinaryMessageHandler binaryMessageHandler, String method, Object args) { + MethodCall methodCall = new MethodCall(method, args); + ByteBuffer encodedMethodCall = StandardMethodCodec.INSTANCE.encodeMethodCall(methodCall); + binaryMessageHandler.onMessage( + (ByteBuffer) encodedMethodCall.flip(), mock(BinaryMessenger.BinaryReply.class)); + } + + @Test + public void respondsToGetKeyboardStateChannelMessage() { + ArgumentCaptor binaryMessageHandlerCaptor = + ArgumentCaptor.forClass(BinaryMessenger.BinaryMessageHandler.class); + DartExecutor mockBinaryMessenger = mock(DartExecutor.class); + KeyboardChannel.KeyboardMethodHandler mockHandler = + mock(KeyboardChannel.KeyboardMethodHandler.class); + KeyboardChannel keyboardChannel = new KeyboardChannel(mockBinaryMessenger); + + keyboardChannel.setKeyboardMethodHandler(mockHandler); + + verify(mockBinaryMessenger, times(1)) + .setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture()); + + BinaryMessenger.BinaryMessageHandler binaryMessageHandler = + binaryMessageHandlerCaptor.getValue(); + + sendToBinaryMessageHandler(binaryMessageHandler, "getKeyboardState", null); + + verify(mockHandler, times(1)).getKeyboardState(); + } +} diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java index b3f22dc485c1a..c2cd76001fc16 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -36,6 +36,7 @@ import io.flutter.embedding.engine.mutatorsstack.FlutterMutatorsStack; import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; +import io.flutter.embedding.engine.systemchannels.KeyboardChannel; import io.flutter.embedding.engine.systemchannels.MouseCursorChannel; import io.flutter.embedding.engine.systemchannels.SettingsChannel; import io.flutter.embedding.engine.systemchannels.TextInputChannel; @@ -1459,6 +1460,7 @@ public void release() {} final FlutterEngine engine = mock(FlutterEngine.class); when(engine.getRenderer()).thenReturn(new FlutterRenderer(jni)); + when(engine.getKeyboardChannel()).thenReturn(mock(KeyboardChannel.class)); when(engine.getMouseCursorChannel()).thenReturn(mock(MouseCursorChannel.class)); when(engine.getTextInputChannel()).thenReturn(mock(TextInputChannel.class)); when(engine.getSettingsChannel()).thenReturn(new SettingsChannel(executor)); From 74f402b9f41dca18fdfda4e9018fc2270965fa53 Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Fri, 5 May 2023 08:11:47 +0200 Subject: [PATCH 2/3] Remove Log.v calls --- .../embedding/engine/systemchannels/KeyboardChannel.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java index 25861caa3b78c..cbe34d2833d2d 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java @@ -6,7 +6,6 @@ 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; @@ -31,12 +30,9 @@ public class KeyboardChannel { @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) { + switch (call.method) { case "getKeyboardState": Map pressedState = new HashMap<>(); try { From 266d54e7da1c369eb6dfc495b52e9cf7ed0566d0 Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Fri, 5 May 2023 08:12:19 +0200 Subject: [PATCH 3/3] Documentation correction --- .../io/flutter/embedding/android/KeyEmbedderResponder.java | 4 ++-- .../android/io/flutter/embedding/android/KeyboardManager.java | 4 ++-- .../embedding/engine/systemchannels/KeyboardChannel.java | 4 ++-- .../android/io/flutter/plugin/keyboard/KeyboardPlugin.java | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/android/KeyEmbedderResponder.java b/shell/platform/android/io/flutter/embedding/android/KeyEmbedderResponder.java index 03efae3c1b9e3..aa1fdec32d514 100644 --- a/shell/platform/android/io/flutter/embedding/android/KeyEmbedderResponder.java +++ b/shell/platform/android/io/flutter/embedding/android/KeyEmbedderResponder.java @@ -411,8 +411,8 @@ public void handleEvent( /** * 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. + * @return A map whose keys are physical keyboard key IDs and values are the corresponding logical + * keyboard key IDs. */ public Map getPressedState() { return Collections.unmodifiableMap(pressingRecords); diff --git a/shell/platform/android/io/flutter/embedding/android/KeyboardManager.java b/shell/platform/android/io/flutter/embedding/android/KeyboardManager.java index 1d953499d6f32..84cee4549c439 100644 --- a/shell/platform/android/io/flutter/embedding/android/KeyboardManager.java +++ b/shell/platform/android/io/flutter/embedding/android/KeyboardManager.java @@ -257,8 +257,8 @@ private void onUnhandled(@NonNull KeyEvent keyEvent) { /** * 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. + * @return A map whose keys are physical keyboard key IDs and values are the corresponding logical + * keyboard key IDs. */ public Map getPressedState() { KeyEmbedderResponder embedderResponder = (KeyEmbedderResponder) responders[0]; diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java index cbe34d2833d2d..956f6e0baa203 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyboardChannel.java @@ -65,8 +65,8 @@ 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. + * @return A map whose keys are physical keyboard key IDs and values are the corresponding + * logical keyboard key IDs. */ Map getKeyboardState(); } diff --git a/shell/platform/android/io/flutter/plugin/keyboard/KeyboardPlugin.java b/shell/platform/android/io/flutter/plugin/keyboard/KeyboardPlugin.java index d9f2a61ae46c1..b2d8c25ec43c8 100644 --- a/shell/platform/android/io/flutter/plugin/keyboard/KeyboardPlugin.java +++ b/shell/platform/android/io/flutter/plugin/keyboard/KeyboardPlugin.java @@ -47,8 +47,8 @@ public void destroy() { /** * Returns the keyboard pressed state. * - * @return A map which keys are physical keyboard key ID and values the corresponding logical - * keyboard key ID. + * @return A map whose keys are physical keyboard key IDs and values are the corresponding logical + * keyboard key IDs. */ @Override public Map getKeyboardState() {