diff options
-rw-r--r-- | include/input/AccelerationCurve.h | 49 | ||||
-rw-r--r-- | include/input/VelocityControl.h | 55 | ||||
-rw-r--r-- | libs/input/AccelerationCurve.cpp | 63 | ||||
-rw-r--r-- | libs/input/Android.bp | 1 | ||||
-rw-r--r-- | libs/input/VelocityControl.cpp | 210 | ||||
-rw-r--r-- | libs/input/input_flags.aconfig | 7 | ||||
-rw-r--r-- | libs/input/tests/Android.bp | 1 | ||||
-rw-r--r-- | libs/input/tests/VelocityControl_test.cpp | 129 | ||||
-rw-r--r-- | services/inputflinger/include/InputReaderBase.h | 15 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/CursorInputMapper.cpp | 31 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/CursorInputMapper.h | 9 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/TouchInputMapper.h | 6 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/TouchpadInputMapper.cpp | 50 | ||||
-rw-r--r-- | services/inputflinger/tests/CursorInputMapper_test.cpp | 44 |
14 files changed, 547 insertions, 123 deletions
diff --git a/include/input/AccelerationCurve.h b/include/input/AccelerationCurve.h new file mode 100644 index 0000000000..0cf648a2f7 --- /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 b78f63e1ae..7c58c87f8b 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 @@ struct VelocityControlParameters { 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 @@ public: * 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 0000000000..0a92a71596 --- /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 dd8dc8dc9d..c5218f685b 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -175,6 +175,7 @@ cc_library { ], 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 c835a081a5..edd31e91d3 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 @@ static constexpr bool DEBUG_ACCELERATION = false; #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 @@ VelocityControl::VelocityControl() { 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::reset() { } 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); - - 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); - } - - 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); - } - - } else { - if (DEBUG_ACCELERATION) { - ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): unknown velocity", - mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold, - mParameters.acceleration); - } - } + 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; +} - if (deltaX) { - *deltaX *= scale; +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); } - if (deltaY) { - *deltaY *= scale; + + 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); + + } 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 (deltaX != nullptr) { + *deltaX *= scale; + } + if (deltaY != nullptr) { + *deltaY *= scale; + } +} + +// --- CurvedVelocityControl --- + +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 11f69941bd..1baeb26879 100644 --- a/libs/input/input_flags.aconfig +++ b/libs/input/input_flags.aconfig @@ -97,3 +97,10 @@ flag { 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 138898fc1f..fa3ea2f57b 100644 --- a/libs/input/tests/Android.bp +++ b/libs/input/tests/Android.bp @@ -25,6 +25,7 @@ cc_test { "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 0000000000..63d64c6048 --- /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 efc8b260f3..40359a42c9 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -126,7 +126,20 @@ struct InputReaderConfiguration { // 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 @@ struct InputReaderConfiguration { 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 58e35a6aba..4cebd646db 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 @@ CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext, 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 @@ std::list<NotifyArgs> CursorInputMapper::reset(nsecs_t when) { mDownTime = 0; mLastEventTime = std::numeric_limits<nsecs_t>::min(); - mPointerVelocityControl.reset(); + mOldPointerVelocityControl.reset(); + mNewPointerVelocityControl.reset(); mWheelXVelocityControl.reset(); mWheelYVelocityControl.reset(); @@ -282,7 +286,11 @@ std::list<NotifyArgs> CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) { 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::configureOnPointerCapture(const InputReaderConfiguration 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 308adaa463..1ddf6f2b5b 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 @@ private: // 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 @@ private: 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 bd9371d263..4b39e4099c 100644 --- a/services/inputflinger/reader/mapper/TouchInputMapper.h +++ b/services/inputflinger/reader/mapper/TouchInputMapper.h @@ -708,9 +708,9 @@ private: } 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 6f697dbc31..bdc164029c 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 @@ const bool DEBUG_TOUCHPAD_GESTURES = __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 @@ std::vector<double> createAccelerationCurveForSensitivity(int32_t sensitivity, // // (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 @@ std::vector<double> createAccelerationCurveForSensitivity(int32_t sensitivity, // (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 66c3256815..e104543523 100644 --- a/services/inputflinger/tests/CursorInputMapper_test.cpp +++ b/services/inputflinger/tests/CursorInputMapper_test.cpp @@ -193,6 +193,7 @@ class CursorInputMapperUnitTest : public CursorInputMapperUnitTestBase { protected: void SetUp() override { input_flags::enable_pointer_choreographer(false); + input_flags::enable_new_mouse_pointer_ballistics(false); CursorInputMapperUnitTestBase::SetUp(); } }; @@ -954,6 +955,7 @@ class CursorInputMapperUnitTestWithChoreographer : public CursorInputMapperUnitT protected: void SetUp() override { input_flags::enable_pointer_choreographer(true); + input_flags::enable_new_mouse_pointer_ballistics(false); CursorInputMapperUnitTestBase::SetUp(); } }; @@ -1280,6 +1282,48 @@ TEST_F(CursorInputMapperUnitTestWithChoreographer, ConfigureDisplayIdNoAssociate 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. |