Modify pilferpointers API to selectively cancel pointers
Behavior changes:
- Pilfer pointers only cancels relevant pointers (If a pointer
is used by the window calling pilferPointers(), only then,
we need to send ACTION_CANCEL/POINTER_UP to other windows using
that particular pointer)
- Any pointers not used by the window calling pilferPointers()
will remain unaffected and the windows using them will keep
receiving the events
- Any future Pointer down events will go to other windows if and
only if the "the pilfering window" that called pilferPointers() do
not need that pointer for its gesture detection (the pointer down
occurs outside the touchable bounds of the pilfering window)
Bug: 220109830
Test: atest inputflinger_tests
Change-Id: Idc59ae06352560fefd7274bc043bd6e72d5e7710
diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h
index 99e2108..fec5f83 100644
--- a/services/inputflinger/dispatcher/CancelationOptions.h
+++ b/services/inputflinger/dispatcher/CancelationOptions.h
@@ -17,9 +17,11 @@
#ifndef _UI_INPUT_INPUTDISPATCHER_CANCELLATIONOPTIONS_H
#define _UI_INPUT_INPUTDISPATCHER_CANCELLATIONOPTIONS_H
+#include <utils/BitSet.h>
#include <optional>
-namespace android::inputdispatcher {
+namespace android {
+namespace inputdispatcher {
/* Specifies which events are to be canceled and why. */
struct CancelationOptions {
@@ -45,9 +47,13 @@
// The specific display id of events to cancel, or nullopt to cancel events on any display.
std::optional<int32_t> displayId = std::nullopt;
+ // The specific pointers to cancel, or nullopt to cancel all pointer events
+ std::optional<BitSet32> pointerIds = std::nullopt;
+
CancelationOptions(Mode mode, const char* reason) : mode(mode), reason(reason) {}
};
-} // namespace android::inputdispatcher
+} // namespace inputdispatcher
+} // namespace android
#endif // _UI_INPUT_INPUTDISPATCHER_CANCELLATIONOPTIONS_H
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index b3843eb..b6488ff 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2195,6 +2195,15 @@
tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds);
}
+
+ // 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;
+ }
+ }
+ tempTouchState.cancelPointersForNonPilferingWindows(canceledPointers);
} else {
/* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
@@ -5579,16 +5588,20 @@
}
TouchState& state = *statePtr;
-
+ TouchedWindow& window = *windowPtr;
// Send cancel events to all the input channels we're stealing from.
CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
"input channel stole pointer stream");
options.deviceId = state.deviceId;
options.displayId = state.displayId;
+ if (state.split) {
+ // If split pointers then selectively cancel pointers otherwise cancel all pointers
+ options.pointerIds = window.pointerIds;
+ }
std::string canceledWindows;
- for (const TouchedWindow& window : state.windows) {
+ for (const TouchedWindow& w : state.windows) {
const std::shared_ptr<InputChannel> channel =
- getInputChannelLocked(window.windowHandle->getToken());
+ getInputChannelLocked(w.windowHandle->getToken());
if (channel != nullptr && channel->getConnectionToken() != token) {
synthesizeCancelationEventsForInputChannelLocked(channel, options);
canceledWindows += canceledWindows.empty() ? "[" : ", ";
@@ -5600,8 +5613,14 @@
canceledWindows.c_str());
// Prevent the gesture from being sent to any other windows.
- state.filterWindowsExcept(token);
- state.preventNewTargets = true;
+ // This only blocks relevant pointers to be sent to other windows
+ window.isPilferingPointers = true;
+
+ if (state.split) {
+ state.cancelPointersForWindowsExcept(window.pointerIds, token);
+ } else {
+ state.filterWindowsExcept(token);
+ }
return OK;
}
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index f46a8bc..047c628 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -286,19 +286,30 @@
for (const MotionMemento& memento : mMotionMementos) {
if (shouldCancelMotion(memento, options)) {
- const int32_t action = memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT
- : AMOTION_EVENT_ACTION_CANCEL;
- events.push_back(
- std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime,
- memento.deviceId, memento.source,
- memento.displayId, memento.policyFlags, action,
- 0 /*actionButton*/, memento.flags, AMETA_NONE,
- 0 /*buttonState*/, MotionClassification::NONE,
- AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
- memento.yPrecision, memento.xCursorPosition,
- memento.yCursorPosition, memento.downTime,
- memento.pointerCount, memento.pointerProperties,
- memento.pointerCoords));
+ if (options.pointerIds == std::nullopt) {
+ const int32_t action = memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT
+ : AMOTION_EVENT_ACTION_CANCEL;
+ events.push_back(
+ std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime,
+ memento.deviceId, memento.source,
+ memento.displayId, memento.policyFlags,
+ action, 0 /*actionButton*/, memento.flags,
+ AMETA_NONE, 0 /*buttonState*/,
+ MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE,
+ memento.xPrecision, memento.yPrecision,
+ memento.xCursorPosition,
+ memento.yCursorPosition, memento.downTime,
+ memento.pointerCount,
+ memento.pointerProperties,
+ memento.pointerCoords));
+ } else {
+ std::vector<std::unique_ptr<MotionEntry>> pointerCancelEvents =
+ synthesizeCancelationEventsForPointers(memento, options.pointerIds.value(),
+ currentTime);
+ events.insert(events.end(), std::make_move_iterator(pointerCancelEvents.begin()),
+ std::make_move_iterator(pointerCancelEvents.end()));
+ }
}
}
return events;
@@ -359,6 +370,73 @@
return events;
}
+std::vector<std::unique_ptr<MotionEntry>> InputState::synthesizeCancelationEventsForPointers(
+ const MotionMemento& memento, const BitSet32 pointerIds, nsecs_t currentTime) {
+ std::vector<std::unique_ptr<MotionEntry>> events;
+ std::vector<uint32_t> canceledPointerIndices;
+ std::vector<PointerProperties> pointerProperties(MAX_POINTERS);
+ std::vector<PointerCoords> pointerCoords(MAX_POINTERS);
+ for (uint32_t pointerIdx = 0; pointerIdx < memento.pointerCount; pointerIdx++) {
+ uint32_t pointerId = uint32_t(memento.pointerProperties[pointerIdx].id);
+ pointerProperties[pointerIdx].copyFrom(memento.pointerProperties[pointerIdx]);
+ pointerCoords[pointerIdx].copyFrom(memento.pointerCoords[pointerIdx]);
+ if (pointerIds.hasBit(pointerId)) {
+ canceledPointerIndices.push_back(pointerIdx);
+ }
+ }
+
+ if (canceledPointerIndices.size() == memento.pointerCount) {
+ const int32_t action =
+ memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL;
+ events.push_back(
+ std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime, memento.deviceId,
+ memento.source, memento.displayId,
+ memento.policyFlags, action, 0 /*actionButton*/,
+ memento.flags, AMETA_NONE, 0 /*buttonState*/,
+ MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
+ memento.yPrecision, memento.xCursorPosition,
+ memento.yCursorPosition, memento.downTime,
+ memento.pointerCount, memento.pointerProperties,
+ memento.pointerCoords));
+ } else {
+ // If we aren't canceling all pointers, we need to generated ACTION_POINTER_UP with
+ // FLAG_CANCELED for each of the canceled pointers. For each event, we must remove the
+ // previously canceled pointers from PointerProperties and PointerCoords, and update
+ // pointerCount appropriately. For convenience, sort the canceled pointer indices so that we
+ // can just slide the remaining pointers to the beginning of the array when a pointer is
+ // canceled.
+ std::sort(canceledPointerIndices.begin(), canceledPointerIndices.end(),
+ std::greater<uint32_t>());
+
+ uint32_t pointerCount = memento.pointerCount;
+ for (const uint32_t pointerIdx : canceledPointerIndices) {
+ const int32_t action = pointerCount == 1 ? AMOTION_EVENT_ACTION_CANCEL
+ : AMOTION_EVENT_ACTION_POINTER_UP |
+ (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+ events.push_back(
+ std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime,
+ memento.deviceId, memento.source,
+ memento.displayId, memento.policyFlags, action,
+ 0 /*actionButton*/,
+ memento.flags | AMOTION_EVENT_FLAG_CANCELED,
+ AMETA_NONE, 0 /*buttonState*/,
+ MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
+ memento.yPrecision, memento.xCursorPosition,
+ memento.yCursorPosition, memento.downTime,
+ pointerCount, pointerProperties.data(),
+ pointerCoords.data()));
+
+ // Cleanup pointer information
+ pointerProperties.erase(pointerProperties.begin() + pointerIdx);
+ pointerCoords.erase(pointerCoords.begin() + pointerIdx);
+ pointerCount--;
+ }
+ }
+ return events;
+}
+
void InputState::clear() {
mKeyMementos.clear();
mMotionMementos.clear();
diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h
index 74ae21f..77a6db1 100644
--- a/services/inputflinger/dispatcher/InputState.h
+++ b/services/inputflinger/dispatcher/InputState.h
@@ -22,7 +22,8 @@
#include <utils/Timers.h>
-namespace android::inputdispatcher {
+namespace android {
+namespace inputdispatcher {
static constexpr int32_t INVALID_POINTER_INDEX = -1;
@@ -125,8 +126,13 @@
static bool shouldCancelKey(const KeyMemento& memento, const CancelationOptions& options);
static bool shouldCancelMotion(const MotionMemento& memento, const CancelationOptions& options);
+
+ // Synthesizes pointer cancel events for a particular set of pointers.
+ std::vector<std::unique_ptr<MotionEntry>> synthesizeCancelationEventsForPointers(
+ const MotionMemento& memento, const BitSet32 pointerIds, nsecs_t currentTime);
};
-} // namespace android::inputdispatcher
+} // namespace inputdispatcher
+} // namespace android
#endif // _UI_INPUT_INPUTDISPATCHER_INPUTSTATE_H
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 61e78cc..51c6826 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -47,8 +47,6 @@
}
}
- if (preventNewTargets) return; // Don't add new TouchedWindows.
-
TouchedWindow touchedWindow;
touchedWindow.windowHandle = windowHandle;
touchedWindow.targetFlags = targetFlags;
@@ -79,6 +77,27 @@
}
}
+void TouchState::cancelPointersForWindowsExcept(const BitSet32 pointerIds,
+ const sp<IBinder>& token) {
+ if (pointerIds.isEmpty()) return;
+ std::for_each(windows.begin(), windows.end(), [&pointerIds, &token](TouchedWindow& w) {
+ if (w.windowHandle->getToken() != token) {
+ w.pointerIds &= BitSet32(~pointerIds.value);
+ }
+ });
+ 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);
+ }
+ });
+ std::erase_if(windows, [](const TouchedWindow& w) { return w.pointerIds.isEmpty(); });
+}
+
void TouchState::filterWindowsExcept(const sp<IBinder>& token) {
std::erase_if(windows,
[&token](const TouchedWindow& w) { return w.windowHandle->getToken() != token; });
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 9efb280..327863f 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -31,7 +31,6 @@
struct TouchState {
bool down = false;
bool split = false;
- bool preventNewTargets = false;
// id of the device that is currently down, others are rejected
int32_t deviceId = -1;
@@ -52,6 +51,13 @@
void removeWindowByToken(const sp<IBinder>& token);
void filterNonAsIsTouchWindows();
void filterWindowsExcept(const sp<IBinder>& token);
+
+ // Cancel pointers for current set of windows except the window with particular binder token.
+ 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);
+
sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
bool isSlippery() const;
sp<android::gui::WindowInfoHandle> getWallpaperWindow() const;
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index 4c31ec3..83b52a4 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -30,6 +30,7 @@
sp<gui::WindowInfoHandle> windowHandle;
int32_t targetFlags;
BitSet32 pointerIds; // zero unless target flag FLAG_SPLIT is set
+ bool isPilferingPointers = false;
};
} // namespace inputdispatcher
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 19955e9..35da4d8 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -6808,123 +6808,6 @@
}
/**
- * A spy window can pilfer pointers. When this happens, touch gestures that are currently sent to
- * any other windows - including other spy windows - will also be cancelled.
- */
-TEST_F(InputDispatcherSpyWindowTest, PilferPointers) {
- auto window = createForeground();
- auto spy1 = createSpy();
- auto spy2 = createSpy();
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy1, spy2, window}}});
-
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
- << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionDown();
- spy1->consumeMotionDown();
- spy2->consumeMotionDown();
-
- // Pilfer pointers from the second spy window.
- EXPECT_EQ(OK, mDispatcher->pilferPointers(spy2->getToken()));
- spy2->assertNoEvents();
- spy1->consumeMotionCancel();
- window->consumeMotionCancel();
-
- // The rest of the gesture should only be sent to the second spy window.
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
- ADISPLAY_ID_DEFAULT))
- << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- spy2->consumeMotionMove();
- spy1->assertNoEvents();
- window->assertNoEvents();
-}
-
-/**
- * A spy window can pilfer pointers for a gesture even after the foreground window has been removed
- * in the middle of the gesture.
- */
-TEST_F(InputDispatcherSpyWindowTest, CanPilferAfterWindowIsRemovedMidStream) {
- auto window = createForeground();
- auto spy = createSpy();
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
-
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
- << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
- spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-
- window->releaseChannel();
-
- EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
-
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
- << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
-}
-
-/**
- * After a spy window pilfers pointers, new pointers that go down in its bounds should be sent to
- * the spy, but not to any other windows.
- */
-TEST_F(InputDispatcherSpyWindowTest, ContinuesToReceiveGestureAfterPilfer) {
- auto spy = createSpy();
- auto window = createForeground();
-
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
-
- // First finger down on the window and the spy.
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
- {100, 200}))
- << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- spy->consumeMotionDown();
- window->consumeMotionDown();
-
- // Spy window pilfers the pointers.
- EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
- window->consumeMotionCancel();
-
- // Second finger down on the window and spy, but the window should not receive the pointer down.
- const MotionEvent secondFingerDownEvent =
- MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
- .displayId(ADISPLAY_ID_DEFAULT)
- .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
- .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
- .x(100)
- .y(200))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
- .build();
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
- InputEventInjectionSync::WAIT_FOR_RESULT))
- << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-
- spy->consumeMotionPointerDown(1 /*pointerIndex*/);
-
- // Third finger goes down outside all windows, so injection should fail.
- const MotionEvent thirdFingerDownEvent =
- MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
- .displayId(ADISPLAY_ID_DEFAULT)
- .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
- .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
- .x(100)
- .y(200))
- .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
- .pointer(PointerBuilder(/* id */ 2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(-5).y(-5))
- .build();
- ASSERT_EQ(InputEventInjectionResult::FAILED,
- injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT,
- InputEventInjectionSync::WAIT_FOR_RESULT))
- << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-
- spy->assertNoEvents();
- window->assertNoEvents();
-}
-
-/**
* Even when a spy window spans over multiple foreground windows, the spy should receive all
* pointers that are down within its bounds.
*/
@@ -7058,6 +6941,277 @@
spy->assertNoEvents();
}
+using InputDispatcherPilferPointersTest = InputDispatcherSpyWindowTest;
+
+/**
+ * A spy window can pilfer pointers. When this happens, touch gestures used by the spy window that
+ * are currently sent to any other windows - including other spy windows - will also be cancelled.
+ */
+TEST_F(InputDispatcherPilferPointersTest, PilferPointers) {
+ auto window = createForeground();
+ auto spy1 = createSpy();
+ auto spy2 = createSpy();
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy1, spy2, window}}});
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ window->consumeMotionDown();
+ spy1->consumeMotionDown();
+ spy2->consumeMotionDown();
+
+ // Pilfer pointers from the second spy window.
+ EXPECT_EQ(OK, mDispatcher->pilferPointers(spy2->getToken()));
+ spy2->assertNoEvents();
+ spy1->consumeMotionCancel();
+ window->consumeMotionCancel();
+
+ // The rest of the gesture should only be sent to the second spy window.
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ spy2->consumeMotionMove();
+ spy1->assertNoEvents();
+ window->assertNoEvents();
+}
+
+/**
+ * A spy window can pilfer pointers for a gesture even after the foreground window has been removed
+ * in the middle of the gesture.
+ */
+TEST_F(InputDispatcherPilferPointersTest, CanPilferAfterWindowIsRemovedMidStream) {
+ auto window = createForeground();
+ auto spy = createSpy();
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+ spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+ window->releaseChannel();
+
+ EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+}
+
+/**
+ * After a spy window pilfers pointers, new pointers that go down in its bounds should be sent to
+ * the spy, but not to any other windows.
+ */
+TEST_F(InputDispatcherPilferPointersTest, ContinuesToReceiveGestureAfterPilfer) {
+ auto spy = createSpy();
+ auto window = createForeground();
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+ // First finger down on the window and the spy.
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {100, 200}))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ spy->consumeMotionDown();
+ window->consumeMotionDown();
+
+ // Spy window pilfers the pointers.
+ EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
+ window->consumeMotionCancel();
+
+ // Second finger down on the window and spy, but the window should not receive the pointer down.
+ const MotionEvent secondFingerDownEvent =
+ MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .displayId(ADISPLAY_ID_DEFAULT)
+ .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+ .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(100)
+ .y(200))
+ .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .build();
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+ InputEventInjectionSync::WAIT_FOR_RESULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+ spy->consumeMotionPointerDown(1 /*pointerIndex*/);
+
+ // Third finger goes down outside all windows, so injection should fail.
+ const MotionEvent thirdFingerDownEvent =
+ MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .displayId(ADISPLAY_ID_DEFAULT)
+ .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+ .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(100)
+ .y(200))
+ .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/* id */ 2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(-5).y(-5))
+ .build();
+ ASSERT_EQ(InputEventInjectionResult::FAILED,
+ injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT,
+ InputEventInjectionSync::WAIT_FOR_RESULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+ spy->assertNoEvents();
+ window->assertNoEvents();
+}
+
+/**
+ * After a spy window pilfers pointers, only the pointers used by the spy should be canceled
+ */
+TEST_F(InputDispatcherPilferPointersTest, PartiallyPilferRequiredPointers) {
+ auto spy = createSpy();
+ spy->setFrame(Rect(0, 0, 100, 100));
+ auto window = createForeground();
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+ // First finger down on the window only
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {150, 150}))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ window->consumeMotionDown();
+
+ // Second finger down on the spy and window
+ const MotionEvent secondFingerDownEvent =
+ MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .displayId(ADISPLAY_ID_DEFAULT)
+ .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+ .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(150)
+ .y(150))
+ .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
+ .build();
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+ InputEventInjectionSync::WAIT_FOR_RESULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ spy->consumeMotionDown();
+ window->consumeMotionPointerDown(1);
+
+ // Third finger down on the spy and window
+ const MotionEvent thirdFingerDownEvent =
+ MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .displayId(ADISPLAY_ID_DEFAULT)
+ .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+ .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(150)
+ .y(150))
+ .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
+ .pointer(PointerBuilder(/* id */ 2, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .build();
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher, thirdFingerDownEvent, INJECT_EVENT_TIMEOUT,
+ InputEventInjectionSync::WAIT_FOR_RESULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ spy->consumeMotionPointerDown(1);
+ window->consumeMotionPointerDown(2);
+
+ // Spy window pilfers the pointers.
+ EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
+ window->consumeMotionPointerUp(/* idx */ 2, ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
+ window->consumeMotionPointerUp(/* idx */ 1, ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
+
+ spy->assertNoEvents();
+ window->assertNoEvents();
+}
+
+/**
+ * After a spy window pilfers pointers, all pilfered pointers that have already been dispatched to
+ * other windows should be canceled. If this results in the cancellation of all pointers for some
+ * window, then that window should receive ACTION_CANCEL.
+ */
+TEST_F(InputDispatcherPilferPointersTest, PilferAllRequiredPointers) {
+ auto spy = createSpy();
+ spy->setFrame(Rect(0, 0, 100, 100));
+ auto window = createForeground();
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+ // First finger down on both spy and window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {10, 10}))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ window->consumeMotionDown();
+ spy->consumeMotionDown();
+
+ // Second finger down on the spy and window
+ const MotionEvent secondFingerDownEvent =
+ MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .displayId(ADISPLAY_ID_DEFAULT)
+ .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+ .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
+ .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .build();
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+ InputEventInjectionSync::WAIT_FOR_RESULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ spy->consumeMotionPointerDown(1);
+ window->consumeMotionPointerDown(1);
+
+ // Spy window pilfers the pointers.
+ EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
+ window->consumeMotionCancel();
+
+ spy->assertNoEvents();
+ window->assertNoEvents();
+}
+
+/**
+ * After a spy window pilfers pointers, new pointers that are not touching the spy window can still
+ * be sent to other windows
+ */
+TEST_F(InputDispatcherPilferPointersTest, CanReceivePointersAfterPilfer) {
+ auto spy = createSpy();
+ spy->setFrame(Rect(0, 0, 100, 100));
+ auto window = createForeground();
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+ // First finger down on both window and spy
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {10, 10}))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ window->consumeMotionDown();
+ spy->consumeMotionDown();
+
+ // Spy window pilfers the pointers.
+ EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
+ window->consumeMotionCancel();
+
+ // Second finger down on the window only
+ const MotionEvent secondFingerDownEvent =
+ MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .displayId(ADISPLAY_ID_DEFAULT)
+ .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+ .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(10).y(10))
+ .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+ .x(150)
+ .y(150))
+ .build();
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+ InputEventInjectionSync::WAIT_FOR_RESULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ window->consumeMotionDown();
+ window->assertNoEvents();
+
+ // TODO(b/232530217): do not send the unnecessary MOVE event and delete the next line
+ spy->consumeMotionMove();
+ spy->assertNoEvents();
+}
+
class InputDispatcherStylusInterceptorTest : public InputDispatcherTest {
public:
std::pair<sp<FakeWindowHandle>, sp<FakeWindowHandle>> setupStylusOverlayScenario() {