CursorInputMapper: share acceleration curves with touchpad
The new touchpad mapper implemented in Android 14 replaced our simple
cursor movement acceleration curves (where the acceleration factor
increased linearly with speed between minimum and maximum values) with
more sophisticated multi-segment curves. However, cursor movement using
mice remained on the old curves. For consistency and to improve pointing
accuracy, use the same curves for mice, too.
This is also a good opportunity to improve the documentation comments
and naming now that I've wrapped my head around the maths a bit better.
Bug: 315313622
Test: atest inputflinger_tests
Test: check pointer movement with a mouse, including changing the
pointer speed setting and checking that the movement speed changes
Change-Id: Ifcf43f4de6017f06b66f37d5e03a13cc257d92d5
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 dd8dc8d..c5218f6 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 138898f..fa3ea2f 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.