Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7ae7afde authored by Siarhei Vishniakou's avatar Siarhei Vishniakou
Browse files

Handle multiple windows in transferTouch

After the recent SPY window refactor, multiple windows could be getting
touch gesture at the same time.
When the 'transferTouch' API is invoked, inputdispatcher needs to find the best
window which to take touch from.

The logic used to be 'take the display which has exactly 1 touched
window, and then take that window'. This breaks when spy windows are in
use, because now both the 'regular' and the spy window would be getting
the same touch.

In this CL, we refine the logic for which window should be losing touch.
We would take the first foreground window that is being touched, on the
specific display.

We will also now explicitly ban split touch transfers. If touch is split
across multiple windows (excluding spy and wallpaper windows),
transferTouch will fail.

Bug: 220077253
Test: atest inputflinger_tests
Change-Id: I20a24b063dc0e0103a1692f85f99849544e7ec60
parent 96329101
Loading
Loading
Loading
Loading
+39 −13
Original line number Diff line number Diff line
@@ -5121,28 +5121,54 @@ bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp<
    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;
        }

        sp<WindowInfoHandle> from = findTouchedForegroundWindowLocked(displayId);
        if (from == nullptr) {
            ALOGE("Could not find a source window in %s for %p", __func__, destChannelToken.get());
            return false;
        }

        TouchState& state = it->second;
        const TouchedWindow& touchedWindow = state.windows[0];
        fromToken = touchedWindow.windowHandle->getToken();
        fromToken = from->getToken();
    } // release lock

    return transferTouchFocus(fromToken, destChannelToken);
+4 −1
Original line number Diff line number Diff line
@@ -125,7 +125,7 @@ public:

    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 @@ private:
    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);

+1 −1
Original line number Diff line number Diff line
@@ -155,7 +155,7 @@ public:
     *
     * 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.
+62 −3
Original line number Diff line number Diff line
@@ -2426,6 +2426,63 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) {
    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>();

@@ -2495,7 +2552,8 @@ INSTANTIATE_TEST_SUITE_P(TransferFunctionTests, TransferTouchFixture,
                         ::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) {
@@ -2603,7 +2661,8 @@ TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) {
    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();
@@ -2727,7 +2786,7 @@ TEST_F(InputDispatcherTest, TransferTouch_CloneSurface) {
    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);