[automerge] Handle multiple windows in transferTouch 2p: 7ae7afde99 2p: bb074dc026
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/native/+/17530999
Bug: 220077253
Change-Id: I44d7b9fbe3ee55430b1e10573f8211dc9b96c858
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index f3d0b65..b696707 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -5146,28 +5146,54 @@
return true;
}
+/**
+ * Get the touched foreground window on the given display.
+ * Return null if there are no windows touched on that display, or if more than one foreground
+ * window is being touched.
+ */
+sp<WindowInfoHandle> InputDispatcher::findTouchedForegroundWindowLocked(int32_t displayId) const {
+ auto stateIt = mTouchStatesByDisplay.find(displayId);
+ if (stateIt == mTouchStatesByDisplay.end()) {
+ ALOGI("No touch state on display %" PRId32, displayId);
+ return nullptr;
+ }
+
+ const TouchState& state = stateIt->second;
+ sp<WindowInfoHandle> touchedForegroundWindow;
+ // If multiple foreground windows are touched, return nullptr
+ for (const TouchedWindow& window : state.windows) {
+ if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ if (touchedForegroundWindow != nullptr) {
+ ALOGI("Two or more foreground windows: %s and %s",
+ touchedForegroundWindow->getName().c_str(),
+ window.windowHandle->getName().c_str());
+ return nullptr;
+ }
+ touchedForegroundWindow = window.windowHandle;
+ }
+ }
+ return touchedForegroundWindow;
+}
+
// Binder call
-bool InputDispatcher::transferTouch(const sp<IBinder>& destChannelToken) {
+bool InputDispatcher::transferTouch(const sp<IBinder>& destChannelToken, int32_t displayId) {
sp<IBinder> fromToken;
{ // acquire lock
std::scoped_lock _l(mLock);
-
- auto it = std::find_if(mTouchStatesByDisplay.begin(), mTouchStatesByDisplay.end(),
- [](const auto& pair) { return pair.second.windows.size() == 1; });
- if (it == mTouchStatesByDisplay.end()) {
- ALOGW("Cannot transfer touch state because there is no exact window being touched");
- return false;
- }
- const int32_t displayId = it->first;
sp<WindowInfoHandle> toWindowHandle = getWindowHandleLocked(destChannelToken, displayId);
if (toWindowHandle == nullptr) {
- ALOGW("Could not find window associated with token=%p", destChannelToken.get());
+ ALOGW("Could not find window associated with token=%p on display %" PRId32,
+ destChannelToken.get(), displayId);
return false;
}
- TouchState& state = it->second;
- const TouchedWindow& touchedWindow = state.windows[0];
- fromToken = touchedWindow.windowHandle->getToken();
+ sp<WindowInfoHandle> from = findTouchedForegroundWindowLocked(displayId);
+ if (from == nullptr) {
+ ALOGE("Could not find a source window in %s for %p", __func__, destChannelToken.get());
+ return false;
+ }
+
+ fromToken = from->getToken();
} // release lock
return transferTouchFocus(fromToken, destChannelToken);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index a9fb5c6..d9677eb 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -125,7 +125,7 @@
bool transferTouchFocus(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
bool isDragDrop = false) override;
- bool transferTouch(const sp<IBinder>& destChannelToken) override;
+ bool transferTouch(const sp<IBinder>& destChannelToken, int32_t displayId) override;
base::Result<std::unique_ptr<InputChannel>> createInputChannel(
const std::string& name) override;
@@ -245,6 +245,9 @@
std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
int32_t displayId, int32_t x, int32_t y, bool isStylus) const REQUIRES(mLock);
+ sp<android::gui::WindowInfoHandle> findTouchedForegroundWindowLocked(int32_t displayId) const
+ REQUIRES(mLock);
+
sp<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 100bd18..67fed8b 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -161,7 +161,7 @@
*
* Return true on success, false if there was no on-going touch.
*/
- virtual bool transferTouch(const sp<IBinder>& destChannelToken) = 0;
+ virtual bool transferTouch(const sp<IBinder>& destChannelToken, int32_t displayId) = 0;
/**
* Sets focus on the specified window.
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index f15ed9e..e22ae2b 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -2476,6 +2476,63 @@
secondWindow->consumeMotionUp();
}
+/**
+ * When 'transferTouch' API is invoked, dispatcher needs to find the "best" window to take touch
+ * from. When we have spy windows, there are several windows to choose from: either spy, or the
+ * 'real' (non-spy) window. Always prefer the 'real' window because that's what would be most
+ * natural to the user.
+ * In this test, we are sending a pointer to both spy window and first window. We then try to
+ * transfer touch to the second window. The dispatcher should identify the first window as the
+ * one that should lose the gesture, and therefore the action should be to move the gesture from
+ * the first window to the second.
+ * The main goal here is to test the behaviour of 'transferTouch' API, but it's still valid to test
+ * the other API, as well.
+ */
+TEST_P(TransferTouchFixture, TransferTouch_MultipleWindowsWithSpy) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ // Create a couple of windows + a spy window
+ sp<FakeWindowHandle> spyWindow =
+ new FakeWindowHandle(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+ sp<FakeWindowHandle> firstWindow =
+ new FakeWindowHandle(application, mDispatcher, "First", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> secondWindow =
+ new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+
+ // Add the windows to the dispatcher
+ mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, firstWindow, secondWindow}}});
+
+ // Send down to the first window
+ NotifyMotionArgs downMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT);
+ mDispatcher->notifyMotion(&downMotionArgs);
+ // Only the first window and spy should get the down event
+ spyWindow->consumeMotionDown();
+ firstWindow->consumeMotionDown();
+
+ // Transfer touch to the second window. Non-spy window should be preferred over the spy window
+ // if f === 'transferTouch'.
+ TransferFunction f = GetParam();
+ const bool success = f(mDispatcher, firstWindow->getToken(), secondWindow->getToken());
+ ASSERT_TRUE(success);
+ // The first window gets cancel and the second gets down
+ firstWindow->consumeMotionCancel();
+ secondWindow->consumeMotionDown();
+
+ // Send up event to the second window
+ NotifyMotionArgs upMotionArgs =
+ generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT);
+ mDispatcher->notifyMotion(&upMotionArgs);
+ // The first window gets no events and the second+spy get up
+ firstWindow->assertNoEvents();
+ spyWindow->consumeMotionUp();
+ secondWindow->consumeMotionUp();
+}
+
TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
@@ -2545,7 +2602,8 @@
::testing::Values(
[&](const std::unique_ptr<InputDispatcher>& dispatcher,
sp<IBinder> /*ignored*/, sp<IBinder> destChannelToken) {
- return dispatcher->transferTouch(destChannelToken);
+ return dispatcher->transferTouch(destChannelToken,
+ ADISPLAY_ID_DEFAULT);
},
[&](const std::unique_ptr<InputDispatcher>& dispatcher,
sp<IBinder> from, sp<IBinder> to) {
@@ -2653,7 +2711,8 @@
secondWindow->consumeMotionDown();
// Transfer touch focus to the second window
- const bool transferred = mDispatcher->transferTouch(secondWindow->getToken());
+ const bool transferred =
+ mDispatcher->transferTouch(secondWindow->getToken(), ADISPLAY_ID_DEFAULT);
// The 'transferTouch' call should not succeed, because there are 2 touched windows
ASSERT_FALSE(transferred);
firstWindow->assertNoEvents();
@@ -2777,7 +2836,7 @@
firstWindowInPrimary->consumeMotionDown(SECOND_DISPLAY_ID);
// Transfer touch focus
- ASSERT_TRUE(mDispatcher->transferTouch(secondWindowInSecondary->getToken()));
+ ASSERT_TRUE(mDispatcher->transferTouch(secondWindowInSecondary->getToken(), SECOND_DISPLAY_ID));
// The first window gets cancel.
firstWindowInPrimary->consumeMotionCancel(SECOND_DISPLAY_ID);