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

Commit 52c5ed25 authored by Arpit Singh's avatar Arpit Singh
Browse files

[CD Cursor] Remove displayId check from windowAcceptsTouchAt

With connected displays assumption that window's display Id should
always match with corresponding TouchState's displayId is not true. We
should rely on window's displayId to find its display.

Due to this we see hover state being unexpectedly cancelled for windows
incorrectly on windowInfo updates.

This CL
1. Removes the explicit displayId check from windowAcceptsTouchAt
2. Uses the WindowInfo to find displayId and corresponding transform to
   update hovering states when we receive a windoInfo update.

Bug: 397154343
Test: atest inputflinger_tests
Test: manually check hover state
Flag: EXEMPT bugfix
Change-Id: Id414b71656775c1737c5e88371409f67ccaa229f
parent e19ba5ca
Loading
Loading
Loading
Loading
+37 −35
Original line number Diff line number Diff line
@@ -578,34 +578,6 @@ bool isUserActivityEvent(const EventEntry& eventEntry) {
    }
}

// Returns true if the given window can accept pointer events at the given display location.
bool windowAcceptsTouchAt(const WindowInfo& windowInfo, ui::LogicalDisplayId displayId, float x,
                          float y, bool isStylus, const ui::Transform& displayTransform) {
    const auto inputConfig = windowInfo.inputConfig;
    if (windowInfo.displayId != displayId ||
        inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) {
        return false;
    }
    const bool windowCanInterceptTouch = isStylus && windowInfo.interceptsStylus();
    if (inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE) && !windowCanInterceptTouch) {
        return false;
    }

    // Window Manager works in the logical display coordinate space. When it specifies bounds for a
    // window as (l, t, r, b), the range of x in [l, r) and y in [t, b) are considered to be inside
    // the window. Points on the right and bottom edges should not be inside the window, so we need
    // to be careful about performing a hit test when the display is rotated, since the "right" and
    // "bottom" of the window will be different in the display (un-rotated) space compared to in the
    // logical display in which WM determined the bounds. Perform the hit test in the logical
    // display space to ensure these edges are considered correctly in all orientations.
    const auto touchableRegion = displayTransform.transform(windowInfo.touchableRegion);
    const auto p = displayTransform.transform(x, y);
    if (!touchableRegion.contains(std::floor(p.x), std::floor(p.y))) {
        return false;
    }
    return true;
}

// Returns true if the given window's frame can occlude pointer events at the given display
// location.
bool windowOccludesTouchAt(const WindowInfo& windowInfo, ui::LogicalDisplayId displayId, float x,
@@ -1460,8 +1432,7 @@ sp<WindowInfoHandle> InputDispatcher::DispatcherWindowInfo::findTouchedWindowAt(
        }

        const WindowInfo& info = *windowHandle->getInfo();
        if (!info.isSpy() &&
            windowAcceptsTouchAt(info, displayId, x, y, isStylus, getDisplayTransform(displayId))) {
        if (!info.isSpy() && windowAcceptsTouchAt(info, displayId, x, y, isStylus)) {
            return windowHandle;
        }
    }
@@ -1502,11 +1473,10 @@ std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAt(
        const DispatcherWindowInfo& windowInfos) {
    // Traverse windows from front to back and gather the touched spy windows.
    std::vector<sp<WindowInfoHandle>> spyWindows;
    const ui::Transform displayTransform = windowInfos.getDisplayTransform(displayId);
    const auto& windowHandles = windowInfos.getWindowHandlesForDisplay(displayId);
    for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
        const WindowInfo& info = *windowHandle->getInfo();
        if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus, displayTransform)) {
        if (!windowInfos.windowAcceptsTouchAt(info, displayId, x, y, isStylus)) {
            // Skip if the pointer is outside of the window.
            continue;
        }
@@ -5268,6 +5238,36 @@ ui::Transform InputDispatcher::DispatcherWindowInfo::getRawTransform(
    return getDisplayTransform(windowInfo.displayId);
}

bool InputDispatcher::DispatcherWindowInfo::windowAcceptsTouchAt(const gui::WindowInfo& windowInfo,
                                                                 ui::LogicalDisplayId displayId,
                                                                 float x, float y,
                                                                 bool isStylus) const {
    const auto inputConfig = windowInfo.inputConfig;
    if (windowInfo.displayId != displayId ||
        inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) {
        return false;
    }
    const bool windowCanInterceptTouch = isStylus && windowInfo.interceptsStylus();
    if (inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE) && !windowCanInterceptTouch) {
        return false;
    }

    // Window Manager works in the logical display coordinate space. When it specifies bounds for a
    // window as (l, t, r, b), the range of x in [l, r) and y in [t, b) are considered to be inside
    // the window. Points on the right and bottom edges should not be inside the window, so we need
    // to be careful about performing a hit test when the display is rotated, since the "right" and
    // "bottom" of the window will be different in the display (un-rotated) space compared to in the
    // logical display in which WM determined the bounds. Perform the hit test in the logical
    // display space to ensure these edges are considered correctly in all orientations.
    const ui::Transform& displayTransform = getDisplayTransform(windowInfo.displayId);
    const auto touchableRegion = displayTransform.transform(windowInfo.touchableRegion);
    const auto p = displayTransform.transform(x, y);
    if (!touchableRegion.contains(std::floor(p.x), std::floor(p.y))) {
        return false;
    }
    return true;
}

ui::LogicalDisplayId InputDispatcher::DispatcherWindowInfo::getPrimaryDisplayId(
        ui::LogicalDisplayId displayId) const {
    if (mTopology.graph.contains(displayId)) {
@@ -5615,14 +5615,16 @@ InputDispatcher::DispatcherTouchState::updateHoveringStateFromWindowInfo(
    std::list<CancellationArgs> cancellations;
    // Check if the hovering should stop because the window is no longer eligible to receive it
    // (for example, if the touchable region changed)
    ui::Transform displayTransform = mWindowInfos.getDisplayTransform(displayId);
    for (TouchedWindow& touchedWindow : state.windows) {
        const gui::WindowInfo& windowInfo = *touchedWindow.windowHandle->getInfo();
        std::vector<DeviceId> erasedDevices = touchedWindow.eraseHoveringPointersIf(
                [&](const PointerProperties& properties, float x, float y) {
                    const bool isStylus = properties.toolType == ToolType::STYLUS;
                    // The touchstate's displayId may be different from window's display on the
                    // connected-displays, for this reason we use use window's displayId here.
                    const bool stillAcceptsTouch =
                            windowAcceptsTouchAt(*touchedWindow.windowHandle->getInfo(), displayId,
                                                 x, y, isStylus, displayTransform);
                            mWindowInfos.windowAcceptsTouchAt(windowInfo, windowInfo.displayId, x,
                                                              y, isStylus);
                    return !stillAcceptsTouch;
                });

+4 −0
Original line number Diff line number Diff line
@@ -342,6 +342,10 @@ private:
        sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow(
                const sp<android::gui::WindowInfoHandle>& windowHandle) const;

        // Returns true if the given window can accept pointer events at the given display location.
        bool windowAcceptsTouchAt(const gui::WindowInfo& windowInfo, ui::LogicalDisplayId displayId,
                                  float x, float y, bool isStylus) const;

        bool isTouchTrusted(const TouchOcclusionInfo& occlusionInfo) const;

        // Returns topology's primary display if the display belongs to it, otherwise the
+45 −1
Original line number Diff line number Diff line
@@ -15488,6 +15488,7 @@ TEST_P(TransferOrDontTransferFixture, MouseAndTouchTransferSimultaneousMultiDevi
INSTANTIATE_TEST_SUITE_P(WithAndWithoutTransfer, TransferOrDontTransferFixture, testing::Bool());
class InputDispatcherConnectedDisplayTest : public InputDispatcherDragTests {
protected:
    constexpr static int DENSITY_MEDIUM = 160;
    const DisplayTopologyGraph mTopology =
@@ -15503,7 +15504,6 @@ class InputDispatcherConnectedDisplayTest : public InputDispatcherDragTests {
                                          {SECOND_DISPLAY_ID, DENSITY_MEDIUM}})
                    .value();
protected:
    void SetUp() override {
        addDisplay(DISPLAY_ID, ui::Transform());
        addDisplay(SECOND_DISPLAY_ID,
@@ -15674,6 +15674,50 @@ TEST_F(InputDispatcherConnectedDisplayTest, MultiDisplayMouseDragAndDropFromNonP
    mWindowOnSecondDisplay->assertNoEvents();
}
/**
 * Test that touch state is maintained across windowInfo updates on the non-primary display.
 *
 * Create two windows, one on the primary display and another on the secondary display.
 * Start hovering on the window on the secondary display.
 *
 * Update the window info, and verify that the hover state is maintained, and no events are
 * generated.
 *
 * Remove the window on the secondary display, and verify that the window receives a HOVER_EXIT
 * event.
 */
TEST_F(InputDispatcherConnectedDisplayTest,
       NonPrimaryDisplayTouchStateIsMaintainedOnWindowInfoUpdate) {
    SCOPED_FLAG_OVERRIDE(connected_displays_cursor, true);
    sp<FakeWindowHandle> window0 =
            sp<FakeWindowHandle>::make(std::make_shared<FakeApplicationHandle>(), mDispatcher,
                                       "TestWindowOnPrimaryDisplay", mTopology.primaryDisplayId);
    window0->setFrame(Rect(0, 0, 500, 500));
    sp<FakeWindowHandle> window1 =
            sp<FakeWindowHandle>::make(std::make_shared<FakeApplicationHandle>(), mDispatcher,
                                       "TestWindowOnNonPrimaryDisplay", SECOND_DISPLAY_ID);
    window1->setFrame(Rect(0, 0, 500, 500));
    mDispatcher->onWindowInfosChanged({{*window0->getInfo(), *window1->getInfo()}, {}, 0, 0});
    // Add hover state to window on display 1.
    mDispatcher->notifyMotion(
            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
                    .displayId(SECOND_DISPLAY_ID)
                    .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE).x(100).y(100))
                    .build());
    window1->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
    // Sending same window info should not generate any events.
    mDispatcher->onWindowInfosChanged({{*window0->getInfo(), *window1->getInfo()}, {}, 0, 0});
    window1->assertNoEvents();
    // Remove the window now, it should receive hover_exit as usual.
    mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
    window1->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
    window0->assertNoEvents();
}
using InputDispatcherConnectedDisplayPointerInWindowTest = InputDispatcherConnectedDisplayTest;
TEST_F(InputDispatcherConnectedDisplayPointerInWindowTest, MouseOnWindowOnPrimaryDisplay) {