blob: 150b68b9d2ba76f16c6ef74a25235b1882afa8d0 [file] [log] [blame]
Jeff Brown5912f952013-07-01 19:10:31 -07001/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "VelocityTracker"
Jeff Brown5912f952013-07-01 19:10:31 -070018
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -070019#include <array>
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -070020#include <inttypes.h>
Jeff Brown5912f952013-07-01 19:10:31 -070021#include <limits.h>
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -070022#include <math.h>
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -070023#include <optional>
Jeff Brown5912f952013-07-01 19:10:31 -070024
Siarhei Vishniakou657a1732023-01-12 11:58:52 -080025#include <input/PrintTools.h>
Jeff Brown5912f952013-07-01 19:10:31 -070026#include <input/VelocityTracker.h>
27#include <utils/BitSet.h>
Jeff Brown5912f952013-07-01 19:10:31 -070028#include <utils/Timers.h>
29
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +000030using std::literals::chrono_literals::operator""ms;
31
Jeff Brown5912f952013-07-01 19:10:31 -070032namespace android {
33
Siarhei Vishniakou276467b2022-03-17 09:43:28 -070034/**
35 * Log debug messages about velocity tracking.
36 * Enable this via "adb shell setprop log.tag.VelocityTrackerVelocity DEBUG" (requires restart)
37 */
38const bool DEBUG_VELOCITY =
39 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Velocity", ANDROID_LOG_INFO);
40
41/**
42 * Log debug messages about the progress of the algorithm itself.
43 * Enable this via "adb shell setprop log.tag.VelocityTrackerStrategy DEBUG" (requires restart)
44 */
45const bool DEBUG_STRATEGY =
46 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Strategy", ANDROID_LOG_INFO);
47
48/**
49 * Log debug messages about the 'impulse' strategy.
50 * Enable this via "adb shell setprop log.tag.VelocityTrackerImpulse DEBUG" (requires restart)
51 */
52const bool DEBUG_IMPULSE =
53 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Impulse", ANDROID_LOG_INFO);
54
Jeff Brown5912f952013-07-01 19:10:31 -070055// Nanoseconds per milliseconds.
56static const nsecs_t NANOS_PER_MS = 1000000;
57
Yeabkal Wubshit4a678b22023-02-23 17:03:40 -080058// Seconds per nanosecond.
59static const float SECONDS_PER_NANO = 1E-9;
60
Yeabkal Wubshit67b3ab02022-09-16 00:18:17 -070061// All axes supported for velocity tracking, mapped to their default strategies.
62// Although other strategies are available for testing and comparison purposes,
63// the default strategy is the one that applications will actually use. Be very careful
64// when adjusting the default strategy because it can dramatically affect
65// (often in a bad way) the user experience.
66static const std::map<int32_t, VelocityTracker::Strategy> DEFAULT_STRATEGY_BY_AXIS =
67 {{AMOTION_EVENT_AXIS_X, VelocityTracker::Strategy::LSQ2},
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -070068 {AMOTION_EVENT_AXIS_Y, VelocityTracker::Strategy::LSQ2},
69 {AMOTION_EVENT_AXIS_SCROLL, VelocityTracker::Strategy::IMPULSE}};
Yeabkal Wubshit67b3ab02022-09-16 00:18:17 -070070
71// Axes specifying location on a 2D plane (i.e. X and Y).
72static const std::set<int32_t> PLANAR_AXES = {AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y};
73
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -070074// Axes whose motion values are differential values (i.e. deltas).
75static const std::set<int32_t> DIFFERENTIAL_AXES = {AMOTION_EVENT_AXIS_SCROLL};
76
Jeff Brown5912f952013-07-01 19:10:31 -070077// Threshold for determining that a pointer has stopped moving.
78// Some input devices do not send ACTION_MOVE events in the case where a pointer has
79// stopped. We need to detect this case so that we can accurately predict the
80// velocity after the pointer starts moving again.
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +000081static const std::chrono::duration ASSUME_POINTER_STOPPED_TIME = 40ms;
Jeff Brown5912f952013-07-01 19:10:31 -070082
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +000083static std::string toString(std::chrono::nanoseconds t) {
84 std::stringstream stream;
85 stream.precision(1);
86 stream << std::fixed << std::chrono::duration<float, std::milli>(t).count() << " ms";
87 return stream.str();
88}
Jeff Brown5912f952013-07-01 19:10:31 -070089
90static float vectorDot(const float* a, const float* b, uint32_t m) {
91 float r = 0;
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -070092 for (size_t i = 0; i < m; i++) {
Jeff Brown5912f952013-07-01 19:10:31 -070093 r += *(a++) * *(b++);
94 }
95 return r;
96}
97
98static float vectorNorm(const float* a, uint32_t m) {
99 float r = 0;
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -0700100 for (size_t i = 0; i < m; i++) {
Jeff Brown5912f952013-07-01 19:10:31 -0700101 float t = *(a++);
102 r += t * t;
103 }
104 return sqrtf(r);
105}
106
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700107static std::string vectorToString(const float* a, uint32_t m) {
108 std::string str;
109 str += "[";
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -0700110 for (size_t i = 0; i < m; i++) {
111 if (i) {
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700112 str += ",";
Jeff Brown5912f952013-07-01 19:10:31 -0700113 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700114 str += android::base::StringPrintf(" %f", *(a++));
Jeff Brown5912f952013-07-01 19:10:31 -0700115 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700116 str += " ]";
Jeff Brown5912f952013-07-01 19:10:31 -0700117 return str;
118}
119
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -0700120static std::string vectorToString(const std::vector<float>& v) {
121 return vectorToString(v.data(), v.size());
122}
123
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700124static std::string matrixToString(const float* a, uint32_t m, uint32_t n, bool rowMajor) {
125 std::string str;
126 str = "[";
Jeff Brown5912f952013-07-01 19:10:31 -0700127 for (size_t i = 0; i < m; i++) {
128 if (i) {
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700129 str += ",";
Jeff Brown5912f952013-07-01 19:10:31 -0700130 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700131 str += " [";
Jeff Brown5912f952013-07-01 19:10:31 -0700132 for (size_t j = 0; j < n; j++) {
133 if (j) {
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700134 str += ",";
Jeff Brown5912f952013-07-01 19:10:31 -0700135 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700136 str += android::base::StringPrintf(" %f", a[rowMajor ? i * n + j : j * m + i]);
Jeff Brown5912f952013-07-01 19:10:31 -0700137 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700138 str += " ]";
Jeff Brown5912f952013-07-01 19:10:31 -0700139 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700140 str += " ]";
Jeff Brown5912f952013-07-01 19:10:31 -0700141 return str;
142}
Jeff Brown5912f952013-07-01 19:10:31 -0700143
144
145// --- VelocityTracker ---
146
Chris Yef8591482020-04-17 11:49:17 -0700147VelocityTracker::VelocityTracker(const Strategy strategy)
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800148 : mLastEventTime(0), mCurrentPointerIdBits(0), mOverrideStrategy(strategy) {}
Jeff Brown5912f952013-07-01 19:10:31 -0700149
150VelocityTracker::~VelocityTracker() {
Jeff Brown5912f952013-07-01 19:10:31 -0700151}
152
Yeabkal Wubshiteca273c2022-10-05 19:06:40 -0700153bool VelocityTracker::isAxisSupported(int32_t axis) {
154 return DEFAULT_STRATEGY_BY_AXIS.find(axis) != DEFAULT_STRATEGY_BY_AXIS.end();
155}
156
Yeabkal Wubshit47ff7082022-09-10 23:09:15 -0700157void VelocityTracker::configureStrategy(int32_t axis) {
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -0700158 const bool isDifferentialAxis = DIFFERENTIAL_AXES.find(axis) != DIFFERENTIAL_AXES.end();
159
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000160 std::unique_ptr<VelocityTrackerStrategy> createdStrategy;
Yeabkal Wubshit47ff7082022-09-10 23:09:15 -0700161 if (mOverrideStrategy != VelocityTracker::Strategy::DEFAULT) {
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -0700162 createdStrategy = createStrategy(mOverrideStrategy, isDifferentialAxis /* deltaValues */);
Chris Yef8591482020-04-17 11:49:17 -0700163 } else {
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -0700164 createdStrategy = createStrategy(DEFAULT_STRATEGY_BY_AXIS.at(axis),
165 isDifferentialAxis /* deltaValues */);
Chris Yef8591482020-04-17 11:49:17 -0700166 }
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000167
Yeabkal Wubshit47ff7082022-09-10 23:09:15 -0700168 LOG_ALWAYS_FATAL_IF(createdStrategy == nullptr,
169 "Could not create velocity tracker strategy for axis '%" PRId32 "'!", axis);
170 mConfiguredStrategies[axis] = std::move(createdStrategy);
Jeff Brown5912f952013-07-01 19:10:31 -0700171}
172
Chris Yef8591482020-04-17 11:49:17 -0700173std::unique_ptr<VelocityTrackerStrategy> VelocityTracker::createStrategy(
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -0700174 VelocityTracker::Strategy strategy, bool deltaValues) {
Chris Yef8591482020-04-17 11:49:17 -0700175 switch (strategy) {
176 case VelocityTracker::Strategy::IMPULSE:
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000177 ALOGI_IF(DEBUG_STRATEGY, "Initializing impulse strategy");
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -0700178 return std::make_unique<ImpulseVelocityTrackerStrategy>(deltaValues);
Chris Yef8591482020-04-17 11:49:17 -0700179
180 case VelocityTracker::Strategy::LSQ1:
181 return std::make_unique<LeastSquaresVelocityTrackerStrategy>(1);
182
183 case VelocityTracker::Strategy::LSQ2:
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000184 ALOGI_IF(DEBUG_STRATEGY && !DEBUG_IMPULSE, "Initializing lsq2 strategy");
Chris Yef8591482020-04-17 11:49:17 -0700185 return std::make_unique<LeastSquaresVelocityTrackerStrategy>(2);
186
187 case VelocityTracker::Strategy::LSQ3:
188 return std::make_unique<LeastSquaresVelocityTrackerStrategy>(3);
189
190 case VelocityTracker::Strategy::WLSQ2_DELTA:
191 return std::make_unique<
192 LeastSquaresVelocityTrackerStrategy>(2,
193 LeastSquaresVelocityTrackerStrategy::
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800194 Weighting::DELTA);
Chris Yef8591482020-04-17 11:49:17 -0700195 case VelocityTracker::Strategy::WLSQ2_CENTRAL:
196 return std::make_unique<
197 LeastSquaresVelocityTrackerStrategy>(2,
198 LeastSquaresVelocityTrackerStrategy::
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800199 Weighting::CENTRAL);
Chris Yef8591482020-04-17 11:49:17 -0700200 case VelocityTracker::Strategy::WLSQ2_RECENT:
201 return std::make_unique<
202 LeastSquaresVelocityTrackerStrategy>(2,
203 LeastSquaresVelocityTrackerStrategy::
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800204 Weighting::RECENT);
Chris Yef8591482020-04-17 11:49:17 -0700205
206 case VelocityTracker::Strategy::INT1:
207 return std::make_unique<IntegratingVelocityTrackerStrategy>(1);
208
209 case VelocityTracker::Strategy::INT2:
210 return std::make_unique<IntegratingVelocityTrackerStrategy>(2);
211
212 case VelocityTracker::Strategy::LEGACY:
213 return std::make_unique<LegacyVelocityTrackerStrategy>();
214
215 default:
216 break;
Jeff Brown5912f952013-07-01 19:10:31 -0700217 }
Yi Kong5bed83b2018-07-17 12:53:47 -0700218 return nullptr;
Jeff Brown5912f952013-07-01 19:10:31 -0700219}
220
221void VelocityTracker::clear() {
222 mCurrentPointerIdBits.clear();
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800223 mActivePointerId = std::nullopt;
Yeabkal Wubshit47ff7082022-09-10 23:09:15 -0700224 mConfiguredStrategies.clear();
Jeff Brown5912f952013-07-01 19:10:31 -0700225}
226
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800227void VelocityTracker::clearPointer(int32_t pointerId) {
228 mCurrentPointerIdBits.clearBit(pointerId);
Jeff Brown5912f952013-07-01 19:10:31 -0700229
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800230 if (mActivePointerId && *mActivePointerId == pointerId) {
231 // The active pointer id is being removed. Mark it invalid and try to find a new one
232 // from the remaining pointers.
233 mActivePointerId = std::nullopt;
234 if (!mCurrentPointerIdBits.isEmpty()) {
235 mActivePointerId = mCurrentPointerIdBits.firstMarkedBit();
236 }
Jeff Brown5912f952013-07-01 19:10:31 -0700237 }
238
Yeabkal Wubshit47ff7082022-09-10 23:09:15 -0700239 for (const auto& [_, strategy] : mConfiguredStrategies) {
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800240 strategy->clearPointer(pointerId);
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000241 }
Jeff Brown5912f952013-07-01 19:10:31 -0700242}
243
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800244void VelocityTracker::addMovement(nsecs_t eventTime, int32_t pointerId, int32_t axis,
245 float position) {
246 if (mCurrentPointerIdBits.hasBit(pointerId) &&
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000247 std::chrono::nanoseconds(eventTime - mLastEventTime) > ASSUME_POINTER_STOPPED_TIME) {
248 ALOGD_IF(DEBUG_VELOCITY, "VelocityTracker: stopped for %s, clearing state.",
249 toString(std::chrono::nanoseconds(eventTime - mLastEventTime)).c_str());
250
Jeff Brown5912f952013-07-01 19:10:31 -0700251 // We have not received any movements for too long. Assume that all pointers
252 // have stopped.
Yeabkal Wubshit47ff7082022-09-10 23:09:15 -0700253 mConfiguredStrategies.clear();
Jeff Brown5912f952013-07-01 19:10:31 -0700254 }
255 mLastEventTime = eventTime;
256
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800257 mCurrentPointerIdBits.markBit(pointerId);
258 if (!mActivePointerId) {
259 // Let this be the new active pointer if no active pointer is currently set
260 mActivePointerId = pointerId;
Jeff Brown5912f952013-07-01 19:10:31 -0700261 }
262
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800263 if (mConfiguredStrategies.find(axis) == mConfiguredStrategies.end()) {
264 configureStrategy(axis);
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000265 }
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800266 mConfiguredStrategies[axis]->addMovement(eventTime, pointerId, position);
Jeff Brown5912f952013-07-01 19:10:31 -0700267
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -0700268 if (DEBUG_VELOCITY) {
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800269 ALOGD("VelocityTracker: addMovement eventTime=%" PRId64 ", pointerId=%" PRId32
270 ", activePointerId=%s",
271 eventTime, pointerId, toString(mActivePointerId).c_str());
272
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800273 ALOGD(" %d: axis=%d, position=%0.3f, velocity=%s", pointerId, axis, position,
274 toString(getVelocity(axis, pointerId)).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700275 }
Jeff Brown5912f952013-07-01 19:10:31 -0700276}
277
278void VelocityTracker::addMovement(const MotionEvent* event) {
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000279 // Stores data about which axes to process based on the incoming motion event.
280 std::set<int32_t> axesToProcess;
Jeff Brown5912f952013-07-01 19:10:31 -0700281 int32_t actionMasked = event->getActionMasked();
282
283 switch (actionMasked) {
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800284 case AMOTION_EVENT_ACTION_DOWN:
285 case AMOTION_EVENT_ACTION_HOVER_ENTER:
286 // Clear all pointers on down before adding the new movement.
287 clear();
288 axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
289 break;
290 case AMOTION_EVENT_ACTION_POINTER_DOWN: {
291 // Start a new movement trace for a pointer that just went down.
292 // We do this on down instead of on up because the client may want to query the
293 // final velocity for a pointer that just went up.
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800294 clearPointer(event->getPointerId(event->getActionIndex()));
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800295 axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
296 break;
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000297 }
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800298 case AMOTION_EVENT_ACTION_MOVE:
299 case AMOTION_EVENT_ACTION_HOVER_MOVE:
300 axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
301 break;
302 case AMOTION_EVENT_ACTION_POINTER_UP:
303 case AMOTION_EVENT_ACTION_UP: {
304 std::chrono::nanoseconds delaySinceLastEvent(event->getEventTime() - mLastEventTime);
305 if (delaySinceLastEvent > ASSUME_POINTER_STOPPED_TIME) {
306 ALOGD_IF(DEBUG_VELOCITY,
307 "VelocityTracker: stopped for %s, clearing state upon pointer liftoff.",
308 toString(delaySinceLastEvent).c_str());
309 // We have not received any movements for too long. Assume that all pointers
310 // have stopped.
311 for (int32_t axis : PLANAR_AXES) {
312 mConfiguredStrategies.erase(axis);
313 }
314 }
315 // These actions because they do not convey any new information about
316 // pointer movement. We also want to preserve the last known velocity of the pointers.
317 // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position
318 // of the pointers that went up. ACTION_POINTER_UP does include the new position of
319 // pointers that remained down but we will also receive an ACTION_MOVE with this
320 // information if any of them actually moved. Since we don't know how many pointers
321 // will be going up at once it makes sense to just wait for the following ACTION_MOVE
322 // before adding the movement.
323 return;
324 }
325 case AMOTION_EVENT_ACTION_SCROLL:
326 axesToProcess.insert(AMOTION_EVENT_AXIS_SCROLL);
327 break;
328 default:
329 // Ignore all other actions.
330 return;
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000331 }
Jeff Brown5912f952013-07-01 19:10:31 -0700332
Siarhei Vishniakouc36d21e2023-01-17 09:59:38 -0800333 const size_t historySize = event->getHistorySize();
Siarhei Vishniakou69e4d0f2020-09-14 19:53:29 -0500334 for (size_t h = 0; h <= historySize; h++) {
Siarhei Vishniakouc36d21e2023-01-17 09:59:38 -0800335 const nsecs_t eventTime = event->getHistoricalEventTime(h);
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800336 for (size_t i = 0; i < event->getPointerCount(); i++) {
Siarhei Vishniakouc36d21e2023-01-17 09:59:38 -0800337 if (event->isResampled(i, h)) {
338 continue; // skip resampled samples
339 }
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800340 const int32_t pointerId = event->getPointerId(i);
341 for (int32_t axis : axesToProcess) {
342 const float position = event->getHistoricalAxisValue(axis, i, h);
343 addMovement(eventTime, pointerId, axis, position);
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000344 }
Jeff Brown5912f952013-07-01 19:10:31 -0700345 }
Jeff Brown5912f952013-07-01 19:10:31 -0700346 }
Jeff Brown5912f952013-07-01 19:10:31 -0700347}
348
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800349std::optional<float> VelocityTracker::getVelocity(int32_t axis, int32_t pointerId) const {
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800350 const auto& it = mConfiguredStrategies.find(axis);
351 if (it != mConfiguredStrategies.end()) {
352 return it->second->getVelocity(pointerId);
Jeff Brown5912f952013-07-01 19:10:31 -0700353 }
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000354 return {};
Jeff Brown5912f952013-07-01 19:10:31 -0700355}
356
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000357VelocityTracker::ComputedVelocity VelocityTracker::getComputedVelocity(int32_t units,
358 float maxVelocity) {
359 ComputedVelocity computedVelocity;
Yeabkal Wubshit47ff7082022-09-10 23:09:15 -0700360 for (const auto& [axis, _] : mConfiguredStrategies) {
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000361 BitSet32 copyIdBits = BitSet32(mCurrentPointerIdBits);
362 while (!copyIdBits.isEmpty()) {
363 uint32_t id = copyIdBits.clearFirstMarkedBit();
364 std::optional<float> velocity = getVelocity(axis, id);
365 if (velocity) {
366 float adjustedVelocity =
367 std::clamp(*velocity * units / 1000, -maxVelocity, maxVelocity);
368 computedVelocity.addVelocity(axis, id, adjustedVelocity);
369 }
370 }
371 }
372 return computedVelocity;
Jeff Brown5912f952013-07-01 19:10:31 -0700373}
374
Yeabkal Wubshit4a678b22023-02-23 17:03:40 -0800375AccumulatingVelocityTrackerStrategy::AccumulatingVelocityTrackerStrategy(
376 nsecs_t horizonNanos, bool maintainHorizonDuringAdd)
377 : mHorizonNanos(horizonNanos), mMaintainHorizonDuringAdd(maintainHorizonDuringAdd) {}
378
Yeabkal Wubshita0e573c2023-03-02 21:08:14 -0800379void AccumulatingVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800380 mMovements.erase(pointerId);
Jeff Brown5912f952013-07-01 19:10:31 -0700381}
382
Yeabkal Wubshita0e573c2023-03-02 21:08:14 -0800383void AccumulatingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800384 float position) {
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800385 auto [movementIt, _] = mMovements.insert({pointerId, RingBuffer<Movement>(HISTORY_SIZE)});
386 RingBuffer<Movement>& movements = movementIt->second;
387 const size_t size = movements.size();
388
389 if (size != 0 && movements[size - 1].eventTime == eventTime) {
Siarhei Vishniakou346ac6a2019-04-10 09:58:05 -0700390 // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
391 // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
392 // the new pointer. If the eventtimes for both events are identical, just update the data
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800393 // for this time (i.e. pop out the last element, and insert the updated movement).
Siarhei Vishniakou346ac6a2019-04-10 09:58:05 -0700394 // We only compare against the last value, as it is likely that addMovement is called
395 // in chronological order as events occur.
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800396 movements.popBack();
Jeff Brown5912f952013-07-01 19:10:31 -0700397 }
398
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800399 movements.pushBack({eventTime, position});
Yeabkal Wubshit4a678b22023-02-23 17:03:40 -0800400
401 // Clear movements that do not fall within `mHorizonNanos` of the latest movement.
402 // Note that, if in the future we decide to use more movements (i.e. increase HISTORY_SIZE),
403 // we can consider making this step binary-search based, which will give us some improvement.
404 if (mMaintainHorizonDuringAdd) {
405 while (eventTime - movements[0].eventTime > mHorizonNanos) {
406 movements.popFront();
407 }
408 }
Jeff Brown5912f952013-07-01 19:10:31 -0700409}
410
Yeabkal Wubshita0e573c2023-03-02 21:08:14 -0800411// --- LeastSquaresVelocityTrackerStrategy ---
412
413LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(uint32_t degree,
414 Weighting weighting)
Yeabkal Wubshit4a678b22023-02-23 17:03:40 -0800415 : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/,
416 false /*maintainHorizonDuringAdd*/),
417 mDegree(degree),
418 mWeighting(weighting) {}
Yeabkal Wubshita0e573c2023-03-02 21:08:14 -0800419
420LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {}
421
Jeff Brown5912f952013-07-01 19:10:31 -0700422/**
423 * Solves a linear least squares problem to obtain a N degree polynomial that fits
424 * the specified input data as nearly as possible.
425 *
426 * Returns true if a solution is found, false otherwise.
427 *
428 * The input consists of two vectors of data points X and Y with indices 0..m-1
429 * along with a weight vector W of the same size.
430 *
431 * The output is a vector B with indices 0..n that describes a polynomial
432 * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1] X[i]
433 * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is minimized.
434 *
435 * Accordingly, the weight vector W should be initialized by the caller with the
436 * reciprocal square root of the variance of the error in each input data point.
437 * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 / stddev(Y[i]).
438 * The weights express the relative importance of each data point. If the weights are
439 * all 1, then the data points are considered to be of equal importance when fitting
440 * the polynomial. It is a good idea to choose weights that diminish the importance
441 * of data points that may have higher than usual error margins.
442 *
443 * Errors among data points are assumed to be independent. W is represented here
444 * as a vector although in the literature it is typically taken to be a diagonal matrix.
445 *
446 * That is to say, the function that generated the input data can be approximated
447 * by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n.
448 *
449 * The coefficient of determination (R^2) is also returned to describe the goodness
450 * of fit of the model for the given data. It is a value between 0 and 1, where 1
451 * indicates perfect correspondence.
452 *
453 * This function first expands the X vector to a m by n matrix A such that
454 * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then
455 * multiplies it by w[i]./
456 *
457 * Then it calculates the QR decomposition of A yielding an m by m orthonormal matrix Q
458 * and an m by n upper triangular matrix R. Because R is upper triangular (lower
459 * part is all zeroes), we can simplify the decomposition into an m by n matrix
460 * Q1 and a n by n matrix R1 such that A = Q1 R1.
461 *
462 * Finally we solve the system of linear equations given by R1 B = (Qtranspose W Y)
463 * to find B.
464 *
465 * For efficiency, we lay out A and Q column-wise in memory because we frequently
466 * operate on the column vectors. Conversely, we lay out R row-wise.
467 *
468 * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
469 * http://en.wikipedia.org/wiki/Gram-Schmidt
470 */
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800471static std::optional<float> solveLeastSquares(const std::vector<float>& x,
472 const std::vector<float>& y,
473 const std::vector<float>& w, uint32_t n) {
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500474 const size_t m = x.size();
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000475
476 ALOGD_IF(DEBUG_STRATEGY, "solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n),
477 vectorToString(x).c_str(), vectorToString(y).c_str(), vectorToString(w).c_str());
478
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500479 LOG_ALWAYS_FATAL_IF(m != y.size() || m != w.size(), "Mismatched vector sizes");
Jeff Brown5912f952013-07-01 19:10:31 -0700480
481 // Expand the X vector to a matrix A, pre-multiplied by the weights.
482 float a[n][m]; // column-major order
483 for (uint32_t h = 0; h < m; h++) {
484 a[0][h] = w[h];
485 for (uint32_t i = 1; i < n; i++) {
486 a[i][h] = a[i - 1][h] * x[h];
487 }
488 }
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000489
490 ALOGD_IF(DEBUG_STRATEGY, " - a=%s",
491 matrixToString(&a[0][0], m, n, false /*rowMajor*/).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700492
493 // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
494 float q[n][m]; // orthonormal basis, column-major order
495 float r[n][n]; // upper triangular matrix, row-major order
496 for (uint32_t j = 0; j < n; j++) {
497 for (uint32_t h = 0; h < m; h++) {
498 q[j][h] = a[j][h];
499 }
500 for (uint32_t i = 0; i < j; i++) {
501 float dot = vectorDot(&q[j][0], &q[i][0], m);
502 for (uint32_t h = 0; h < m; h++) {
503 q[j][h] -= dot * q[i][h];
504 }
505 }
506
507 float norm = vectorNorm(&q[j][0], m);
508 if (norm < 0.000001f) {
509 // vectors are linearly dependent or zero so no solution
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000510 ALOGD_IF(DEBUG_STRATEGY, " - no solution, norm=%f", norm);
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800511 return {};
Jeff Brown5912f952013-07-01 19:10:31 -0700512 }
513
514 float invNorm = 1.0f / norm;
515 for (uint32_t h = 0; h < m; h++) {
516 q[j][h] *= invNorm;
517 }
518 for (uint32_t i = 0; i < n; i++) {
519 r[j][i] = i < j ? 0 : vectorDot(&q[j][0], &a[i][0], m);
520 }
521 }
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -0700522 if (DEBUG_STRATEGY) {
523 ALOGD(" - q=%s", matrixToString(&q[0][0], m, n, false /*rowMajor*/).c_str());
524 ALOGD(" - r=%s", matrixToString(&r[0][0], n, n, true /*rowMajor*/).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700525
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -0700526 // calculate QR, if we factored A correctly then QR should equal A
527 float qr[n][m];
528 for (uint32_t h = 0; h < m; h++) {
529 for (uint32_t i = 0; i < n; i++) {
530 qr[i][h] = 0;
531 for (uint32_t j = 0; j < n; j++) {
532 qr[i][h] += q[j][h] * r[j][i];
533 }
Jeff Brown5912f952013-07-01 19:10:31 -0700534 }
535 }
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -0700536 ALOGD(" - qr=%s", matrixToString(&qr[0][0], m, n, false /*rowMajor*/).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700537 }
Jeff Brown5912f952013-07-01 19:10:31 -0700538
539 // Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
540 // We just work from bottom-right to top-left calculating B's coefficients.
541 float wy[m];
542 for (uint32_t h = 0; h < m; h++) {
543 wy[h] = y[h] * w[h];
544 }
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800545 std::array<float, VelocityTracker::MAX_DEGREE + 1> outB;
Dan Austin389ddba2015-09-22 14:32:03 -0700546 for (uint32_t i = n; i != 0; ) {
547 i--;
Jeff Brown5912f952013-07-01 19:10:31 -0700548 outB[i] = vectorDot(&q[i][0], wy, m);
549 for (uint32_t j = n - 1; j > i; j--) {
550 outB[i] -= r[i][j] * outB[j];
551 }
552 outB[i] /= r[i][i];
553 }
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000554
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800555 ALOGD_IF(DEBUG_STRATEGY, " - b=%s", vectorToString(outB.data(), n).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700556
557 // Calculate the coefficient of determination as 1 - (SSerr / SStot) where
558 // SSerr is the residual sum of squares (variance of the error),
559 // and SStot is the total sum of squares (variance of the data) where each
560 // has been weighted.
561 float ymean = 0;
562 for (uint32_t h = 0; h < m; h++) {
563 ymean += y[h];
564 }
565 ymean /= m;
566
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800567 if (DEBUG_STRATEGY) {
568 float sserr = 0;
569 float sstot = 0;
570 for (uint32_t h = 0; h < m; h++) {
571 float err = y[h] - outB[0];
572 float term = 1;
573 for (uint32_t i = 1; i < n; i++) {
574 term *= x[h];
575 err -= term * outB[i];
576 }
577 sserr += w[h] * w[h] * err * err;
578 float var = y[h] - ymean;
579 sstot += w[h] * w[h] * var * var;
Jeff Brown5912f952013-07-01 19:10:31 -0700580 }
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800581 ALOGD(" - sserr=%f", sserr);
582 ALOGD(" - sstot=%f", sstot);
Jeff Brown5912f952013-07-01 19:10:31 -0700583 }
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000584
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800585 return outB[1];
Jeff Brown5912f952013-07-01 19:10:31 -0700586}
587
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100588/*
589 * Optimized unweighted second-order least squares fit. About 2x speed improvement compared to
590 * the default implementation
591 */
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800592static std::optional<float> solveUnweightedLeastSquaresDeg2(const std::vector<float>& x,
593 const std::vector<float>& y) {
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500594 const size_t count = x.size();
595 LOG_ALWAYS_FATAL_IF(count != y.size(), "Mismatching array sizes");
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700596 // Solving y = a*x^2 + b*x + c
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100597 float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0;
598
599 for (size_t i = 0; i < count; i++) {
600 float xi = x[i];
601 float yi = y[i];
602 float xi2 = xi*xi;
603 float xi3 = xi2*xi;
604 float xi4 = xi3*xi;
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100605 float xiyi = xi*yi;
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700606 float xi2yi = xi2*yi;
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100607
608 sxi += xi;
609 sxi2 += xi2;
610 sxiyi += xiyi;
611 sxi2yi += xi2yi;
612 syi += yi;
613 sxi3 += xi3;
614 sxi4 += xi4;
615 }
616
617 float Sxx = sxi2 - sxi*sxi / count;
618 float Sxy = sxiyi - sxi*syi / count;
619 float Sxx2 = sxi3 - sxi*sxi2 / count;
620 float Sx2y = sxi2yi - sxi2*syi / count;
621 float Sx2x2 = sxi4 - sxi2*sxi2 / count;
622
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100623 float denominator = Sxx*Sx2x2 - Sxx2*Sxx2;
624 if (denominator == 0) {
625 ALOGW("division by 0 when computing velocity, Sxx=%f, Sx2x2=%f, Sxx2=%f", Sxx, Sx2x2, Sxx2);
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700626 return std::nullopt;
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100627 }
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700628
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800629 return (Sxy * Sx2x2 - Sx2y * Sxx2) / denominator;
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100630}
631
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800632std::optional<float> LeastSquaresVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800633 const auto movementIt = mMovements.find(pointerId);
634 if (movementIt == mMovements.end()) {
635 return std::nullopt; // no data
636 }
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800637
638 const RingBuffer<Movement>& movements = movementIt->second;
639 const size_t size = movements.size();
640 if (size == 0) {
641 return std::nullopt; // no data
642 }
643
Jeff Brown5912f952013-07-01 19:10:31 -0700644 // Iterate over movement samples in reverse time order and collect samples.
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000645 std::vector<float> positions;
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500646 std::vector<float> w;
647 std::vector<float> time;
648
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800649 const Movement& newestMovement = movements[size - 1];
650 for (ssize_t i = size - 1; i >= 0; i--) {
651 const Movement& movement = movements[i];
Jeff Brown5912f952013-07-01 19:10:31 -0700652
653 nsecs_t age = newestMovement.eventTime - movement.eventTime;
654 if (age > HORIZON) {
655 break;
656 }
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800657 positions.push_back(movement.position);
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800658 w.push_back(chooseWeight(pointerId, i));
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500659 time.push_back(-age * 0.000000001f);
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800660 }
Jeff Brown5912f952013-07-01 19:10:31 -0700661
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000662 const size_t m = positions.size();
Jeff Brown5912f952013-07-01 19:10:31 -0700663 if (m == 0) {
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800664 return std::nullopt; // no data
Jeff Brown5912f952013-07-01 19:10:31 -0700665 }
666
667 // Calculate a least squares polynomial fit.
668 uint32_t degree = mDegree;
669 if (degree > m - 1) {
670 degree = m - 1;
671 }
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700672
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800673 if (degree <= 0) {
674 return std::nullopt;
675 }
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800676 if (degree == 2 && mWeighting == Weighting::NONE) {
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700677 // Optimize unweighted, quadratic polynomial fit
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800678 return solveUnweightedLeastSquaresDeg2(time, positions);
Jeff Brown5912f952013-07-01 19:10:31 -0700679 }
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800680 // General case for an Nth degree polynomial fit
681 return solveLeastSquares(time, positions, w, degree + 1);
Jeff Brown5912f952013-07-01 19:10:31 -0700682}
683
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800684float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint32_t index) const {
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800685 const RingBuffer<Movement>& movements = mMovements.at(pointerId);
686 const size_t size = movements.size();
Jeff Brown5912f952013-07-01 19:10:31 -0700687 switch (mWeighting) {
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800688 case Weighting::DELTA: {
689 // Weight points based on how much time elapsed between them and the next
690 // point so that points that "cover" a shorter time span are weighed less.
691 // delta 0ms: 0.5
692 // delta 10ms: 1.0
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800693 if (index == size - 1) {
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800694 return 1.0f;
695 }
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800696 float deltaMillis =
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800697 (movements[index + 1].eventTime - movements[index].eventTime) * 0.000001f;
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800698 if (deltaMillis < 0) {
699 return 0.5f;
700 }
701 if (deltaMillis < 10) {
702 return 0.5f + deltaMillis * 0.05;
703 }
Jeff Brown5912f952013-07-01 19:10:31 -0700704 return 1.0f;
705 }
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800706
707 case Weighting::CENTRAL: {
708 // Weight points based on their age, weighing very recent and very old points less.
709 // age 0ms: 0.5
710 // age 10ms: 1.0
711 // age 50ms: 1.0
712 // age 60ms: 0.5
713 float ageMillis =
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800714 (movements[size - 1].eventTime - movements[index].eventTime) * 0.000001f;
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800715 if (ageMillis < 0) {
716 return 0.5f;
717 }
718 if (ageMillis < 10) {
719 return 0.5f + ageMillis * 0.05;
720 }
721 if (ageMillis < 50) {
722 return 1.0f;
723 }
724 if (ageMillis < 60) {
725 return 0.5f + (60 - ageMillis) * 0.05;
726 }
Jeff Brown5912f952013-07-01 19:10:31 -0700727 return 0.5f;
728 }
Jeff Brown5912f952013-07-01 19:10:31 -0700729
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800730 case Weighting::RECENT: {
731 // Weight points based on their age, weighing older points less.
732 // age 0ms: 1.0
733 // age 50ms: 1.0
734 // age 100ms: 0.5
735 float ageMillis =
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800736 (movements[size - 1].eventTime - movements[index].eventTime) * 0.000001f;
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800737 if (ageMillis < 50) {
738 return 1.0f;
739 }
740 if (ageMillis < 100) {
741 return 0.5f + (100 - ageMillis) * 0.01f;
742 }
Jeff Brown5912f952013-07-01 19:10:31 -0700743 return 0.5f;
744 }
Jeff Brown5912f952013-07-01 19:10:31 -0700745
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800746 case Weighting::NONE:
Jeff Brown5912f952013-07-01 19:10:31 -0700747 return 1.0f;
Jeff Brown5912f952013-07-01 19:10:31 -0700748 }
749}
750
Jeff Brown5912f952013-07-01 19:10:31 -0700751// --- IntegratingVelocityTrackerStrategy ---
752
753IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(uint32_t degree) :
754 mDegree(degree) {
755}
756
757IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {
758}
759
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800760void IntegratingVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
761 mPointerIdBits.clearBit(pointerId);
Jeff Brown5912f952013-07-01 19:10:31 -0700762}
763
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800764void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
765 float position) {
766 State& state = mPointerState[pointerId];
767 if (mPointerIdBits.hasBit(pointerId)) {
768 updateState(state, eventTime, position);
769 } else {
770 initState(state, eventTime, position);
Jeff Brown5912f952013-07-01 19:10:31 -0700771 }
772
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800773 mPointerIdBits.markBit(pointerId);
Jeff Brown5912f952013-07-01 19:10:31 -0700774}
775
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800776std::optional<float> IntegratingVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800777 if (mPointerIdBits.hasBit(pointerId)) {
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800778 return mPointerState[pointerId].vel;
Jeff Brown5912f952013-07-01 19:10:31 -0700779 }
780
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800781 return std::nullopt;
Jeff Brown5912f952013-07-01 19:10:31 -0700782}
783
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000784void IntegratingVelocityTrackerStrategy::initState(State& state, nsecs_t eventTime,
785 float pos) const {
Jeff Brown5912f952013-07-01 19:10:31 -0700786 state.updateTime = eventTime;
787 state.degree = 0;
788
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000789 state.pos = pos;
790 state.accel = 0;
791 state.vel = 0;
Jeff Brown5912f952013-07-01 19:10:31 -0700792}
793
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000794void IntegratingVelocityTrackerStrategy::updateState(State& state, nsecs_t eventTime,
795 float pos) const {
Jeff Brown5912f952013-07-01 19:10:31 -0700796 const nsecs_t MIN_TIME_DELTA = 2 * NANOS_PER_MS;
797 const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds
798
799 if (eventTime <= state.updateTime + MIN_TIME_DELTA) {
800 return;
801 }
802
803 float dt = (eventTime - state.updateTime) * 0.000000001f;
804 state.updateTime = eventTime;
805
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000806 float vel = (pos - state.pos) / dt;
Jeff Brown5912f952013-07-01 19:10:31 -0700807 if (state.degree == 0) {
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000808 state.vel = vel;
Jeff Brown5912f952013-07-01 19:10:31 -0700809 state.degree = 1;
810 } else {
811 float alpha = dt / (FILTER_TIME_CONSTANT + dt);
812 if (mDegree == 1) {
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000813 state.vel += (vel - state.vel) * alpha;
Jeff Brown5912f952013-07-01 19:10:31 -0700814 } else {
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000815 float accel = (vel - state.vel) / dt;
Jeff Brown5912f952013-07-01 19:10:31 -0700816 if (state.degree == 1) {
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000817 state.accel = accel;
Jeff Brown5912f952013-07-01 19:10:31 -0700818 state.degree = 2;
819 } else {
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000820 state.accel += (accel - state.accel) * alpha;
Jeff Brown5912f952013-07-01 19:10:31 -0700821 }
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000822 state.vel += (state.accel * dt) * alpha;
Jeff Brown5912f952013-07-01 19:10:31 -0700823 }
824 }
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000825 state.pos = pos;
Jeff Brown5912f952013-07-01 19:10:31 -0700826}
827
Jeff Brown5912f952013-07-01 19:10:31 -0700828// --- LegacyVelocityTrackerStrategy ---
829
Yeabkal Wubshit4a678b22023-02-23 17:03:40 -0800830LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy()
831 : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/,
832 false /*maintainHorizonDuringAdd*/) {}
Jeff Brown5912f952013-07-01 19:10:31 -0700833
834LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() {
835}
836
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800837std::optional<float> LegacyVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800838 const auto movementIt = mMovements.find(pointerId);
839 if (movementIt == mMovements.end()) {
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800840 return std::nullopt; // no data
Jeff Brown5912f952013-07-01 19:10:31 -0700841 }
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800842
843 const RingBuffer<Movement>& movements = movementIt->second;
844 const size_t size = movements.size();
845 if (size == 0) {
846 return std::nullopt; // no data
847 }
848
849 const Movement& newestMovement = movements[size - 1];
Jeff Brown5912f952013-07-01 19:10:31 -0700850
851 // Find the oldest sample that contains the pointer and that is not older than HORIZON.
852 nsecs_t minTime = newestMovement.eventTime - HORIZON;
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800853 uint32_t oldestIndex = size - 1;
854 for (ssize_t i = size - 1; i >= 0; i--) {
855 const Movement& nextOldestMovement = movements[i];
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800856 if (nextOldestMovement.eventTime < minTime) {
Jeff Brown5912f952013-07-01 19:10:31 -0700857 break;
858 }
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800859 oldestIndex = i;
860 }
Jeff Brown5912f952013-07-01 19:10:31 -0700861
862 // Calculate an exponentially weighted moving average of the velocity estimate
863 // at different points in time measured relative to the oldest sample.
864 // This is essentially an IIR filter. Newer samples are weighted more heavily
865 // than older samples. Samples at equal time points are weighted more or less
866 // equally.
867 //
868 // One tricky problem is that the sample data may be poorly conditioned.
869 // Sometimes samples arrive very close together in time which can cause us to
870 // overestimate the velocity at that time point. Most samples might be measured
871 // 16ms apart but some consecutive samples could be only 0.5sm apart because
872 // the hardware or driver reports them irregularly or in bursts.
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000873 float accumV = 0;
Jeff Brown5912f952013-07-01 19:10:31 -0700874 uint32_t samplesUsed = 0;
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800875 const Movement& oldestMovement = movements[oldestIndex];
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800876 float oldestPosition = oldestMovement.position;
Jeff Brown5912f952013-07-01 19:10:31 -0700877 nsecs_t lastDuration = 0;
878
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800879 for (size_t i = oldestIndex; i < size; i++) {
880 const Movement& movement = movements[i];
Jeff Brown5912f952013-07-01 19:10:31 -0700881 nsecs_t duration = movement.eventTime - oldestMovement.eventTime;
882
883 // If the duration between samples is small, we may significantly overestimate
884 // the velocity. Consequently, we impose a minimum duration constraint on the
885 // samples that we include in the calculation.
886 if (duration >= MIN_DURATION) {
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800887 float position = movement.position;
Jeff Brown5912f952013-07-01 19:10:31 -0700888 float scale = 1000000000.0f / duration; // one over time delta in seconds
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000889 float v = (position - oldestPosition) * scale;
890 accumV = (accumV * lastDuration + v * duration) / (duration + lastDuration);
Jeff Brown5912f952013-07-01 19:10:31 -0700891 lastDuration = duration;
892 samplesUsed += 1;
893 }
894 }
895
Jeff Brown5912f952013-07-01 19:10:31 -0700896 if (samplesUsed) {
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800897 return accumV;
Jeff Brown5912f952013-07-01 19:10:31 -0700898 }
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800899 return std::nullopt;
Jeff Brown5912f952013-07-01 19:10:31 -0700900}
901
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +0100902// --- ImpulseVelocityTrackerStrategy ---
903
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -0700904ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy(bool deltaValues)
Yeabkal Wubshit4a678b22023-02-23 17:03:40 -0800905 : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/,
906 true /*maintainHorizonDuringAdd*/),
907 mDeltaValues(deltaValues) {}
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +0100908
909ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() {
910}
911
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +0100912/**
913 * Calculate the total impulse provided to the screen and the resulting velocity.
914 *
915 * The touchscreen is modeled as a physical object.
916 * Initial condition is discussed below, but for now suppose that v(t=0) = 0
917 *
918 * The kinetic energy of the object at the release is E=0.5*m*v^2
919 * Then vfinal = sqrt(2E/m). The goal is to calculate E.
920 *
921 * The kinetic energy at the release is equal to the total work done on the object by the finger.
922 * The total work W is the sum of all dW along the path.
923 *
924 * dW = F*dx, where dx is the piece of path traveled.
925 * Force is change of momentum over time, F = dp/dt = m dv/dt.
926 * Then substituting:
927 * dW = m (dv/dt) * dx = m * v * dv
928 *
929 * Summing along the path, we get:
930 * W = sum(dW) = sum(m * v * dv) = m * sum(v * dv)
931 * Since the mass stays constant, the equation for final velocity is:
932 * vfinal = sqrt(2*sum(v * dv))
933 *
934 * Here,
935 * dv : change of velocity = (v[i+1]-v[i])
936 * dx : change of distance = (x[i+1]-x[i])
937 * dt : change of time = (t[i+1]-t[i])
938 * v : instantaneous velocity = dx/dt
939 *
940 * The final formula is:
941 * vfinal = sqrt(2) * sqrt(sum((v[i]-v[i-1])*|v[i]|)) for all i
942 * The absolute value is needed to properly account for the sign. If the velocity over a
943 * particular segment descreases, then this indicates braking, which means that negative
944 * work was done. So for two positive, but decreasing, velocities, this contribution would be
945 * negative and will cause a smaller final velocity.
946 *
947 * Initial condition
948 * There are two ways to deal with initial condition:
949 * 1) Assume that v(0) = 0, which would mean that the screen is initially at rest.
950 * This is not entirely accurate. We are only taking the past X ms of touch data, where X is
951 * currently equal to 100. However, a touch event that created a fling probably lasted for longer
952 * than that, which would mean that the user has already been interacting with the touchscreen
953 * and it has probably already been moving.
954 * 2) Assume that the touchscreen has already been moving at a certain velocity, calculate this
955 * initial velocity and the equivalent energy, and start with this initial energy.
956 * Consider an example where we have the following data, consisting of 3 points:
957 * time: t0, t1, t2
958 * x : x0, x1, x2
959 * v : 0 , v1, v2
960 * Here is what will happen in each of these scenarios:
961 * 1) By directly applying the formula above with the v(0) = 0 boundary condition, we will get
962 * vfinal = sqrt(2*(|v1|*(v1-v0) + |v2|*(v2-v1))). This can be simplified since v0=0
963 * vfinal = sqrt(2*(|v1|*v1 + |v2|*(v2-v1))) = sqrt(2*(v1^2 + |v2|*(v2 - v1)))
964 * since velocity is a real number
965 * 2) If we treat the screen as already moving, then it must already have an energy (per mass)
966 * equal to 1/2*v1^2. Then the initial energy should be 1/2*v1*2, and only the second segment
967 * will contribute to the total kinetic energy (since we can effectively consider that v0=v1).
968 * This will give the following expression for the final velocity:
969 * vfinal = sqrt(2*(1/2*v1^2 + |v2|*(v2-v1)))
970 * This analysis can be generalized to an arbitrary number of samples.
971 *
972 *
973 * Comparing the two equations above, we see that the only mathematical difference
974 * is the factor of 1/2 in front of the first velocity term.
975 * This boundary condition would allow for the "proper" calculation of the case when all of the
976 * samples are equally spaced in time and distance, which should suggest a constant velocity.
977 *
978 * Note that approach 2) is sensitive to the proper ordering of the data in time, since
979 * the boundary condition must be applied to the oldest sample to be accurate.
980 */
Siarhei Vishniakou97b5e182017-09-01 13:52:33 -0700981static float kineticEnergyToVelocity(float work) {
982 static constexpr float sqrt2 = 1.41421356237;
983 return (work < 0 ? -1.0 : 1.0) * sqrtf(fabsf(work)) * sqrt2;
984}
985
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800986std::optional<float> ImpulseVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800987 const auto movementIt = mMovements.find(pointerId);
988 if (movementIt == mMovements.end()) {
989 return std::nullopt; // no data
990 }
991
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800992 const RingBuffer<Movement>& movements = movementIt->second;
993 const size_t size = movements.size();
994 if (size == 0) {
995 return std::nullopt; // no data
996 }
997
Yeabkal Wubshit4a678b22023-02-23 17:03:40 -0800998 float work = 0;
999 for (size_t i = 0; i < size - 1; i++) {
1000 const Movement& mvt = movements[i];
1001 const Movement& nextMvt = movements[i + 1];
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001002
Yeabkal Wubshit4a678b22023-02-23 17:03:40 -08001003 float vprev = kineticEnergyToVelocity(work);
1004 float delta = mDeltaValues ? nextMvt.position : nextMvt.position - mvt.position;
1005 float vcurr = delta / (SECONDS_PER_NANO * (nextMvt.eventTime - mvt.eventTime));
1006 work += (vcurr - vprev) * fabsf(vcurr);
1007
1008 if (i == 0) {
1009 work *= 0.5; // initial condition, case 2) above
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001010 }
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -08001011 }
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001012
Yeabkal Wubshit4a678b22023-02-23 17:03:40 -08001013 const float velocity = kineticEnergyToVelocity(work);
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -08001014 ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", velocity);
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +00001015
Siarhei Vishniakou276467b2022-03-17 09:43:28 -07001016 if (DEBUG_IMPULSE) {
1017 // TODO(b/134179997): delete this block once the switch to 'impulse' is complete.
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +00001018 // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons.
1019 // X axis chosen arbitrarily for velocity comparisons.
Siarhei Vishniakou276467b2022-03-17 09:43:28 -07001020 VelocityTracker lsq2(VelocityTracker::Strategy::LSQ2);
Yeabkal Wubshit4a678b22023-02-23 17:03:40 -08001021 for (size_t i = 0; i < size; i++) {
1022 const Movement& mvt = movements[i];
1023 lsq2.addMovement(mvt.eventTime, pointerId, AMOTION_EVENT_AXIS_X, mvt.position);
Siarhei Vishniakou276467b2022-03-17 09:43:28 -07001024 }
Siarhei Vishniakou8d232032023-01-11 08:17:21 -08001025 std::optional<float> v = lsq2.getVelocity(AMOTION_EVENT_AXIS_X, pointerId);
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +00001026 if (v) {
1027 ALOGD("lsq2 velocity: %.1f", *v);
Siarhei Vishniakou276467b2022-03-17 09:43:28 -07001028 } else {
1029 ALOGD("lsq2 velocity: could not compute velocity");
1030 }
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -07001031 }
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -08001032 return velocity;
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001033}
1034
Jeff Brown5912f952013-07-01 19:10:31 -07001035} // namespace android