Only send events to windows with pointers
Due to the recent refactor, some of the windows with only hovering
pointers are getting persisted inside TouchState. This is the case for
the hovering pointers from mouse, for example. Mouse usually never
leaves the screen, so it's always inside some window.
However, we are still currently iterating over TouchedWindows inside
the TouchState in order to determine which targets should receive the
current entry.
That means that the windows that are hovered over will always be there,
which is not something that we want. It would cause the events to go
to windows that are not directly getting touched.
To avoid this, make sure that the pointers are indeed supposed to be
going to the current window. In a future refactor, we will store the pointers
per-device, as well.
The added test reproduces a condition where we crash due to mismatching
downtimes.
There are few issues that it exposes.
1) We currently use hover events for setting the downtime of a window,
which we shouldn't do.
2) If a window is not receiving the events from the current device, it
shouldn't be in the dispatching list. So we should not be sending the
events there to begin with.
Bug: 266455987
Test: m inputflinger_tests && $ANDROID_HOST_OUT/nativetest64/inputflinger_tests/inputflinger_tests --gtest_filter="*HoverFromLeftToRightAndTap*"
Change-Id: Ic0d2a1ed6d053e18077bc7216b1ce02e88017b4a
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 0f3dc5c..44c133c 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2303,8 +2303,13 @@
pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
}
+ const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
+ maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
+
tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds,
- entry.eventTime);
+ isDownOrPointerDown
+ ? std::make_optional(entry.eventTime)
+ : std::nullopt);
// If this is the pointer going down and the touched window has a wallpaper
// then also add the touched wallpaper windows so they are locked in for the duration
@@ -2312,8 +2317,7 @@
// We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
// engine only supports touch events. We would need to add a mechanism similar
// to View.onGenericMotionEvent to enable wallpapers to handle these events.
- if (maskedAction == AMOTION_EVENT_ACTION_DOWN ||
- maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+ if (isDownOrPointerDown) {
if (targetFlags.test(InputTarget::Flags::FOREGROUND) &&
windowHandle->getInfo()->inputConfig.test(
gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
@@ -2517,6 +2521,12 @@
// Success! Output targets from the touch state.
tempTouchState.clearWindowsWithoutPointers();
for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
+ if (touchedWindow.pointerIds.isEmpty() &&
+ !touchedWindow.hasHoveringPointers(entry.deviceId)) {
+ // Windows with hovering pointers are getting persisted inside TouchState.
+ // Do not send this event to those windows.
+ continue;
+ }
addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
touchedWindow.pointerIds, touchedWindow.firstDownTimeInTarget,
targets);
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index e9c6ad5..c257ee5 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -63,7 +63,7 @@
void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle,
ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds,
- std::optional<nsecs_t> eventTime) {
+ std::optional<nsecs_t> firstDownTimeInTarget) {
for (TouchedWindow& touchedWindow : windows) {
// We do not compare windows by token here because two windows that share the same token
// may have a different transform
@@ -77,7 +77,7 @@
// the window.
touchedWindow.pointerIds.value |= pointerIds.value;
if (!touchedWindow.firstDownTimeInTarget.has_value()) {
- touchedWindow.firstDownTimeInTarget = eventTime;
+ touchedWindow.firstDownTimeInTarget = firstDownTimeInTarget;
}
return;
}
@@ -86,7 +86,7 @@
touchedWindow.windowHandle = windowHandle;
touchedWindow.targetFlags = targetFlags;
touchedWindow.pointerIds = pointerIds;
- touchedWindow.firstDownTimeInTarget = eventTime;
+ touchedWindow.firstDownTimeInTarget = firstDownTimeInTarget;
windows.push_back(touchedWindow);
}
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 0092f1d..f1409d6 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -47,7 +47,7 @@
const sp<android::gui::WindowInfoHandle>& windowHandle);
void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
ftl::Flags<InputTarget::Flags> targetFlags, BitSet32 pointerIds,
- std::optional<nsecs_t> eventTime = std::nullopt);
+ std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt);
void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
int32_t deviceId, int32_t hoveringPointerId);
void removeHoveringPointer(int32_t deviceId, int32_t hoveringPointerId);
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index 562067e..99e1c86 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -29,6 +29,10 @@
return !mHoveringPointerIdsByDevice.empty();
}
+bool TouchedWindow::hasHoveringPointers(int32_t deviceId) const {
+ return mHoveringPointerIdsByDevice.find(deviceId) != mHoveringPointerIdsByDevice.end();
+}
+
void TouchedWindow::clearHoveringPointers() {
mHoveringPointerIdsByDevice.clear();
}
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index af75304..4ec33ac 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -38,6 +38,7 @@
std::optional<nsecs_t> firstDownTimeInTarget;
bool hasHoveringPointers() const;
+ bool hasHoveringPointers(int32_t deviceId) const;
bool hasHoveringPointer(int32_t deviceId, int32_t pointerId) const;
void addHoveringPointer(int32_t deviceId, int32_t pointerId);
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index c04227d..20a1977 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -1470,6 +1470,7 @@
mAction = action;
mSource = source;
mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mDownTime = mEventTime;
}
MotionEventBuilder& deviceId(int32_t deviceId) {
@@ -1477,6 +1478,11 @@
return *this;
}
+ MotionEventBuilder& downTime(nsecs_t downTime) {
+ mDownTime = downTime;
+ return *this;
+ }
+
MotionEventBuilder& eventTime(nsecs_t eventTime) {
mEventTime = eventTime;
return *this;
@@ -1539,7 +1545,7 @@
mAction, mActionButton, mFlags, /* edgeFlags */ 0, AMETA_NONE,
mButtonState, MotionClassification::NONE, identityTransform,
/* xPrecision */ 0, /* yPrecision */ 0, mRawXCursorPosition,
- mRawYCursorPosition, identityTransform, mEventTime, mEventTime,
+ mRawYCursorPosition, identityTransform, mDownTime, mEventTime,
mPointers.size(), pointerProperties.data(), pointerCoords.data());
return event;
@@ -1549,6 +1555,7 @@
int32_t mAction;
int32_t mDeviceId = DEVICE_ID;
int32_t mSource;
+ nsecs_t mDownTime;
nsecs_t mEventTime;
int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
int32_t mActionButton{0};
@@ -2064,6 +2071,123 @@
}
/**
+ * Two windows: a window on the left and a window on the right.
+ * Mouse is hovered from the right window into the left window.
+ * Next, we tap on the left window, where the cursor was last seen.
+ * The second tap is done onto the right window.
+ * The mouse and tap are from two different devices.
+ * We technically don't need to set the downtime / eventtime for these events, but setting these
+ * explicitly helps during debugging.
+ * This test reproduces a crash where there is a mismatch between the downTime and eventTime.
+ * In the buggy implementation, a tap on the right window would cause a crash.
+ */
+TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow}}});
+ // All times need to start at the current time, otherwise the dispatcher will drop the events as
+ // stale.
+ const nsecs_t baseTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ const int32_t mouseDeviceId = 6;
+ const int32_t touchDeviceId = 4;
+ // Move the cursor from right
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+ AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .downTime(baseTime + 10)
+ .eventTime(baseTime + 20)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+ .x(300)
+ .y(100))
+ .build()));
+ rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+ // .. to the left window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+ AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .downTime(baseTime + 10)
+ .eventTime(baseTime + 30)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+ .x(110)
+ .y(100))
+ .build()));
+ rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+ // Now tap the left window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .downTime(baseTime + 40)
+ .eventTime(baseTime + 40)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(100)
+ .y(100))
+ .build()));
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+ // release tap
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .downTime(baseTime + 40)
+ .eventTime(baseTime + 50)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(100)
+ .y(100))
+ .build()));
+ leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+ // Tap the window on the right
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .downTime(baseTime + 60)
+ .eventTime(baseTime + 60)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(300)
+ .y(100))
+ .build()));
+ rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+ // release tap
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .downTime(baseTime + 60)
+ .eventTime(baseTime + 70)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(300)
+ .y(100))
+ .build()));
+ rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+ // No more events
+ leftWindow->assertNoEvents();
+ rightWindow->assertNoEvents();
+}
+
+/**
* On the display, have a single window, and also an area where there's no window.
* First pointer touches the "no window" area of the screen. Second pointer touches the window.
* Make sure that the window receives the second pointer, and first pointer is simply ignored.