blob: edd31e91d36960b1a75285fc4051227ed757419b [file] [log] [blame]
/*
* 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