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

Commit e33845cf authored by Arpit Singh's avatar Arpit Singh
Browse files

Enable cursor to transition across multiple displays

This CL enables cursor to move between displays. It uses a fake
topology that assumes all available displays are connected in the
following order:
default-display (top-edge) -> next-display (right-edge)
                           -> next-display (right-edge) ...

Test: atest inputflinger_tests
Test: verify cursor can move between displays as expected
Bug: 367659738
Bug: 367660694
Flag: com.android.input.flags.connected_displays_cursor
Change-Id: I8b3b7c3c0e68dca1e7ce281cb7ff6efab263a500
parent 8a3f4096
Loading
Loading
Loading
Loading
+174 −18
Original line number Diff line number Diff line
@@ -205,12 +205,16 @@ void PointerChoreographer::fadeMouseCursorOnKeyPress(const android::NotifyKeyArg
}

NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& args) {
    NotifyMotionArgs newArgs(args);
    PointerDisplayChange pointerDisplayChange;
    { // acquire lock
        std::scoped_lock _l(getLock());

        if (isFromMouse(args)) {
        return processMouseEventLocked(args);
            newArgs = processMouseEventLocked(args);
            pointerDisplayChange = calculatePointerDisplayChangeToNotify();
        } else if (isFromTouchpad(args)) {
        return processTouchpadEventLocked(args);
            newArgs = processTouchpadEventLocked(args);
            pointerDisplayChange = calculatePointerDisplayChangeToNotify();
        } else if (isFromDrawingTablet(args)) {
            processDrawingTabletEventLocked(args);
        } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
@@ -218,7 +222,13 @@ NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& arg
        } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
            processTouchscreenAndStylusEventLocked(args);
        }
    return args;
    } // release lock

    if (pointerDisplayChange) {
        // pointer display may have changed if mouse crossed display boundary
        notifyPointerDisplayChange(pointerDisplayChange, mPolicy);
    }
    return newArgs;
}

NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotionArgs& args) {
@@ -245,7 +255,8 @@ NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotio
        // This is a relative mouse, so move the cursor by the specified amount.
        processPointerDeviceMotionEventLocked(/*byref*/ newArgs, /*byref*/ pc);
    }
    if (canUnfadeOnDisplay(displayId)) {
    // Note displayId may have changed if the cursor moved to a different display
    if (canUnfadeOnDisplay(newArgs.displayId)) {
        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
    }
    return newArgs;
@@ -272,7 +283,9 @@ NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMo
        newArgs.xCursorPosition = x;
        newArgs.yCursorPosition = y;
    }
    if (canUnfadeOnDisplay(displayId)) {

    // Note displayId may have changed if the cursor moved to a different display
    if (canUnfadeOnDisplay(newArgs.displayId)) {
        pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
    }
    return newArgs;
@@ -283,7 +296,14 @@ void PointerChoreographer::processPointerDeviceMotionEventLocked(NotifyMotionArg
    const float deltaX = newArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
    const float deltaY = newArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);

    pc.move(deltaX, deltaY);
    FloatPoint unconsumedDelta = pc.move(deltaX, deltaY);
    if (com::android::input::flags::connected_displays_cursor() &&
        (std::abs(unconsumedDelta.x) > 0 || std::abs(unconsumedDelta.y) > 0)) {
        handleUnconsumedDeltaLocked(pc, unconsumedDelta);
        // pointer may have moved to a different viewport
        newArgs.displayId = pc.getDisplayId();
    }

    const auto [x, y] = pc.getPosition();
    newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
    newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
@@ -291,6 +311,34 @@ void PointerChoreographer::processPointerDeviceMotionEventLocked(NotifyMotionArg
    newArgs.yCursorPosition = y;
}

void PointerChoreographer::handleUnconsumedDeltaLocked(PointerControllerInterface& pc,
                                                       const FloatPoint& unconsumedDelta) {
    const ui::LogicalDisplayId sourceDisplayId = pc.getDisplayId();
    const auto& sourceViewport = *findViewportByIdLocked(sourceDisplayId);
    std::optional<const DisplayViewport*> destination =
            findDestinationDisplayLocked(sourceViewport, unconsumedDelta);
    if (!destination) {
        // no adjacent display
        return;
    }

    const DisplayViewport* destinationViewport = *destination;

    if (mMousePointersByDisplay.find(destinationViewport->displayId) !=
        mMousePointersByDisplay.end()) {
        LOG(FATAL) << "A cursor already exists on destination display"
                   << destinationViewport->displayId;
    }
    mDefaultMouseDisplayId = destinationViewport->displayId;
    auto pcNode = mMousePointersByDisplay.extract(sourceDisplayId);
    pcNode.key() = destinationViewport->displayId;
    mMousePointersByDisplay.insert(std::move(pcNode));

    // This will place cursor at the center of the target display for now
    // TODO (b/367660694) place the cursor at the appropriate position in destination display
    pc.setDisplayViewport(*destinationViewport);
}

void PointerChoreographer::processDrawingTabletEventLocked(const android::NotifyMotionArgs& args) {
    if (args.displayId == ui::LogicalDisplayId::INVALID) {
        return;
@@ -436,7 +484,8 @@ void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args)
}

void PointerChoreographer::onControllerAddedOrRemovedLocked() {
    if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows()) {
    if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows() &&
        !com::android::input::flags::connected_displays_cursor()) {
        return;
    }
    bool requireListener = !mTouchPointersByDevice.empty() || !mMousePointersByDisplay.empty() ||
@@ -502,6 +551,13 @@ void PointerChoreographer::notifyPointerCaptureChanged(
    mNextListener.notify(args);
}

void PointerChoreographer::setDisplayTopology(
        const std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>>&
                displayTopology) {
    std::scoped_lock _l(getLock());
    mTopology = displayTopology;
}

void PointerChoreographer::dump(std::string& dump) {
    std::scoped_lock _l(getLock());

@@ -873,6 +929,104 @@ PointerChoreographer::ControllerConstructor PointerChoreographer::getStylusContr
    return ConstructorDelegate(std::move(ctor));
}

void PointerChoreographer::populateFakeDisplayTopologyLocked(
        const std::vector<gui::DisplayInfo>& displayInfos) {
    if (!com::android::input::flags::connected_displays_cursor()) {
        return;
    }

    if (displayInfos.size() == mTopology.size()) {
        bool displaysChanged = false;
        for (const auto& displayInfo : displayInfos) {
            if (mTopology.find(displayInfo.displayId) == mTopology.end()) {
                displaysChanged = true;
                break;
            }
        }

        if (!displaysChanged) {
            return;
        }
    }

    // create a fake topology assuming following order
    // default-display (top-edge) -> next-display (right-edge) -> next-display (right-edge) ...
    // ┌─────────┬─────────┐
    // │ next    │ next 2  │ ...
    // ├─────────┼─────────┘
    // │ default │
    // └─────────┘
    mTopology.clear();

    // treat default display as base, in real topology it should be the primary-display
    ui::LogicalDisplayId previousDisplay = ui::LogicalDisplayId::DEFAULT;
    for (const auto& displayInfo : displayInfos) {
        if (displayInfo.displayId == ui::LogicalDisplayId::DEFAULT) {
            continue;
        }
        if (previousDisplay == ui::LogicalDisplayId::DEFAULT) {
            mTopology[previousDisplay].push_back({displayInfo.displayId, DisplayPosition::TOP, 0});
            mTopology[displayInfo.displayId].push_back(
                    {previousDisplay, DisplayPosition::BOTTOM, 0});
        } else {
            mTopology[previousDisplay].push_back(
                    {displayInfo.displayId, DisplayPosition::RIGHT, 0});
            mTopology[displayInfo.displayId].push_back({previousDisplay, DisplayPosition::LEFT, 0});
        }
        previousDisplay = displayInfo.displayId;
    }

    // update default pointer display. In real topology it should be the primary-display
    if (mTopology.find(mDefaultMouseDisplayId) == mTopology.end()) {
        mDefaultMouseDisplayId = ui::LogicalDisplayId::DEFAULT;
    }
}

std::optional<const DisplayViewport*> PointerChoreographer::findDestinationDisplayLocked(
        const DisplayViewport& sourceViewport, const FloatPoint& unconsumedDelta) const {
    DisplayPosition sourceBoundary;
    if (unconsumedDelta.x > 0) {
        sourceBoundary = DisplayPosition::RIGHT;
    } else if (unconsumedDelta.x < 0) {
        sourceBoundary = DisplayPosition::LEFT;
    } else if (unconsumedDelta.y > 0) {
        sourceBoundary = DisplayPosition::BOTTOM;
    } else {
        sourceBoundary = DisplayPosition::TOP;
    }

    // Choreographer works in un-rotate coordinate space so we need to rotate boundary by viewport
    // orientation to find the rotated boundary
    constexpr int MOD = ftl::to_underlying(ui::Rotation::ftl_last) + 1;
    sourceBoundary = static_cast<DisplayPosition>(
            (ftl::to_underlying(sourceBoundary) + ftl::to_underlying(sourceViewport.orientation)) %
            MOD);

    const auto& destination = mTopology.find(sourceViewport.displayId);
    if (destination == mTopology.end()) {
        // Topology is likely out of sync with viewport info, wait for it to be updated
        LOG(WARNING) << "Source display missing from topology " << sourceViewport.displayId;
        return std::nullopt;
    }

    for (const auto& adjacentDisplay : destination->second) {
        if (adjacentDisplay.position != sourceBoundary) {
            continue;
        }
        const DisplayViewport* destinationViewport =
                findViewportByIdLocked(adjacentDisplay.displayId);
        if (destinationViewport == nullptr) {
            // Topology is likely out of sync with viewport info, wait for them to be updated
            LOG(WARNING) << "Cannot find viewport for adjacent display "
                         << adjacentDisplay.displayId << "of source display "
                         << sourceViewport.displayId;
            break;
        }
        return destinationViewport;
    }
    return std::nullopt;
}

// --- PointerChoreographer::PointerChoreographerDisplayInfoListener ---

void PointerChoreographer::PointerChoreographerDisplayInfoListener::onWindowInfosChanged(
@@ -883,12 +1037,14 @@ void PointerChoreographer::PointerChoreographerDisplayInfoListener::onWindowInfo
    }
    auto newPrivacySensitiveDisplays =
            getPrivacySensitiveDisplaysFromWindowInfos(windowInfosUpdate.windowInfos);
    if (newPrivacySensitiveDisplays != mPrivacySensitiveDisplays) {
        mPrivacySensitiveDisplays = std::move(newPrivacySensitiveDisplays);

    // PointerChoreographer uses Listener's lock.
    base::ScopedLockAssertion assumeLocked(mPointerChoreographer->getLock());
    if (newPrivacySensitiveDisplays != mPrivacySensitiveDisplays) {
        mPrivacySensitiveDisplays = std::move(newPrivacySensitiveDisplays);
        mPointerChoreographer->onPrivacySensitiveDisplaysChangedLocked(mPrivacySensitiveDisplays);
    }
    mPointerChoreographer->populateFakeDisplayTopologyLocked(windowInfosUpdate.displayInfos);
}

void PointerChoreographer::PointerChoreographerDisplayInfoListener::setInitialDisplayInfosLocked(
+31 −0
Original line number Diff line number Diff line
@@ -113,6 +113,24 @@ public:
    void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
    void notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) override;

    // TODO(b/362719483) remove these when real topology is available
    enum class DisplayPosition : int32_t {
        RIGHT = 0,
        TOP = 1,
        LEFT = 2,
        BOTTOM = 3,
        ftl_last = BOTTOM,
    };

    struct AdjacentDisplay {
        ui::LogicalDisplayId displayId;
        DisplayPosition position;
        float offsetPx;
    };
    void setDisplayTopology(
            const std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>>&
                    displayTopology);

    void dump(std::string& dump) override;

private:
@@ -153,6 +171,19 @@ private:
            const std::unordered_set<ui::LogicalDisplayId>& privacySensitiveDisplays)
            REQUIRES(getLock());

    void handleUnconsumedDeltaLocked(PointerControllerInterface& pc,
                                     const FloatPoint& unconsumedDelta) REQUIRES(getLock());

    void populateFakeDisplayTopologyLocked(const std::vector<gui::DisplayInfo>& displayInfos)
            REQUIRES(getLock());

    std::optional<const DisplayViewport*> findDestinationDisplayLocked(
            const DisplayViewport& sourceViewport, const FloatPoint& unconsumedDelta) const
            REQUIRES(getLock());

    std::unordered_map<ui::LogicalDisplayId, std::vector<AdjacentDisplay>> mTopology
            GUARDED_BY(getLock());

    /* This listener keeps tracks of visible privacy sensitive displays and updates the
     * choreographer if there are any changes.
     *
+6 −2
Original line number Diff line number Diff line
@@ -72,8 +72,12 @@ public:
    /* Dumps the state of the pointer controller. */
    virtual std::string dump() = 0;

    /* Move the pointer. */
    virtual void move(float deltaX, float deltaY) = 0;
    /* Move the pointer and return unconsumed delta if the pointer has crossed the current
     * viewport bounds .
     *
     * Return value may be used to move pointer to corresponding adjacent display, if it exists in
     * the display-topology */
    [[nodiscard]] virtual FloatPoint move(float deltaX, float deltaY) = 0;

    /* Sets the absolute location of the pointer. */
    virtual void setPosition(float x, float y) = 0;
+8 −3
Original line number Diff line number Diff line
@@ -148,15 +148,20 @@ bool FakePointerController::isPointerShown() {
    return mIsPointerShown;
}

void FakePointerController::move(float deltaX, float deltaY) {
    if (!mEnabled) return;
FloatPoint FakePointerController::move(float deltaX, float deltaY) {
    if (!mEnabled) return {0, 0};

    mX += deltaX;
    mY += deltaY;

    const FloatPoint position(mX, mY);

    if (mX < mMinX) mX = mMinX;
    if (mX > mMaxX) mX = mMaxX;
    mY += deltaY;
    if (mY < mMinY) mY = mMinY;
    if (mY > mMaxY) mY = mMaxY;

    return {position.x - mX, position.y - mY};
}

void FakePointerController::fade(Transition) {
+1 −1
Original line number Diff line number Diff line
@@ -65,7 +65,7 @@ public:

private:
    std::string dump() override { return ""; }
    void move(float deltaX, float deltaY) override;
    FloatPoint move(float deltaX, float deltaY) override;
    void unfade(Transition) override;
    void setPresentation(Presentation) override {}
    void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
Loading