diff options
author | Siarhei Vishniakou <svv@google.com> | 2023-12-26 18:09:32 -0800 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-12-29 03:16:42 +0000 |
commit | e4bdb383aad8f9bb32fc0a2099d12c4f4c5494ce (patch) | |
tree | 501d68b5269a3bab6ec824ad5f8fa6cbfc0f6eb7 | |
parent | e4b3c4d20162bbc90e1459c0c4976fa23b3f2f87 (diff) | |
download | native-e4bdb383aad8f9bb32fc0a2099d12c4f4c5494ce.tar.gz |
Only prune non-pointer events
The "pruning input queue" functionality is present in order to prevent
the dispatcher from being blocked. However, only focus-dispatched events
(like keys and joystick events) can block the dispatcher, because they
require a focused window. Pointer events, like touches, go to the
touched window, and are not blocked.
Furthermore, dropping pointer events is not a good idea, because it can
cause inconsistent input streams to be processed inside the dispatcher.
In this CL, we prevent pointer events from being dropped. This resolves
a crash in InputDispatcher, because an inconsistent input stream would
lead to unexpected behaviour later on in the pipeline.
To reproduce the original crash:
1) Set device to 3-button navigation mode
2) Launch an app, then quickly press the back button
3) Then, quickly touch the app again
The WM would not yet send the "setFocusedApplication" call to match the
newly launched app (maybe it's slow, or it's just a bug). The dispatcher
would be waiting for a focused window, and the touch into the newly
launched app would cause the dispatcher to drop events because "the user
is interacting with a different app than the one that's currently
focused". And finally, since the navigation bar listens for outside
touches, the dispatcher would try to send an ACTION_OUTSIDE event to the
navigation bar, and this would conflict with the current dispatcher
state because there's already a pointer inside navigation bar (and the
dispatcher dropped the ACTION_UP event instead of processing it).
Bug: 308531018
Test: TEST=inputflinger_tests; m $TEST && $ANDROID_HOST_OUT/nativetest64/$TEST/$TEST
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:99e407b0e7e748cbb82bdaa2e21c740c308d60b3)
Merged-In: I7ac5f7bd607c7411e3917977888d1cfeaf96615c
Change-Id: I7ac5f7bd607c7411e3917977888d1cfeaf96615c
-rw-r--r-- | services/inputflinger/dispatcher/InputDispatcher.cpp | 10 | ||||
-rw-r--r-- | services/inputflinger/tests/InputDispatcher_test.cpp | 98 |
2 files changed, 105 insertions, 3 deletions
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp index d356549e76..f8d7b8f5ef 100644 --- a/services/inputflinger/dispatcher/InputDispatcher.cpp +++ b/services/inputflinger/dispatcher/InputDispatcher.cpp @@ -1099,7 +1099,10 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) { } } if (dropReason == DropReason::NOT_DROPPED && mNextUnblockedEvent) { - dropReason = DropReason::BLOCKED; + if (!isFromSource(motionEntry->source, AINPUT_SOURCE_CLASS_POINTER)) { + // Only drop events that are focus-dispatched. + dropReason = DropReason::BLOCKED; + } } done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime); break; @@ -1370,8 +1373,9 @@ void InputDispatcher::dropInboundEventLocked(const EventEntry& entry, DropReason reason = "inbound event was dropped because of pending overdue app switch"; break; case DropReason::BLOCKED: - ALOGI("Dropped event because the current application is not responding and the user " - "has started interacting with a different application."); + LOG(INFO) << "Dropping because the current application is not responding and the user " + "has started interacting with a different application: " + << entry.getDescription(); reason = "inbound event was dropped because the current application is not responding " "and the user has started interacting with a different application"; break; diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp index 5002391f61..cdd2bfccd3 100644 --- a/services/inputflinger/tests/InputDispatcher_test.cpp +++ b/services/inputflinger/tests/InputDispatcher_test.cpp @@ -8691,6 +8691,104 @@ TEST_F(InputDispatcherMultiWindowAnr, FocusedWindowWithoutSetFocusedApplication_ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyAnrWasNotCalled()); } +/** + * If we are pruning input queue, we should never drop pointer events. Otherwise, we risk having + * an inconsistent event stream inside the dispatcher. In this test, we make sure that the + * dispatcher doesn't prune pointer events incorrectly. + * + * This test reproduces a crash in InputDispatcher. + * To reproduce the crash, we need to simulate the conditions for "pruning input queue" to occur. + * + * Keep the currently focused application (mApplication), and have no focused window. + * We set up two additional windows: + * 1) The navigation bar window. This simulates the system "NavigationBar", which is used in the + * 3-button navigation mode. This window injects a BACK button when it's touched. 2) The application + * window. This window is not focusable, but is touchable. + * + * We first touch the navigation bar, which causes it to inject a key. Since there's no focused + * window, the dispatcher doesn't process this key, and all other events inside dispatcher are now + * blocked. The dispatcher is waiting for 'mApplication' to add a focused window. + * + * Now, we touch "Another window". This window is owned by a different application than + * 'mApplication'. This causes the dispatcher to stop waiting for 'mApplication' to add a focused + * window. Now, the "pruning input queue" behaviour should kick in, and the dispatcher should start + * dropping the events from its queue. Ensure that no crash occurs. + * + * In this test, we are setting long timeouts to prevent ANRs and events dropped due to being stale. + * This does not affect the test running time. + */ +TEST_F(InputDispatcherMultiWindowAnr, PruningInputQueueShouldNotDropPointerEvents) { + std::shared_ptr<FakeApplicationHandle> systemUiApplication = + std::make_shared<FakeApplicationHandle>(); + systemUiApplication->setDispatchingTimeout(3000ms); + mFakePolicy->setStaleEventTimeout(3000ms); + sp<FakeWindowHandle> navigationBar = + sp<FakeWindowHandle>::make(systemUiApplication, mDispatcher, "NavigationBar", + ADISPLAY_ID_DEFAULT); + navigationBar->setFocusable(false); + navigationBar->setWatchOutsideTouch(true); + navigationBar->setFrame(Rect(0, 0, 100, 100)); + + mApplication->setDispatchingTimeout(3000ms); + // 'mApplication' is already focused, but we call it again here to make it explicit. + mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication); + + std::shared_ptr<FakeApplicationHandle> anotherApplication = + std::make_shared<FakeApplicationHandle>(); + sp<FakeWindowHandle> appWindow = + sp<FakeWindowHandle>::make(anotherApplication, mDispatcher, "Another window", + ADISPLAY_ID_DEFAULT); + appWindow->setFocusable(false); + appWindow->setFrame(Rect(100, 100, 200, 200)); + + mDispatcher->onWindowInfosChanged( + {{*navigationBar->getInfo(), *appWindow->getInfo()}, {}, 0, 0}); + // 'mFocusedWindow' is no longer in the dispatcher window list, and therefore loses focus + mFocusedWindow->consumeFocusEvent(false); + + // Touch down the navigation bar. It consumes the touch and injects a key into the dispatcher + // in response. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); + navigationBar->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + // Key will not be sent anywhere because we have no focused window. It will remain pending. + // Pretend we are injecting KEYCODE_BACK, but it doesn't actually matter what key it is. + InputEventInjectionResult result = + injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms, + /*allowKeyRepeat=*/false); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); + + // Finish the gesture - lift up finger and inject ACTION_UP key event + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50)) + .build()); + result = injectKey(*mDispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT, + InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms, + /*allowKeyRepeat=*/false); + ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result); + // The key that was injected is blocking the dispatcher, so the navigation bar shouldn't be + // getting any events yet. + navigationBar->assertNoEvents(); + + // Now touch "Another window". This touch is going to a different application than the one we + // are waiting for (which is 'mApplication'). + // This should cause the dispatcher to drop the pending focus-dispatched events (like the key + // trying to be injected) and to continue processing the rest of the events in the original + // order. + mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(150)) + .build()); + navigationBar->consumeMotionEvent(WithMotionAction(ACTION_UP)); + navigationBar->consumeMotionEvent(WithMotionAction(ACTION_OUTSIDE)); + appWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + + appWindow->assertNoEvents(); + navigationBar->assertNoEvents(); +} + // These tests ensure we cannot send touch events to a window that's positioned behind a window // that has feature NO_INPUT_CHANNEL. // Layout: |