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

Commit 7a6bfc5a authored by Arpit Singh's avatar Arpit Singh
Browse files

[3/n CD Cursor] Enable cross-display mouse gestures

Enable mouse based gestures to continue across connected displays.

This Relands d6e83206

Bug: 367661487
Test: atest inputflinger_tests
Test: adb shell setprop persist.device_config.aconfig_flags.\
	lse_desktop_experience.com.android.input.flags.\
	connected_displays_cursor true && atest inputflinger_tests
Flag: com.android.input.flags.connected_displays_cursor

Change-Id: If674593f1cbe9169886674d4f9423d7f484771d8
parent 25bf4c4f
Loading
Loading
Loading
Loading
+29 −15
Original line number Diff line number Diff line
@@ -165,8 +165,6 @@ constexpr int LOGTAG_INPUT_INTERACTION = 62000;
constexpr int LOGTAG_INPUT_FOCUS = 62001;
constexpr int LOGTAG_INPUT_CANCEL = 62003;

static const bool USE_TOPOLOGY = com::android::input::flags::connected_displays_cursor();

const ui::Transform kIdentityTransform;

inline nsecs_t now() {
@@ -2398,7 +2396,7 @@ InputDispatcher::DispatcherTouchState::findTouchedWindowTargets(
    // Copy current touch state into tempTouchState.
    // This state will be used to update saved touch state at the end of this function.
    // If no state for the specified display exists, then our initial state will be empty.
    const TouchState* oldState = getTouchStateForMotionEntry(entry);
    const TouchState* oldState = getTouchStateForMotionEntry(entry, windowInfos);
    TouchState tempTouchState;
    if (oldState != nullptr) {
        tempTouchState = *oldState;
@@ -2787,9 +2785,9 @@ InputDispatcher::DispatcherTouchState::findTouchedWindowTargets(
    if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) {
        if (displayId >= ui::LogicalDisplayId::DEFAULT) {
            tempTouchState.clearWindowsWithoutPointers();
            saveTouchStateForMotionEntry(entry, std::move(tempTouchState));
            saveTouchStateForMotionEntry(entry, std::move(tempTouchState), windowInfos);
        } else {
            eraseTouchStateForMotionEntry(entry);
            eraseTouchStateForMotionEntry(entry, windowInfos);
        }
    }

@@ -5182,6 +5180,14 @@ ui::Transform InputDispatcher::DispatcherWindowInfo::getRawTransform(
            : getDisplayTransform(windowInfo.displayId);
}

ui::LogicalDisplayId InputDispatcher::DispatcherWindowInfo::getPrimaryDisplayId(
        ui::LogicalDisplayId displayId) const {
    if (mTopology.graph.contains(displayId)) {
        return mTopology.primaryDisplayId;
    }
    return displayId;
}

std::string InputDispatcher::DispatcherWindowInfo::dumpDisplayAndWindowInfo() const {
    std::string dump;
    if (!mWindowHandlesByDisplay.empty()) {
@@ -7475,32 +7481,40 @@ void InputDispatcher::DispatcherTouchState::clear() {

void InputDispatcher::DispatcherTouchState::saveTouchStateForMotionEntry(
        const android::inputdispatcher::MotionEntry& entry,
        android::inputdispatcher::TouchState&& touchState) {
        android::inputdispatcher::TouchState&& touchState,
        const DispatcherWindowInfo& windowInfos) {
    if (touchState.windows.empty()) {
        eraseTouchStateForMotionEntry(entry);
        eraseTouchStateForMotionEntry(entry, windowInfos);
        return;
    }

    if (USE_TOPOLOGY && isMouseOrTouchpad(entry.source)) {
        mCursorStateByDisplay[entry.displayId] = std::move(touchState);
    if (com::android::input::flags::connected_displays_cursor() &&
        isMouseOrTouchpad(entry.source)) {
        mCursorStateByDisplay[windowInfos.getPrimaryDisplayId(entry.displayId)] =
                std::move(touchState);
    } else {
        mTouchStatesByDisplay[entry.displayId] = std::move(touchState);
    }
}

void InputDispatcher::DispatcherTouchState::eraseTouchStateForMotionEntry(
        const android::inputdispatcher::MotionEntry& entry) {
    if (USE_TOPOLOGY && isMouseOrTouchpad(entry.source)) {
        mCursorStateByDisplay.erase(entry.displayId);
        const android::inputdispatcher::MotionEntry& entry,
        const DispatcherWindowInfo& windowInfos) {
    if (com::android::input::flags::connected_displays_cursor() &&
        isMouseOrTouchpad(entry.source)) {
        mCursorStateByDisplay.erase(windowInfos.getPrimaryDisplayId(entry.displayId));
    } else {
        mTouchStatesByDisplay.erase(entry.displayId);
    }
}

const TouchState* InputDispatcher::DispatcherTouchState::getTouchStateForMotionEntry(
        const android::inputdispatcher::MotionEntry& entry) const {
    if (USE_TOPOLOGY && isMouseOrTouchpad(entry.source)) {
        auto touchStateIt = mCursorStateByDisplay.find(entry.displayId);
        const android::inputdispatcher::MotionEntry& entry,
        const DispatcherWindowInfo& windowInfos) const {
    if (com::android::input::flags::connected_displays_cursor() &&
        isMouseOrTouchpad(entry.source)) {
        auto touchStateIt =
                mCursorStateByDisplay.find(windowInfos.getPrimaryDisplayId(entry.displayId));
        if (touchStateIt != mCursorStateByDisplay.end()) {
            return &touchStateIt->second;
        }
+10 −3
Original line number Diff line number Diff line
@@ -336,6 +336,10 @@ private:

        bool isTouchTrusted(const TouchOcclusionInfo& occlusionInfo) const;

        // Returns topology's primary display if the display belongs to it, otherwise the
        // same displayId.
        ui::LogicalDisplayId getPrimaryDisplayId(ui::LogicalDisplayId displayId) const;

        std::string dumpDisplayAndWindowInfo() const;

    private:
@@ -466,12 +470,15 @@ private:
                ftl::Flags<InputTarget::Flags> newTargetFlags,
                const DispatcherWindowInfo& windowInfos, const ConnectionManager& connections);

        void saveTouchStateForMotionEntry(const MotionEntry& entry, TouchState&& touchState);
        void saveTouchStateForMotionEntry(const MotionEntry& entry, TouchState&& touchState,
                                          const DispatcherWindowInfo& windowInfos);

        void eraseTouchStateForMotionEntry(const MotionEntry& entry);
        void eraseTouchStateForMotionEntry(const MotionEntry& entry,
                                           const DispatcherWindowInfo& windowInfos);

        const TouchState* getTouchStateForMotionEntry(
                const android::inputdispatcher::MotionEntry& entry) const;
                const android::inputdispatcher::MotionEntry& entry,
                const DispatcherWindowInfo& windowInfos) const;

        bool canWindowReceiveMotion(const sp<gui::WindowInfoHandle>& window,
                                    const MotionEntry& motionEntry,
+21 −2
Original line number Diff line number Diff line
@@ -24,6 +24,17 @@

namespace android::inputdispatcher {

namespace {

bool isMouseOrTouchpad(uint32_t sources) {
    // Check if this is a mouse or touchpad, but not a drawing tablet.
    return isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE) ||
            (isFromSource(sources, AINPUT_SOURCE_MOUSE) &&
             !isFromSource(sources, AINPUT_SOURCE_STYLUS));
}

} // namespace

InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerator) {}

InputState::~InputState() {}
@@ -221,10 +232,15 @@ ssize_t InputState::findKeyMemento(const KeyEntry& entry) const {
}

ssize_t InputState::findMotionMemento(const MotionEntry& entry, bool hovering) const {
    // If we have connected displays a mouse can move between displays and displayId may change
    // while a gesture is in-progress.
    const bool skipDisplayCheck = com::android::input::flags::connected_displays_cursor() &&
            isMouseOrTouchpad(entry.source);
    for (size_t i = 0; i < mMotionMementos.size(); i++) {
        const MotionMemento& memento = mMotionMementos[i];
        if (memento.deviceId == entry.deviceId && memento.source == entry.source &&
            memento.displayId == entry.displayId && memento.hovering == hovering) {
            memento.hovering == hovering &&
            (skipDisplayCheck || memento.displayId == entry.displayId)) {
            return i;
        }
    }
@@ -338,7 +354,10 @@ bool InputState::shouldCancelPreviousStream(const MotionEntry& motionEntry) cons
        // would receive different events from each display. Since the TouchStates are per-display,
        // it's unlikely that those two streams would be consistent with each other. Therefore,
        // cancel the previous gesture if the display id changes.
        if (motionEntry.displayId != lastMemento.displayId) {
        // Except when we have connected-displays where a mouse may move across display boundaries.
        const bool skipDisplayCheck = (com::android::input::flags::connected_displays_cursor() &&
                                       isMouseOrTouchpad(motionEntry.source));
        if (!skipDisplayCheck && motionEntry.displayId != lastMemento.displayId) {
            LOG(INFO) << "Canceling stream: last displayId was " << lastMemento.displayId
                      << " and new event is " << motionEntry;
            return true;
+87 −0
Original line number Diff line number Diff line
@@ -15185,4 +15185,91 @@ TEST_P(TransferOrDontTransferFixture, MouseAndTouchTransferSimultaneousMultiDevi
INSTANTIATE_TEST_SUITE_P(WithAndWithoutTransfer, TransferOrDontTransferFixture, testing::Bool());
class InputDispatcherConnectedDisplayTest : public InputDispatcherTest {
    constexpr static int DENSITY_MEDIUM = 160;
    const DisplayTopologyGraph
            mTopology{.primaryDisplayId = DISPLAY_ID,
                      .graph = {{DISPLAY_ID,
                                 {{SECOND_DISPLAY_ID, DisplayTopologyPosition::TOP, 0.0f}}},
                                {SECOND_DISPLAY_ID,
                                 {{DISPLAY_ID, DisplayTopologyPosition::BOTTOM, 0.0f}}}},
                      .displaysDensity = {{DISPLAY_ID, DENSITY_MEDIUM},
                                          {SECOND_DISPLAY_ID, DENSITY_MEDIUM}}};
protected:
    sp<FakeWindowHandle> mWindow;
    void SetUp() override {
        InputDispatcherTest::SetUp();
        mDispatcher->setDisplayTopology(mTopology);
        mWindow = sp<FakeWindowHandle>::make(std::make_shared<FakeApplicationHandle>(), mDispatcher,
                                             "Window", DISPLAY_ID);
        mWindow->setFrame({0, 0, 100, 100});
        mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
    }
};
TEST_F(InputDispatcherConnectedDisplayTest, MultiDisplayMouseGesture) {
    SCOPED_FLAG_OVERRIDE(connected_displays_cursor, true);
    // pointer-down
    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
                                      .displayId(DISPLAY_ID)
                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(60).y(60))
                                      .build());
    mWindow->consumeMotionEvent(
            AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(DISPLAY_ID), WithRawCoords(60, 60)));
    mDispatcher->notifyMotion(
            MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS, AINPUT_SOURCE_MOUSE)
                    .displayId(DISPLAY_ID)
                    .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
                    .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(60).y(60))
                    .build());
    mWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
                                      WithDisplayId(DISPLAY_ID), WithRawCoords(60, 60)));
    // pointer-move
    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
                                      .displayId(DISPLAY_ID)
                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(60).y(60))
                                      .build());
    mWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                                      WithDisplayId(DISPLAY_ID), WithRawCoords(60, 60)));
    // pointer-move with different display
    // TODO (b/383092013): by default windows will not be topology aware and receive events as it
    // was in the same display. This behaviour has not been implemented yet.
    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
                                      .displayId(SECOND_DISPLAY_ID)
                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(70).y(70))
                                      .build());
    mWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                                      WithDisplayId(SECOND_DISPLAY_ID), WithRawCoords(70, 70)));
    // pointer-up
    mDispatcher->notifyMotion(
            MotionArgsBuilder(AMOTION_EVENT_ACTION_BUTTON_RELEASE, AINPUT_SOURCE_MOUSE)
                    .displayId(SECOND_DISPLAY_ID)
                    .buttonState(0)
                    .actionButton(AMOTION_EVENT_BUTTON_PRIMARY)
                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(70).y(70))
                    .build());
    mWindow->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
                                      WithDisplayId(SECOND_DISPLAY_ID), WithRawCoords(70, 70)));
    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE)
                                      .displayId(SECOND_DISPLAY_ID)
                                      .buttonState(0)
                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(70).y(70))
                                      .build());
    mWindow->consumeMotionUp(SECOND_DISPLAY_ID);
}
} // namespace android::inputdispatcher