Add multiple pointer support to LegacyResampler with tests

Added multiple pointer support to LegacyResampler and included the
corresponding unit tests to ensure correctness.

Bug: 297226446
Flag: EXEMPT refactor
Test: TEST=libinput_tests; m $TEST && $ANDROID_HOST_OUT/nativetest64/$TEST/$TEST --gtest_filter="ResamplerTest*"
Change-Id: Ib639942c31311dcdcf6d72ed33a4d4476b76fe7f
diff --git a/libs/input/tests/Resampler_test.cpp b/libs/input/tests/Resampler_test.cpp
index 135f8b4..b372c0b 100644
--- a/libs/input/tests/Resampler_test.cpp
+++ b/libs/input/tests/Resampler_test.cpp
@@ -59,6 +59,9 @@
 struct InputSample {
     std::chrono::milliseconds eventTime{0};
     std::vector<Pointer> pointers{};
+
+    explicit InputSample(std::chrono::milliseconds eventTime, const std::vector<Pointer>& pointers)
+          : eventTime{eventTime}, pointers{pointers} {}
     /**
      * Converts from InputSample to InputMessage. Enables calling LegacyResampler methods only with
      * the relevant data for tests.
@@ -73,6 +76,7 @@
     message.body.motion.eventTime = static_cast<std::chrono::nanoseconds>(eventTime).count();
     message.body.motion.source = AINPUT_SOURCE_CLASS_POINTER;
     message.body.motion.downTime = 0;
+
     const uint32_t pointerCount = message.body.motion.pointerCount;
     for (uint32_t i = 0; i < pointerCount; ++i) {
         message.body.motion.pointers[i].properties.id = pointers[i].id;
@@ -132,14 +136,6 @@
 
     std::unique_ptr<Resampler> mResampler;
 
-    MotionEvent buildMotionEvent(const int32_t action, const nsecs_t eventTime,
-                                 const std::vector<PointerBuilder>& pointers);
-
-    InputMessage createMessage(const uint32_t pointerCount, const nsecs_t eventTime,
-                               const int32_t action,
-                               const std::vector<PointerProperties>& properties,
-                               const std::vector<PointerCoords>& coords);
-
     /**
      * Checks that beforeCall and afterCall are equal except for the mutated attributes by addSample
      * member function.
@@ -153,42 +149,14 @@
      * Asserts the MotionEvent is resampled by checking an increment in history size and that the
      * resampled coordinates are near the expected ones.
      */
-    void assertMotionEventIsResampledAndCoordsNear(const MotionEvent& original,
-                                                   const MotionEvent& resampled,
-                                                   const PointerCoords& expectedCoords);
+    void assertMotionEventIsResampledAndCoordsNear(
+            const MotionEvent& original, const MotionEvent& resampled,
+            const std::vector<PointerCoords>& expectedCoords);
 
     void assertMotionEventIsNotResampled(const MotionEvent& original,
                                          const MotionEvent& notResampled);
 };
 
-MotionEvent ResamplerTest::buildMotionEvent(const int32_t action, const nsecs_t eventTime,
-                                            const std::vector<PointerBuilder>& pointerBuilders) {
-    MotionEventBuilder motionEventBuilder = MotionEventBuilder(action, AINPUT_SOURCE_CLASS_POINTER)
-                                                    .downTime(0)
-                                                    .eventTime(eventTime);
-    for (const PointerBuilder& pointerBuilder : pointerBuilders) {
-        motionEventBuilder.pointer(pointerBuilder);
-    }
-    return motionEventBuilder.build();
-}
-
-InputMessage ResamplerTest::createMessage(const uint32_t pointerCount, const nsecs_t eventTime,
-                                          const int32_t action,
-                                          const std::vector<PointerProperties>& properties,
-                                          const std::vector<PointerCoords>& coords) {
-    InputMessage message;
-    message.header.type = InputMessage::Type::MOTION;
-    message.body.motion.pointerCount = pointerCount;
-    message.body.motion.eventTime = eventTime;
-    message.body.motion.source = AINPUT_SOURCE_CLASS_POINTER;
-    message.body.motion.downTime = 0;
-    for (uint32_t i = 0; i < pointerCount; ++i) {
-        message.body.motion.pointers[i].properties = properties[i];
-        message.body.motion.pointers[i].coords = coords[i];
-    }
-    return message;
-}
-
 void ResamplerTest::assertMotionEventMetaDataDidNotMutate(const MotionEvent& beforeCall,
                                                           const MotionEvent& afterCall) {
     EXPECT_EQ(beforeCall.getDeviceId(), afterCall.getDeviceId());
@@ -207,18 +175,29 @@
     EXPECT_EQ(beforeCall.getDisplayId(), afterCall.getDisplayId());
 }
 
-void ResamplerTest::assertMotionEventIsResampledAndCoordsNear(const MotionEvent& original,
-                                                              const MotionEvent& resampled,
-                                                              const PointerCoords& expectedCoords) {
+void ResamplerTest::assertMotionEventIsResampledAndCoordsNear(
+        const MotionEvent& original, const MotionEvent& resampled,
+        const std::vector<PointerCoords>& expectedCoords) {
     assertMotionEventMetaDataDidNotMutate(original, resampled);
+
     const size_t originalSampleSize = original.getHistorySize() + 1;
     const size_t resampledSampleSize = resampled.getHistorySize() + 1;
     EXPECT_EQ(originalSampleSize + 1, resampledSampleSize);
-    const PointerCoords& resampledCoords =
-            resampled.getSamplePointerCoords()[resampled.getHistorySize()];
-    EXPECT_TRUE(resampledCoords.isResampled);
-    EXPECT_NEAR(expectedCoords.getX(), resampledCoords.getX(), EPSILON);
-    EXPECT_NEAR(expectedCoords.getY(), resampledCoords.getY(), EPSILON);
+
+    const size_t numPointers = resampled.getPointerCount();
+    const size_t beginLatestSample = resampledSampleSize - 1;
+    for (size_t i = 0; i < numPointers; ++i) {
+        SCOPED_TRACE(i);
+        EXPECT_EQ(original.getPointerId(i), resampled.getPointerId(i));
+        EXPECT_EQ(original.getToolType(i), resampled.getToolType(i));
+
+        const PointerCoords& resampledCoords =
+                resampled.getSamplePointerCoords()[beginLatestSample * numPointers + i];
+
+        EXPECT_TRUE(resampledCoords.isResampled);
+        EXPECT_NEAR(expectedCoords[i].getX(), resampledCoords.getX(), EPSILON);
+        EXPECT_NEAR(expectedCoords[i].getY(), resampledCoords.getY(), EPSILON);
+    }
 }
 
 void ResamplerTest::assertMotionEventIsNotResampled(const MotionEvent& original,
@@ -233,7 +212,7 @@
     constexpr float TOUCH_MAJOR_VALUE = 1.0f;
 
     MotionEvent motionEvent =
-            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
 
     constexpr std::chrono::nanoseconds eventTime{10ms};
@@ -255,34 +234,40 @@
     EXPECT_EQ(motionEvent.getTouchMajor(0), TOUCH_MAJOR_VALUE);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
-                                              Pointer{.id = 0,
-                                                      .x = 2.2f,
-                                                      .y = 2.4f,
-                                                      .isResampled = true});
+                                              {Pointer{.id = 0,
+                                                       .x = 2.2f,
+                                                       .y = 2.4f,
+                                                       .isResampled = true}});
 }
 
 TEST_F(ResamplerTest, SinglePointerNotEnoughDataToResample) {
     MotionEvent motionEvent =
-            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
-                        AMOTION_EVENT_ACTION_MOVE,
-                        .deviceId = 0};
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
     const MotionEvent originalMotionEvent = motionEvent;
-    mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, /*futureSample=*/nullptr);
+
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
 
 TEST_F(ResamplerTest, SinglePointerDifferentDeviceIdBetweenMotionEvents) {
     MotionEvent motionFromFirstDevice =
-            InputStream{{{4ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
-                         {8ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+            InputStream{{InputSample{4ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
+                         InputSample{8ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE,
                         .deviceId = 0};
+
     mResampler->resampleMotionEvent(10ms, motionFromFirstDevice, nullptr);
+
     MotionEvent motionFromSecondDevice =
-            InputStream{{{11ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+            InputStream{{InputSample{11ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE,
                         .deviceId = 1};
     const MotionEvent originalMotionEvent = motionFromSecondDevice;
+
     mResampler->resampleMotionEvent(12ms, motionFromSecondDevice, nullptr);
     // The MotionEvent should not be resampled because the second event came from a different device
     // than the previous event.
@@ -308,28 +293,30 @@
  */
 TEST_F(ResamplerTest, SinglePointerSingleSampleInterpolation) {
     MotionEvent motionEvent =
-            InputStream{{{10ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
     const InputMessage futureSample =
-            InputSample{15ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}};
+            InputSample{15ms, {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}};
 
     const MotionEvent originalMotionEvent = motionEvent;
 
     mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
-                                              Pointer{.id = 0,
-                                                      .x = 1.2f,
-                                                      .y = 1.2f,
-                                                      .isResampled = true});
+                                              {Pointer{.id = 0,
+                                                       .x = 1.2f,
+                                                       .y = 2.4f,
+                                                       .isResampled = true}});
 }
 
 TEST_F(ResamplerTest, SinglePointerDeltaTooSmallInterpolation) {
     MotionEvent motionEvent =
-            InputStream{{{10ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
     const InputMessage futureSample =
-            InputSample{11ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}};
+            InputSample{11ms, {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}};
 
     const MotionEvent originalMotionEvent = motionEvent;
 
@@ -342,25 +329,26 @@
  * Tests extrapolation given two MotionEvents with a single sample.
  */
 TEST_F(ResamplerTest, SinglePointerSingleSampleExtrapolation) {
-    MotionEvent previousMotionEvent =
-            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
 
-    mResampler->resampleMotionEvent(10ms, previousMotionEvent, nullptr);
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, nullptr);
 
-    MotionEvent motionEvent =
-            InputStream{{{10ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
 
-    const MotionEvent originalMotionEvent = motionEvent;
+    const MotionEvent originalMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
+    mResampler->resampleMotionEvent(11ms, secondMotionEvent, nullptr);
 
-    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
-                                              Pointer{.id = 0,
-                                                      .x = 1.0f,
-                                                      .y = 1.0f,
-                                                      .isResampled = true});
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, secondMotionEvent,
+                                              {Pointer{.id = 0,
+                                                       .x = 2.2f,
+                                                       .y = 4.4f,
+                                                       .isResampled = true}});
     // Integrity of the whole motionEvent
     // History size should increment by 1
     // Check if the resampled value is the last one
@@ -370,27 +358,30 @@
 
 TEST_F(ResamplerTest, SinglePointerMultipleSampleInterpolation) {
     MotionEvent motionEvent =
-            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
-                         {10ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{10ms,
+                                     {{.id = 0, .x = 2.0f, .y = 3.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
+
     const InputMessage futureSample =
-            InputSample{15ms, {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}};
+            InputSample{15ms, {{.id = 0, .x = 3.0f, .y = 5.0f, .isResampled = false}}};
 
     const MotionEvent originalMotionEvent = motionEvent;
 
     mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
-                                              Pointer{.id = 0,
-                                                      .x = 2.2f,
-                                                      .y = 2.2f,
-                                                      .isResampled = true});
+                                              {Pointer{.id = 0,
+                                                       .x = 2.2f,
+                                                       .y = 3.4f,
+                                                       .isResampled = true}});
 }
 
 TEST_F(ResamplerTest, SinglePointerMultipleSampleExtrapolation) {
     MotionEvent motionEvent =
-            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
-                         {10ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{10ms,
+                                     {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
 
     const MotionEvent originalMotionEvent = motionEvent;
@@ -398,16 +389,17 @@
     mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
-                                              Pointer{.id = 0,
-                                                      .x = 2.2f,
-                                                      .y = 2.2f,
-                                                      .isResampled = true});
+                                              {Pointer{.id = 0,
+                                                       .x = 2.2f,
+                                                       .y = 4.4f,
+                                                       .isResampled = true}});
 }
 
 TEST_F(ResamplerTest, SinglePointerDeltaTooSmallExtrapolation) {
     MotionEvent motionEvent =
-            InputStream{{{9ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
-                         {10ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+            InputStream{{InputSample{9ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{10ms,
+                                     {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
 
     const MotionEvent originalMotionEvent = motionEvent;
@@ -419,8 +411,9 @@
 
 TEST_F(ResamplerTest, SinglePointerDeltaTooLargeExtrapolation) {
     MotionEvent motionEvent =
-            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
-                         {26ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{26ms,
+                                     {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
 
     const MotionEvent originalMotionEvent = motionEvent;
@@ -432,8 +425,9 @@
 
 TEST_F(ResamplerTest, SinglePointerResampleTimeTooFarExtrapolation) {
     MotionEvent motionEvent =
-            InputStream{{{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
-                         {25ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{25ms,
+                                     {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}}},
                         AMOTION_EVENT_ACTION_MOVE};
 
     const MotionEvent originalMotionEvent = motionEvent;
@@ -441,9 +435,424 @@
     mResampler->resampleMotionEvent(43ms, motionEvent, nullptr);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
-                                              Pointer{.id = 0,
-                                                      .x = 2.4f,
-                                                      .y = 2.4f,
-                                                      .isResampled = true});
+                                              {Pointer{.id = 0,
+                                                       .x = 2.4f,
+                                                       .y = 4.8f,
+                                                       .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerSingleSampleInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                         {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.x = 2.2f, .y = 2.2f, .isResampled = true},
+                                               Pointer{.x = 3.2f, .y = 3.2f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerSingleSampleExtrapolation) {
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, secondMotionEvent,
+                                              {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
+                                               Pointer{.x = 4.4f, .y = 4.4f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerMultipleSampleInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 0, .x = 5.0f, .y = 5.0f, .isResampled = false},
+                         {.id = 1, .x = 6.0f, .y = 6.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
+                                               Pointer{.x = 4.4f, .y = 4.4f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerMultipleSampleExtrapolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
+                                               Pointer{.x = 4.4f, .y = 4.4f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerIncreaseNumPointersInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                         {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                         {.id = 2, .x = 5.0f, .y = 5.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.x = 1.4f, .y = 1.4f, .isResampled = true},
+                                               Pointer{.x = 2.4f, .y = 2.4f, .isResampled = true}});
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{25ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                                      {.id = 2, .x = 5.0f, .y = 5.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage secondFutureSample =
+            InputSample{30ms,
+                        {{.id = 0, .x = 5.0f, .y = 5.0f, .isResampled = false},
+                         {.id = 1, .x = 6.0f, .y = 6.0f, .isResampled = false},
+                         {.id = 2, .x = 7.0f, .y = 7.0f, .isResampled = false}}};
+
+    const MotionEvent originalSecondMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(27ms, secondMotionEvent, &secondFutureSample);
+
+    assertMotionEventIsResampledAndCoordsNear(originalSecondMotionEvent, secondMotionEvent,
+                                              {Pointer{.x = 3.8f, .y = 3.8f, .isResampled = true},
+                                               Pointer{.x = 4.8f, .y = 4.8f, .isResampled = true},
+                                               Pointer{.x = 5.8f, .y = 5.8f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerIncreaseNumPointersExtrapolation) {
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                                      {.id = 2, .x = 5.0f, .y = 5.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDecreaseNumPointersInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                                      {.id = 2, .x = 5.0f, .y = 5.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 0, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                         {.id = 1, .x = 5.0f, .y = 5.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDecreaseNumPointersExtrapolation) {
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false},
+                                      {.id = 2, .x = 3.0f, .y = 3.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsResampledAndCoordsNear(secondOriginalMotionEvent, secondMotionEvent,
+                                              {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
+                                               Pointer{.x = 4.4f, .y = 4.4f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentIdOrderInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                         {.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentIdOrderExtrapolation) {
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                                      {.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentIdsInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                         {.id = 2, .x = 3.0f, .y = 3.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentIdsExtrapolation) {
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                                      {.id = 2, .x = 3.0f, .y = 3.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentToolTypeInterpolation) {
+    MotionEvent motionEvent = InputStream{{InputSample{10ms,
+                                                       {{.id = 0,
+                                                         .toolType = ToolType::FINGER,
+                                                         .x = 1.0f,
+                                                         .y = 1.0f,
+                                                         .isResampled = false},
+                                                        {.id = 1,
+                                                         .toolType = ToolType::FINGER,
+                                                         .x = 2.0f,
+                                                         .y = 2.0f,
+                                                         .isResampled = false}}}},
+                                          AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample = InputSample{15ms,
+                                                  {{.id = 0,
+                                                    .toolType = ToolType::FINGER,
+                                                    .x = 3.0,
+                                                    .y = 3.0,
+                                                    .isResampled = false},
+                                                   {.id = 1,
+                                                    .toolType = ToolType::STYLUS,
+                                                    .x = 4.0,
+                                                    .y = 4.0,
+                                                    .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentToolTypeExtrapolation) {
+    MotionEvent firstMotionEvent = InputStream{{InputSample{5ms,
+                                                            {{.id = 0,
+                                                              .toolType = ToolType::FINGER,
+                                                              .x = 1.0f,
+                                                              .y = 1.0f,
+                                                              .isResampled = false},
+                                                             {.id = 1,
+                                                              .toolType = ToolType::FINGER,
+                                                              .x = 2.0f,
+                                                              .y = 2.0f,
+                                                              .isResampled = false}}}},
+                                               AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent = InputStream{{InputSample{10ms,
+                                                             {{.id = 0,
+                                                               .toolType = ToolType::FINGER,
+                                                               .x = 1.0f,
+                                                               .y = 1.0f,
+                                                               .isResampled = false},
+                                                              {.id = 1,
+                                                               .toolType = ToolType::STYLUS,
+                                                               .x = 2.0f,
+                                                               .y = 2.0f,
+                                                               .isResampled = false}}}},
+                                                AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerShouldNotResampleToolTypeInterpolation) {
+    MotionEvent motionEvent = InputStream{{InputSample{10ms,
+                                                       {{.id = 0,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 1.0f,
+                                                         .y = 1.0f,
+                                                         .isResampled = false},
+                                                        {.id = 1,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 2.0f,
+                                                         .y = 2.0f,
+                                                         .isResampled = false}}}},
+                                          AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample = InputSample{15ms,
+                                                  {{.id = 0,
+                                                    .toolType = ToolType::PALM,
+                                                    .x = 3.0,
+                                                    .y = 3.0,
+                                                    .isResampled = false},
+                                                   {.id = 1,
+                                                    .toolType = ToolType::PALM,
+                                                    .x = 4.0,
+                                                    .y = 4.0,
+                                                    .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerShouldNotResampleToolTypeExtrapolation) {
+    MotionEvent motionEvent = InputStream{{InputSample{5ms,
+                                                       {{.id = 0,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 1.0f,
+                                                         .y = 1.0f,
+                                                         .isResampled = false},
+                                                        {.id = 1,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 2.0f,
+                                                         .y = 2.0f,
+                                                         .isResampled = false}}},
+                                           InputSample{10ms,
+                                                       {{.id = 0,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 3.0f,
+                                                         .y = 3.0f,
+                                                         .isResampled = false},
+                                                        {.id = 1,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 4.0f,
+                                                         .y = 4.0f,
+                                                         .isResampled = false}}}},
+                                          AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(11ms, motionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
 } // namespace android