Fix window jumping when crossing a display boundary

This CL does the following:
- Do not resample from an event on a different display
- Do not put events from different displays into one batch event
to prevent from sending an event with meaningless coordinates.

Bug: 402625886
Test: libinput_tests
Test: go/cd-smoke
Flag: com.android.window.flags.enable_connected_displays_window_drag
Change-Id: I9aba34a2962f4a0171ee995d9e749e7a9dd226e3
diff --git a/include/input/InputConsumer.h b/include/input/InputConsumer.h
index 611478c..4a12d82 100644
--- a/include/input/InputConsumer.h
+++ b/include/input/InputConsumer.h
@@ -135,6 +135,7 @@
         BitSet32 idBits;
         int32_t idToIndex[MAX_POINTER_ID + 1];
         PointerCoords pointers[MAX_POINTERS];
+        int32_t displayId;
 
         void initializeFrom(const InputMessage& msg) {
             eventTime = msg.body.motion.eventTime;
@@ -145,6 +146,7 @@
                 idToIndex[id] = i;
                 pointers[i].copyFrom(msg.body.motion.pointers[i].coords);
             }
+            displayId = msg.body.motion.displayId;
         }
 
         void initializeFrom(const History& other) {
@@ -157,6 +159,7 @@
                 pointers[index].copyFrom(other.pointers[index]);
             }
             idBits = other.idBits; // final copy
+            displayId = other.displayId;
         }
 
         const PointerCoords& getPointerById(uint32_t id) const { return pointers[idToIndex[id]]; }
diff --git a/include/input/InputConsumerNoResampling.h b/include/input/InputConsumerNoResampling.h
index 70d00d1..724ebe7 100644
--- a/include/input/InputConsumerNoResampling.h
+++ b/include/input/InputConsumerNoResampling.h
@@ -260,6 +260,11 @@
      * the batched MotionEvent that it received.
      */
     std::map<uint32_t, std::vector<uint32_t>> mBatchedSequenceNumbers;
+
+    /**
+     * Checks if a given input event is okay to be added to an existing batch or not.
+     */
+    bool isBatchableEvent(const InputMessage& message) const;
 };
 
 } // namespace android
diff --git a/libs/input/InputConsumer.cpp b/libs/input/InputConsumer.cpp
index 1eeb4e6..b073833 100644
--- a/libs/input/InputConsumer.cpp
+++ b/libs/input/InputConsumer.cpp
@@ -658,6 +658,11 @@
         }
     }
 
+    if (current->displayId != other->displayId) {
+        ALOGD_IF(debugResampling(), "Not resampled, the other is on a different display");
+        return;
+    }
+
     // Resample touch coordinates.
     History oldLastResample;
     oldLastResample.initializeFrom(touchState.lastResample);
@@ -840,9 +845,11 @@
     const InputMessage& head = batch.samples[0];
     uint32_t pointerCount = msg->body.motion.pointerCount;
     if (head.body.motion.pointerCount != pointerCount ||
-        head.body.motion.action != msg->body.motion.action) {
+        head.body.motion.action != msg->body.motion.action ||
+        head.body.motion.displayId != msg->body.motion.displayId) {
         return false;
     }
+
     for (size_t i = 0; i < pointerCount; i++) {
         if (head.body.motion.pointers[i].properties != msg->body.motion.pointers[i].properties) {
             return false;
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
index 9578639..c13354f 100644
--- a/libs/input/InputConsumerNoResampling.cpp
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -348,10 +348,7 @@
             const int32_t action = msg.body.motion.action;
             const DeviceId deviceId = msg.body.motion.deviceId;
             const int32_t source = msg.body.motion.source;
-            const bool batchableEvent = (action == AMOTION_EVENT_ACTION_MOVE ||
-                                         action == AMOTION_EVENT_ACTION_HOVER_MOVE) &&
-                    (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER) ||
-                     isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK));
+            const bool batchableEvent = isBatchableEvent(msg);
 
             const bool canResample = (mResamplerCreator != nullptr) &&
                     (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER));
@@ -624,4 +621,25 @@
     return out;
 }
 
+bool InputConsumerNoResampling::isBatchableEvent(const InputMessage& message) const {
+    const int32_t action = message.body.motion.action;
+    const int32_t source = message.body.motion.source;
+    const bool batchableEventTypeAndSource =
+            (action == AMOTION_EVENT_ACTION_MOVE || action == AMOTION_EVENT_ACTION_HOVER_MOVE) &&
+            (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER) ||
+             isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK));
+    if (!batchableEventTypeAndSource) {
+        return false;
+    }
+
+    const DeviceId deviceId = message.body.motion.deviceId;
+    const auto& it = mBatches.find(deviceId);
+    // If there is no pending event from the device, it's okay to add the event to the batch.
+    if (it == mBatches.end() || it->second.size() == 0) {
+        return true;
+    }
+    // It's okay to add the event to the existing batch if it's on the same display.
+    return it->second.front().body.motion.displayId == message.body.motion.displayId;
+}
+
 } // namespace android
diff --git a/libs/input/tests/InputConsumerResampling_test.cpp b/libs/input/tests/InputConsumerResampling_test.cpp
index 97688a8..748cc69 100644
--- a/libs/input/tests/InputConsumerResampling_test.cpp
+++ b/libs/input/tests/InputConsumerResampling_test.cpp
@@ -56,6 +56,7 @@
     std::chrono::nanoseconds eventTime{0};
     std::vector<Pointer> pointers{};
     int32_t action{-1};
+    ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT;
 };
 
 } // namespace
@@ -137,7 +138,8 @@
                                                  .eventTime(entry.eventTime.count())
                                                  .deviceId(1)
                                                  .action(entry.action)
-                                                 .downTime(0);
+                                                 .downTime(0)
+                                                 .displayId(entry.displayId);
     for (const Pointer& pointer : entry.pointers) {
         messageBuilder.pointer(pointer.asPointerBuilder());
     }
@@ -741,4 +743,50 @@
                              AMOTION_EVENT_ACTION_MOVE}});
 }
 
+/**
+ * Events should not be resampled when they are on different displays.
+ */
+TEST_F(InputConsumerResamplingTest, EventsOnDifferentDisplaysAreNotResampled) {
+    // Send the initial ACTION_DOWN separately, so that the first consumed event will only return an
+    // InputEvent with a single action.
+    mClientTestChannel->enqueueMessage(nextPointerMessage(
+            {0ms, {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}}, AMOTION_EVENT_ACTION_DOWN}));
+
+    invokeLooperCallback();
+    assertReceivedMotionEvent({InputEventEntry{0ms,
+                                               {Pointer{.id = 0, .x = 10.0f, .y = 20.0f}},
+                                               AMOTION_EVENT_ACTION_DOWN}});
+
+    // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y, but on
+    // different displays
+    mClientTestChannel->enqueueMessage(
+            nextPointerMessage({10ms,
+                                {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}},
+                                AMOTION_EVENT_ACTION_MOVE,
+                                ui::LogicalDisplayId::DEFAULT}));
+    mClientTestChannel->enqueueMessage(
+            nextPointerMessage({20ms,
+                                {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}},
+                                AMOTION_EVENT_ACTION_MOVE,
+                                ui::LogicalDisplayId{1}}));
+
+    invokeLooperCallback();
+    mConsumer->consumeBatchedInputEvents(nanoseconds{20ms + RESAMPLE_LATENCY * 2}.count());
+
+    // MotionEvent should not be resampled because the resample time falls exactly on the existing
+    // event time.
+    assertReceivedMotionEvent({InputEventEntry{10ms,
+                                               {Pointer{.id = 0, .x = 20.0f, .y = 30.0f}},
+                                               AMOTION_EVENT_ACTION_MOVE,
+                                               ui::LogicalDisplayId::DEFAULT}});
+    assertReceivedMotionEvent({InputEventEntry{20ms,
+                                               {Pointer{.id = 0, .x = 30.0f, .y = 30.0f}},
+                                               AMOTION_EVENT_ACTION_MOVE,
+                                               ui::LogicalDisplayId{1}}});
+
+    mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+
 } // namespace android
diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index 9841c03..1bac644 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -40,6 +40,7 @@
     std::chrono::nanoseconds eventTime;
     std::vector<Pointer> pointers;
     int32_t action;
+    ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT;
 };
 
 } // namespace
@@ -65,9 +66,10 @@
 
     status_t publishSimpleMotionEventWithCoords(int32_t action, nsecs_t eventTime,
                                                 const std::vector<PointerProperties>& properties,
-                                                const std::vector<PointerCoords>& coords);
+                                                const std::vector<PointerCoords>& coords,
+                                                ui::LogicalDisplayId displayId);
     void publishSimpleMotionEvent(int32_t action, nsecs_t eventTime,
-                                  const std::vector<Pointer>& pointers);
+                                  const std::vector<Pointer>& pointers, ui::LogicalDisplayId);
     void publishInputEventEntries(const std::vector<InputEventEntry>& entries);
     void consumeInputEventEntries(const std::vector<InputEventEntry>& entries,
                                   std::chrono::nanoseconds frameTime);
@@ -76,7 +78,7 @@
 
 status_t TouchResamplingTest::publishSimpleMotionEventWithCoords(
         int32_t action, nsecs_t eventTime, const std::vector<PointerProperties>& properties,
-        const std::vector<PointerCoords>& coords) {
+        const std::vector<PointerCoords>& coords, ui::LogicalDisplayId displayId) {
     const ui::Transform identityTransform;
     const nsecs_t downTime = 0;
 
@@ -84,8 +86,8 @@
         ADD_FAILURE() << "Downtime should be equal to 0 (hardcoded for convenience)";
     }
     return mPublisher->publishMotionEvent(mSeq++, InputEvent::nextId(), /*deviceId=*/1,
-                                          AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
-                                          INVALID_HMAC, action, /*actionButton=*/0, /*flags=*/0,
+                                          AINPUT_SOURCE_TOUCHSCREEN, displayId, INVALID_HMAC,
+                                          action, /*actionButton=*/0, /*flags=*/0,
                                           /*edgeFlags=*/0, AMETA_NONE, /*buttonState=*/0,
                                           MotionClassification::NONE, identityTransform,
                                           /*xPrecision=*/0, /*yPrecision=*/0,
@@ -96,7 +98,8 @@
 }
 
 void TouchResamplingTest::publishSimpleMotionEvent(int32_t action, nsecs_t eventTime,
-                                                   const std::vector<Pointer>& pointers) {
+                                                   const std::vector<Pointer>& pointers,
+                                                   ui::LogicalDisplayId displayId) {
     std::vector<PointerProperties> properties;
     std::vector<PointerCoords> coords;
 
@@ -112,7 +115,8 @@
         coords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, pointer.y);
     }
 
-    status_t result = publishSimpleMotionEventWithCoords(action, eventTime, properties, coords);
+    status_t result =
+            publishSimpleMotionEventWithCoords(action, eventTime, properties, coords, displayId);
     ASSERT_EQ(OK, result);
 }
 
@@ -122,7 +126,8 @@
  */
 void TouchResamplingTest::publishInputEventEntries(const std::vector<InputEventEntry>& entries) {
     for (const InputEventEntry& entry : entries) {
-        publishSimpleMotionEvent(entry.action, entry.eventTime.count(), entry.pointers);
+        publishSimpleMotionEvent(entry.action, entry.eventTime.count(), entry.pointers,
+                                 entry.displayId);
     }
 }
 
@@ -178,6 +183,7 @@
         ASSERT_EQ(entry.action, motionEvent->getAction());
         ASSERT_EQ(entry.eventTime.count(), motionEvent->getHistoricalEventTime(i));
         ASSERT_EQ(entry.pointers.size(), motionEvent->getPointerCount());
+        ASSERT_EQ(entry.displayId, motionEvent->getDisplayId());
 
         for (size_t p = 0; p < motionEvent->getPointerCount(); p++) {
             SCOPED_TRACE(p);
@@ -697,4 +703,45 @@
     consumeInputEventEntries(expectedEntries, frameTime);
 }
 
+TEST_F(TouchResamplingTest, EventsOnDifferentDisplaysAreNotResampled) {
+    std::chrono::nanoseconds frameTime;
+    std::vector<InputEventEntry> entries, expectedEntries;
+
+    // Initial ACTION_DOWN should be separate, because the first consume event will only return
+    // InputEvent with a single action.
+    entries = {
+            //      id  x   y
+            {0ms, {{0, 10, 20}}, AMOTION_EVENT_ACTION_DOWN},
+    };
+    publishInputEventEntries(entries);
+    frameTime = 5ms;
+    expectedEntries = {
+            //      id  x   y
+            {0ms, {{0, 10, 20}}, AMOTION_EVENT_ACTION_DOWN},
+    };
+    consumeInputEventEntries(expectedEntries, frameTime);
+
+    // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y, but on
+    // different displays.
+    entries = {
+            //      id  x   y
+            {10ms, {{0, 20, 30}}, AMOTION_EVENT_ACTION_MOVE, ui::LogicalDisplayId::DEFAULT},
+            {20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE, ui::LogicalDisplayId(1)},
+    };
+    publishInputEventEntries(entries);
+
+    // They are not resampled and sent as two separate events.
+    frameTime = 35ms;
+    expectedEntries = {
+            //      id  x   y
+            {10ms, {{0, 20, 30}}, AMOTION_EVENT_ACTION_MOVE, ui::LogicalDisplayId::DEFAULT},
+    };
+    consumeInputEventEntries(expectedEntries, frameTime);
+    expectedEntries = {
+            //      id  x   y
+            {20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE, ui::LogicalDisplayId(1)},
+    };
+    consumeInputEventEntries(expectedEntries, frameTime);
+}
+
 } // namespace android