Merge "Keep track of pilfered pointer explicitly"
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 5d9ee5f..0f3dc5c 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2333,14 +2333,22 @@
}
}
- // If any existing window is pilfering pointers from newly added window, remove it
- BitSet32 canceledPointers = BitSet32(0);
- for (const TouchedWindow& window : tempTouchState.windows) {
- if (window.isPilferingPointers) {
- canceledPointers |= window.pointerIds;
+ // If a window is already pilfering some pointers, give it this new pointer as well and
+ // make it pilfering. This will prevent other non-spy windows from getting this pointer,
+ // which is a specific behaviour that we want.
+ const int32_t pointerId = entry.pointerProperties[pointerIndex].id;
+ for (TouchedWindow& touchedWindow : tempTouchState.windows) {
+ if (touchedWindow.pointerIds.hasBit(pointerId) &&
+ touchedWindow.pilferedPointerIds.count() > 0) {
+ // This window is already pilfering some pointers, and this new pointer is also
+ // going to it. Therefore, take over this pointer and don't give it to anyone
+ // else.
+ touchedWindow.pilferedPointerIds.set(pointerId);
}
}
- tempTouchState.cancelPointersForNonPilferingWindows(canceledPointers);
+
+ // Restrict all pilfered pointers to the pilfering windows.
+ tempTouchState.cancelPointersForNonPilferingWindows();
} else {
/* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
@@ -3942,8 +3950,8 @@
if (action == AMOTION_EVENT_ACTION_DOWN) {
LOG_ALWAYS_FATAL_IF(splitDownTime != originalMotionEntry.eventTime,
"Split motion event has mismatching downTime and eventTime for "
- "ACTION_DOWN, motionEntry=%s, splitDownTime=%" PRId64 "ms",
- originalMotionEntry.getDescription().c_str(), ns2ms(splitDownTime));
+ "ACTION_DOWN, motionEntry=%s, splitDownTime=%" PRId64,
+ originalMotionEntry.getDescription().c_str(), splitDownTime);
}
int32_t newId = mIdGenerator.nextId();
@@ -5778,7 +5786,10 @@
// Prevent the gesture from being sent to any other windows.
// This only blocks relevant pointers to be sent to other windows
- window.isPilferingPointers = true;
+ for (BitSet32 idBits(window.pointerIds); !idBits.isEmpty();) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ window.pilferedPointerIds.set(id);
+ }
state.cancelPointersForWindowsExcept(window.pointerIds, token);
return OK;
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index ad37d02..e9c6ad5 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -34,6 +34,7 @@
void TouchState::removeTouchedPointer(int32_t pointerId) {
for (TouchedWindow& touchedWindow : windows) {
touchedWindow.pointerIds.clearBit(pointerId);
+ touchedWindow.pilferedPointerIds.reset(pointerId);
}
}
@@ -42,6 +43,7 @@
for (TouchedWindow& touchedWindow : windows) {
if (touchedWindow.windowHandle == windowHandle) {
touchedWindow.pointerIds.clearBit(pointerId);
+ touchedWindow.pilferedPointerIds.reset(pointerId);
return;
}
}
@@ -137,11 +139,40 @@
std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.isEmpty(); });
}
-void TouchState::cancelPointersForNonPilferingWindows(const BitSet32 pointerIds) {
- if (pointerIds.isEmpty()) return;
- std::for_each(windows.begin(), windows.end(), [&pointerIds](TouchedWindow& w) {
- if (!w.isPilferingPointers) {
- w.pointerIds &= BitSet32(~pointerIds.value);
+/**
+ * For any pointer that's being pilfered, remove it from all of the other windows that currently
+ * aren't pilfering it. For example, if we determined that pointer 1 is going to both window A and
+ * window B, but window A is currently pilfering pointer 1, then pointer 1 should not go to window
+ * B.
+ */
+void TouchState::cancelPointersForNonPilferingWindows() {
+ // First, find all pointers that are being pilfered, across all windows
+ std::bitset<MAX_POINTERS> allPilferedPointerIds;
+ std::for_each(windows.begin(), windows.end(), [&allPilferedPointerIds](const TouchedWindow& w) {
+ allPilferedPointerIds |= w.pilferedPointerIds;
+ });
+
+ // Optimization: most of the time, pilfering does not occur
+ if (allPilferedPointerIds.none()) return;
+
+ // Now, remove all pointers from every window that's being pilfered by other windows.
+ // For example, if window A is pilfering pointer 1 (only), and window B is pilfering pointer 2
+ // (only), the remove pointer 2 from window A and pointer 1 from window B. Usually, the set of
+ // pilfered pointers will be disjoint across all windows, but there's no reason to cause that
+ // limitation here.
+ std::for_each(windows.begin(), windows.end(), [&allPilferedPointerIds](TouchedWindow& w) {
+ std::bitset<MAX_POINTERS> pilferedByOtherWindows =
+ w.pilferedPointerIds ^ allPilferedPointerIds;
+ // TODO(b/211379801) : convert pointerIds to use std::bitset, which would allow us to
+ // replace the loop below with a bitwise operation. Currently, the XOR operation above is
+ // redundant, but is done to make the code more explicit / easier to convert later.
+ for (std::size_t i = 0; i < pilferedByOtherWindows.size(); i++) {
+ if (pilferedByOtherWindows.test(i) && !w.pilferedPointerIds.test(i)) {
+ // Pointer is pilfered by other windows, but not by this one! Remove it from here.
+ // We could call 'removeTouchedPointerFromWindow' here, but it's faster to directly
+ // manipulate it.
+ w.pointerIds.clearBit(i);
+ }
}
});
std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.isEmpty(); });
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 4025db8..0092f1d 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -59,7 +59,7 @@
void cancelPointersForWindowsExcept(const BitSet32 pointerIds, const sp<IBinder>& token);
// Cancel pointers for current set of non-pilfering windows i.e. windows with isPilferingWindow
// set to false.
- void cancelPointersForNonPilferingWindows(const BitSet32 pointerIds);
+ void cancelPointersForNonPilferingWindows();
sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
bool isSlippery() const;
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index 3704edd..562067e 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -63,10 +63,10 @@
std::string hoveringPointers =
dumpMap(mHoveringPointerIdsByDevice, constToString, bitsetToString);
out += StringPrintf("name='%s', pointerIds=0x%0x, targetFlags=%s, firstDownTimeInTarget=%s, "
- "mHoveringPointerIdsByDevice=%s\n",
+ "mHoveringPointerIdsByDevice=%s, pilferedPointerIds=%s\n",
windowHandle->getName().c_str(), pointerIds.value,
targetFlags.string().c_str(), toString(firstDownTimeInTarget).c_str(),
- hoveringPointers.c_str());
+ hoveringPointers.c_str(), bitsetToString(pilferedPointerIds).c_str());
return out;
}
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index add6b61..af75304 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -31,7 +31,8 @@
sp<gui::WindowInfoHandle> windowHandle;
ftl::Flags<InputTarget::Flags> targetFlags;
BitSet32 pointerIds;
- bool isPilferingPointers = false;
+ // The pointer ids of the pointers that this window is currently pilfering
+ std::bitset<MAX_POINTERS> pilferedPointerIds;
// Time at which the first action down occurred on this window.
// NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE scenario.
std::optional<nsecs_t> firstDownTimeInTarget;
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index dfe3d16..c04227d 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -52,6 +52,7 @@
// An arbitrary device id.
static constexpr int32_t DEVICE_ID = 1;
+static constexpr int32_t SECOND_DEVICE_ID = 2;
// An arbitrary display id.
static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
@@ -1471,6 +1472,11 @@
mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
}
+ MotionEventBuilder& deviceId(int32_t deviceId) {
+ mDeviceId = deviceId;
+ return *this;
+ }
+
MotionEventBuilder& eventTime(nsecs_t eventTime) {
mEventTime = eventTime;
return *this;
@@ -1529,7 +1535,7 @@
MotionEvent event;
ui::Transform identityTransform;
- event.initialize(InputEvent::nextId(), DEVICE_ID, mSource, mDisplayId, INVALID_HMAC,
+ event.initialize(InputEvent::nextId(), mDeviceId, mSource, mDisplayId, INVALID_HMAC,
mAction, mActionButton, mFlags, /* edgeFlags */ 0, AMETA_NONE,
mButtonState, MotionClassification::NONE, identityTransform,
/* xPrecision */ 0, /* yPrecision */ 0, mRawXCursorPosition,
@@ -1541,6 +1547,7 @@
private:
int32_t mAction;
+ int32_t mDeviceId = DEVICE_ID;
int32_t mSource;
nsecs_t mEventTime;
int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
@@ -2308,6 +2315,124 @@
spyWindow->assertNoEvents();
}
+TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> spyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setFrame(Rect(0, 0, 600, 800));
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 600, 800));
+
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, window}}});
+
+ // Send mouse cursor to the window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+ AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+ .x(100)
+ .y(100))
+ .build()));
+
+ // Move mouse cursor
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+ AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
+ .x(110)
+ .y(110))
+ .build()));
+
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+ WithSource(AINPUT_SOURCE_MOUSE)));
+ spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+ WithSource(AINPUT_SOURCE_MOUSE)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithSource(AINPUT_SOURCE_MOUSE)));
+ spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithSource(AINPUT_SOURCE_MOUSE)));
+ // Touch down on the window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(SECOND_DEVICE_ID)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(200)
+ .y(200))
+ .build()));
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+ WithSource(AINPUT_SOURCE_MOUSE)));
+ spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+ WithSource(AINPUT_SOURCE_MOUSE)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+ spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+ // pilfer the motion, retaining the gesture on the spy window.
+ EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken()));
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+ // Touch UP on the window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(SECOND_DEVICE_ID)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(200)
+ .y(200))
+ .build()));
+ spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+ // Previously, a touch was pilfered. However, that gesture was just finished. Now, we are going
+ // to send a new gesture. It should again go to both windows (spy and the window below), just
+ // like the first gesture did, before pilfering. The window configuration has not changed.
+
+ // One more tap - DOWN
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(SECOND_DEVICE_ID)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(250)
+ .y(250))
+ .build()));
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+ spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+ // Touch UP on the window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(SECOND_DEVICE_ID)
+ .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(250)
+ .y(250))
+ .build()));
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+ spyWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+ window->assertNoEvents();
+ spyWindow->assertNoEvents();
+}
+
// This test is different from the test above that HOVER_ENTER and HOVER_EXIT events are injected
// directly in this test.
TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) {