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

Commit 486ca6d5 authored by Paul Ramirez's avatar Paul Ramirez
Browse files

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
parent d7f52be5
Loading
Loading
Loading
Loading
+27 −14
Original line number Diff line number Diff line
@@ -44,7 +44,7 @@ struct Resampler {
     * 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 @@ public:
     * 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 @@ private:
    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 @@ private:
    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.
     */
    bool canInterpolate(const InputMessage& futureSample) const;

    /**
     * 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 interpolate(const std::chrono::nanoseconds resampleTime, MotionEvent& motionEvent,
    std::optional<Sample> attemptInterpolation(std::chrono::nanoseconds resampleTime,
                                               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.
     * 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.
     */
    void extrapolate(const std::chrono::nanoseconds resampleTime, MotionEvent& motionEvent) const;
    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
+72 −32
Original line number Diff line number Diff line
@@ -60,8 +60,8 @@ inline float lerp(float a, float b, float alpha) {
    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 @@ const PointerCoords calculateResampledCoords(const PointerCoords& a, const Point
} // 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 @@ void LegacyResampler::extrapolate(const nanoseconds resampleTime, MotionEvent& m
    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}};
}

inline void LegacyResampler::addSampleToMotionEvent(const Sample& sample,
                                                    MotionEvent& motionEvent) {
    motionEvent.addSample(sample.eventTime.count(), &sample.pointer.coords, motionEvent.getId());
}

void LegacyResampler::resampleMotionEvent(const nanoseconds resampleTime, MotionEvent& motionEvent,
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