Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix race condition in key event handling on Android #22658

Merged
merged 3 commits into from
Dec 1, 2020

Conversation

gspencergoog
Copy link
Contributor

Description

This fixes a problem in Android key event handling where, because I was only using a single bool to indicate that we were re-dispatching, there was a race condition when multiple keys were pending (sent to the framework, awaiting responses).

This fixes that by switching to a mechanism that uses a fingerprint calculated from the event information itself (a combination of event action (up/down), scan code, and event timestamp).

In doing this, I realized that because key events can come from either the dispatchEvent call, or through the InputConnectionAdaptor, I needed to handle both routes properly so that the events would all be handled, and all go through the same mechanism on the framework side.

Related Issues

Tests

  • Added tests to make sure that key events received through the InputConnectionAdaptor get sent to the AndroidKeyProcessor, but when the key processor send the events to the InputConnection, it doesn't get them back again.

Breaking Change

  • No, no existing tests failed, so this is not a breaking change.

* framework.
*/
public static long computeEventId(@NonNull KeyEvent event) {
return (event.getEventTime() & 0xffffffff)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getEventTime() seems to return a long, and according to this stackoverflow question the bit mask is not going to work because of sign extension.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need an id? Is it possible to use == instead since we are re-dispatching the same KeyEvent instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I hadn't considered that, but perhaps we can do that. I'll give it a try.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we do need some kind of ID, because I need to be able to identify the event when it comes back from the framework, but I can probably just use event.hashCode() instead of figuring out my own ID.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the framework response only contains a "handled" field, so we actually have access to the original FlutterKeyEvent object?

BasicMessageChannel.Reply<Object> 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<Object> channel;
public void keyUp(@NonNull FlutterKeyEvent keyEvent) {
Map<String, Object> message = new HashMap<>();
message.put("type", "keyup");
message.put("keymap", "android");
encodeKeyEvent(keyEvent, message);
channel.send(message, createReplyHandler(keyEvent.eventId));
}
public void keyDown(@NonNull FlutterKeyEvent keyEvent) {
Map<String, Object> message = new HashMap<>();
message.put("type", "keydown");
message.put("keymap", "android");
encodeKeyEvent(keyEvent, message);
channel.send(message, createReplyHandler(keyEvent.eventId));
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, good point. The FlutterKeyEvent doesn't include the original Android KeyEvent, so I'll switch to using the FlutterKeyEvent as the ID.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I simplified the FlutterKeyEvent to just have what it needed and keep the Android KeyEvent it was created with.

}

// Since the framework didn't handle it, dispatch the event 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.getRootView().dispatchKeyEvent(event);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case that the even is consumed by some other view before reaching our FlutterView (not sure if possible, maybe in add-to-app?), it will never be removed from the queue?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I was assuming that it wasn't possible: the event should be given to the view again, but I suppose it might be possible to construct an add2app scenario where a view was inserted before this view, and then have it handle the event before the view received it again.

I'm not sure how to prevent that. At the moment, it will assert after that happens, since the events will begin to arrive out of order. Perhaps that assert should just be a log message instead of an assertion?

One way to address this would be to just keep track of at most the last 1000 events, and not do any checking to make sure that they arrive in order. Then, even if this happens, there is a limited amount of memory it can waste. It's unlikely in normal operation that 1000 key events would be produced before the framework can respond to any of them. It would be slightly slower, since it would have to do a hash map lookup to find the event instead of assuming that it was at the head of the deque.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to directly deliver the event to the FlutterView since this is a re-dispatch?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish, but no. If I don't deliver it to the root view, then the back button doesn't get processed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see. The root view dispatches the event to the activity.

}

// Since the framework didn't handle it, dispatch the event 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.getRootView().dispatchKeyEvent(event);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see. The root view dispatches the event to the activity.

@gspencergoog gspencergoog merged commit 40fa345 into flutter:master Dec 1, 2020
@gspencergoog gspencergoog deleted the fix_key_event_race branch December 1, 2020 17:42
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Dec 1, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Dec 1, 2020
cbracken pushed a commit to flutter/flutter that referenced this pull request Dec 1, 2020
* d6beaed Roll Fuchsia Linux SDK from gkfmiRsIl... to un3JixwuO... (flutter/engine#22744)

* 8832b48 Roll Skia from 888c5d3e57eb to 51b74afb84d4 (12 revisions) (flutter/engine#22746)

* e890901 Don't register CanvasKit with `define` (flutter/engine#22745)

* 3c51679 Roll Skia from 51b74afb84d4 to 452369182f6e (1 revision) (flutter/engine#22749)

* 5bf6533 Introduce a delegate class for gpu metal rendering (flutter/engine#22611)

* 5131aa4 Roll Skia from 452369182f6e to f2efb80bc316 (4 revisions) (flutter/engine#22750)

* 7b5f79f fuchsia: Ensure full-screen input interceptor (flutter/engine#22687)

* cec8a6e Manual roll of Dart SDK from ce76503f5b46 to dcd5a8f005a (flutter/engine#22766)

* 001a511 Roll Fuchsia Linux SDK from un3JixwuO... to Bnaeivv07... (flutter/engine#22757)

* b9615b1 Roll Fuchsia Mac SDK from 36uDTGJQp... to qpkZl0s5J... (flutter/engine#22753)

* c4c4763 Roll Skia from f2efb80bc316 to 8d78da910e45 (5 revisions) (flutter/engine#22754)

* dbd1abe Roll Dart SDK from dcd5a8f005a2 to 960620d2e811 (794 revisions) (flutter/engine#22768)

* 1c2a6bd Fix the unchecked conversion warning for searchPaths in PlayStoreDynamicFeatureManager (flutter/engine#22654)

* 81af789 add file package to deps in prep for glob update (flutter/engine#22770)

* a35e3fe Let FlutterFragment not pop the whole activity by default when more fragments are in the activity (flutter/engine#22692)

* adb3312 Revert "Introduce a delegate class for gpu metal rendering (#22611)" (flutter/engine#22775)

* bcc8832 Cleanup dart_runner examples & tests. (flutter/engine#22769)

* 609307d Roll Skia from 8d78da910e45 to fd41d878b13d (20 revisions) (flutter/engine#22772)

* 587c023 [web] Add new line break type (prohibited) (flutter/engine#22771)

* 6b2ed2b Roll Skia from fd41d878b13d to 70fe17e12f38 (6 revisions) (flutter/engine#22776)

* 7910a17 Roll Dart SDK from 960620d2e811 to 7a2a3968ef53 (12 revisions) (flutter/engine#22778)

* f4ada80 Roll Skia from 70fe17e12f38 to 4c6f57a23e63 (1 revision) (flutter/engine#22781)

* 3101dff [web] Optimize Matrix4.identity (flutter/engine#22622)

* a4ce848 Add FlutterPlayStoreSplitApplication for simpler opt in to Split AOT (flutter/engine#22752)

* 747b791 Add file.dart to DEPS (flutter/engine#22794)

* 40fa345 Fix race condition in key event handling on Android (flutter/engine#22658)

* d2ad441 Fix PlatformDispatcher.locale to return something meaningful when there are no locales. (flutter/engine#22608)
gspencergoog added a commit to gspencergoog/engine that referenced this pull request Dec 2, 2020
gspencergoog added a commit that referenced this pull request Dec 2, 2020
…#22823)

This reverts commit 40fa345 (#22658) because it breaks some Google tests. Will investigate and re-land.
gspencergoog added a commit to gspencergoog/engine that referenced this pull request Dec 2, 2020
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Dec 4, 2020
cbracken pushed a commit to engine-flutter-autoroll/flutter that referenced this pull request Dec 4, 2020
cbracken pushed a commit to flutter/flutter that referenced this pull request Dec 4, 2020
* d6beaed Roll Fuchsia Linux SDK from gkfmiRsIl... to un3JixwuO... (flutter/engine#22744)

* 8832b48 Roll Skia from 888c5d3e57eb to 51b74afb84d4 (12 revisions) (flutter/engine#22746)

* e890901 Don't register CanvasKit with `define` (flutter/engine#22745)

* 3c51679 Roll Skia from 51b74afb84d4 to 452369182f6e (1 revision) (flutter/engine#22749)

* 5bf6533 Introduce a delegate class for gpu metal rendering (flutter/engine#22611)

* 5131aa4 Roll Skia from 452369182f6e to f2efb80bc316 (4 revisions) (flutter/engine#22750)

* 7b5f79f fuchsia: Ensure full-screen input interceptor (flutter/engine#22687)

* cec8a6e Manual roll of Dart SDK from ce76503f5b46 to dcd5a8f005a (flutter/engine#22766)

* 001a511 Roll Fuchsia Linux SDK from un3JixwuO... to Bnaeivv07... (flutter/engine#22757)

* b9615b1 Roll Fuchsia Mac SDK from 36uDTGJQp... to qpkZl0s5J... (flutter/engine#22753)

* c4c4763 Roll Skia from f2efb80bc316 to 8d78da910e45 (5 revisions) (flutter/engine#22754)

* dbd1abe Roll Dart SDK from dcd5a8f005a2 to 960620d2e811 (794 revisions) (flutter/engine#22768)

* 1c2a6bd Fix the unchecked conversion warning for searchPaths in PlayStoreDynamicFeatureManager (flutter/engine#22654)

* 81af789 add file package to deps in prep for glob update (flutter/engine#22770)

* a35e3fe Let FlutterFragment not pop the whole activity by default when more fragments are in the activity (flutter/engine#22692)

* adb3312 Revert "Introduce a delegate class for gpu metal rendering (#22611)" (flutter/engine#22775)

* bcc8832 Cleanup dart_runner examples & tests. (flutter/engine#22769)

* 609307d Roll Skia from 8d78da910e45 to fd41d878b13d (20 revisions) (flutter/engine#22772)

* 587c023 [web] Add new line break type (prohibited) (flutter/engine#22771)

* 6b2ed2b Roll Skia from fd41d878b13d to 70fe17e12f38 (6 revisions) (flutter/engine#22776)

* 7910a17 Roll Dart SDK from 960620d2e811 to 7a2a3968ef53 (12 revisions) (flutter/engine#22778)

* f4ada80 Roll Skia from 70fe17e12f38 to 4c6f57a23e63 (1 revision) (flutter/engine#22781)

* 3101dff [web] Optimize Matrix4.identity (flutter/engine#22622)

* a4ce848 Add FlutterPlayStoreSplitApplication for simpler opt in to Split AOT (flutter/engine#22752)

* 747b791 Add file.dart to DEPS (flutter/engine#22794)

* 40fa345 Fix race condition in key event handling on Android (flutter/engine#22658)

* d2ad441 Fix PlatformDispatcher.locale to return something meaningful when there are no locales. (flutter/engine#22608)

* b9a0b5e Roll Skia from 4c6f57a23e63 to a927771c9cce (10 revisions) (flutter/engine#22802)

* 96d63e5 Roll Dart SDK from 7a2a3968ef53 to e9a03fd98faa (5 revisions) (flutter/engine#22801)

* cdf72da Roll Skia from a927771c9cce to 7b776b514933 (3 revisions) (flutter/engine#22803)

* a0c8b67 Roll buildroot and benchmark (flutter/engine#22804)

* c3c3ec6 Roll Fuchsia Mac SDK from qpkZl0s5J... to 7O11wjLVX... (flutter/engine#22805)

* 6625308 Revert "Roll buildroot and benchmark (#22804)" (flutter/engine#22816)

* 64d9add Add a golden scenario test for fallback font rendering on iOS take 3 (flutter/engine#22736)

* 7d7a260 Add static text trait to plain semantics object with label in iOS (flutter/engine#22811)

* 22e1143 Roll Skia from 7b776b514933 to c504ecda03b8 (6 revisions) (flutter/engine#22808)

* 65254eb Roll Dart SDK from e9a03fd98faa to 5acaa5f14b03 (1 revision) (flutter/engine#22810)

* 3926b21 Roll Fuchsia Linux SDK from Bnaeivv07... to W14Qninrb... (flutter/engine#22817)

* 5eb505f Roll Fuchsia Mac SDK from 7O11wjLVX... to Z_-ciOYM9... (flutter/engine#22820)

* d85cb10 add trace kernel flag to allowlist (flutter/engine#22812)

* 14cb066 [embedder] Compositor can specify that no backing stores be cached (flutter/engine#22780)

* eb6eabc Reland "Introduce a delegate class for gpu metal rendering (#22611)" (flutter/engine#22777)

* 644dd65 Temporarily reduce e2e test matrix to stop flaky web engine builds (flutter/engine#22824)

* 105004d Stop using the List constructor. (flutter/engine#22793)

* 34f49a1 Roll Dart SDK from 5acaa5f14b03 to cfaa7606cbf5 (2 revisions) (flutter/engine#22827)

* 6c8342f Revert "Fix race condition in key event handling on Android (#22658)" (flutter/engine#22823)

* 1c2a8f9 Roll Skia from c504ecda03b8 to 9443d58af292 (16 revisions) (flutter/engine#22828)

* b63e911 Better handle image codec instantiation failure (flutter/engine#22809)

* 1358fda Generate Maven metadata files for engine artifacts (flutter/engine#22685)

* 079c669 Generate gen_snapshot_armv7 and gen_snapshot_arm64 (flutter/engine#22818)

* fcbfa9f Split AOT Engine Runtime (flutter/engine#22624)

* 24d289e Roll Fuchsia Linux SDK from W14Qninrb... to M_8svVndh... (flutter/engine#22842)

* 78b567f Reland: "Fix race condition in key event handling on Android (#22658)" (flutter/engine#22834)

* 7d32cea (MacOS) Add FlutterGLCompositor with support for rendering multiple layers (flutter/engine#22782)

* a713174 Roll Skia from 9443d58af292 to c7112edbe0f4 (10 revisions) (flutter/engine#22839)

* bee352c Roll Dart SDK from cfaa7606cbf5 to 97cfd05b3cb3 (2 revisions) (flutter/engine#22840)

* e5f510f [web] Fix event transform between mousedown/up due to mouse move event (flutter/engine#22813)

* 04b98dc Roll Fuchsia Mac SDK from Z_-ciOYM9... to DRN4P3zbe... (flutter/engine#22841)

* 0e3b2cf Roll Skia from c7112edbe0f4 to d39aec0e40ec (17 revisions) (flutter/engine#22844)

* e71c6f4 leaving only html tests (flutter/engine#22846)

* 3773835 Make CkPicture resurrectable (flutter/engine#22807)

* bd394a1 Roll Skia from d39aec0e40ec to 38921cafe1bb (7 revisions) (flutter/engine#22847)

* 66f44c6 Roll Dart SDK from 97cfd05b3cb3 to a37a4d42e53d (4 revisions) (flutter/engine#22849)

* bdadaad Add delayed event delivery for Linux. (flutter/engine#22577)

* 48befc5 More rename from GPU thread to raster thread (flutter/engine#22819)

* 9b1b7f6 Roll Skia from 38921cafe1bb to abcc1ecdfd0c (8 revisions) (flutter/engine#22851)

* 14a6fd9 Fix NPE when platform plugin delegate is null (flutter/engine#22852)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants