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

Commit 54942141 authored by Prabir Pradhan's avatar Prabir Pradhan Committed by Android (Google) Code Review
Browse files

Merge changes from topic "dispatcher-hit-test-logical-display" into udc-dev

* changes:
  InputDispatcher: Perform hit test in logical display space
  InputDispatcher: Use floats to represent location coordinates
parents 849d7822 33e3baa3
Loading
Loading
Loading
Loading
+34 −18
Original line number Diff line number Diff line
@@ -110,6 +110,8 @@ constexpr int LOGTAG_INPUT_INTERACTION = 62000;
constexpr int LOGTAG_INPUT_FOCUS = 62001;
constexpr int LOGTAG_INPUT_CANCEL = 62003;

const ui::Transform kIdentityTransform;

inline nsecs_t now() {
    return systemTime(SYSTEM_TIME_MONOTONIC);
}
@@ -475,8 +477,8 @@ 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, int32_t displayId, int32_t x, int32_t y,
                          bool isStylus) {
bool windowAcceptsTouchAt(const WindowInfo& windowInfo, int32_t 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)) {
@@ -486,7 +488,17 @@ bool windowAcceptsTouchAt(const WindowInfo& windowInfo, int32_t displayId, int32
    if (inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE) && !windowCanInterceptTouch) {
        return false;
    }
    if (!windowInfo.touchableRegionContainsPoint(x, y)) {

    // 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;
@@ -540,19 +552,16 @@ std::optional<std::string> verifyTargetedInjection(const sp<WindowInfoHandle>& w
    return {};
}

Point resolveTouchedPosition(const MotionEntry& entry) {
std::pair<float, float> resolveTouchedPosition(const MotionEntry& entry) {
    const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE);
    // Always dispatch mouse events to cursor position.
    if (isFromMouse) {
        return Point(static_cast<int32_t>(entry.xCursorPosition),
                     static_cast<int32_t>(entry.yCursorPosition));
        return {entry.xCursorPosition, entry.yCursorPosition};
    }

    const int32_t pointerIndex = getMotionEventActionPointerIndex(entry.action);
    return Point(static_cast<int32_t>(
                         entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X)),
                 static_cast<int32_t>(
                         entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y)));
    return {entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X),
            entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y)};
}

std::optional<nsecs_t> getDownTime(const EventEntry& eventEntry) {
@@ -1159,7 +1168,7 @@ void InputDispatcher::addRecentEventLocked(std::shared_ptr<EventEntry> entry) {
}

std::pair<sp<WindowInfoHandle>, std::vector<InputTarget>>
InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t y, bool isStylus,
InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, float x, float y, bool isStylus,
                                           bool ignoreDragWindow) const {
    // Traverse windows from front to back to find touched window.
    std::vector<InputTarget> outsideTargets;
@@ -1170,7 +1179,8 @@ InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t
        }

        const WindowInfo& info = *windowHandle->getInfo();
        if (!info.isSpy() && windowAcceptsTouchAt(info, displayId, x, y, isStylus)) {
        if (!info.isSpy() &&
            windowAcceptsTouchAt(info, displayId, x, y, isStylus, getTransformLocked(displayId))) {
            return {windowHandle, outsideTargets};
        }

@@ -1184,14 +1194,14 @@ InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t
}

std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAtLocked(
        int32_t displayId, int32_t x, int32_t y, bool isStylus) const {
        int32_t displayId, float x, float y, bool isStylus) 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, isStylus)) {
        if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus, getTransformLocked(displayId))) {
            continue;
        }
        if (!info.isSpy()) {
@@ -2231,8 +2241,7 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked(
        }
        // Handle the case where we did not find a window.
        if (newTouchedWindowHandle == nullptr) {
            ALOGD("No new touched window at (%" PRId32 ", %" PRId32 ") in display %" PRId32, x, y,
                  displayId);
            ALOGD("No new touched window at (%.1f, %.1f) in display %" PRId32, x, y, displayId);
            // Try to assign the pointer to the first foreground window we find, if there is one.
            newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle();
        }
@@ -2270,7 +2279,8 @@ std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked(
        }

        if (newTouchedWindows.empty()) {
            ALOGI("Dropping event because there is no touchable window at (%d, %d) on display %d.",
            ALOGI("Dropping event because there is no touchable window at (%.1f, %.1f) on display "
                  "%d.",
                  x, y, displayId);
            outInjectionResult = InputEventInjectionResult::FAILED;
            return {};
@@ -4764,6 +4774,12 @@ sp<WindowInfoHandle> InputDispatcher::getFocusedWindowHandleLocked(int displayId
    return getWindowHandleLocked(focusedToken, displayId);
}

ui::Transform InputDispatcher::getTransformLocked(int32_t displayId) const {
    auto displayInfoIt = mDisplayInfos.find(displayId);
    return displayInfoIt != mDisplayInfos.end() ? displayInfoIt->second.transform
                                                : kIdentityTransform;
}

bool InputDispatcher::canWindowReceiveMotionLocked(const sp<WindowInfoHandle>& window,
                                                   const MotionEntry& motionEntry) const {
    const WindowInfo& info = *window->getInfo();
@@ -4801,7 +4817,7 @@ bool InputDispatcher::canWindowReceiveMotionLocked(const sp<WindowInfoHandle>& w
    TouchOcclusionInfo occlusionInfo = computeTouchOcclusionInfoLocked(window, x, y);
    if (!isTouchTrustedLocked(occlusionInfo)) {
        if (DEBUG_TOUCH_OCCLUSION) {
            ALOGD("Stack of obscuring windows during untrusted touch (%d, %d):", x, y);
            ALOGD("Stack of obscuring windows during untrusted touch (%.1f, %.1f):", x, y);
            for (const auto& log : occlusionInfo.debugInfo) {
                ALOGD("%s", log.c_str());
            }
+3 −2
Original line number Diff line number Diff line
@@ -239,11 +239,11 @@ private:
    std::shared_ptr<EventEntry> mNextUnblockedEvent GUARDED_BY(mLock);

    std::pair<sp<android::gui::WindowInfoHandle>, std::vector<InputTarget>>
    findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t y, bool isStylus = false,
    findTouchedWindowAtLocked(int32_t displayId, float x, float y, bool isStylus = false,
                              bool ignoreDragWindow = false) const REQUIRES(mLock);

    std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
            int32_t displayId, int32_t x, int32_t y, bool isStylus) const REQUIRES(mLock);
            int32_t displayId, float x, float y, bool isStylus) const REQUIRES(mLock);

    sp<android::gui::WindowInfoHandle> findTouchedForegroundWindowLocked(int32_t displayId) const
            REQUIRES(mLock);
@@ -374,6 +374,7 @@ private:
            int32_t displayId) const REQUIRES(mLock);
    sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
            const sp<IBinder>& windowHandleToken) const REQUIRES(mLock);
    ui::Transform getTransformLocked(int32_t displayId) const REQUIRES(mLock);

    // Same function as above, but faster. Since displayId is provided, this avoids the need
    // to loop through all displays.
+84 −5
Original line number Diff line number Diff line
@@ -3938,8 +3938,7 @@ class InputDispatcherDisplayProjectionTest : public InputDispatcherTest {
public:
    void SetUp() override {
        InputDispatcherTest::SetUp();
        mDisplayInfos.clear();
        mWindowInfos.clear();
        removeAllWindowsAndDisplays();
    }

    void addDisplayInfo(int displayId, const ui::Transform& transform) {
@@ -3955,6 +3954,11 @@ public:
        mDispatcher->onWindowInfosChanged(mWindowInfos, mDisplayInfos);
    }

    void removeAllWindowsAndDisplays() {
        mDisplayInfos.clear();
        mWindowInfos.clear();
    }

    // Set up a test scenario where the display has a scaled projection and there are two windows
    // on the display.
    std::pair<sp<FakeWindowHandle>, sp<FakeWindowHandle>> setupScaledDisplayScenario() {
@@ -3987,11 +3991,11 @@ private:
    std::vector<gui::WindowInfo> mWindowInfos;
};

TEST_F(InputDispatcherDisplayProjectionTest, HitTestsInDisplaySpace) {
TEST_F(InputDispatcherDisplayProjectionTest, HitTestCoordinateSpaceConsistency) {
    auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
    // Send down to the first window. The point is represented in the display space. The point is
    // selected so that if the hit test was done with the transform applied to it, then it would
    // end up in the incorrect window.
    // selected so that if the hit test was performed with the point and the bounds being in
    // different coordinate spaces, the event would end up in the incorrect window.
    NotifyMotionArgs downMotionArgs =
            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
                               ADISPLAY_ID_DEFAULT, {PointF{75, 55}});
@@ -4066,6 +4070,81 @@ TEST_F(InputDispatcherDisplayProjectionTest, WindowGetsEventsInCorrectCoordinate
    EXPECT_EQ(80, event->getY(0));
}

/** Ensure consistent behavior of InputDispatcher in all orientations. */
class InputDispatcherDisplayOrientationFixture
      : public InputDispatcherDisplayProjectionTest,
        public ::testing::WithParamInterface<ui::Rotation> {};

// This test verifies the touchable region of a window for all rotations of the display by tapping
// in different locations on the display, specifically points close to the four corners of a
// window.
TEST_P(InputDispatcherDisplayOrientationFixture, HitTestInDifferentOrientations) {
    constexpr static int32_t displayWidth = 400;
    constexpr static int32_t displayHeight = 800;

    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();

    const auto rotation = GetParam();

    // Set up the display with the specified rotation.
    const bool isRotated = rotation == ui::ROTATION_90 || rotation == ui::ROTATION_270;
    const int32_t logicalDisplayWidth = isRotated ? displayHeight : displayWidth;
    const int32_t logicalDisplayHeight = isRotated ? displayWidth : displayHeight;
    const ui::Transform displayTransform(ui::Transform::toRotationFlags(rotation),
                                         logicalDisplayWidth, logicalDisplayHeight);
    addDisplayInfo(ADISPLAY_ID_DEFAULT, displayTransform);

    // Create a window with its bounds determined in the logical display.
    const Rect frameInLogicalDisplay(100, 100, 200, 300);
    const Rect frameInDisplay = displayTransform.inverse().transform(frameInLogicalDisplay);
    sp<FakeWindowHandle> window =
            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
    window->setFrame(frameInDisplay, displayTransform);
    addWindow(window);

    // The following points in logical display space should be inside the window.
    static const std::array<vec2, 4> insidePoints{
            {{100, 100}, {199.99, 100}, {100, 299.99}, {199.99, 299.99}}};
    for (const auto pointInsideWindow : insidePoints) {
        const vec2 p = displayTransform.inverse().transform(pointInsideWindow);
        const PointF pointInDisplaySpace{p.x, p.y};
        const auto down = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
                                             ADISPLAY_ID_DEFAULT, {pointInDisplaySpace});
        mDispatcher->notifyMotion(&down);
        window->consumeMotionDown();

        const auto up = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
                                           ADISPLAY_ID_DEFAULT, {pointInDisplaySpace});
        mDispatcher->notifyMotion(&up);
        window->consumeMotionUp();
    }

    // The following points in logical display space should be outside the window.
    static const std::array<vec2, 5> outsidePoints{
            {{200, 100}, {100, 300}, {200, 300}, {100, 99.99}, {99.99, 100}}};
    for (const auto pointOutsideWindow : outsidePoints) {
        const vec2 p = displayTransform.inverse().transform(pointOutsideWindow);
        const PointF pointInDisplaySpace{p.x, p.y};
        const auto down = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
                                             ADISPLAY_ID_DEFAULT, {pointInDisplaySpace});
        mDispatcher->notifyMotion(&down);

        const auto up = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
                                           ADISPLAY_ID_DEFAULT, {pointInDisplaySpace});
        mDispatcher->notifyMotion(&up);
    }
    window->assertNoEvents();
}

// Run the precision tests for all rotations.
INSTANTIATE_TEST_SUITE_P(InputDispatcherDisplayOrientationTests,
                         InputDispatcherDisplayOrientationFixture,
                         ::testing::Values(ui::ROTATION_0, ui::ROTATION_90, ui::ROTATION_180,
                                           ui::ROTATION_270),
                         [](const testing::TestParamInfo<ui::Rotation>& testParamInfo) {
                             return ftl::enum_string(testParamInfo.param);
                         });

using TransferFunction = std::function<bool(const std::unique_ptr<InputDispatcher>& dispatcher,
                                            sp<IBinder>, sp<IBinder>)>;