| /* |
| * Copyright (C) 2012 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. |
| */ |
| |
| #define LOG_TAG "VelocityControl" |
| |
| // Log debug messages about acceleration. |
| 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> |
| |
| namespace android { |
| |
| // --- VelocityControl --- |
| |
| const nsecs_t VelocityControl::STOP_TIME; |
| |
| VelocityControl::VelocityControl() { |
| reset(); |
| } |
| |
| void VelocityControl::reset() { |
| mLastMovementTime = LLONG_MIN; |
| mRawPositionX = 0; |
| mRawPositionY = 0; |
| mVelocityTracker.clear(); |
| } |
| |
| void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) { |
| 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); |
| } |
| |
| 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 |