From 27cc8d6228f162c2962cfb9451f76c56f35e1d0a Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Wed, 22 Jul 2020 11:15:49 -0700 Subject: [PATCH] Revert "Implement delayed event synthesis key event handling for Android (#19024)" This reverts commit 8825f9178d884d07b43590ef52bb929ac9a3773f because it breaks flutter_gallery__back_button_memory and a customer test. --- shell/platform/android/BUILD.gn | 2 - .../android/AndroidKeyProcessor.java | 190 +----------------- .../embedding/android/FlutterView.java | 8 +- .../systemchannels/AccessibilityChannel.java | 1 - .../systemchannels/KeyEventChannel.java | 182 ++--------------- .../plugin/editing/TextInputPlugin.java | 2 +- .../android/io/flutter/view/FlutterView.java | 8 +- .../test/io/flutter/FlutterTestSuite.java | 16 +- .../android/AndroidKeyProcessorTest.java | 146 -------------- ...lutterActivityAndFragmentDelegateTest.java | 18 +- .../systemchannels/KeyEventChannelTest.java | 124 ------------ 11 files changed, 46 insertions(+), 651 deletions(-) delete mode 100644 shell/platform/android/test/io/flutter/embedding/android/AndroidKeyProcessorTest.java delete mode 100644 shell/platform/android/test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 6a941dba74acb..93e6b140a7304 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -430,7 +430,6 @@ action("robolectric_tests") { sources = [ "test/io/flutter/FlutterTestSuite.java", "test/io/flutter/SmokeTest.java", - "test/io/flutter/embedding/android/AndroidKeyProcessorTest.java", "test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java", "test/io/flutter/embedding/android/FlutterActivityTest.java", "test/io/flutter/embedding/android/FlutterAndroidComponentTest.java", @@ -448,7 +447,6 @@ action("robolectric_tests") { "test/io/flutter/embedding/engine/dart/DartExecutorTest.java", "test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java", "test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java", - "test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java", "test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java", "test/io/flutter/external/FlutterLaunchTests.java", "test/io/flutter/plugin/common/StandardMessageCodecTest.java", diff --git a/shell/platform/android/io/flutter/embedding/android/AndroidKeyProcessor.java b/shell/platform/android/io/flutter/embedding/android/AndroidKeyProcessor.java index d0d5c1d68cdab..5d823ef8ab674 100644 --- a/shell/platform/android/io/flutter/embedding/android/AndroidKeyProcessor.java +++ b/shell/platform/android/io/flutter/embedding/android/AndroidKeyProcessor.java @@ -4,122 +4,37 @@ package io.flutter.embedding.android; -import android.util.Log; import android.view.KeyCharacterMap; import android.view.KeyEvent; -import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.embedding.engine.systemchannels.KeyEventChannel; import io.flutter.plugin.editing.TextInputPlugin; -import java.util.AbstractMap.SimpleImmutableEntry; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.Map.Entry; -/** - * A class to process key events from Android, passing them to the framework as messages using - * {@link KeyEventChannel}. - * - *

A class that sends Android key events to the framework, and re-dispatches those not handled by - * the framework. - * - *

Flutter uses asynchronous event handling to avoid blocking the UI thread, but Android requires - * that events are handled synchronously. So, when a key event is received by Flutter, it tells - * Android synchronously that the key has been handled so that it won't propagate to other - * components. Flutter then uses "delayed event synthesis", where it sends the event to the - * framework, and if the framework responds that it has not handled the event, then this class - * synthesizes a new event to send to Android, without handling it this time. - */ public class AndroidKeyProcessor { - private static final String TAG = "AndroidKeyProcessor"; - private static long eventIdSerial = 0; - @NonNull private final KeyEventChannel keyEventChannel; @NonNull private final TextInputPlugin textInputPlugin; private int combiningCharacter; - @NonNull private EventResponder eventResponder; - /** - * Constructor for AndroidKeyProcessor. - * - *

The view is used as the destination to send the synthesized key to. This means that the the - * next thing in the focus chain will get the event when the framework returns false from - * onKeyDown/onKeyUp - * - *

It is possible that that in the middle of the async round trip, the focus chain could - * change, and instead of the native widget that was "next" when the event was fired getting the - * event, it may be the next widget when the event is synthesized that gets it. In practice, this - * shouldn't be a huge problem, as this is an unlikely occurance to happen without user input, and - * it may actually be desired behavior, but it is possible. - * - * @param view takes the activity to use for re-dispatching of events that were not handled by the - * framework. - * @param keyEventChannel the event channel to listen to for new key events. - * @param textInputPlugin a plugin, which, if set, is given key events before the framework is, - * and if it has a valid input connection and is accepting text, then it will handle the event - * and the framework will not receive it. - */ public AndroidKeyProcessor( - @NonNull View view, - @NonNull KeyEventChannel keyEventChannel, - @NonNull TextInputPlugin textInputPlugin) { + @NonNull KeyEventChannel keyEventChannel, @NonNull TextInputPlugin textInputPlugin) { this.keyEventChannel = keyEventChannel; this.textInputPlugin = textInputPlugin; - this.eventResponder = new EventResponder(view); - this.keyEventChannel.setEventResponseHandler(eventResponder); } - /** - * Called when a key up event is received by the {@link FlutterView}. - * - * @param keyEvent the Android key event to respond to. - * @return true if the key event should not be propagated to other Android components. Delayed - * synthesis events will return false, so that other components may handle them. - */ - public boolean onKeyUp(@NonNull KeyEvent keyEvent) { - if (eventResponder.dispatchingKeyEvent) { - // Don't handle it if it is from our own delayed event synthesis. - return false; - } - + public void onKeyUp(@NonNull KeyEvent keyEvent) { Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar()); - KeyEventChannel.FlutterKeyEvent flutterEvent = - new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter, eventIdSerial++); - keyEventChannel.keyUp(flutterEvent); - eventResponder.addEvent(flutterEvent.eventId, keyEvent); - return true; + keyEventChannel.keyUp(new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter)); } - /** - * Called when a key down event is received by the {@link FlutterView}. - * - * @param keyEvent the Android key event to respond to. - * @return true if the key event should not be propagated to other Android components. Delayed - * synthesis events will return false, so that other components may handle them. - */ - public boolean onKeyDown(@NonNull KeyEvent keyEvent) { - if (eventResponder.dispatchingKeyEvent) { - // Don't handle it if it is from our own delayed event synthesis. - return false; - } - - // If the textInputPlugin is still valid and accepting text, then we'll try - // and send the key event to it, assuming that if the event can be sent, - // that it has been handled. + public void onKeyDown(@NonNull KeyEvent keyEvent) { if (textInputPlugin.getLastInputConnection() != null && textInputPlugin.getInputMethodManager().isAcceptingText()) { - if (textInputPlugin.getLastInputConnection().sendKeyEvent(keyEvent)) { - return true; - } + textInputPlugin.getLastInputConnection().sendKeyEvent(keyEvent); } Character complexCharacter = applyCombiningCharacterToBaseCharacter(keyEvent.getUnicodeChar()); - KeyEventChannel.FlutterKeyEvent flutterEvent = - new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter, eventIdSerial++); - keyEventChannel.keyDown(flutterEvent); - eventResponder.addEvent(flutterEvent.eventId, keyEvent); - return true; + keyEventChannel.keyDown(new KeyEventChannel.FlutterKeyEvent(keyEvent, complexCharacter)); } /** @@ -155,7 +70,7 @@ private Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoi return null; } - char complexCharacter = (char) newCharacterCodePoint; + Character complexCharacter = (char) newCharacterCodePoint; boolean isNewCodePointACombiningCharacter = (newCharacterCodePoint & KeyCharacterMap.COMBINING_ACCENT) != 0; if (isNewCodePointACombiningCharacter) { @@ -167,8 +82,7 @@ private Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoi combiningCharacter = plainCodePoint; } } else { - // The new character is a regular character. Apply combiningCharacter to it, if - // it exists. + // The new character is a regular character. Apply combiningCharacter to it, if it exists. if (combiningCharacter != 0) { int combinedChar = KeyCharacterMap.getDeadChar(combiningCharacter, newCharacterCodePoint); if (combinedChar > 0) { @@ -180,92 +94,4 @@ private Character applyCombiningCharacterToBaseCharacter(int newCharacterCodePoi return complexCharacter; } - - private static class EventResponder implements KeyEventChannel.EventResponseHandler { - // The maximum number of pending events that are held before starting to - // complain. - private static final long MAX_PENDING_EVENTS = 1000; - final Deque> pendingEvents = new ArrayDeque>(); - @NonNull private final View view; - boolean dispatchingKeyEvent = false; - - public EventResponder(@NonNull View view) { - this.view = view; - } - - /** - * Removes the pending event with the given id from the cache of pending events. - * - * @param id the id of the event to be removed. - */ - private KeyEvent removePendingEvent(long id) { - if (pendingEvents.getFirst().getKey() != id) { - throw new AssertionError( - "Event response received out of order. Should have seen event " - + pendingEvents.getFirst().getKey() - + " first. Instead, received " - + id); - } - return pendingEvents.removeFirst().getValue(); - } - - /** - * Called whenever the framework responds that a given key event was handled by the framework. - * - * @param id the event id of the event to be marked as being handled by the framework. Must not - * be null. - */ - @Override - public void onKeyEventHandled(long id) { - removePendingEvent(id); - } - - /** - * Called whenever the framework responds that a given key event wasn't handled by the - * framework. - * - * @param id the event id of the event to be marked as not being handled by the framework. Must - * not be null. - */ - @Override - public void onKeyEventNotHandled(long id) { - dispatchKeyEvent(removePendingEvent(id)); - } - - /** Adds an Android key event with an id to the event responder to wait for a response. */ - public void addEvent(long id, @NonNull KeyEvent event) { - if (pendingEvents.size() > 0 && pendingEvents.getFirst().getKey() >= id) { - throw new AssertionError( - "New events must have ids greater than the most recent pending event. New id " - + id - + " is less than or equal to the last event id of " - + pendingEvents.getFirst().getKey()); - } - pendingEvents.addLast(new SimpleImmutableEntry(id, event)); - if (pendingEvents.size() > MAX_PENDING_EVENTS) { - Log.e( - TAG, - "There are " - + pendingEvents.size() - + " keyboard events " - + "that have not yet received a response. Are responses being sent?"); - } - } - - /** - * Dispatches the event to the activity associated with the context. - * - * @param event the event to be dispatched to the activity. - */ - public void dispatchKeyEvent(KeyEvent event) { - // Since the framework didn't handle it, dispatch the key again. - if (view != null) { - // Turn on dispatchingKeyEvent so that we don't dispatch to ourselves and - // send it to the framework again. - dispatchingKeyEvent = true; - view.dispatchKeyEvent(event); - dispatchingKeyEvent = false; - } - } - } } diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index fa198d38190f0..8cf69c24cf568 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -679,7 +679,8 @@ public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) { return super.onKeyUp(keyCode, event); } - return androidKeyProcessor.onKeyUp(event) || super.onKeyUp(keyCode, event); + androidKeyProcessor.onKeyUp(event); + return super.onKeyUp(keyCode, event); } /** @@ -699,7 +700,8 @@ public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { return super.onKeyDown(keyCode, event); } - return androidKeyProcessor.onKeyDown(event) || super.onKeyDown(keyCode, event); + androidKeyProcessor.onKeyDown(event); + return super.onKeyDown(keyCode, event); } /** @@ -849,7 +851,7 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) { this.flutterEngine.getPlatformViewsController()); localizationPlugin = this.flutterEngine.getLocalizationPlugin(); androidKeyProcessor = - new AndroidKeyProcessor(this, this.flutterEngine.getKeyEventChannel(), textInputPlugin); + new AndroidKeyProcessor(this.flutterEngine.getKeyEventChannel(), textInputPlugin); androidTouchProcessor = new AndroidTouchProcessor(this.flutterEngine.getRenderer(), /*trackMotionEvents=*/ false); accessibilityBridge = diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java index 87db8cf09f245..90455bcecefc2 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java @@ -73,7 +73,6 @@ public void onMessage( break; } } - reply.reply(null); } }; diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java index 3638c2d364ae7..1b03c94184221 100644 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/KeyEventChannel.java @@ -9,104 +9,29 @@ import android.view.KeyEvent; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import io.flutter.Log; +import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.plugin.common.BasicMessageChannel; -import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.JSONMessageCodec; import java.util.HashMap; import java.util.Map; -import org.json.JSONException; -import org.json.JSONObject; -/** - * Event message channel for key events to/from the Flutter framework. - * - *

Sends key up/down events to the framework, and receives asynchronous messages from the - * framework about whether or not the key was handled. - */ +/** TODO(mattcarroll): fill in javadoc for KeyEventChannel. */ public class KeyEventChannel { - private static final String TAG = "KeyEventChannel"; - /** - * Sets the event response handler to be used to receive key event response messages from the - * framework on this channel. - */ - public void setEventResponseHandler(EventResponseHandler handler) { - this.eventResponseHandler = handler; - } - - private EventResponseHandler eventResponseHandler; - - /** A handler of incoming key handling messages. */ - public interface EventResponseHandler { - - /** - * Called whenever the framework responds that a given key event was handled by the framework. - * - * @param id the event id of the event to be marked as being handled by the framework. Must not - * be null. - */ - public void onKeyEventHandled(long id); - - /** - * Called whenever the framework responds that a given key event wasn't handled by the - * framework. - * - * @param id the event id of the event to be marked as not being handled by the framework. Must - * not be null. - */ - public void onKeyEventNotHandled(long id); - } + @NonNull public final BasicMessageChannel channel; - /** - * A constructor that creates a KeyEventChannel with the default message handler. - * - * @param binaryMessenger the binary messenger used to send messages on this channel. - */ - public KeyEventChannel(@NonNull BinaryMessenger binaryMessenger) { + public KeyEventChannel(@NonNull DartExecutor dartExecutor) { this.channel = - new BasicMessageChannel<>(binaryMessenger, "flutter/keyevent", JSONMessageCodec.INSTANCE); + new BasicMessageChannel<>(dartExecutor, "flutter/keyevent", JSONMessageCodec.INSTANCE); } - /** - * Creates a reply handler for this an event with the given eventId. - * - * @param eventId the event ID to create a reply for. - */ - BasicMessageChannel.Reply createReplyHandler(long eventId) { - return message -> { - if (eventResponseHandler == null) { - return; - } - - try { - if (message == null) { - eventResponseHandler.onKeyEventNotHandled(eventId); - return; - } - final JSONObject annotatedEvent = (JSONObject) message; - final boolean handled = annotatedEvent.getBoolean("handled"); - if (handled) { - eventResponseHandler.onKeyEventHandled(eventId); - } else { - eventResponseHandler.onKeyEventNotHandled(eventId); - } - } catch (JSONException e) { - Log.e(TAG, "Unable to unpack JSON message: " + e); - eventResponseHandler.onKeyEventNotHandled(eventId); - } - }; - } - - @NonNull public final BasicMessageChannel channel; - public void keyUp(@NonNull FlutterKeyEvent keyEvent) { Map message = new HashMap<>(); message.put("type", "keyup"); message.put("keymap", "android"); encodeKeyEvent(keyEvent, message); - channel.send(message, createReplyHandler(keyEvent.eventId)); + channel.send(message); } public void keyDown(@NonNull FlutterKeyEvent keyEvent) { @@ -115,7 +40,7 @@ public void keyDown(@NonNull FlutterKeyEvent keyEvent) { message.put("keymap", "android"); encodeKeyEvent(keyEvent, message); - channel.send(message, createReplyHandler(keyEvent.eventId)); + channel.send(message); } private void encodeKeyEvent( @@ -136,105 +61,27 @@ private void encodeKeyEvent( message.put("repeatCount", event.repeatCount); } - /** A key event as defined by Flutter. */ + /** Key event as defined by Flutter. */ public static class FlutterKeyEvent { - /** - * The id for the device this event came from. - * - * @see KeyEvent.getDeviceId() - */ public final int deviceId; - /** - * The flags for this key event. - * - * @see KeyEvent.getFlags() - */ public final int flags; - /** - * The code point for the Unicode character produced by this event if no meta keys were pressed - * (by passing 0 to {@code KeyEvent.getUnicodeChar(int)}). - * - * @see KeyEvent.getUnicodeChar(int) - */ public final int plainCodePoint; - /** - * The code point for the Unicode character produced by this event, taking into account the meta - * keys currently pressed. - * - * @see KeyEvent.getUnicodeChar() - */ public final int codePoint; - /** - * The Android key code for this event. - * - * @see KeyEvent.getKeyCode() - */ public final int keyCode; - /** - * The character produced by this event, including any combining characters pressed before it. - */ @Nullable public final Character complexCharacter; - /** - * The Android scan code for the key pressed. - * - * @see KeyEvent.getScanCode() - */ public final int scanCode; - /** - * The meta key state for the Android key event. - * - * @see KeyEvent.getMetaState() - */ public final int metaState; - /** - * The source of the key event. - * - * @see KeyEvent.getSource() - */ public final int source; - /** - * The vendorId of the device that produced this key event. - * - * @see InputDevice.getVendorId() - */ public final int vendorId; - /** - * The productId of the device that produced this key event. - * - * @see InputDevice.getProductId() - */ public final int productId; - /** - * The repeat count for this event. - * - * @see KeyEvent.getRepeatCount() - */ public final int repeatCount; - /** - * The unique id for this Flutter key event. - * - *

This id is used to identify pending events when results are received from the framework. - * This ID does not come from Android. - */ - public final long eventId; - public FlutterKeyEvent(@NonNull KeyEvent androidKeyEvent, long eventId) { - this(androidKeyEvent, null, eventId); + public FlutterKeyEvent(@NonNull KeyEvent androidKeyEvent) { + this(androidKeyEvent, null); } public FlutterKeyEvent( - @NonNull KeyEvent androidKeyEvent, @Nullable Character complexCharacter, long eventId) { + @NonNull KeyEvent androidKeyEvent, @Nullable Character complexCharacter) { this( androidKeyEvent.getDeviceId(), androidKeyEvent.getFlags(), @@ -245,8 +92,7 @@ public FlutterKeyEvent( androidKeyEvent.getScanCode(), androidKeyEvent.getMetaState(), androidKeyEvent.getSource(), - androidKeyEvent.getRepeatCount(), - eventId); + androidKeyEvent.getRepeatCount()); } public FlutterKeyEvent( @@ -259,8 +105,7 @@ public FlutterKeyEvent( int scanCode, int metaState, int source, - int repeatCount, - long eventId) { + int repeatCount) { this.deviceId = deviceId; this.flags = flags; this.plainCodePoint = plainCodePoint; @@ -271,7 +116,6 @@ public FlutterKeyEvent( this.metaState = metaState; this.source = source; this.repeatCount = repeatCount; - this.eventId = eventId; InputDevice device = InputDevice.getDevice(deviceId); if (device != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 716246d189299..698a44cccfd73 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -127,7 +127,7 @@ Editable getEditable() { } /** - * Use the current platform view input connection until unlockPlatformViewInputConnection is + * * Use the current platform view input connection until unlockPlatformViewInputConnection is * called. * *

The current input connection instance is cached and any following call to @{link diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index fea199e0a84e2..ed1499ced9641 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -231,7 +231,7 @@ public void onPostResume() { mMouseCursorPlugin = null; } mLocalizationPlugin = new LocalizationPlugin(context, localizationChannel); - androidKeyProcessor = new AndroidKeyProcessor(this, keyEventChannel, mTextInputPlugin); + androidKeyProcessor = new AndroidKeyProcessor(keyEventChannel, mTextInputPlugin); androidTouchProcessor = new AndroidTouchProcessor(flutterRenderer, /*trackMotionEvents=*/ false); platformViewsController.attachToFlutterRenderer(flutterRenderer); @@ -270,7 +270,8 @@ public boolean onKeyUp(int keyCode, KeyEvent event) { if (!isAttached()) { return super.onKeyUp(keyCode, event); } - return androidKeyProcessor.onKeyUp(event) || super.onKeyUp(keyCode, event); + androidKeyProcessor.onKeyUp(event); + return super.onKeyUp(keyCode, event); } @Override @@ -278,7 +279,8 @@ public boolean onKeyDown(int keyCode, KeyEvent event) { if (!isAttached()) { return super.onKeyDown(keyCode, event); } - return androidKeyProcessor.onKeyDown(event) || super.onKeyDown(keyCode, event); + androidKeyProcessor.onKeyDown(event); + return super.onKeyDown(keyCode, event); } public FlutterNativeView getFlutterNativeView() { diff --git a/shell/platform/android/test/io/flutter/FlutterTestSuite.java b/shell/platform/android/test/io/flutter/FlutterTestSuite.java index 05e295eec8667..075e33c24b53e 100644 --- a/shell/platform/android/test/io/flutter/FlutterTestSuite.java +++ b/shell/platform/android/test/io/flutter/FlutterTestSuite.java @@ -4,7 +4,6 @@ package io.flutter; -import io.flutter.embedding.android.AndroidKeyProcessorTest; import io.flutter.embedding.android.FlutterActivityAndFragmentDelegateTest; import io.flutter.embedding.android.FlutterActivityTest; import io.flutter.embedding.android.FlutterAndroidComponentTest; @@ -18,7 +17,6 @@ import io.flutter.embedding.engine.RenderingComponentTest; import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistryTest; import io.flutter.embedding.engine.renderer.FlutterRendererTest; -import io.flutter.embedding.engine.systemchannels.KeyEventChannelTest; import io.flutter.embedding.engine.systemchannels.RestorationChannelTest; import io.flutter.external.FlutterLaunchTests; import io.flutter.plugin.common.StandardMessageCodecTest; @@ -41,8 +39,6 @@ @RunWith(Suite.class) @SuiteClasses({ - AccessibilityBridgeTest.class, - AndroidKeyProcessorTest.class, DartExecutorTest.class, FlutterActivityAndFragmentDelegateTest.class, FlutterActivityTest.class, @@ -54,25 +50,25 @@ FlutterFragmentTest.class, FlutterJNITest.class, FlutterLaunchTests.class, - FlutterRendererTest.class, FlutterShellArgsTest.class, + FlutterRendererTest.class, FlutterViewTest.class, InputConnectionAdaptorTest.class, - KeyEventChannelTest.class, LocalizationPluginTest.class, - MouseCursorPluginTest.class, PlatformPluginTest.class, PlatformViewsControllerTest.class, PluginComponentTest.class, PreconditionsTest.class, RenderingComponentTest.class, - RestorationChannelTest.class, + StandardMessageCodecTest.class, + StandardMethodCodecTest.class, ShimPluginRegistryTest.class, SingleViewPresentationTest.class, SmokeTest.class, - StandardMessageCodecTest.class, - StandardMethodCodecTest.class, TextInputPluginTest.class, + MouseCursorPluginTest.class, + AccessibilityBridgeTest.class, + RestorationChannelTest.class, }) /** Runs all of the unit tests listed in the {@code @SuiteClasses} annotation. */ public class FlutterTestSuite {} diff --git a/shell/platform/android/test/io/flutter/embedding/android/AndroidKeyProcessorTest.java b/shell/platform/android/test/io/flutter/embedding/android/AndroidKeyProcessorTest.java deleted file mode 100644 index 0f885c7d2642b..0000000000000 --- a/shell/platform/android/test/io/flutter/embedding/android/AndroidKeyProcessorTest.java +++ /dev/null @@ -1,146 +0,0 @@ -package io.flutter.embedding.android; - -import static junit.framework.TestCase.assertEquals; -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 static org.mockito.Mockito.when; - -import android.annotation.TargetApi; -import android.view.KeyEvent; -import android.view.View; -import androidx.annotation.NonNull; -import io.flutter.embedding.engine.FlutterEngine; -import io.flutter.embedding.engine.FlutterJNI; -import io.flutter.embedding.engine.systemchannels.KeyEventChannel; -import io.flutter.embedding.engine.systemchannels.TextInputChannel; -import io.flutter.plugin.editing.TextInputPlugin; -import io.flutter.util.FakeKeyEvent; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@Config(manifest = Config.NONE) -@RunWith(RobolectricTestRunner.class) -@TargetApi(28) -public class AndroidKeyProcessorTest { - @Mock FlutterJNI mockFlutterJni; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - when(mockFlutterJni.isAttached()).thenReturn(true); - } - - @Test - public void respondsTrueWhenHandlingNewEvents() { - FlutterEngine flutterEngine = mockFlutterEngine(); - KeyEventChannel fakeKeyEventChannel = flutterEngine.getKeyEventChannel(); - View fakeView = mock(View.class); - - AndroidKeyProcessor processor = - new AndroidKeyProcessor(fakeView, fakeKeyEventChannel, mock(TextInputPlugin.class)); - - boolean result = processor.onKeyDown(new FakeKeyEvent(KeyEvent.ACTION_DOWN, 65)); - assertEquals(true, result); - verify(fakeKeyEventChannel, times(1)).keyDown(any(KeyEventChannel.FlutterKeyEvent.class)); - verify(fakeKeyEventChannel, times(0)).keyUp(any(KeyEventChannel.FlutterKeyEvent.class)); - verify(fakeView, times(0)).dispatchKeyEvent(any(KeyEvent.class)); - } - - public void synthesizesEventsWhenKeyDownNotHandled() { - FlutterEngine flutterEngine = mockFlutterEngine(); - KeyEventChannel fakeKeyEventChannel = flutterEngine.getKeyEventChannel(); - View fakeView = mock(View.class); - ArgumentCaptor handlerCaptor = - ArgumentCaptor.forClass(KeyEventChannel.EventResponseHandler.class); - verify(fakeKeyEventChannel).setEventResponseHandler(handlerCaptor.capture()); - AndroidKeyProcessor processor = - new AndroidKeyProcessor(fakeView, fakeKeyEventChannel, mock(TextInputPlugin.class)); - ArgumentCaptor eventCaptor = - ArgumentCaptor.forClass(KeyEventChannel.FlutterKeyEvent.class); - FakeKeyEvent fakeKeyEvent = new FakeKeyEvent(KeyEvent.ACTION_DOWN, 65); - - boolean result = processor.onKeyDown(fakeKeyEvent); - assertEquals(true, result); - - // Capture the FlutterKeyEvent so we can find out its event ID to use when - // faking our response. - verify(fakeKeyEventChannel, times(1)).keyDown(eventCaptor.capture()); - boolean[] dispatchResult = {true}; - when(fakeView.dispatchKeyEvent(any(KeyEvent.class))) - .then( - new Answer() { - @Override - public Boolean answer(InvocationOnMock invocation) throws Throwable { - KeyEvent event = (KeyEvent) invocation.getArguments()[0]; - assertEquals(fakeKeyEvent, event); - dispatchResult[0] = processor.onKeyDown(event); - return dispatchResult[0]; - } - }); - - // Fake a response from the framework. - handlerCaptor.getValue().onKeyEventNotHandled(eventCaptor.getValue().eventId); - verify(fakeView, times(1)).dispatchKeyEvent(fakeKeyEvent); - assertEquals(false, dispatchResult[0]); - verify(fakeKeyEventChannel, times(0)).keyUp(any(KeyEventChannel.FlutterKeyEvent.class)); - } - - public void synthesizesEventsWhenKeyUpNotHandled() { - FlutterEngine flutterEngine = mockFlutterEngine(); - KeyEventChannel fakeKeyEventChannel = flutterEngine.getKeyEventChannel(); - View fakeView = mock(View.class); - ArgumentCaptor handlerCaptor = - ArgumentCaptor.forClass(KeyEventChannel.EventResponseHandler.class); - verify(fakeKeyEventChannel).setEventResponseHandler(handlerCaptor.capture()); - AndroidKeyProcessor processor = - new AndroidKeyProcessor(fakeView, fakeKeyEventChannel, mock(TextInputPlugin.class)); - ArgumentCaptor eventCaptor = - ArgumentCaptor.forClass(KeyEventChannel.FlutterKeyEvent.class); - FakeKeyEvent fakeKeyEvent = new FakeKeyEvent(KeyEvent.ACTION_UP, 65); - - boolean result = processor.onKeyUp(fakeKeyEvent); - assertEquals(true, result); - - // Capture the FlutterKeyEvent so we can find out its event ID to use when - // faking our response. - verify(fakeKeyEventChannel, times(1)).keyUp(eventCaptor.capture()); - boolean[] dispatchResult = {true}; - when(fakeView.dispatchKeyEvent(any(KeyEvent.class))) - .then( - new Answer() { - @Override - public Boolean answer(InvocationOnMock invocation) throws Throwable { - KeyEvent event = (KeyEvent) invocation.getArguments()[0]; - assertEquals(fakeKeyEvent, event); - dispatchResult[0] = processor.onKeyUp(event); - return dispatchResult[0]; - } - }); - - // Fake a response from the framework. - handlerCaptor.getValue().onKeyEventNotHandled(eventCaptor.getValue().eventId); - verify(fakeView, times(1)).dispatchKeyEvent(fakeKeyEvent); - assertEquals(false, dispatchResult[0]); - verify(fakeKeyEventChannel, times(0)).keyUp(any(KeyEventChannel.FlutterKeyEvent.class)); - } - - @NonNull - private FlutterEngine mockFlutterEngine() { - // Mock FlutterEngine and all of its required direct calls. - FlutterEngine engine = mock(FlutterEngine.class); - when(engine.getKeyEventChannel()).thenReturn(mock(KeyEventChannel.class)); - when(engine.getTextInputChannel()).thenReturn(mock(TextInputChannel.class)); - - return engine; - } -} 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 8967656fa870e..2c8e243375fd7 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -25,7 +25,6 @@ import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface; import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; -import io.flutter.embedding.engine.systemchannels.KeyEventChannel; import io.flutter.embedding.engine.systemchannels.LifecycleChannel; import io.flutter.embedding.engine.systemchannels.LocalizationChannel; import io.flutter.embedding.engine.systemchannels.MouseCursorChannel; @@ -616,20 +615,19 @@ private FlutterEngine mockFlutterEngine() { // Mock FlutterEngine and all of its required direct calls. FlutterEngine engine = mock(FlutterEngine.class); - when(engine.getAccessibilityChannel()).thenReturn(mock(AccessibilityChannel.class)); - when(engine.getActivityControlSurface()).thenReturn(mock(ActivityControlSurface.class)); when(engine.getDartExecutor()).thenReturn(mock(DartExecutor.class)); - when(engine.getKeyEventChannel()).thenReturn(mock(KeyEventChannel.class)); - when(engine.getLifecycleChannel()).thenReturn(mock(LifecycleChannel.class)); - when(engine.getLocalizationChannel()).thenReturn(mock(LocalizationChannel.class)); - when(engine.getLocalizationPlugin()).thenReturn(mock(LocalizationPlugin.class)); - when(engine.getMouseCursorChannel()).thenReturn(mock(MouseCursorChannel.class)); - when(engine.getNavigationChannel()).thenReturn(mock(NavigationChannel.class)); - when(engine.getPlatformViewsController()).thenReturn(mock(PlatformViewsController.class)); when(engine.getRenderer()).thenReturn(mock(FlutterRenderer.class)); + when(engine.getPlatformViewsController()).thenReturn(mock(PlatformViewsController.class)); + when(engine.getAccessibilityChannel()).thenReturn(mock(AccessibilityChannel.class)); when(engine.getSettingsChannel()).thenReturn(fakeSettingsChannel); + when(engine.getLocalizationChannel()).thenReturn(mock(LocalizationChannel.class)); + when(engine.getLifecycleChannel()).thenReturn(mock(LifecycleChannel.class)); + when(engine.getNavigationChannel()).thenReturn(mock(NavigationChannel.class)); when(engine.getSystemChannel()).thenReturn(mock(SystemChannel.class)); when(engine.getTextInputChannel()).thenReturn(mock(TextInputChannel.class)); + when(engine.getMouseCursorChannel()).thenReturn(mock(MouseCursorChannel.class)); + when(engine.getActivityControlSurface()).thenReturn(mock(ActivityControlSurface.class)); + when(engine.getLocalizationPlugin()).thenReturn(mock(LocalizationPlugin.class)); return engine; } diff --git a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java b/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java deleted file mode 100644 index 81b34b3f3b5ad..0000000000000 --- a/shell/platform/android/test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java +++ /dev/null @@ -1,124 +0,0 @@ -package io.flutter.embedding.engine.systemchannels; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -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 android.annotation.TargetApi; -import android.view.KeyEvent; -import androidx.annotation.NonNull; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.JSONMessageCodec; -import io.flutter.util.FakeKeyEvent; -import java.nio.ByteBuffer; -import org.json.JSONException; -import org.json.JSONObject; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@Config( - manifest = Config.NONE, - shadows = {}) -@RunWith(RobolectricTestRunner.class) -@TargetApi(24) -public class KeyEventChannelTest { - - private void sendReply(boolean handled, BinaryMessenger.BinaryReply messengerReply) - throws JSONException { - JSONObject reply = new JSONObject(); - reply.put("handled", true); - ByteBuffer binaryReply = JSONMessageCodec.INSTANCE.encodeMessage(reply); - assertNotNull(binaryReply); - binaryReply.rewind(); - messengerReply.reply(binaryReply); - } - - @Test - public void keyDownEventIsSentToFramework() throws JSONException { - BinaryMessenger fakeMessenger = mock(BinaryMessenger.class); - KeyEventChannel keyEventChannel = new KeyEventChannel(fakeMessenger); - final boolean[] handled = {false}; - final long[] handledId = {-1}; - keyEventChannel.setEventResponseHandler( - new KeyEventChannel.EventResponseHandler() { - public void onKeyEventHandled(@NonNull long id) { - handled[0] = true; - handledId[0] = id; - } - - public void onKeyEventNotHandled(@NonNull long id) { - handled[0] = false; - handledId[0] = id; - } - }); - verify(fakeMessenger, times(0)).send(any(), any(), any()); - - KeyEvent event = new FakeKeyEvent(KeyEvent.ACTION_DOWN, 65); - KeyEventChannel.FlutterKeyEvent flutterKeyEvent = - new KeyEventChannel.FlutterKeyEvent(event, null, 10); - keyEventChannel.keyDown(flutterKeyEvent); - ArgumentCaptor byteBufferArgumentCaptor = ArgumentCaptor.forClass(ByteBuffer.class); - ArgumentCaptor replyArgumentCaptor = - ArgumentCaptor.forClass(BinaryMessenger.BinaryReply.class); - verify(fakeMessenger, times(1)) - .send(any(), byteBufferArgumentCaptor.capture(), replyArgumentCaptor.capture()); - ByteBuffer capturedMessage = byteBufferArgumentCaptor.getValue(); - capturedMessage.rewind(); - JSONObject message = (JSONObject) JSONMessageCodec.INSTANCE.decodeMessage(capturedMessage); - assertNotNull(message); - assertEquals("keydown", message.get("type")); - - // Simulate a reply, and see that it is handled. - sendReply(true, replyArgumentCaptor.getValue()); - assertTrue(handled[0]); - assertEquals(10, handledId[0]); - } - - @Test - public void keyUpEventIsSentToFramework() throws JSONException { - BinaryMessenger fakeMessenger = mock(BinaryMessenger.class); - KeyEventChannel keyEventChannel = new KeyEventChannel(fakeMessenger); - final boolean[] handled = {false}; - final long[] handledId = {-1}; - keyEventChannel.setEventResponseHandler( - new KeyEventChannel.EventResponseHandler() { - public void onKeyEventHandled(long id) { - handled[0] = true; - handledId[0] = id; - } - - public void onKeyEventNotHandled(long id) { - handled[0] = false; - handledId[0] = id; - } - }); - verify(fakeMessenger, times(0)).send(any(), any(), any()); - - KeyEvent event = new FakeKeyEvent(KeyEvent.ACTION_UP, 65); - KeyEventChannel.FlutterKeyEvent flutterKeyEvent = - new KeyEventChannel.FlutterKeyEvent(event, null, 10); - keyEventChannel.keyUp(flutterKeyEvent); - ArgumentCaptor byteBufferArgumentCaptor = ArgumentCaptor.forClass(ByteBuffer.class); - ArgumentCaptor replyArgumentCaptor = - ArgumentCaptor.forClass(BinaryMessenger.BinaryReply.class); - verify(fakeMessenger, times(1)) - .send(any(), byteBufferArgumentCaptor.capture(), replyArgumentCaptor.capture()); - ByteBuffer capturedMessage = byteBufferArgumentCaptor.getValue(); - capturedMessage.rewind(); - JSONObject message = (JSONObject) JSONMessageCodec.INSTANCE.decodeMessage(capturedMessage); - assertNotNull(message); - assertEquals("keyup", message.get("type")); - - // Simulate a reply, and see that it is handled. - sendReply(true, replyArgumentCaptor.getValue()); - assertTrue(handled[0]); - assertEquals(10, handledId[0]); - } -}