summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSiarhei Vishniakou <svv@google.com>2023-12-26 18:09:32 -0800
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-12-29 03:16:42 +0000
commite4bdb383aad8f9bb32fc0a2099d12c4f4c5494ce (patch)
tree501d68b5269a3bab6ec824ad5f8fa6cbfc0f6eb7
parente4b3c4d20162bbc90e1459c0c4976fa23b3f2f87 (diff)
downloadnative-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.cpp10
-rw-r--r--services/inputflinger/tests/InputDispatcher_test.cpp98
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: