Merge "CursorInputMapper: share acceleration curves with touchpad" into main
diff --git a/include/input/AccelerationCurve.h b/include/input/AccelerationCurve.h
new file mode 100644
index 0000000..0cf648a
--- /dev/null
+++ b/include/input/AccelerationCurve.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <vector>
+
+namespace android {
+
+/**
+ * Describes a section of an acceleration curve as a function which outputs a scaling factor (gain)
+ * for the pointer movement, given the speed of the mouse or finger (in mm/s):
+ *
+ *     gain(input_speed_mm_per_s) = baseGain + reciprocal / input_speed_mm_per_s
+ */
+struct AccelerationCurveSegment {
+    /**
+     * The maximum pointer speed at which this segment should apply, in mm/s. The last segment in a
+     * curve should always set this to infinity.
+     */
+    double maxPointerSpeedMmPerS;
+    /** The gain for this segment before the reciprocal is taken into account. */
+    double baseGain;
+    /** The reciprocal part of the formula, which should be divided by the input speed. */
+    double reciprocal;
+};
+
+/**
+ * Creates an acceleration curve for the given pointer sensitivity value. The sensitivity value
+ * should be between -7 (for the lowest sensitivity) and 7, inclusive.
+ */
+std::vector<AccelerationCurveSegment> createAccelerationCurveForPointerSensitivity(
+        int32_t sensitivity);
+
+} // namespace android
diff --git a/include/input/VelocityControl.h b/include/input/VelocityControl.h
index b78f63e..7c58c87 100644
--- a/include/input/VelocityControl.h
+++ b/include/input/VelocityControl.h
@@ -16,7 +16,10 @@
 
 #pragma once
 
+#include <vector>
+
 #include <android-base/stringprintf.h>
+#include <input/AccelerationCurve.h>
 #include <input/Input.h>
 #include <input/VelocityTracker.h>
 #include <utils/Timers.h>
@@ -86,12 +89,7 @@
 class VelocityControl {
 public:
     VelocityControl();
-
-    /* Gets the various parameters. */
-    const VelocityControlParameters& getParameters() const;
-
-    /* Sets the various parameters. */
-    void setParameters(const VelocityControlParameters& parameters);
+    virtual ~VelocityControl() {}
 
     /* Resets the current movement counters to zero.
      * This has the effect of nullifying any acceleration. */
@@ -101,16 +99,55 @@
      * scaled / accelerated delta based on the current velocity. */
     void move(nsecs_t eventTime, float* deltaX, float* deltaY);
 
-private:
+protected:
+    virtual void scaleDeltas(float* deltaX, float* deltaY) = 0;
+
     // If no movements are received within this amount of time,
     // we assume the movement has stopped and reset the movement counters.
     static const nsecs_t STOP_TIME = 500 * 1000000; // 500 ms
 
-    VelocityControlParameters mParameters;
-
     nsecs_t mLastMovementTime;
     float mRawPositionX, mRawPositionY;
     VelocityTracker mVelocityTracker;
 };
 
+/**
+ * Velocity control using a simple acceleration curve where the acceleration factor increases
+ * linearly with movement speed, subject to minimum and maximum values.
+ */
+class SimpleVelocityControl : public VelocityControl {
+public:
+    /** Gets the various parameters. */
+    const VelocityControlParameters& getParameters() const;
+
+    /** Sets the various parameters. */
+    void setParameters(const VelocityControlParameters& parameters);
+
+protected:
+    virtual void scaleDeltas(float* deltaX, float* deltaY) override;
+
+private:
+    VelocityControlParameters mParameters;
+};
+
+/** Velocity control using a curve made up of multiple reciprocal segments. */
+class CurvedVelocityControl : public VelocityControl {
+public:
+    CurvedVelocityControl();
+
+    /** Sets the curve to be used for acceleration. */
+    void setCurve(const std::vector<AccelerationCurveSegment>& curve);
+
+    void setAccelerationEnabled(bool enabled);
+
+protected:
+    virtual void scaleDeltas(float* deltaX, float* deltaY) override;
+
+private:
+    const AccelerationCurveSegment& segmentForSpeed(float speedMmPerS);
+
+    bool mAccelerationEnabled = true;
+    std::vector<AccelerationCurveSegment> mCurveSegments;
+};
+
 } // namespace android
diff --git a/libs/input/AccelerationCurve.cpp b/libs/input/AccelerationCurve.cpp
new file mode 100644
index 0000000..0a92a71
--- /dev/null
+++ b/libs/input/AccelerationCurve.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <input/AccelerationCurve.h>
+
+#include <array>
+#include <limits>
+
+#include <log/log_main.h>
+
+#define LOG_TAG "AccelerationCurve"
+
+namespace android {
+
+namespace {
+
+// The last segment must have an infinite maximum speed, so that all speeds are covered.
+constexpr std::array<AccelerationCurveSegment, 4> kSegments = {{
+        {32.002, 3.19, 0},
+        {52.83, 4.79, -51.254},
+        {119.124, 7.28, -182.737},
+        {std::numeric_limits<double>::infinity(), 15.04, -1107.556},
+}};
+
+static_assert(kSegments.back().maxPointerSpeedMmPerS == std::numeric_limits<double>::infinity());
+
+constexpr std::array<double, 15> kSensitivityFactors = {1,  2,  4,  6,  7,  8,  9, 10,
+                                                        11, 12, 13, 14, 16, 18, 20};
+
+} // namespace
+
+std::vector<AccelerationCurveSegment> createAccelerationCurveForPointerSensitivity(
+        int32_t sensitivity) {
+    LOG_ALWAYS_FATAL_IF(sensitivity < -7 || sensitivity > 7, "Invalid pointer sensitivity value");
+    std::vector<AccelerationCurveSegment> output;
+    output.reserve(kSegments.size());
+
+    // The curves we want to produce for different sensitivity values are actually the same curve,
+    // just scaled in the Y (gain) axis by a sensitivity factor and a couple of constants.
+    double commonFactor = 0.64 * kSensitivityFactors[sensitivity + 7] / 10;
+    for (AccelerationCurveSegment seg : kSegments) {
+        output.push_back(AccelerationCurveSegment{seg.maxPointerSpeedMmPerS,
+                                                  commonFactor * seg.baseGain,
+                                                  commonFactor * seg.reciprocal});
+    }
+
+    return output;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index b74c3b2..3d3bf13 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -175,6 +175,7 @@
     ],
     srcs: [
         "android/os/IInputFlinger.aidl",
+        "AccelerationCurve.cpp",
         "Input.cpp",
         "InputDevice.cpp",
         "InputEventLabels.cpp",
diff --git a/libs/input/VelocityControl.cpp b/libs/input/VelocityControl.cpp
index c835a08..edd31e9 100644
--- a/libs/input/VelocityControl.cpp
+++ b/libs/input/VelocityControl.cpp
@@ -15,7 +15,6 @@
  */
 
 #define LOG_TAG "VelocityControl"
-//#define LOG_NDEBUG 0
 
 // Log debug messages about acceleration.
 static constexpr bool DEBUG_ACCELERATION = false;
@@ -23,6 +22,7 @@
 #include <math.h>
 #include <limits.h>
 
+#include <android-base/logging.h>
 #include <input/VelocityControl.h>
 #include <utils/BitSet.h>
 #include <utils/Timers.h>
@@ -37,15 +37,6 @@
     reset();
 }
 
-const VelocityControlParameters& VelocityControl::getParameters() const{
-    return mParameters;
-}
-
-void VelocityControl::setParameters(const VelocityControlParameters& parameters) {
-    mParameters = parameters;
-    reset();
-}
-
 void VelocityControl::reset() {
     mLastMovementTime = LLONG_MIN;
     mRawPositionX = 0;
@@ -54,65 +45,156 @@
 }
 
 void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) {
-    if ((deltaX && *deltaX) || (deltaY && *deltaY)) {
-        if (eventTime >= mLastMovementTime + STOP_TIME) {
-            if (DEBUG_ACCELERATION && mLastMovementTime != LLONG_MIN) {
-                ALOGD("VelocityControl: stopped, last movement was %0.3fms ago",
-                           (eventTime - mLastMovementTime) * 0.000001f);
-            }
-            reset();
+    if ((deltaX == nullptr || *deltaX == 0) && (deltaY == nullptr || *deltaY == 0)) {
+        return;
+    }
+    if (eventTime >= mLastMovementTime + STOP_TIME) {
+        ALOGD_IF(DEBUG_ACCELERATION && mLastMovementTime != LLONG_MIN,
+                 "VelocityControl: stopped, last movement was %0.3fms ago",
+                 (eventTime - mLastMovementTime) * 0.000001f);
+        reset();
+    }
+
+    mLastMovementTime = eventTime;
+    if (deltaX) {
+        mRawPositionX += *deltaX;
+    }
+    if (deltaY) {
+        mRawPositionY += *deltaY;
+    }
+    mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_X, mRawPositionX);
+    mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_Y, mRawPositionY);
+    scaleDeltas(deltaX, deltaY);
+}
+
+// --- SimpleVelocityControl ---
+
+const VelocityControlParameters& SimpleVelocityControl::getParameters() const {
+    return mParameters;
+}
+
+void SimpleVelocityControl::setParameters(const VelocityControlParameters& parameters) {
+    mParameters = parameters;
+    reset();
+}
+
+void SimpleVelocityControl::scaleDeltas(float* deltaX, float* deltaY) {
+    std::optional<float> vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0);
+    std::optional<float> vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0);
+    float scale = mParameters.scale;
+    if (vx.has_value() && vy.has_value()) {
+        float speed = hypotf(*vx, *vy) * scale;
+        if (speed >= mParameters.highThreshold) {
+            // Apply full acceleration above the high speed threshold.
+            scale *= mParameters.acceleration;
+        } else if (speed > mParameters.lowThreshold) {
+            // Linearly interpolate the acceleration to apply between the low and high
+            // speed thresholds.
+            scale *= 1 +
+                    (speed - mParameters.lowThreshold) /
+                            (mParameters.highThreshold - mParameters.lowThreshold) *
+                            (mParameters.acceleration - 1);
         }
 
-        mLastMovementTime = eventTime;
-        if (deltaX) {
-            mRawPositionX += *deltaX;
-        }
-        if (deltaY) {
-            mRawPositionY += *deltaY;
-        }
-        mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_X,
-                                     mRawPositionX);
-        mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_Y,
-                                     mRawPositionY);
+        ALOGD_IF(DEBUG_ACCELERATION,
+                 "SimpleVelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): "
+                 "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f",
+                 mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
+                 mParameters.acceleration, *vx, *vy, speed, scale / mParameters.scale);
 
-        std::optional<float> vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0);
-        std::optional<float> vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0);
-        float scale = mParameters.scale;
-        if (vx && vy) {
-            float speed = hypotf(*vx, *vy) * scale;
-            if (speed >= mParameters.highThreshold) {
-                // Apply full acceleration above the high speed threshold.
-                scale *= mParameters.acceleration;
-            } else if (speed > mParameters.lowThreshold) {
-                // Linearly interpolate the acceleration to apply between the low and high
-                // speed thresholds.
-                scale *= 1 + (speed - mParameters.lowThreshold)
-                        / (mParameters.highThreshold - mParameters.lowThreshold)
-                        * (mParameters.acceleration - 1);
-            }
+    } else {
+        ALOGD_IF(DEBUG_ACCELERATION,
+                 "SimpleVelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): unknown velocity",
+                 mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
+                 mParameters.acceleration);
+    }
 
-            if (DEBUG_ACCELERATION) {
-                ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): "
-                      "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f",
-                      mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
-                      mParameters.acceleration, *vx, *vy, speed, scale / mParameters.scale);
-            }
+    if (deltaX != nullptr) {
+        *deltaX *= scale;
+    }
+    if (deltaY != nullptr) {
+        *deltaY *= scale;
+    }
+}
 
-        } else {
-            if (DEBUG_ACCELERATION) {
-                ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): unknown velocity",
-                        mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
-                        mParameters.acceleration);
-            }
-        }
+// --- CurvedVelocityControl ---
 
-        if (deltaX) {
-            *deltaX *= scale;
-        }
-        if (deltaY) {
-            *deltaY *= scale;
+namespace {
+
+/**
+ * The resolution that we assume a mouse to have, in counts per inch.
+ *
+ * Mouse resolutions vary wildly, but 800 CPI is probably the most common. There should be enough
+ * range in the available sensitivity settings to accommodate users of mice with other resolutions.
+ */
+constexpr int32_t MOUSE_CPI = 800;
+
+float countsToMm(float counts) {
+    return counts / MOUSE_CPI * 25.4;
+}
+
+} // namespace
+
+CurvedVelocityControl::CurvedVelocityControl()
+      : mCurveSegments(createAccelerationCurveForPointerSensitivity(0)) {}
+
+void CurvedVelocityControl::setCurve(const std::vector<AccelerationCurveSegment>& curve) {
+    mCurveSegments = curve;
+}
+
+void CurvedVelocityControl::setAccelerationEnabled(bool enabled) {
+    mAccelerationEnabled = enabled;
+}
+
+void CurvedVelocityControl::scaleDeltas(float* deltaX, float* deltaY) {
+    if (!mAccelerationEnabled) {
+        ALOGD_IF(DEBUG_ACCELERATION, "CurvedVelocityControl: acceleration disabled");
+        return;
+    }
+
+    std::optional<float> vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0);
+    std::optional<float> vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0);
+
+    float ratio;
+    if (vx.has_value() && vy.has_value()) {
+        float vxMmPerS = countsToMm(*vx);
+        float vyMmPerS = countsToMm(*vy);
+        float speedMmPerS = sqrtf(vxMmPerS * vxMmPerS + vyMmPerS * vyMmPerS);
+
+        const AccelerationCurveSegment& seg = segmentForSpeed(speedMmPerS);
+        ratio = seg.baseGain + seg.reciprocal / speedMmPerS;
+        ALOGD_IF(DEBUG_ACCELERATION,
+                 "CurvedVelocityControl: velocities (%0.3f, %0.3f) → speed %0.3f → ratio %0.3f",
+                 vxMmPerS, vyMmPerS, speedMmPerS, ratio);
+    } else {
+        // We don't have enough data to compute a velocity yet. This happens early in the movement,
+        // when the speed is presumably low, so use the base gain of the first segment of the curve.
+        // (This would behave oddly for curves with a reciprocal term on the first segment, but we
+        // don't have any of those, and they'd be very strange at velocities close to zero anyway.)
+        ratio = mCurveSegments[0].baseGain;
+        ALOGD_IF(DEBUG_ACCELERATION,
+                 "CurvedVelocityControl: unknown velocity, using base gain of first segment (%.3f)",
+                 ratio);
+    }
+
+    if (deltaX != nullptr) {
+        *deltaX *= ratio;
+    }
+    if (deltaY != nullptr) {
+        *deltaY *= ratio;
+    }
+}
+
+const AccelerationCurveSegment& CurvedVelocityControl::segmentForSpeed(float speedMmPerS) {
+    for (const AccelerationCurveSegment& seg : mCurveSegments) {
+        if (speedMmPerS <= seg.maxPointerSpeedMmPerS) {
+            return seg;
         }
     }
+    ALOGE("CurvedVelocityControl: No segment found for speed %.3f; last segment should always have "
+          "a max speed of infinity.",
+          speedMmPerS);
+    return mCurveSegments.back();
 }
 
 } // namespace android
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index 11f6994..1baeb26 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -97,3 +97,10 @@
   description: "Remove pointer event tracking in WM after the Pointer Icon Refactor"
   bug: "315321016"
 }
+
+flag {
+  name: "enable_new_mouse_pointer_ballistics"
+  namespace: "input"
+  description: "Change the acceleration curves for mouse pointer movements to match the touchpad ones"
+  bug: "315313622"
+}
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 13cfb49..0485ff6 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -25,6 +25,7 @@
         "TfLiteMotionPredictor_test.cpp",
         "TouchResampling_test.cpp",
         "TouchVideoFrame_test.cpp",
+        "VelocityControl_test.cpp",
         "VelocityTracker_test.cpp",
         "VerifiedInputEvent_test.cpp",
     ],
diff --git a/libs/input/tests/VelocityControl_test.cpp b/libs/input/tests/VelocityControl_test.cpp
new file mode 100644
index 0000000..63d64c6
--- /dev/null
+++ b/libs/input/tests/VelocityControl_test.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <input/VelocityControl.h>
+
+#include <limits>
+
+#include <gtest/gtest.h>
+#include <input/AccelerationCurve.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+namespace {
+
+constexpr float EPSILON = 0.001;
+constexpr float COUNTS_PER_MM = 800 / 25.4;
+
+} // namespace
+
+class CurvedVelocityControlTest : public testing::Test {
+protected:
+    CurvedVelocityControl mCtrl;
+
+    void moveWithoutCheckingResult(nsecs_t eventTime, float deltaX, float deltaY) {
+        mCtrl.move(eventTime, &deltaX, &deltaY);
+    }
+
+    void moveAndCheckRatio(nsecs_t eventTime, const float deltaX, const float deltaY,
+                           float expectedRatio) {
+        float newDeltaX = deltaX, newDeltaY = deltaY;
+        mCtrl.move(eventTime, &newDeltaX, &newDeltaY);
+        ASSERT_NEAR(expectedRatio * deltaX, newDeltaX, EPSILON)
+                << "Expected ratio of " << expectedRatio << " in X, but actual ratio was "
+                << newDeltaX / deltaX;
+        ASSERT_NEAR(expectedRatio * deltaY, newDeltaY, EPSILON)
+                << "Expected ratio of " << expectedRatio << " in Y, but actual ratio was "
+                << newDeltaY / deltaY;
+    }
+};
+
+TEST_F(CurvedVelocityControlTest, SegmentSelection) {
+    // To make the maths simple, use a "curve" that's actually just a sequence of steps.
+    mCtrl.setCurve({
+            {10, 2, 0},
+            {20, 3, 0},
+            {30, 4, 0},
+            {std::numeric_limits<double>::infinity(), 5, 0},
+    });
+
+    // Establish a velocity of 16 mm/s.
+    moveWithoutCheckingResult(0, 0, 0);
+    moveWithoutCheckingResult(10'000'000, 0.16 * COUNTS_PER_MM, 0);
+    moveWithoutCheckingResult(20'000'000, 0.16 * COUNTS_PER_MM, 0);
+    moveWithoutCheckingResult(30'000'000, 0.16 * COUNTS_PER_MM, 0);
+    ASSERT_NO_FATAL_FAILURE(
+            moveAndCheckRatio(40'000'000, 0.16 * COUNTS_PER_MM, 0, /*expectedRatio=*/3));
+
+    // Establish a velocity of 50 mm/s.
+    mCtrl.reset();
+    moveWithoutCheckingResult(100'000'000, 0, 0);
+    moveWithoutCheckingResult(110'000'000, 0.50 * COUNTS_PER_MM, 0);
+    moveWithoutCheckingResult(120'000'000, 0.50 * COUNTS_PER_MM, 0);
+    moveWithoutCheckingResult(130'000'000, 0.50 * COUNTS_PER_MM, 0);
+    ASSERT_NO_FATAL_FAILURE(
+            moveAndCheckRatio(140'000'000, 0.50 * COUNTS_PER_MM, 0, /*expectedRatio=*/5));
+}
+
+TEST_F(CurvedVelocityControlTest, RatioDefaultsToFirstSegmentWhenVelocityIsUnknown) {
+    mCtrl.setCurve({
+            {10, 3, 0},
+            {20, 2, 0},
+            {std::numeric_limits<double>::infinity(), 4, 0},
+    });
+
+    // Only send two moves, which won't be enough for VelocityTracker to calculate a velocity from.
+    moveWithoutCheckingResult(0, 0, 0);
+    ASSERT_NO_FATAL_FAILURE(
+            moveAndCheckRatio(10'000'000, 0.25 * COUNTS_PER_MM, 0, /*expectedRatio=*/3));
+}
+
+TEST_F(CurvedVelocityControlTest, VelocityCalculatedUsingBothAxes) {
+    mCtrl.setCurve({
+            {8.0, 3, 0},
+            {8.1, 2, 0},
+            {std::numeric_limits<double>::infinity(), 4, 0},
+    });
+
+    // Establish a velocity of 8.06 (= √65 = √(7²+4²)) mm/s between the two axes.
+    moveWithoutCheckingResult(0, 0, 0);
+    moveWithoutCheckingResult(10'000'000, 0.07 * COUNTS_PER_MM, 0.04 * COUNTS_PER_MM);
+    moveWithoutCheckingResult(20'000'000, 0.07 * COUNTS_PER_MM, 0.04 * COUNTS_PER_MM);
+    moveWithoutCheckingResult(30'000'000, 0.07 * COUNTS_PER_MM, 0.04 * COUNTS_PER_MM);
+    ASSERT_NO_FATAL_FAILURE(moveAndCheckRatio(40'000'000, 0.07 * COUNTS_PER_MM,
+                                              0.04 * COUNTS_PER_MM,
+                                              /*expectedRatio=*/2));
+}
+
+TEST_F(CurvedVelocityControlTest, ReciprocalTerm) {
+    mCtrl.setCurve({
+            {10, 2, 0},
+            {20, 3, -10},
+            {std::numeric_limits<double>::infinity(), 3, 0},
+    });
+
+    // Establish a velocity of 15 mm/s.
+    moveWithoutCheckingResult(0, 0, 0);
+    moveWithoutCheckingResult(10'000'000, 0, 0.15 * COUNTS_PER_MM);
+    moveWithoutCheckingResult(20'000'000, 0, 0.15 * COUNTS_PER_MM);
+    moveWithoutCheckingResult(30'000'000, 0, 0.15 * COUNTS_PER_MM);
+    // Expected ratio is 3 - 10 / 15 = 2.33333...
+    ASSERT_NO_FATAL_FAILURE(
+            moveAndCheckRatio(40'000'000, 0, 0.15 * COUNTS_PER_MM, /*expectedRatio=*/2.33333));
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index efc8b26..40359a4 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -126,7 +126,20 @@
     // The suggested display ID to show the cursor.
     int32_t defaultPointerDisplayId;
 
+    // The mouse pointer speed, as a number from -7 (slowest) to 7 (fastest).
+    //
+    // Currently only used when the enable_new_mouse_pointer_ballistics flag is enabled.
+    int32_t mousePointerSpeed;
+
+    // Whether to apply an acceleration curve to pointer movements from mice.
+    //
+    // Currently only used when the enable_new_mouse_pointer_ballistics flag is enabled.
+    bool mousePointerAccelerationEnabled;
+
     // Velocity control parameters for mouse pointer movements.
+    //
+    // If the enable_new_mouse_pointer_ballistics flag is enabled, these are ignored and the values
+    // of mousePointerSpeed and mousePointerAccelerationEnabled used instead.
     VelocityControlParameters pointerVelocityControlParameters;
 
     // Velocity control parameters for mouse wheel movements.
@@ -229,6 +242,8 @@
 
     InputReaderConfiguration()
           : virtualKeyQuietTime(0),
+            mousePointerSpeed(0),
+            mousePointerAccelerationEnabled(true),
             pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f,
                                              static_cast<float>(
                                                      android::os::IInputConstants::
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 58e35a6..4cebd64 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -20,9 +20,11 @@
 
 #include "CursorInputMapper.h"
 
-#include <com_android_input_flags.h>
 #include <optional>
 
+#include <com_android_input_flags.h>
+#include <input/AccelerationCurve.h>
+
 #include "CursorButtonAccumulator.h"
 #include "CursorScrollAccumulator.h"
 #include "PointerControllerInterface.h"
@@ -75,7 +77,8 @@
                                      const InputReaderConfiguration& readerConfig)
       : InputMapper(deviceContext, readerConfig),
         mLastEventTime(std::numeric_limits<nsecs_t>::min()),
-        mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()) {}
+        mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()),
+        mEnableNewMousePointerBallistics(input_flags::enable_new_mouse_pointer_ballistics()) {}
 
 CursorInputMapper::~CursorInputMapper() {
     if (mPointerController != nullptr) {
@@ -204,7 +207,8 @@
     mDownTime = 0;
     mLastEventTime = std::numeric_limits<nsecs_t>::min();
 
-    mPointerVelocityControl.reset();
+    mOldPointerVelocityControl.reset();
+    mNewPointerVelocityControl.reset();
     mWheelXVelocityControl.reset();
     mWheelYVelocityControl.reset();
 
@@ -282,7 +286,11 @@
     mWheelYVelocityControl.move(when, nullptr, &vscroll);
     mWheelXVelocityControl.move(when, &hscroll, nullptr);
 
-    mPointerVelocityControl.move(when, &deltaX, &deltaY);
+    if (mEnableNewMousePointerBallistics) {
+        mNewPointerVelocityControl.move(when, &deltaX, &deltaY);
+    } else {
+        mOldPointerVelocityControl.move(when, &deltaX, &deltaY);
+    }
 
     float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
@@ -492,11 +500,22 @@
 void CursorInputMapper::configureOnChangePointerSpeed(const InputReaderConfiguration& config) {
     if (mParameters.mode == Parameters::Mode::POINTER_RELATIVE) {
         // Disable any acceleration or scaling for the pointer when Pointer Capture is enabled.
-        mPointerVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
+        if (mEnableNewMousePointerBallistics) {
+            mNewPointerVelocityControl.setAccelerationEnabled(false);
+        } else {
+            mOldPointerVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
+        }
         mWheelXVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
         mWheelYVelocityControl.setParameters(FLAT_VELOCITY_CONTROL_PARAMS);
     } else {
-        mPointerVelocityControl.setParameters(config.pointerVelocityControlParameters);
+        if (mEnableNewMousePointerBallistics) {
+            mNewPointerVelocityControl.setAccelerationEnabled(
+                    config.mousePointerAccelerationEnabled);
+            mNewPointerVelocityControl.setCurve(
+                    createAccelerationCurveForPointerSensitivity(config.mousePointerSpeed));
+        } else {
+            mOldPointerVelocityControl.setParameters(config.pointerVelocityControlParameters);
+        }
         mWheelXVelocityControl.setParameters(config.wheelVelocityControlParameters);
         mWheelYVelocityControl.setParameters(config.wheelVelocityControlParameters);
     }
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 308adaa..1ddf6f2 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -26,7 +26,6 @@
 
 namespace android {
 
-class VelocityControl;
 class PointerControllerInterface;
 
 class CursorButtonAccumulator;
@@ -111,9 +110,10 @@
 
     // Velocity controls for mouse pointer and wheel movements.
     // The controls for X and Y wheel movements are separate to keep them decoupled.
-    VelocityControl mPointerVelocityControl;
-    VelocityControl mWheelXVelocityControl;
-    VelocityControl mWheelYVelocityControl;
+    SimpleVelocityControl mOldPointerVelocityControl;
+    CurvedVelocityControl mNewPointerVelocityControl;
+    SimpleVelocityControl mWheelXVelocityControl;
+    SimpleVelocityControl mWheelYVelocityControl;
 
     // The display that events generated by this mapper should target. This can be set to
     // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e.
@@ -129,6 +129,7 @@
     nsecs_t mLastEventTime;
 
     const bool mEnablePointerChoreographer;
+    const bool mEnableNewMousePointerBallistics;
 
     explicit CursorInputMapper(InputDeviceContext& deviceContext,
                                const InputReaderConfiguration& readerConfig);
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index bd9371d..4b39e40 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -708,9 +708,9 @@
     } mPointerSimple;
 
     // The pointer and scroll velocity controls.
-    VelocityControl mPointerVelocityControl;
-    VelocityControl mWheelXVelocityControl;
-    VelocityControl mWheelYVelocityControl;
+    SimpleVelocityControl mPointerVelocityControl;
+    SimpleVelocityControl mWheelXVelocityControl;
+    SimpleVelocityControl mWheelYVelocityControl;
 
     std::optional<DisplayViewport> findViewport();
 
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 6f697db..bdc1640 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -29,6 +29,7 @@
 #include <android/input.h>
 #include <com_android_input_flags.h>
 #include <ftl/enum.h>
+#include <input/AccelerationCurve.h>
 #include <input/PrintTools.h>
 #include <linux/input-event-codes.h>
 #include <log/log_main.h>
@@ -55,27 +56,10 @@
         __android_log_is_loggable(ANDROID_LOG_DEBUG, "TouchpadInputMapperGestures",
                                   ANDROID_LOG_INFO);
 
-// Describes a segment of the acceleration curve.
-struct CurveSegment {
-    // The maximum pointer speed which this segment should apply. The last segment in a curve should
-    // always set this to infinity.
-    double maxPointerSpeedMmPerS;
-    double slope;
-    double intercept;
-};
-
-const std::vector<CurveSegment> segments = {
-        {32.002, 3.19, 0},
-        {52.83, 4.79, -51.254},
-        {119.124, 7.28, -182.737},
-        {std::numeric_limits<double>::infinity(), 15.04, -1107.556},
-};
-
-const std::vector<double> sensitivityFactors = {1,  2,  4,  6,  7,  8,  9, 10,
-                                                11, 12, 13, 14, 16, 18, 20};
-
 std::vector<double> createAccelerationCurveForSensitivity(int32_t sensitivity,
                                                           size_t propertySize) {
+    std::vector<AccelerationCurveSegment> segments =
+            createAccelerationCurveForPointerSensitivity(sensitivity);
     LOG_ALWAYS_FATAL_IF(propertySize < 4 * segments.size());
     std::vector<double> output(propertySize, 0);
 
@@ -85,31 +69,23 @@
     //
     // (a, b, and c are also called sqr_, mul_, and int_ in the Gestures library code.)
     //
-    // We are trying to implement the following function, where slope and intercept are the
-    // parameters specified in the `segments` array above:
-    //     gain(input_speed_mm) =
-    //             0.64 * (sensitivityFactor / 10) * (slope + intercept / input_speed_mm)
+    // createAccelerationCurveForPointerSensitivity gives us parameters for a function of the form:
+    //     gain(input_speed_mm) = baseGain + reciprocal / input_speed_mm
     // Where "gain" is a multiplier applied to the input speed to produce the output speed:
     //     output_speed(input_speed_mm) = input_speed_mm * gain(input_speed_mm)
     //
     // To put our function in the library's form, we substitute it into the function above:
-    //     output_speed(input_speed_mm) =
-    //             input_speed_mm * (0.64 * (sensitivityFactor / 10) *
-    //             (slope + 25.4 * intercept / input_speed_mm))
-    // then expand the brackets so that input_speed_mm cancels out for the intercept term:
-    //     gain(input_speed_mm) =
-    //             0.64 * (sensitivityFactor / 10) * slope * input_speed_mm +
-    //             0.64 * (sensitivityFactor / 10) * intercept
+    //     output_speed(input_speed_mm) = input_speed_mm * (baseGain + reciprocal / input_speed_mm)
+    // then expand the brackets so that input_speed_mm cancels out for the reciprocal term:
+    //     gain(input_speed_mm) = baseGain * input_speed_mm + reciprocal
     //
     // This gives us the following parameters for the Gestures library function form:
     //     a = 0
-    //     b = 0.64 * (sensitivityFactor / 10) * slope
-    //     c = 0.64 * (sensitivityFactor / 10) * intercept
-
-    double commonFactor = 0.64 * sensitivityFactors[sensitivity + 7] / 10;
+    //     b = baseGain
+    //     c = reciprocal
 
     size_t i = 0;
-    for (CurveSegment seg : segments) {
+    for (AccelerationCurveSegment seg : segments) {
         // The library's curve format consists of four doubles per segment:
         // * maximum pointer speed for the segment (mm/s)
         // * multiplier for the x² term (a.k.a. "a" or "sqr")
@@ -118,8 +94,8 @@
         // (see struct CurveSegment in the library's AccelFilterInterpreter)
         output[i + 0] = seg.maxPointerSpeedMmPerS;
         output[i + 1] = 0;
-        output[i + 2] = commonFactor * seg.slope;
-        output[i + 3] = commonFactor * seg.intercept;
+        output[i + 2] = seg.baseGain;
+        output[i + 3] = seg.reciprocal;
         i += 4;
     }
 
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index 66c3256..e104543 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -193,6 +193,7 @@
 protected:
     void SetUp() override {
         input_flags::enable_pointer_choreographer(false);
+        input_flags::enable_new_mouse_pointer_ballistics(false);
         CursorInputMapperUnitTestBase::SetUp();
     }
 };
@@ -954,6 +955,7 @@
 protected:
     void SetUp() override {
         input_flags::enable_pointer_choreographer(true);
+        input_flags::enable_new_mouse_pointer_ballistics(false);
         CursorInputMapperUnitTestBase::SetUp();
     }
 };
@@ -1280,6 +1282,48 @@
                               WithCoords(0.0f, 0.0f)))));
 }
 
+// TODO(b/320433834): De-duplicate the test cases once the flag is removed.
+class CursorInputMapperUnitTestWithNewBallistics : public CursorInputMapperUnitTestBase {
+protected:
+    void SetUp() override {
+        input_flags::enable_pointer_choreographer(true);
+        input_flags::enable_new_mouse_pointer_ballistics(true);
+        CursorInputMapperUnitTestBase::SetUp();
+    }
+};
+
+TEST_F(CursorInputMapperUnitTestWithNewBallistics, PointerCaptureDisablesVelocityProcessing) {
+    mPropertyMap.addProperty("cursor.mode", "pointer");
+    createMapper();
+
+    NotifyMotionArgs motionArgs;
+    std::list<NotifyArgs> args;
+
+    // Move and verify scale is applied.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    motionArgs = std::get<NotifyMotionArgs>(args.front());
+    const float relX = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const float relY = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    ASSERT_GT(relX, 10);
+    ASSERT_GT(relY, 20);
+    args.clear();
+
+    // Enable Pointer Capture
+    setPointerCapture(true);
+
+    // Move and verify scale is not applied.
+    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
+    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    motionArgs = std::get<NotifyMotionArgs>(args.front());
+    const float relX2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+    const float relY2 = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+    ASSERT_EQ(10, relX2);
+    ASSERT_EQ(20, relY2);
+}
+
 namespace {
 
 // Minimum timestamp separation between subsequent input events from a Bluetooth device.