Refactor resampler logic to constrain MotionEvent mutation

Refactored resampler logic to constrain the functions where MotionEvent
can mutate.

Bug: 297226446
Flag: EXEMPT refactor
Test: TEST=libinput_tests; m $TEST && $ANDROID_HOST_OUT/nativetest64/$TEST/$TEST --gtest_filter="ResamplerTest*"
Change-Id: I4b9c9140b73cd45f866dc51931b03cdf2883e5a3
diff --git a/include/input/Resampler.h b/include/input/Resampler.h
index ff9c4b0..5980d5d 100644
--- a/include/input/Resampler.h
+++ b/include/input/Resampler.h
@@ -44,7 +44,7 @@
      * samples by the end of the resampling. No other field of motionEvent should be modified.
      * - If resampling does not occur, then motionEvent must not be modified in any way.
      */
-    virtual void resampleMotionEvent(const std::chrono::nanoseconds resampleTime,
+    virtual void resampleMotionEvent(std::chrono::nanoseconds resampleTime,
                                      MotionEvent& motionEvent,
                                      const InputMessage* futureSample) = 0;
 };
@@ -60,7 +60,7 @@
      * data, LegacyResampler will extrapolate. Otherwise, no resampling takes place and
      * `motionEvent` is unmodified.
      */
-    void resampleMotionEvent(const std::chrono::nanoseconds resampleTime, MotionEvent& motionEvent,
+    void resampleMotionEvent(std::chrono::nanoseconds resampleTime, MotionEvent& motionEvent,
                              const InputMessage* futureSample) override;
 
 private:
@@ -72,10 +72,6 @@
     struct Sample {
         std::chrono::nanoseconds eventTime;
         Pointer pointer;
-
-        Sample(const std::chrono::nanoseconds eventTime, const PointerProperties& properties,
-               const PointerCoords& coords)
-              : eventTime{eventTime}, pointer{properties, coords} {}
     };
 
     /**
@@ -99,17 +95,34 @@
     void updateLatestSamples(const MotionEvent& motionEvent);
 
     /**
-     * May add a sample at the end of motionEvent with eventTime equal to resampleTime, and
-     * interpolated coordinates between the latest motionEvent sample and futureSample.
+     * Checks if there are necessary conditions to interpolate. For example, interpolation cannot
+     * take place if samples are too far apart in time. mLatestSamples must have at least one sample
+     * when canInterpolate is invoked.
      */
-    void interpolate(const std::chrono::nanoseconds resampleTime, MotionEvent& motionEvent,
-                     const InputMessage& futureSample) const;
+    bool canInterpolate(const InputMessage& futureSample) const;
 
     /**
-     * May add a sample at the end of motionEvent by extrapolating from the latest two samples. The
-     * added sample either has eventTime equal to resampleTime, or an earlier time if resampleTime
-     * is too far in the future.
+     * Returns a sample interpolated between the latest sample of mLatestSamples and futureSample,
+     * if the conditions from canInterpolate are satisfied. Otherwise, returns nullopt.
+     * mLatestSamples must have at least one sample when attemptInterpolation is called.
      */
-    void extrapolate(const std::chrono::nanoseconds resampleTime, MotionEvent& motionEvent) const;
+    std::optional<Sample> attemptInterpolation(std::chrono::nanoseconds resampleTime,
+                                               const InputMessage& futureSample) const;
+
+    /**
+     * Checks if there are necessary conditions to extrapolate. That is, there are at least two
+     * samples in mLatestSamples, and delta is bounded within a time interval.
+     */
+    bool canExtrapolate() const;
+
+    /**
+     * Returns a sample extrapolated from the two samples of mLatestSamples, if the conditions from
+     * canExtrapolate are satisfied. The returned sample either has eventTime equal to resampleTime,
+     * or an earlier time if resampleTime is too far in the future. If canExtrapolate returns false,
+     * this function returns nullopt.
+     */
+    std::optional<Sample> attemptExtrapolation(std::chrono::nanoseconds resampleTime) const;
+
+    inline static void addSampleToMotionEvent(const Sample& sample, MotionEvent& motionEvent);
 };
 } // namespace android
\ No newline at end of file
diff --git a/libs/input/Resampler.cpp b/libs/input/Resampler.cpp
index 836a97a..342f7f5 100644
--- a/libs/input/Resampler.cpp
+++ b/libs/input/Resampler.cpp
@@ -60,8 +60,8 @@
     return a + alpha * (b - a);
 }
 
-const PointerCoords calculateResampledCoords(const PointerCoords& a, const PointerCoords& b,
-                                             const float alpha) {
+PointerCoords calculateResampledCoords(const PointerCoords& a, const PointerCoords& b,
+                                       float alpha) {
     // We use the value of alpha to initialize resampledCoords with the latest sample information.
     PointerCoords resampledCoords = (alpha < 1.0f) ? a : b;
     resampledCoords.isResampled = true;
@@ -72,52 +72,85 @@
 } // namespace
 
 void LegacyResampler::updateLatestSamples(const MotionEvent& motionEvent) {
-    const size_t motionEventSampleSize = motionEvent.getHistorySize() + 1;
-    for (size_t i = 0; i < motionEventSampleSize; ++i) {
-        Sample sample{static_cast<nanoseconds>(motionEvent.getHistoricalEventTime(i)),
-                      *motionEvent.getPointerProperties(0),
-                      motionEvent.getSamplePointerCoords()[i]};
-        mLatestSamples.pushBack(sample);
+    const size_t numSamples = motionEvent.getHistorySize() + 1;
+    for (size_t i = 0; i < numSamples; ++i) {
+        mLatestSamples.pushBack(
+                Sample{static_cast<nanoseconds>(motionEvent.getHistoricalEventTime(i)),
+                       Pointer{*motionEvent.getPointerProperties(0),
+                               motionEvent.getSamplePointerCoords()[i]}});
     }
 }
 
-void LegacyResampler::interpolate(const nanoseconds resampleTime, MotionEvent& motionEvent,
-                                  const InputMessage& futureSample) const {
-    const Sample pastSample = mLatestSamples.back();
+bool LegacyResampler::canInterpolate(const InputMessage& futureSample) const {
+    LOG_IF(FATAL, mLatestSamples.empty())
+            << "Not resampled. mLatestSamples must not be empty to interpolate.";
+
+    const Sample& pastSample = *(mLatestSamples.end() - 1);
     const nanoseconds delta =
             static_cast<nanoseconds>(futureSample.body.motion.eventTime) - pastSample.eventTime;
     if (delta < RESAMPLE_MIN_DELTA) {
         LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns.";
-        return;
+        return false;
     }
+    return true;
+}
+
+std::optional<LegacyResampler::Sample> LegacyResampler::attemptInterpolation(
+        nanoseconds resampleTime, const InputMessage& futureSample) const {
+    if (!canInterpolate(futureSample)) {
+        return std::nullopt;
+    }
+    LOG_IF(FATAL, mLatestSamples.empty())
+            << "Not resampled. mLatestSamples must not be empty to interpolate.";
+
+    const Sample& pastSample = *(mLatestSamples.end() - 1);
+    const nanoseconds delta =
+            static_cast<nanoseconds>(futureSample.body.motion.eventTime) - pastSample.eventTime;
     const float alpha =
             std::chrono::duration<float, std::milli>(resampleTime - pastSample.eventTime) / delta;
-
     const PointerCoords resampledCoords =
             calculateResampledCoords(pastSample.pointer.coords,
                                      futureSample.body.motion.pointers[0].coords, alpha);
-    motionEvent.addSample(resampleTime.count(), &resampledCoords, motionEvent.getId());
+
+    return Sample{resampleTime, Pointer{pastSample.pointer.properties, resampledCoords}};
 }
 
-void LegacyResampler::extrapolate(const nanoseconds resampleTime, MotionEvent& motionEvent) const {
+bool LegacyResampler::canExtrapolate() const {
     if (mLatestSamples.size() < 2) {
-        return;
+        LOG_IF(INFO, debugResampling()) << "Not resampled. Not enough data.";
+        return false;
     }
-    const Sample pastSample = *(mLatestSamples.end() - 2);
-    const Sample presentSample = *(mLatestSamples.end() - 1);
-    const nanoseconds delta =
-            static_cast<nanoseconds>(presentSample.eventTime - pastSample.eventTime);
+
+    const Sample& pastSample = *(mLatestSamples.end() - 2);
+    const Sample& presentSample = *(mLatestSamples.end() - 1);
+
+    const nanoseconds delta = presentSample.eventTime - pastSample.eventTime;
     if (delta < RESAMPLE_MIN_DELTA) {
         LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns.";
-        return;
+        return false;
     } else if (delta > RESAMPLE_MAX_DELTA) {
         LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too large: " << delta << "ns.";
-        return;
+        return false;
     }
+    return true;
+}
+
+std::optional<LegacyResampler::Sample> LegacyResampler::attemptExtrapolation(
+        nanoseconds resampleTime) const {
+    if (!canExtrapolate()) {
+        return std::nullopt;
+    }
+    LOG_IF(FATAL, mLatestSamples.size() < 2)
+            << "Not resampled. mLatestSamples must have at least two samples to extrapolate.";
+
+    const Sample& pastSample = *(mLatestSamples.end() - 2);
+    const Sample& presentSample = *(mLatestSamples.end() - 1);
+
+    const nanoseconds delta = presentSample.eventTime - pastSample.eventTime;
     // The farthest future time to which we can extrapolate. If the given resampleTime exceeds this,
     // we use this value as the resample time target.
-    const nanoseconds farthestPrediction = static_cast<nanoseconds>(presentSample.eventTime) +
-            std::min<nanoseconds>(delta / 2, RESAMPLE_MAX_PREDICTION);
+    const nanoseconds farthestPrediction =
+            presentSample.eventTime + std::min<nanoseconds>(delta / 2, RESAMPLE_MAX_PREDICTION);
     const nanoseconds newResampleTime =
             (resampleTime > farthestPrediction) ? (farthestPrediction) : (resampleTime);
     LOG_IF(INFO, debugResampling() && newResampleTime == farthestPrediction)
@@ -127,25 +160,32 @@
     const float alpha =
             std::chrono::duration<float, std::milli>(newResampleTime - pastSample.eventTime) /
             delta;
-
     const PointerCoords resampledCoords =
             calculateResampledCoords(pastSample.pointer.coords, presentSample.pointer.coords,
                                      alpha);
-    motionEvent.addSample(newResampleTime.count(), &resampledCoords, motionEvent.getId());
+
+    return Sample{newResampleTime, Pointer{presentSample.pointer.properties, resampledCoords}};
 }
 
-void LegacyResampler::resampleMotionEvent(const nanoseconds resampleTime, MotionEvent& motionEvent,
+inline void LegacyResampler::addSampleToMotionEvent(const Sample& sample,
+                                                    MotionEvent& motionEvent) {
+    motionEvent.addSample(sample.eventTime.count(), &sample.pointer.coords, motionEvent.getId());
+}
+
+void LegacyResampler::resampleMotionEvent(nanoseconds resampleTime, MotionEvent& motionEvent,
                                           const InputMessage* futureSample) {
     if (mPreviousDeviceId && *mPreviousDeviceId != motionEvent.getDeviceId()) {
         mLatestSamples.clear();
     }
     mPreviousDeviceId = motionEvent.getDeviceId();
+
     updateLatestSamples(motionEvent);
-    if (futureSample) {
-        interpolate(resampleTime, motionEvent, *futureSample);
-    } else {
-        extrapolate(resampleTime, motionEvent);
+
+    const std::optional<Sample> sample = (futureSample != nullptr)
+            ? (attemptInterpolation(resampleTime, *futureSample))
+            : (attemptExtrapolation(resampleTime));
+    if (sample.has_value()) {
+        addSampleToMotionEvent(*sample, motionEvent);
     }
-    LOG_IF(INFO, debugResampling()) << "Not resampled. Not enough data.";
 }
 } // namespace android