summaryrefslogtreecommitdiff
path: root/libs/input/VelocityControl.cpp
blob: edd31e91d36960b1a75285fc4051227ed757419b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
/*
 * 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