InputDispatcher: Fix crash when there is an ANR after window removal
This addresses a bug in the change:
I47744cbd677cc74e26a102c50a2c11c68bc8aa89
InputDispatcher has an invariant that it can only send events to a
connection if it has a window. We did not check if the channel receiving
an ANR had a window before attempting to synthesize cancellations for
it.
Bug: 324330557
Bug: 210460522
Test: atest inputflinger_tests
Change-Id: I5f3013fe93c0f4d1bb0f58e7b2a241cffe5c5bf2
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 1c37da0..31c7c67 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -155,7 +155,7 @@
class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
struct AnrResult {
sp<IBinder> token{};
- gui::Pid pid{gui::Pid::INVALID};
+ std::optional<gui::Pid> pid{};
};
/* Stores data about a user-activity-poke event from the dispatcher. */
struct UserActivityPokeEvent {
@@ -260,7 +260,7 @@
void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout,
const sp<IBinder>& expectedToken,
- gui::Pid expectedPid) {
+ std::optional<gui::Pid> expectedPid) {
std::unique_lock lock(mLock);
android::base::ScopedLockAssertion assumeLocked(mLock);
AnrResult result;
@@ -280,7 +280,7 @@
}
void assertNotifyWindowResponsiveWasCalled(const sp<IBinder>& expectedToken,
- gui::Pid expectedPid) {
+ std::optional<gui::Pid> expectedPid) {
std::unique_lock lock(mLock);
android::base::ScopedLockAssertion assumeLocked(mLock);
AnrResult result;
@@ -524,16 +524,14 @@
void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<gui::Pid> pid,
const std::string&) override {
std::scoped_lock lock(mLock);
- ASSERT_TRUE(pid.has_value());
- mAnrWindows.push({connectionToken, *pid});
+ mAnrWindows.push({connectionToken, pid});
mNotifyAnr.notify_all();
}
void notifyWindowResponsive(const sp<IBinder>& connectionToken,
std::optional<gui::Pid> pid) override {
std::scoped_lock lock(mLock);
- ASSERT_TRUE(pid.has_value());
- mResponsiveWindows.push({connectionToken, *pid});
+ mResponsiveWindows.push({connectionToken, pid});
mNotifyAnr.notify_all();
}
@@ -9059,6 +9057,61 @@
mWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
}
+// Send an event to the app and have the app not respond right away. Then remove the app window.
+// When the window is removed, the dispatcher will cancel the events for that window.
+// So InputDispatcher will enqueue ACTION_CANCEL event as well.
+TEST_F(InputDispatcherSingleWindowAnr, AnrAfterWindowRemoval) {
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {WINDOW_LOCATION}));
+
+ const auto [sequenceNum, _] = mWindow->receiveEvent(); // ACTION_DOWN
+ ASSERT_TRUE(sequenceNum);
+
+ // Remove the window, but the input channel should remain alive.
+ mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
+
+ const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+ // Since the window was removed, Dispatcher does not know the PID associated with the window
+ // anymore, so the policy is notified without the PID.
+ mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow->getToken(),
+ /*pid=*/std::nullopt);
+
+ mWindow->finishEvent(*sequenceNum);
+ // The cancellation was generated when the window was removed, along with the focus event.
+ mWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ mWindow->consumeFocusEvent(false);
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+ mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), /*pid=*/std::nullopt);
+}
+
+// Send an event to the app and have the app not respond right away. Wait for the policy to be
+// notified of the unresponsive window, then remove the app window.
+TEST_F(InputDispatcherSingleWindowAnr, AnrFollowedByWindowRemoval) {
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {WINDOW_LOCATION}));
+
+ const auto [sequenceNum, _] = mWindow->receiveEvent(); // ACTION_DOWN
+ ASSERT_TRUE(sequenceNum);
+ const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+ mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
+
+ // Remove the window, but the input channel should remain alive.
+ mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
+
+ mWindow->finishEvent(*sequenceNum);
+ // The cancellation was generated during the ANR, and the window lost focus when it was removed.
+ mWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ mWindow->consumeFocusEvent(false);
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+ // Since the window was removed, Dispatcher does not know the PID associated with the window
+ // becoming responsive, so the policy is notified without the PID.
+ mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), /*pid=*/std::nullopt);
+}
+
class InputDispatcherMultiWindowAnr : public InputDispatcherTest {
virtual void SetUp() override {
InputDispatcherTest::SetUp();