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

Enable mouse based gestures to continue across connected displays.

This Relands d6e83206dd69409c070c37fc7fb993bca0524c43

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
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index bc2904e..f0a627c 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -165,8 +165,6 @@
 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 @@
     // 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 @@
     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 @@
             : 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::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;
         }
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index c2224de..d765a86 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -336,6 +336,10 @@
 
         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 @@
                 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,
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 9b5a79b..5abd65a 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -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::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 @@
         // 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;
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index e0a4afb..0c72d76 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -15185,4 +15185,91 @@
 
 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