/* * 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 #include #include #include #include #include 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 vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0); std::optional 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& 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 vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0); std::optional 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