InputDispatcher: Implement spy windows
We implement spy windows as outlined in go/spy-windows.
Bug: 162194035
Test: atest inputflinger_tests
Change-Id: Iea3404329184ab40492666f2a66396e9d8cb3594
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 5f3a726..e92be01 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -43,6 +43,10 @@
return flags.test(Flag::SPLIT_TOUCH);
}
+bool WindowInfo::isSpy() const {
+ return inputFeatures.test(Feature::SPY);
+}
+
bool WindowInfo::overlaps(const WindowInfo* other) const {
return frameLeft < other->frameRight && frameRight > other->frameLeft &&
frameTop < other->frameBottom && frameBottom > other->frameTop;
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index 54a372c..808afe4 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -137,6 +137,7 @@
DISABLE_USER_ACTIVITY = 1u << 2,
DROP_INPUT = 1u << 3,
DROP_INPUT_IF_OBSCURED = 1u << 4,
+ SPY = 1u << 5,
};
/* These values are filled in by the WM and passed through SurfaceFlinger
@@ -215,6 +216,8 @@
bool supportsSplitTouch() const;
+ bool isSpy() const;
+
bool overlaps(const WindowInfo* other) const;
bool operator==(const WindowInfo& inputChannel) const;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index f50c9e1..00a3bb0 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -1051,14 +1051,14 @@
LOG_ALWAYS_FATAL("Must provide a valid touch state if adding outside targets");
}
// Traverse windows from front to back to find touched window.
- const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
+ const auto& windowHandles = getWindowHandlesLocked(displayId);
for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
if (ignoreDragWindow && haveSameToken(windowHandle, mDragState->dragWindow)) {
continue;
}
const WindowInfo& info = *windowHandle->getInfo();
- if (windowAcceptsTouchAt(info, displayId, x, y)) {
+ if (!info.isSpy() && windowAcceptsTouchAt(info, displayId, x, y)) {
return windowHandle;
}
@@ -1070,6 +1070,27 @@
return nullptr;
}
+std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAtLocked(int32_t displayId,
+ int32_t x,
+ int32_t y) const {
+ // Traverse windows from front to back and gather the touched spy windows.
+ std::vector<sp<WindowInfoHandle>> spyWindows;
+ const auto& windowHandles = getWindowHandlesLocked(displayId);
+ for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
+ const WindowInfo& info = *windowHandle->getInfo();
+
+ if (!windowAcceptsTouchAt(info, displayId, x, y)) {
+ continue;
+ }
+ if (!info.isSpy()) {
+ // The first touched non-spy window was found, so return the spy windows touched so far.
+ return spyWindows;
+ }
+ spyWindows.push_back(windowHandle);
+ }
+ return spyWindows;
+}
+
void InputDispatcher::dropInboundEventLocked(const EventEntry& entry, DropReason dropReason) {
const char* reason;
switch (dropReason) {
@@ -2078,9 +2099,11 @@
}
}
- std::vector<sp<WindowInfoHandle>> newTouchedWindows;
+ std::vector<sp<WindowInfoHandle>> newTouchedWindows =
+ findTouchedSpyWindowsAtLocked(displayId, x, y);
if (newTouchedWindowHandle != nullptr) {
- newTouchedWindows.push_back(newTouchedWindowHandle);
+ // Process the foreground window first so that it is the first to receive the event.
+ newTouchedWindows.insert(newTouchedWindows.begin(), newTouchedWindowHandle);
}
for (const sp<WindowInfoHandle>& windowHandle : newTouchedWindows) {
@@ -2128,8 +2151,10 @@
// Set target flags.
int32_t targetFlags = InputTarget::FLAG_DISPATCH_AS_IS;
- // There should only be one new foreground (non-spy) window at this location.
- targetFlags |= InputTarget::FLAG_FOREGROUND;
+ if (!info.isSpy()) {
+ // There should only be one new foreground (non-spy) window at this location.
+ targetFlags |= InputTarget::FLAG_FOREGROUND;
+ }
if (isSplit) {
targetFlags |= InputTarget::FLAG_SPLIT;
@@ -2268,10 +2293,17 @@
// Check permission to inject into all touched foreground windows and ensure there
// is at least one touched foreground window.
{
- bool haveForegroundWindow = false;
+ bool haveForegroundOrSpyWindow = false;
for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
- if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
- haveForegroundWindow = true;
+ const bool isForeground =
+ (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) != 0;
+ if (touchedWindow.windowHandle->getInfo()->isSpy()) {
+ haveForegroundOrSpyWindow = true;
+ LOG_ALWAYS_FATAL_IF(isForeground,
+ "Spy window cannot be dispatched as a foreground window.");
+ }
+ if (isForeground) {
+ haveForegroundOrSpyWindow = true;
if (!checkInjectionPermission(touchedWindow.windowHandle, entry.injectionState)) {
injectionResult = InputEventInjectionResult::PERMISSION_DENIED;
injectionPermission = INJECTION_PERMISSION_DENIED;
@@ -2280,8 +2312,8 @@
}
}
bool hasGestureMonitor = !tempTouchState.gestureMonitors.empty();
- if (!haveForegroundWindow && !hasGestureMonitor) {
- ALOGI("Dropping event because there is no touched foreground window in display "
+ if (!haveForegroundOrSpyWindow && !hasGestureMonitor) {
+ ALOGI("Dropping event because there is no touched window in display "
"%" PRId32 " or gesture monitor to receive it.",
displayId);
injectionResult = InputEventInjectionResult::FAILED;
@@ -5521,58 +5553,78 @@
status_t InputDispatcher::pilferPointers(const sp<IBinder>& token) {
{ // acquire lock
std::scoped_lock _l(mLock);
- std::optional<int32_t> foundDisplayId = findGestureMonitorDisplayByTokenLocked(token);
- if (!foundDisplayId) {
- ALOGW("Attempted to pilfer pointers from an un-registered monitor or invalid token");
- return BAD_VALUE;
- }
- int32_t displayId = foundDisplayId.value();
-
- std::unordered_map<int32_t, TouchState>::iterator stateIt =
- mTouchStatesByDisplay.find(displayId);
- if (stateIt == mTouchStatesByDisplay.end()) {
- ALOGW("Failed to pilfer pointers: no pointers on display %" PRId32 ".", displayId);
- return BAD_VALUE;
- }
-
- TouchState& state = stateIt->second;
+ TouchState* statePtr = nullptr;
std::shared_ptr<InputChannel> requestingChannel;
- std::optional<int32_t> foundDeviceId;
- for (const auto& monitor : state.gestureMonitors) {
- if (monitor.inputChannel->getConnectionToken() == token) {
- requestingChannel = monitor.inputChannel;
- foundDeviceId = state.deviceId;
+ int32_t displayId;
+ int32_t deviceId;
+ const std::optional<int32_t> foundGestureMonitorDisplayId =
+ findGestureMonitorDisplayByTokenLocked(token);
+
+ // TODO: Optimize this function for pilfering from windows when removing gesture monitors.
+ if (foundGestureMonitorDisplayId) {
+ // A gesture monitor has requested to pilfer pointers.
+ displayId = *foundGestureMonitorDisplayId;
+ auto stateIt = mTouchStatesByDisplay.find(displayId);
+ if (stateIt == mTouchStatesByDisplay.end()) {
+ ALOGW("Failed to pilfer pointers: no pointers on display %" PRId32 ".", displayId);
+ return BAD_VALUE;
+ }
+ statePtr = &stateIt->second;
+
+ for (const auto& monitor : statePtr->gestureMonitors) {
+ if (monitor.inputChannel->getConnectionToken() == token) {
+ requestingChannel = monitor.inputChannel;
+ deviceId = statePtr->deviceId;
+ }
+ }
+ } else {
+ // Check if a window has requested to pilfer pointers.
+ for (auto& [curDisplayId, state] : mTouchStatesByDisplay) {
+ const sp<WindowInfoHandle>& windowHandle = state.getWindow(token);
+ if (windowHandle != nullptr) {
+ displayId = curDisplayId;
+ requestingChannel = getInputChannelLocked(token);
+ deviceId = state.deviceId;
+ statePtr = &state;
+ break;
+ }
}
}
- if (!foundDeviceId || !state.down) {
- ALOGW("Attempted to pilfer points from a monitor without any on-going pointer streams."
+
+ if (requestingChannel == nullptr) {
+ ALOGW("Attempted to pilfer pointers from an un-registered channel or invalid token");
+ return BAD_VALUE;
+ }
+ TouchState& state = *statePtr;
+ if (!state.down) {
+ ALOGW("Attempted to pilfer points from a channel without any on-going pointer streams."
" Ignoring.");
return BAD_VALUE;
}
- int32_t deviceId = foundDeviceId.value();
// Send cancel events to all the input channels we're stealing from.
CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
- "gesture monitor stole pointer stream");
+ "input channel stole pointer stream");
options.deviceId = deviceId;
options.displayId = displayId;
- std::string canceledWindows = "[";
+ std::string canceledWindows;
for (const TouchedWindow& window : state.windows) {
std::shared_ptr<InputChannel> channel =
getInputChannelLocked(window.windowHandle->getToken());
- if (channel != nullptr) {
+ if (channel != nullptr && channel->getConnectionToken() != token) {
synthesizeCancelationEventsForInputChannelLocked(channel, options);
- canceledWindows += channel->getName() + ", ";
+ canceledWindows += canceledWindows.empty() ? "[" : ", ";
+ canceledWindows += channel->getName();
}
}
- canceledWindows += "]";
- ALOGI("Monitor %s is stealing touch from %s", requestingChannel->getName().c_str(),
+ canceledWindows += canceledWindows.empty() ? "[]" : "]";
+ ALOGI("Channel %s is stealing touch from %s", requestingChannel->getName().c_str(),
canceledWindows.c_str());
// Then clear the current touch state so we stop dispatching to them as well.
state.split = false;
- state.filterNonMonitors();
+ state.filterWindowsExcept(token);
}
return OK;
}
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 8a551cf..cd176b5 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -238,6 +238,11 @@
bool ignoreDragWindow = false)
REQUIRES(mLock);
+ std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(int32_t displayId,
+ int32_t x,
+ int32_t y) const
+ REQUIRES(mLock);
+
sp<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 759b3e7..d624e99 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -105,8 +105,11 @@
}
}
-void TouchState::filterNonMonitors() {
- windows.clear();
+void TouchState::filterWindowsExcept(const sp<IBinder>& token) {
+ auto it = std::remove_if(windows.begin(), windows.end(), [&token](const TouchedWindow& w) {
+ return w.windowHandle->getToken() != token;
+ });
+ windows.erase(it, windows.end());
}
sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle() const {
@@ -144,4 +147,14 @@
return nullptr;
}
+sp<WindowInfoHandle> TouchState::getWindow(const sp<IBinder>& token) const {
+ for (const TouchedWindow& touchedWindow : windows) {
+ const auto& windowHandle = touchedWindow.windowHandle;
+ if (windowHandle->getToken() == token) {
+ return windowHandle;
+ }
+ }
+ return nullptr;
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 762cc8a..83ca901 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -47,10 +47,11 @@
void addGestureMonitors(const std::vector<Monitor>& monitors);
void removeWindowByToken(const sp<IBinder>& token);
void filterNonAsIsTouchWindows();
- void filterNonMonitors();
+ void filterWindowsExcept(const sp<IBinder>& token);
sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
bool isSlippery() const;
sp<android::gui::WindowInfoHandle> getWallpaperWindow() const;
+ sp<android::gui::WindowInfoHandle> getWindow(const sp<IBinder>&) const;
};
} // namespace inputdispatcher
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 2c64271..0dd990e 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -23,6 +23,7 @@
#include <gtest/gtest.h>
#include <input/Input.h>
#include <linux/input.h>
+#include <sys/epoll.h>
#include <cinttypes>
#include <thread>
@@ -953,6 +954,8 @@
sp<IBinder> getToken() { return mConsumer->getChannel()->getConnectionToken(); }
+ int getChannelFd() { return mConsumer->getChannel()->getFd().get(); }
+
protected:
std::unique_ptr<InputConsumer> mConsumer;
PreallocatedInputEventFactory mEventFactory;
@@ -1041,6 +1044,8 @@
mInfo.transform = translate * displayTransform;
}
+ void setTouchableRegion(const Region& region) { mInfo.touchableRegion = region; }
+
void setType(WindowInfo::Type type) { mInfo.type = type; }
void setHasWallpaper(bool hasWallpaper) { mInfo.hasWallpaper = hasWallpaper; }
@@ -1208,6 +1213,8 @@
void destroyReceiver() { mInputReceiver = nullptr; }
+ int getChannelFd() { return mInputReceiver->getChannelFd(); }
+
private:
const std::string mName;
std::unique_ptr<FakeInputReceiver> mInputReceiver;
@@ -1387,7 +1394,7 @@
static InputEventInjectionResult injectMotionEvent(
const std::unique_ptr<InputDispatcher>& dispatcher, int32_t action, int32_t source,
- int32_t displayId, const PointF& position,
+ int32_t displayId, const PointF& position = {100, 200},
const PointF& cursorPosition = {AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION},
std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
@@ -6237,4 +6244,317 @@
mSecondWindow->assertNoEvents();
}
+class InputDispatcherSpyWindowTest : public InputDispatcherTest {
+public:
+ sp<FakeWindowHandle> createSpy(const Flags<WindowInfo::Flag> flags) {
+ std::shared_ptr<FakeApplicationHandle> application =
+ std::make_shared<FakeApplicationHandle>();
+ std::string name = "Fake Spy ";
+ name += std::to_string(mSpyCount++);
+ sp<FakeWindowHandle> spy =
+ new FakeWindowHandle(application, mDispatcher, name.c_str(), ADISPLAY_ID_DEFAULT);
+ spy->setInputFeatures(WindowInfo::Feature::SPY);
+ spy->addFlags(flags);
+ return spy;
+ }
+
+ sp<FakeWindowHandle> createForeground() {
+ std::shared_ptr<FakeApplicationHandle> application =
+ std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+ window->addFlags(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::SPLIT_TOUCH);
+ return window;
+ }
+
+private:
+ int mSpyCount{0};
+};
+
+/**
+ * Input injection into a display with a spy window but no foreground windows should succeed.
+ */
+TEST_F(InputDispatcherSpyWindowTest, NoForegroundWindow) {
+ auto spy = createSpy(WindowInfo::Flag::NOT_TOUCH_MODAL);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy}}});
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+}
+
+/**
+ * Verify the order in which different input windows receive events. The touched foreground window
+ * (if there is one) should always receive the event first. When there are multiple spy windows, the
+ * spy windows will receive the event according to their Z-order, where the top-most spy window will
+ * receive events before ones belows it.
+ *
+ * Here, we set up a scenario with four windows in the following Z order from the top:
+ * spy1, spy2, window, spy3.
+ * We then inject an event and verify that the foreground "window" receives it first, followed by
+ * "spy1" and "spy2". The "spy3" does not receive the event because it is underneath the foreground
+ * window.
+ */
+TEST_F(InputDispatcherSpyWindowTest, ReceivesInputInOrder) {
+ auto window = createForeground();
+ auto spy1 = createSpy(WindowInfo::Flag::NOT_TOUCH_MODAL);
+ auto spy2 = createSpy(WindowInfo::Flag::NOT_TOUCH_MODAL);
+ auto spy3 = createSpy(WindowInfo::Flag::NOT_TOUCH_MODAL);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy1, spy2, window, spy3}}});
+ const std::vector<sp<FakeWindowHandle>> channels{spy1, spy2, window, spy3};
+ const size_t numChannels = channels.size();
+
+ base::unique_fd epollFd(epoll_create1(0 /*flags*/));
+ if (!epollFd.ok()) {
+ FAIL() << "Failed to create epoll fd";
+ }
+
+ for (size_t i = 0; i < numChannels; i++) {
+ struct epoll_event event = {.events = EPOLLIN, .data.u64 = i};
+ if (epoll_ctl(epollFd.get(), EPOLL_CTL_ADD, channels[i]->getChannelFd(), &event) < 0) {
+ FAIL() << "Failed to add fd to epoll";
+ }
+ }
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+ std::vector<size_t> eventOrder;
+ std::vector<struct epoll_event> events(numChannels);
+ for (;;) {
+ const int nFds = epoll_wait(epollFd.get(), events.data(), static_cast<int>(numChannels),
+ (100ms).count());
+ if (nFds < 0) {
+ FAIL() << "Failed to call epoll_wait";
+ }
+ if (nFds == 0) {
+ break; // epoll_wait timed out
+ }
+ for (int i = 0; i < nFds; i++) {
+ ASSERT_EQ(EPOLLIN, events[i].events);
+ eventOrder.push_back(events[i].data.u64);
+ channels[i]->consumeMotionDown();
+ }
+ }
+
+ // Verify the order in which the events were received.
+ EXPECT_EQ(3u, eventOrder.size());
+ EXPECT_EQ(2u, eventOrder[0]); // index 2: window
+ EXPECT_EQ(0u, eventOrder[1]); // index 0: spy1
+ EXPECT_EQ(1u, eventOrder[2]); // index 1: spy2
+}
+
+/**
+ * A spy window using the NOT_TOUCHABLE flag does not receive events.
+ */
+TEST_F(InputDispatcherSpyWindowTest, NotTouchable) {
+ auto window = createForeground();
+ auto spy = createSpy(WindowInfo::Flag::NOT_TOUCHABLE);
+ 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->assertNoEvents();
+}
+
+/**
+ * A spy window will only receive gestures that originate within its touchable region. Gestures that
+ * have their ACTION_DOWN outside of the touchable region of the spy window will not be dispatched
+ * to the window.
+ */
+TEST_F(InputDispatcherSpyWindowTest, TouchableRegion) {
+ auto window = createForeground();
+ auto spy = createSpy(WindowInfo::Flag::NOT_TOUCH_MODAL);
+ spy->setTouchableRegion(Region{{0, 0, 20, 20}});
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+ // Inject an event outside the spy window's touchable region.
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ window->consumeMotionDown();
+ spy->assertNoEvents();
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ window->consumeMotionUp();
+ spy->assertNoEvents();
+
+ // Inject an event inside the spy window's touchable region.
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {5, 10}))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ window->consumeMotionDown();
+ spy->consumeMotionDown();
+}
+
+/**
+ * A spy window that is a modal window will receive gestures outside of its frame and touchable
+ * region.
+ */
+TEST_F(InputDispatcherSpyWindowTest, ModalWindow) {
+ auto window = createForeground();
+ auto spy = createSpy(static_cast<WindowInfo::Flag>(0));
+ // This spy window does not have the NOT_TOUCH_MODAL flag set.
+ spy->setFrame(Rect{0, 0, 20, 20});
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+ // Inject an event outside the spy window's frame and touchable region.
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ window->consumeMotionDown();
+ spy->consumeMotionDown();
+}
+
+/**
+ * A spy window can listen for touches outside its touchable region using the WATCH_OUTSIDE_TOUCHES
+ * flag.
+ */
+TEST_F(InputDispatcherSpyWindowTest, WatchOutsideTouches) {
+ auto window = createForeground();
+ auto spy = createSpy(WindowInfo::Flag::NOT_TOUCH_MODAL | WindowInfo::Flag::WATCH_OUTSIDE_TOUCH);
+ spy->setFrame(Rect{0, 0, 20, 20});
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+ // Inject an event outside the spy window's frame and touchable region.
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ window->consumeMotionDown();
+ spy->consumeMotionOutside();
+}
+
+/**
+ * When configured to block untrusted touches, events will not be dispatched to windows below a spy
+ * window if it is not a trusted overly.
+ */
+TEST_F(InputDispatcherSpyWindowTest, BlockUntrustedTouches) {
+ mDispatcher->setBlockUntrustedTouchesMode(android::os::BlockUntrustedTouchesMode::BLOCK);
+
+ auto window = createForeground();
+ auto spy = createSpy(WindowInfo::Flag::NOT_TOUCH_MODAL);
+ window->setOwnerInfo(111, 111);
+ spy->setOwnerInfo(222, 222);
+ spy->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED);
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+
+ // Inject an event outside the spy window's frame and touchable region.
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ spy->consumeMotionDown();
+ window->assertNoEvents();
+}
+
+/**
+ * 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(WindowInfo::Flag::NOT_TOUCH_MODAL);
+ auto spy2 = createSpy(WindowInfo::Flag::NOT_TOUCH_MODAL);
+ 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.
+ 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();
+}
+
+/**
+ * Even when a spy window spans over multiple foreground windows, the spy should receive all
+ * pointers that are down within its bounds.
+ */
+TEST_F(InputDispatcherSpyWindowTest, ReceivesMultiplePointers) {
+ auto windowLeft = createForeground();
+ windowLeft->setFrame({0, 0, 100, 200});
+ auto windowRight = createForeground();
+ windowRight->setFrame({100, 0, 200, 200});
+ auto spy = createSpy(WindowInfo::Flag::NOT_TOUCH_MODAL);
+ spy->setFrame({0, 0, 200, 200});
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, windowLeft, windowRight}}});
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {50, 50}))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ windowLeft->consumeMotionDown();
+ spy->consumeMotionDown();
+
+ const MotionEvent secondFingerDownEvent =
+ MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+ .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .pointer(
+ PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
+ .build();
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+ InputEventInjectionSync::WAIT_FOR_RESULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ windowRight->consumeMotionDown();
+ spy->consumeMotionPointerDown(1 /*pointerIndex*/);
+}
+
+/**
+ * When the first pointer lands outside the spy window and the second pointer lands inside it, the
+ * the spy should receive the second pointer with ACTION_DOWN.
+ */
+TEST_F(InputDispatcherSpyWindowTest, ReceivesSecondPointerAsDown) {
+ auto window = createForeground();
+ window->setFrame({0, 0, 200, 200});
+ auto spyRight = createSpy(WindowInfo::Flag::NOT_TOUCH_MODAL);
+ spyRight->setFrame({100, 0, 200, 200});
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyRight, window}}});
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {50, 50}))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ window->consumeMotionDown();
+ spyRight->assertNoEvents();
+
+ const MotionEvent secondFingerDownEvent =
+ MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+ .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(50).y(50))
+ .pointer(
+ PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER).x(150).y(50))
+ .build();
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+ InputEventInjectionSync::WAIT_FOR_RESULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ window->consumeMotionPointerDown(1 /*pointerIndex*/);
+ spyRight->consumeMotionDown();
+}
+
} // namespace android::inputdispatcher