blob: ba266b39690319cee2be428ca83fb618dedc9e66 [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 Wubshit67b3ab02022-09-16 00:18:17 -070058// All axes supported for velocity tracking, mapped to their default strategies.
59// Although other strategies are available for testing and comparison purposes,
60// the default strategy is the one that applications will actually use. Be very careful
61// when adjusting the default strategy because it can dramatically affect
62// (often in a bad way) the user experience.
63static const std::map<int32_t, VelocityTracker::Strategy> DEFAULT_STRATEGY_BY_AXIS =
64 {{AMOTION_EVENT_AXIS_X, VelocityTracker::Strategy::LSQ2},
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -070065 {AMOTION_EVENT_AXIS_Y, VelocityTracker::Strategy::LSQ2},
66 {AMOTION_EVENT_AXIS_SCROLL, VelocityTracker::Strategy::IMPULSE}};
Yeabkal Wubshit67b3ab02022-09-16 00:18:17 -070067
68// Axes specifying location on a 2D plane (i.e. X and Y).
69static const std::set<int32_t> PLANAR_AXES = {AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y};
70
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -070071// Axes whose motion values are differential values (i.e. deltas).
72static const std::set<int32_t> DIFFERENTIAL_AXES = {AMOTION_EVENT_AXIS_SCROLL};
73
Jeff Brown5912f952013-07-01 19:10:31 -070074// Threshold for determining that a pointer has stopped moving.
75// Some input devices do not send ACTION_MOVE events in the case where a pointer has
76// stopped. We need to detect this case so that we can accurately predict the
77// velocity after the pointer starts moving again.
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +000078static const std::chrono::duration ASSUME_POINTER_STOPPED_TIME = 40ms;
Jeff Brown5912f952013-07-01 19:10:31 -070079
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +000080static std::string toString(std::chrono::nanoseconds t) {
81 std::stringstream stream;
82 stream.precision(1);
83 stream << std::fixed << std::chrono::duration<float, std::milli>(t).count() << " ms";
84 return stream.str();
85}
Jeff Brown5912f952013-07-01 19:10:31 -070086
87static float vectorDot(const float* a, const float* b, uint32_t m) {
88 float r = 0;
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -070089 for (size_t i = 0; i < m; i++) {
Jeff Brown5912f952013-07-01 19:10:31 -070090 r += *(a++) * *(b++);
91 }
92 return r;
93}
94
95static float vectorNorm(const float* a, uint32_t m) {
96 float r = 0;
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -070097 for (size_t i = 0; i < m; i++) {
Jeff Brown5912f952013-07-01 19:10:31 -070098 float t = *(a++);
99 r += t * t;
100 }
101 return sqrtf(r);
102}
103
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700104static std::string vectorToString(const float* a, uint32_t m) {
105 std::string str;
106 str += "[";
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -0700107 for (size_t i = 0; i < m; i++) {
108 if (i) {
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700109 str += ",";
Jeff Brown5912f952013-07-01 19:10:31 -0700110 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700111 str += android::base::StringPrintf(" %f", *(a++));
Jeff Brown5912f952013-07-01 19:10:31 -0700112 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700113 str += " ]";
Jeff Brown5912f952013-07-01 19:10:31 -0700114 return str;
115}
116
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -0700117static std::string vectorToString(const std::vector<float>& v) {
118 return vectorToString(v.data(), v.size());
119}
120
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700121static std::string matrixToString(const float* a, uint32_t m, uint32_t n, bool rowMajor) {
122 std::string str;
123 str = "[";
Jeff Brown5912f952013-07-01 19:10:31 -0700124 for (size_t i = 0; i < m; i++) {
125 if (i) {
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700126 str += ",";
Jeff Brown5912f952013-07-01 19:10:31 -0700127 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700128 str += " [";
Jeff Brown5912f952013-07-01 19:10:31 -0700129 for (size_t j = 0; j < n; j++) {
130 if (j) {
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700131 str += ",";
Jeff Brown5912f952013-07-01 19:10:31 -0700132 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700133 str += android::base::StringPrintf(" %f", a[rowMajor ? i * n + j : j * m + i]);
Jeff Brown5912f952013-07-01 19:10:31 -0700134 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700135 str += " ]";
Jeff Brown5912f952013-07-01 19:10:31 -0700136 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700137 str += " ]";
Jeff Brown5912f952013-07-01 19:10:31 -0700138 return str;
139}
Jeff Brown5912f952013-07-01 19:10:31 -0700140
141
142// --- VelocityTracker ---
143
Chris Yef8591482020-04-17 11:49:17 -0700144VelocityTracker::VelocityTracker(const Strategy strategy)
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800145 : mLastEventTime(0), mCurrentPointerIdBits(0), mOverrideStrategy(strategy) {}
Jeff Brown5912f952013-07-01 19:10:31 -0700146
147VelocityTracker::~VelocityTracker() {
Jeff Brown5912f952013-07-01 19:10:31 -0700148}
149
Yeabkal Wubshiteca273c2022-10-05 19:06:40 -0700150bool VelocityTracker::isAxisSupported(int32_t axis) {
151 return DEFAULT_STRATEGY_BY_AXIS.find(axis) != DEFAULT_STRATEGY_BY_AXIS.end();
152}
153
Yeabkal Wubshit47ff7082022-09-10 23:09:15 -0700154void VelocityTracker::configureStrategy(int32_t axis) {
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -0700155 const bool isDifferentialAxis = DIFFERENTIAL_AXES.find(axis) != DIFFERENTIAL_AXES.end();
156
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000157 std::unique_ptr<VelocityTrackerStrategy> createdStrategy;
Yeabkal Wubshit47ff7082022-09-10 23:09:15 -0700158 if (mOverrideStrategy != VelocityTracker::Strategy::DEFAULT) {
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -0700159 createdStrategy = createStrategy(mOverrideStrategy, isDifferentialAxis /* deltaValues */);
Chris Yef8591482020-04-17 11:49:17 -0700160 } else {
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -0700161 createdStrategy = createStrategy(DEFAULT_STRATEGY_BY_AXIS.at(axis),
162 isDifferentialAxis /* deltaValues */);
Chris Yef8591482020-04-17 11:49:17 -0700163 }
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000164
Yeabkal Wubshit47ff7082022-09-10 23:09:15 -0700165 LOG_ALWAYS_FATAL_IF(createdStrategy == nullptr,
166 "Could not create velocity tracker strategy for axis '%" PRId32 "'!", axis);
167 mConfiguredStrategies[axis] = std::move(createdStrategy);
Jeff Brown5912f952013-07-01 19:10:31 -0700168}
169
Chris Yef8591482020-04-17 11:49:17 -0700170std::unique_ptr<VelocityTrackerStrategy> VelocityTracker::createStrategy(
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -0700171 VelocityTracker::Strategy strategy, bool deltaValues) {
Chris Yef8591482020-04-17 11:49:17 -0700172 switch (strategy) {
173 case VelocityTracker::Strategy::IMPULSE:
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000174 ALOGI_IF(DEBUG_STRATEGY, "Initializing impulse strategy");
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -0700175 return std::make_unique<ImpulseVelocityTrackerStrategy>(deltaValues);
Chris Yef8591482020-04-17 11:49:17 -0700176
177 case VelocityTracker::Strategy::LSQ1:
178 return std::make_unique<LeastSquaresVelocityTrackerStrategy>(1);
179
180 case VelocityTracker::Strategy::LSQ2:
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000181 ALOGI_IF(DEBUG_STRATEGY && !DEBUG_IMPULSE, "Initializing lsq2 strategy");
Chris Yef8591482020-04-17 11:49:17 -0700182 return std::make_unique<LeastSquaresVelocityTrackerStrategy>(2);
183
184 case VelocityTracker::Strategy::LSQ3:
185 return std::make_unique<LeastSquaresVelocityTrackerStrategy>(3);
186
187 case VelocityTracker::Strategy::WLSQ2_DELTA:
188 return std::make_unique<
189 LeastSquaresVelocityTrackerStrategy>(2,
190 LeastSquaresVelocityTrackerStrategy::
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800191 Weighting::DELTA);
Chris Yef8591482020-04-17 11:49:17 -0700192 case VelocityTracker::Strategy::WLSQ2_CENTRAL:
193 return std::make_unique<
194 LeastSquaresVelocityTrackerStrategy>(2,
195 LeastSquaresVelocityTrackerStrategy::
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800196 Weighting::CENTRAL);
Chris Yef8591482020-04-17 11:49:17 -0700197 case VelocityTracker::Strategy::WLSQ2_RECENT:
198 return std::make_unique<
199 LeastSquaresVelocityTrackerStrategy>(2,
200 LeastSquaresVelocityTrackerStrategy::
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800201 Weighting::RECENT);
Chris Yef8591482020-04-17 11:49:17 -0700202
203 case VelocityTracker::Strategy::INT1:
204 return std::make_unique<IntegratingVelocityTrackerStrategy>(1);
205
206 case VelocityTracker::Strategy::INT2:
207 return std::make_unique<IntegratingVelocityTrackerStrategy>(2);
208
209 case VelocityTracker::Strategy::LEGACY:
210 return std::make_unique<LegacyVelocityTrackerStrategy>();
211
212 default:
213 break;
Jeff Brown5912f952013-07-01 19:10:31 -0700214 }
Yi Kong5bed83b2018-07-17 12:53:47 -0700215 return nullptr;
Jeff Brown5912f952013-07-01 19:10:31 -0700216}
217
218void VelocityTracker::clear() {
219 mCurrentPointerIdBits.clear();
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800220 mActivePointerId = std::nullopt;
Yeabkal Wubshit47ff7082022-09-10 23:09:15 -0700221 mConfiguredStrategies.clear();
Jeff Brown5912f952013-07-01 19:10:31 -0700222}
223
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800224void VelocityTracker::clearPointer(int32_t pointerId) {
225 mCurrentPointerIdBits.clearBit(pointerId);
Jeff Brown5912f952013-07-01 19:10:31 -0700226
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800227 if (mActivePointerId && *mActivePointerId == pointerId) {
228 // The active pointer id is being removed. Mark it invalid and try to find a new one
229 // from the remaining pointers.
230 mActivePointerId = std::nullopt;
231 if (!mCurrentPointerIdBits.isEmpty()) {
232 mActivePointerId = mCurrentPointerIdBits.firstMarkedBit();
233 }
Jeff Brown5912f952013-07-01 19:10:31 -0700234 }
235
Yeabkal Wubshit47ff7082022-09-10 23:09:15 -0700236 for (const auto& [_, strategy] : mConfiguredStrategies) {
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800237 strategy->clearPointer(pointerId);
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000238 }
Jeff Brown5912f952013-07-01 19:10:31 -0700239}
240
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800241void VelocityTracker::addMovement(nsecs_t eventTime, int32_t pointerId, int32_t axis,
242 float position) {
243 if (mCurrentPointerIdBits.hasBit(pointerId) &&
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000244 std::chrono::nanoseconds(eventTime - mLastEventTime) > ASSUME_POINTER_STOPPED_TIME) {
245 ALOGD_IF(DEBUG_VELOCITY, "VelocityTracker: stopped for %s, clearing state.",
246 toString(std::chrono::nanoseconds(eventTime - mLastEventTime)).c_str());
247
Jeff Brown5912f952013-07-01 19:10:31 -0700248 // We have not received any movements for too long. Assume that all pointers
249 // have stopped.
Yeabkal Wubshit47ff7082022-09-10 23:09:15 -0700250 mConfiguredStrategies.clear();
Jeff Brown5912f952013-07-01 19:10:31 -0700251 }
252 mLastEventTime = eventTime;
253
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800254 mCurrentPointerIdBits.markBit(pointerId);
255 if (!mActivePointerId) {
256 // Let this be the new active pointer if no active pointer is currently set
257 mActivePointerId = pointerId;
Jeff Brown5912f952013-07-01 19:10:31 -0700258 }
259
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800260 if (mConfiguredStrategies.find(axis) == mConfiguredStrategies.end()) {
261 configureStrategy(axis);
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000262 }
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800263 mConfiguredStrategies[axis]->addMovement(eventTime, pointerId, position);
Jeff Brown5912f952013-07-01 19:10:31 -0700264
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -0700265 if (DEBUG_VELOCITY) {
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800266 ALOGD("VelocityTracker: addMovement eventTime=%" PRId64 ", pointerId=%" PRId32
267 ", activePointerId=%s",
268 eventTime, pointerId, toString(mActivePointerId).c_str());
269
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800270 ALOGD(" %d: axis=%d, position=%0.3f, velocity=%s", pointerId, axis, position,
271 toString(getVelocity(axis, pointerId)).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700272 }
Jeff Brown5912f952013-07-01 19:10:31 -0700273}
274
275void VelocityTracker::addMovement(const MotionEvent* event) {
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000276 // Stores data about which axes to process based on the incoming motion event.
277 std::set<int32_t> axesToProcess;
Jeff Brown5912f952013-07-01 19:10:31 -0700278 int32_t actionMasked = event->getActionMasked();
279
280 switch (actionMasked) {
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800281 case AMOTION_EVENT_ACTION_DOWN:
282 case AMOTION_EVENT_ACTION_HOVER_ENTER:
283 // Clear all pointers on down before adding the new movement.
284 clear();
285 axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
286 break;
287 case AMOTION_EVENT_ACTION_POINTER_DOWN: {
288 // Start a new movement trace for a pointer that just went down.
289 // We do this on down instead of on up because the client may want to query the
290 // final velocity for a pointer that just went up.
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800291 clearPointer(event->getPointerId(event->getActionIndex()));
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800292 axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
293 break;
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000294 }
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800295 case AMOTION_EVENT_ACTION_MOVE:
296 case AMOTION_EVENT_ACTION_HOVER_MOVE:
297 axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
298 break;
299 case AMOTION_EVENT_ACTION_POINTER_UP:
300 case AMOTION_EVENT_ACTION_UP: {
301 std::chrono::nanoseconds delaySinceLastEvent(event->getEventTime() - mLastEventTime);
302 if (delaySinceLastEvent > ASSUME_POINTER_STOPPED_TIME) {
303 ALOGD_IF(DEBUG_VELOCITY,
304 "VelocityTracker: stopped for %s, clearing state upon pointer liftoff.",
305 toString(delaySinceLastEvent).c_str());
306 // We have not received any movements for too long. Assume that all pointers
307 // have stopped.
308 for (int32_t axis : PLANAR_AXES) {
309 mConfiguredStrategies.erase(axis);
310 }
311 }
312 // These actions because they do not convey any new information about
313 // pointer movement. We also want to preserve the last known velocity of the pointers.
314 // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position
315 // of the pointers that went up. ACTION_POINTER_UP does include the new position of
316 // pointers that remained down but we will also receive an ACTION_MOVE with this
317 // information if any of them actually moved. Since we don't know how many pointers
318 // will be going up at once it makes sense to just wait for the following ACTION_MOVE
319 // before adding the movement.
320 return;
321 }
322 case AMOTION_EVENT_ACTION_SCROLL:
323 axesToProcess.insert(AMOTION_EVENT_AXIS_SCROLL);
324 break;
325 default:
326 // Ignore all other actions.
327 return;
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000328 }
Jeff Brown5912f952013-07-01 19:10:31 -0700329
Siarhei Vishniakouc36d21e2023-01-17 09:59:38 -0800330 const size_t historySize = event->getHistorySize();
Siarhei Vishniakou69e4d0f2020-09-14 19:53:29 -0500331 for (size_t h = 0; h <= historySize; h++) {
Siarhei Vishniakouc36d21e2023-01-17 09:59:38 -0800332 const nsecs_t eventTime = event->getHistoricalEventTime(h);
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800333 for (size_t i = 0; i < event->getPointerCount(); i++) {
Siarhei Vishniakouc36d21e2023-01-17 09:59:38 -0800334 if (event->isResampled(i, h)) {
335 continue; // skip resampled samples
336 }
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800337 const int32_t pointerId = event->getPointerId(i);
338 for (int32_t axis : axesToProcess) {
339 const float position = event->getHistoricalAxisValue(axis, i, h);
340 addMovement(eventTime, pointerId, axis, position);
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000341 }
Jeff Brown5912f952013-07-01 19:10:31 -0700342 }
Jeff Brown5912f952013-07-01 19:10:31 -0700343 }
Jeff Brown5912f952013-07-01 19:10:31 -0700344}
345
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800346std::optional<float> VelocityTracker::getVelocity(int32_t axis, int32_t pointerId) const {
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800347 const auto& it = mConfiguredStrategies.find(axis);
348 if (it != mConfiguredStrategies.end()) {
349 return it->second->getVelocity(pointerId);
Jeff Brown5912f952013-07-01 19:10:31 -0700350 }
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000351 return {};
Jeff Brown5912f952013-07-01 19:10:31 -0700352}
353
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000354VelocityTracker::ComputedVelocity VelocityTracker::getComputedVelocity(int32_t units,
355 float maxVelocity) {
356 ComputedVelocity computedVelocity;
Yeabkal Wubshit47ff7082022-09-10 23:09:15 -0700357 for (const auto& [axis, _] : mConfiguredStrategies) {
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000358 BitSet32 copyIdBits = BitSet32(mCurrentPointerIdBits);
359 while (!copyIdBits.isEmpty()) {
360 uint32_t id = copyIdBits.clearFirstMarkedBit();
361 std::optional<float> velocity = getVelocity(axis, id);
362 if (velocity) {
363 float adjustedVelocity =
364 std::clamp(*velocity * units / 1000, -maxVelocity, maxVelocity);
365 computedVelocity.addVelocity(axis, id, adjustedVelocity);
366 }
367 }
368 }
369 return computedVelocity;
Jeff Brown5912f952013-07-01 19:10:31 -0700370}
371
Yeabkal Wubshita0e573c2023-03-02 21:08:14 -0800372void AccumulatingVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800373 mMovements.erase(pointerId);
Jeff Brown5912f952013-07-01 19:10:31 -0700374}
375
Yeabkal Wubshita0e573c2023-03-02 21:08:14 -0800376void AccumulatingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800377 float position) {
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800378 auto [movementIt, _] = mMovements.insert({pointerId, RingBuffer<Movement>(HISTORY_SIZE)});
379 RingBuffer<Movement>& movements = movementIt->second;
380 const size_t size = movements.size();
381
382 if (size != 0 && movements[size - 1].eventTime == eventTime) {
Siarhei Vishniakou346ac6a2019-04-10 09:58:05 -0700383 // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
384 // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
385 // the new pointer. If the eventtimes for both events are identical, just update the data
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800386 // for this time (i.e. pop out the last element, and insert the updated movement).
Siarhei Vishniakou346ac6a2019-04-10 09:58:05 -0700387 // We only compare against the last value, as it is likely that addMovement is called
388 // in chronological order as events occur.
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800389 movements.popBack();
Jeff Brown5912f952013-07-01 19:10:31 -0700390 }
391
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800392 movements.pushBack({eventTime, position});
Jeff Brown5912f952013-07-01 19:10:31 -0700393}
394
Yeabkal Wubshita0e573c2023-03-02 21:08:14 -0800395// --- LeastSquaresVelocityTrackerStrategy ---
396
397LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(uint32_t degree,
398 Weighting weighting)
399 : mDegree(degree), mWeighting(weighting) {}
400
401LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {}
402
Jeff Brown5912f952013-07-01 19:10:31 -0700403/**
404 * Solves a linear least squares problem to obtain a N degree polynomial that fits
405 * the specified input data as nearly as possible.
406 *
407 * Returns true if a solution is found, false otherwise.
408 *
409 * The input consists of two vectors of data points X and Y with indices 0..m-1
410 * along with a weight vector W of the same size.
411 *
412 * The output is a vector B with indices 0..n that describes a polynomial
413 * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1] X[i]
414 * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is minimized.
415 *
416 * Accordingly, the weight vector W should be initialized by the caller with the
417 * reciprocal square root of the variance of the error in each input data point.
418 * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 / stddev(Y[i]).
419 * The weights express the relative importance of each data point. If the weights are
420 * all 1, then the data points are considered to be of equal importance when fitting
421 * the polynomial. It is a good idea to choose weights that diminish the importance
422 * of data points that may have higher than usual error margins.
423 *
424 * Errors among data points are assumed to be independent. W is represented here
425 * as a vector although in the literature it is typically taken to be a diagonal matrix.
426 *
427 * That is to say, the function that generated the input data can be approximated
428 * by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n.
429 *
430 * The coefficient of determination (R^2) is also returned to describe the goodness
431 * of fit of the model for the given data. It is a value between 0 and 1, where 1
432 * indicates perfect correspondence.
433 *
434 * This function first expands the X vector to a m by n matrix A such that
435 * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then
436 * multiplies it by w[i]./
437 *
438 * Then it calculates the QR decomposition of A yielding an m by m orthonormal matrix Q
439 * and an m by n upper triangular matrix R. Because R is upper triangular (lower
440 * part is all zeroes), we can simplify the decomposition into an m by n matrix
441 * Q1 and a n by n matrix R1 such that A = Q1 R1.
442 *
443 * Finally we solve the system of linear equations given by R1 B = (Qtranspose W Y)
444 * to find B.
445 *
446 * For efficiency, we lay out A and Q column-wise in memory because we frequently
447 * operate on the column vectors. Conversely, we lay out R row-wise.
448 *
449 * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
450 * http://en.wikipedia.org/wiki/Gram-Schmidt
451 */
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800452static std::optional<float> solveLeastSquares(const std::vector<float>& x,
453 const std::vector<float>& y,
454 const std::vector<float>& w, uint32_t n) {
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500455 const size_t m = x.size();
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000456
457 ALOGD_IF(DEBUG_STRATEGY, "solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n),
458 vectorToString(x).c_str(), vectorToString(y).c_str(), vectorToString(w).c_str());
459
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500460 LOG_ALWAYS_FATAL_IF(m != y.size() || m != w.size(), "Mismatched vector sizes");
Jeff Brown5912f952013-07-01 19:10:31 -0700461
462 // Expand the X vector to a matrix A, pre-multiplied by the weights.
463 float a[n][m]; // column-major order
464 for (uint32_t h = 0; h < m; h++) {
465 a[0][h] = w[h];
466 for (uint32_t i = 1; i < n; i++) {
467 a[i][h] = a[i - 1][h] * x[h];
468 }
469 }
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000470
471 ALOGD_IF(DEBUG_STRATEGY, " - a=%s",
472 matrixToString(&a[0][0], m, n, false /*rowMajor*/).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700473
474 // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
475 float q[n][m]; // orthonormal basis, column-major order
476 float r[n][n]; // upper triangular matrix, row-major order
477 for (uint32_t j = 0; j < n; j++) {
478 for (uint32_t h = 0; h < m; h++) {
479 q[j][h] = a[j][h];
480 }
481 for (uint32_t i = 0; i < j; i++) {
482 float dot = vectorDot(&q[j][0], &q[i][0], m);
483 for (uint32_t h = 0; h < m; h++) {
484 q[j][h] -= dot * q[i][h];
485 }
486 }
487
488 float norm = vectorNorm(&q[j][0], m);
489 if (norm < 0.000001f) {
490 // vectors are linearly dependent or zero so no solution
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000491 ALOGD_IF(DEBUG_STRATEGY, " - no solution, norm=%f", norm);
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800492 return {};
Jeff Brown5912f952013-07-01 19:10:31 -0700493 }
494
495 float invNorm = 1.0f / norm;
496 for (uint32_t h = 0; h < m; h++) {
497 q[j][h] *= invNorm;
498 }
499 for (uint32_t i = 0; i < n; i++) {
500 r[j][i] = i < j ? 0 : vectorDot(&q[j][0], &a[i][0], m);
501 }
502 }
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -0700503 if (DEBUG_STRATEGY) {
504 ALOGD(" - q=%s", matrixToString(&q[0][0], m, n, false /*rowMajor*/).c_str());
505 ALOGD(" - r=%s", matrixToString(&r[0][0], n, n, true /*rowMajor*/).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700506
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -0700507 // calculate QR, if we factored A correctly then QR should equal A
508 float qr[n][m];
509 for (uint32_t h = 0; h < m; h++) {
510 for (uint32_t i = 0; i < n; i++) {
511 qr[i][h] = 0;
512 for (uint32_t j = 0; j < n; j++) {
513 qr[i][h] += q[j][h] * r[j][i];
514 }
Jeff Brown5912f952013-07-01 19:10:31 -0700515 }
516 }
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -0700517 ALOGD(" - qr=%s", matrixToString(&qr[0][0], m, n, false /*rowMajor*/).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700518 }
Jeff Brown5912f952013-07-01 19:10:31 -0700519
520 // Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
521 // We just work from bottom-right to top-left calculating B's coefficients.
522 float wy[m];
523 for (uint32_t h = 0; h < m; h++) {
524 wy[h] = y[h] * w[h];
525 }
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800526 std::array<float, VelocityTracker::MAX_DEGREE + 1> outB;
Dan Austin389ddba2015-09-22 14:32:03 -0700527 for (uint32_t i = n; i != 0; ) {
528 i--;
Jeff Brown5912f952013-07-01 19:10:31 -0700529 outB[i] = vectorDot(&q[i][0], wy, m);
530 for (uint32_t j = n - 1; j > i; j--) {
531 outB[i] -= r[i][j] * outB[j];
532 }
533 outB[i] /= r[i][i];
534 }
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000535
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800536 ALOGD_IF(DEBUG_STRATEGY, " - b=%s", vectorToString(outB.data(), n).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700537
538 // Calculate the coefficient of determination as 1 - (SSerr / SStot) where
539 // SSerr is the residual sum of squares (variance of the error),
540 // and SStot is the total sum of squares (variance of the data) where each
541 // has been weighted.
542 float ymean = 0;
543 for (uint32_t h = 0; h < m; h++) {
544 ymean += y[h];
545 }
546 ymean /= m;
547
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800548 if (DEBUG_STRATEGY) {
549 float sserr = 0;
550 float sstot = 0;
551 for (uint32_t h = 0; h < m; h++) {
552 float err = y[h] - outB[0];
553 float term = 1;
554 for (uint32_t i = 1; i < n; i++) {
555 term *= x[h];
556 err -= term * outB[i];
557 }
558 sserr += w[h] * w[h] * err * err;
559 float var = y[h] - ymean;
560 sstot += w[h] * w[h] * var * var;
Jeff Brown5912f952013-07-01 19:10:31 -0700561 }
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800562 ALOGD(" - sserr=%f", sserr);
563 ALOGD(" - sstot=%f", sstot);
Jeff Brown5912f952013-07-01 19:10:31 -0700564 }
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +0000565
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800566 return outB[1];
Jeff Brown5912f952013-07-01 19:10:31 -0700567}
568
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100569/*
570 * Optimized unweighted second-order least squares fit. About 2x speed improvement compared to
571 * the default implementation
572 */
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800573static std::optional<float> solveUnweightedLeastSquaresDeg2(const std::vector<float>& x,
574 const std::vector<float>& y) {
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500575 const size_t count = x.size();
576 LOG_ALWAYS_FATAL_IF(count != y.size(), "Mismatching array sizes");
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700577 // Solving y = a*x^2 + b*x + c
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100578 float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0;
579
580 for (size_t i = 0; i < count; i++) {
581 float xi = x[i];
582 float yi = y[i];
583 float xi2 = xi*xi;
584 float xi3 = xi2*xi;
585 float xi4 = xi3*xi;
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100586 float xiyi = xi*yi;
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700587 float xi2yi = xi2*yi;
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100588
589 sxi += xi;
590 sxi2 += xi2;
591 sxiyi += xiyi;
592 sxi2yi += xi2yi;
593 syi += yi;
594 sxi3 += xi3;
595 sxi4 += xi4;
596 }
597
598 float Sxx = sxi2 - sxi*sxi / count;
599 float Sxy = sxiyi - sxi*syi / count;
600 float Sxx2 = sxi3 - sxi*sxi2 / count;
601 float Sx2y = sxi2yi - sxi2*syi / count;
602 float Sx2x2 = sxi4 - sxi2*sxi2 / count;
603
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100604 float denominator = Sxx*Sx2x2 - Sxx2*Sxx2;
605 if (denominator == 0) {
606 ALOGW("division by 0 when computing velocity, Sxx=%f, Sx2x2=%f, Sxx2=%f", Sxx, Sx2x2, Sxx2);
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700607 return std::nullopt;
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100608 }
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700609
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800610 return (Sxy * Sx2x2 - Sx2y * Sxx2) / denominator;
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100611}
612
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800613std::optional<float> LeastSquaresVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800614 const auto movementIt = mMovements.find(pointerId);
615 if (movementIt == mMovements.end()) {
616 return std::nullopt; // no data
617 }
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800618
619 const RingBuffer<Movement>& movements = movementIt->second;
620 const size_t size = movements.size();
621 if (size == 0) {
622 return std::nullopt; // no data
623 }
624
Jeff Brown5912f952013-07-01 19:10:31 -0700625 // Iterate over movement samples in reverse time order and collect samples.
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000626 std::vector<float> positions;
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500627 std::vector<float> w;
628 std::vector<float> time;
629
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800630 const Movement& newestMovement = movements[size - 1];
631 for (ssize_t i = size - 1; i >= 0; i--) {
632 const Movement& movement = movements[i];
Jeff Brown5912f952013-07-01 19:10:31 -0700633
634 nsecs_t age = newestMovement.eventTime - movement.eventTime;
635 if (age > HORIZON) {
636 break;
637 }
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800638 positions.push_back(movement.position);
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800639 w.push_back(chooseWeight(pointerId, i));
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500640 time.push_back(-age * 0.000000001f);
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800641 }
Jeff Brown5912f952013-07-01 19:10:31 -0700642
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000643 const size_t m = positions.size();
Jeff Brown5912f952013-07-01 19:10:31 -0700644 if (m == 0) {
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800645 return std::nullopt; // no data
Jeff Brown5912f952013-07-01 19:10:31 -0700646 }
647
648 // Calculate a least squares polynomial fit.
649 uint32_t degree = mDegree;
650 if (degree > m - 1) {
651 degree = m - 1;
652 }
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700653
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800654 if (degree <= 0) {
655 return std::nullopt;
656 }
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800657 if (degree == 2 && mWeighting == Weighting::NONE) {
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700658 // Optimize unweighted, quadratic polynomial fit
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800659 return solveUnweightedLeastSquaresDeg2(time, positions);
Jeff Brown5912f952013-07-01 19:10:31 -0700660 }
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800661 // General case for an Nth degree polynomial fit
662 return solveLeastSquares(time, positions, w, degree + 1);
Jeff Brown5912f952013-07-01 19:10:31 -0700663}
664
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800665float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint32_t index) const {
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800666 const RingBuffer<Movement>& movements = mMovements.at(pointerId);
667 const size_t size = movements.size();
Jeff Brown5912f952013-07-01 19:10:31 -0700668 switch (mWeighting) {
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800669 case Weighting::DELTA: {
670 // Weight points based on how much time elapsed between them and the next
671 // point so that points that "cover" a shorter time span are weighed less.
672 // delta 0ms: 0.5
673 // delta 10ms: 1.0
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800674 if (index == size - 1) {
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800675 return 1.0f;
676 }
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800677 float deltaMillis =
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800678 (movements[index + 1].eventTime - movements[index].eventTime) * 0.000001f;
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800679 if (deltaMillis < 0) {
680 return 0.5f;
681 }
682 if (deltaMillis < 10) {
683 return 0.5f + deltaMillis * 0.05;
684 }
Jeff Brown5912f952013-07-01 19:10:31 -0700685 return 1.0f;
686 }
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800687
688 case Weighting::CENTRAL: {
689 // Weight points based on their age, weighing very recent and very old points less.
690 // age 0ms: 0.5
691 // age 10ms: 1.0
692 // age 50ms: 1.0
693 // age 60ms: 0.5
694 float ageMillis =
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800695 (movements[size - 1].eventTime - movements[index].eventTime) * 0.000001f;
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800696 if (ageMillis < 0) {
697 return 0.5f;
698 }
699 if (ageMillis < 10) {
700 return 0.5f + ageMillis * 0.05;
701 }
702 if (ageMillis < 50) {
703 return 1.0f;
704 }
705 if (ageMillis < 60) {
706 return 0.5f + (60 - ageMillis) * 0.05;
707 }
Jeff Brown5912f952013-07-01 19:10:31 -0700708 return 0.5f;
709 }
Jeff Brown5912f952013-07-01 19:10:31 -0700710
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800711 case Weighting::RECENT: {
712 // Weight points based on their age, weighing older points less.
713 // age 0ms: 1.0
714 // age 50ms: 1.0
715 // age 100ms: 0.5
716 float ageMillis =
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800717 (movements[size - 1].eventTime - movements[index].eventTime) * 0.000001f;
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800718 if (ageMillis < 50) {
719 return 1.0f;
720 }
721 if (ageMillis < 100) {
722 return 0.5f + (100 - ageMillis) * 0.01f;
723 }
Jeff Brown5912f952013-07-01 19:10:31 -0700724 return 0.5f;
725 }
Jeff Brown5912f952013-07-01 19:10:31 -0700726
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800727 case Weighting::NONE:
Jeff Brown5912f952013-07-01 19:10:31 -0700728 return 1.0f;
Jeff Brown5912f952013-07-01 19:10:31 -0700729 }
730}
731
Jeff Brown5912f952013-07-01 19:10:31 -0700732// --- IntegratingVelocityTrackerStrategy ---
733
734IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(uint32_t degree) :
735 mDegree(degree) {
736}
737
738IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {
739}
740
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800741void IntegratingVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
742 mPointerIdBits.clearBit(pointerId);
Jeff Brown5912f952013-07-01 19:10:31 -0700743}
744
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800745void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
746 float position) {
747 State& state = mPointerState[pointerId];
748 if (mPointerIdBits.hasBit(pointerId)) {
749 updateState(state, eventTime, position);
750 } else {
751 initState(state, eventTime, position);
Jeff Brown5912f952013-07-01 19:10:31 -0700752 }
753
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800754 mPointerIdBits.markBit(pointerId);
Jeff Brown5912f952013-07-01 19:10:31 -0700755}
756
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800757std::optional<float> IntegratingVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800758 if (mPointerIdBits.hasBit(pointerId)) {
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800759 return mPointerState[pointerId].vel;
Jeff Brown5912f952013-07-01 19:10:31 -0700760 }
761
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800762 return std::nullopt;
Jeff Brown5912f952013-07-01 19:10:31 -0700763}
764
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000765void IntegratingVelocityTrackerStrategy::initState(State& state, nsecs_t eventTime,
766 float pos) const {
Jeff Brown5912f952013-07-01 19:10:31 -0700767 state.updateTime = eventTime;
768 state.degree = 0;
769
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000770 state.pos = pos;
771 state.accel = 0;
772 state.vel = 0;
Jeff Brown5912f952013-07-01 19:10:31 -0700773}
774
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000775void IntegratingVelocityTrackerStrategy::updateState(State& state, nsecs_t eventTime,
776 float pos) const {
Jeff Brown5912f952013-07-01 19:10:31 -0700777 const nsecs_t MIN_TIME_DELTA = 2 * NANOS_PER_MS;
778 const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds
779
780 if (eventTime <= state.updateTime + MIN_TIME_DELTA) {
781 return;
782 }
783
784 float dt = (eventTime - state.updateTime) * 0.000000001f;
785 state.updateTime = eventTime;
786
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000787 float vel = (pos - state.pos) / dt;
Jeff Brown5912f952013-07-01 19:10:31 -0700788 if (state.degree == 0) {
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000789 state.vel = vel;
Jeff Brown5912f952013-07-01 19:10:31 -0700790 state.degree = 1;
791 } else {
792 float alpha = dt / (FILTER_TIME_CONSTANT + dt);
793 if (mDegree == 1) {
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000794 state.vel += (vel - state.vel) * alpha;
Jeff Brown5912f952013-07-01 19:10:31 -0700795 } else {
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000796 float accel = (vel - state.vel) / dt;
Jeff Brown5912f952013-07-01 19:10:31 -0700797 if (state.degree == 1) {
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000798 state.accel = accel;
Jeff Brown5912f952013-07-01 19:10:31 -0700799 state.degree = 2;
800 } else {
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000801 state.accel += (accel - state.accel) * alpha;
Jeff Brown5912f952013-07-01 19:10:31 -0700802 }
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000803 state.vel += (state.accel * dt) * alpha;
Jeff Brown5912f952013-07-01 19:10:31 -0700804 }
805 }
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000806 state.pos = pos;
Jeff Brown5912f952013-07-01 19:10:31 -0700807}
808
Jeff Brown5912f952013-07-01 19:10:31 -0700809// --- LegacyVelocityTrackerStrategy ---
810
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800811LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() {}
Jeff Brown5912f952013-07-01 19:10:31 -0700812
813LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() {
814}
815
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800816std::optional<float> LegacyVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800817 const auto movementIt = mMovements.find(pointerId);
818 if (movementIt == mMovements.end()) {
Siarhei Vishniakou657a1732023-01-12 11:58:52 -0800819 return std::nullopt; // no data
Jeff Brown5912f952013-07-01 19:10:31 -0700820 }
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800821
822 const RingBuffer<Movement>& movements = movementIt->second;
823 const size_t size = movements.size();
824 if (size == 0) {
825 return std::nullopt; // no data
826 }
827
828 const Movement& newestMovement = movements[size - 1];
Jeff Brown5912f952013-07-01 19:10:31 -0700829
830 // Find the oldest sample that contains the pointer and that is not older than HORIZON.
831 nsecs_t minTime = newestMovement.eventTime - HORIZON;
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800832 uint32_t oldestIndex = size - 1;
833 for (ssize_t i = size - 1; i >= 0; i--) {
834 const Movement& nextOldestMovement = movements[i];
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800835 if (nextOldestMovement.eventTime < minTime) {
Jeff Brown5912f952013-07-01 19:10:31 -0700836 break;
837 }
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800838 oldestIndex = i;
839 }
Jeff Brown5912f952013-07-01 19:10:31 -0700840
841 // Calculate an exponentially weighted moving average of the velocity estimate
842 // at different points in time measured relative to the oldest sample.
843 // This is essentially an IIR filter. Newer samples are weighted more heavily
844 // than older samples. Samples at equal time points are weighted more or less
845 // equally.
846 //
847 // One tricky problem is that the sample data may be poorly conditioned.
848 // Sometimes samples arrive very close together in time which can cause us to
849 // overestimate the velocity at that time point. Most samples might be measured
850 // 16ms apart but some consecutive samples could be only 0.5sm apart because
851 // the hardware or driver reports them irregularly or in bursts.
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000852 float accumV = 0;
Jeff Brown5912f952013-07-01 19:10:31 -0700853 uint32_t samplesUsed = 0;
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800854 const Movement& oldestMovement = movements[oldestIndex];
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800855 float oldestPosition = oldestMovement.position;
Jeff Brown5912f952013-07-01 19:10:31 -0700856 nsecs_t lastDuration = 0;
857
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -0800858 for (size_t i = oldestIndex; i < size; i++) {
859 const Movement& movement = movements[i];
Jeff Brown5912f952013-07-01 19:10:31 -0700860 nsecs_t duration = movement.eventTime - oldestMovement.eventTime;
861
862 // If the duration between samples is small, we may significantly overestimate
863 // the velocity. Consequently, we impose a minimum duration constraint on the
864 // samples that we include in the calculation.
865 if (duration >= MIN_DURATION) {
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800866 float position = movement.position;
Jeff Brown5912f952013-07-01 19:10:31 -0700867 float scale = 1000000000.0f / duration; // one over time delta in seconds
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +0000868 float v = (position - oldestPosition) * scale;
869 accumV = (accumV * lastDuration + v * duration) / (duration + lastDuration);
Jeff Brown5912f952013-07-01 19:10:31 -0700870 lastDuration = duration;
871 samplesUsed += 1;
872 }
873 }
874
Jeff Brown5912f952013-07-01 19:10:31 -0700875 if (samplesUsed) {
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800876 return accumV;
Jeff Brown5912f952013-07-01 19:10:31 -0700877 }
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -0800878 return std::nullopt;
Jeff Brown5912f952013-07-01 19:10:31 -0700879}
880
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +0100881// --- ImpulseVelocityTrackerStrategy ---
882
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -0700883ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy(bool deltaValues)
Siarhei Vishniakou8d232032023-01-11 08:17:21 -0800884 : mDeltaValues(deltaValues) {}
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +0100885
886ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() {
887}
888
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +0100889/**
890 * Calculate the total impulse provided to the screen and the resulting velocity.
891 *
892 * The touchscreen is modeled as a physical object.
893 * Initial condition is discussed below, but for now suppose that v(t=0) = 0
894 *
895 * The kinetic energy of the object at the release is E=0.5*m*v^2
896 * Then vfinal = sqrt(2E/m). The goal is to calculate E.
897 *
898 * The kinetic energy at the release is equal to the total work done on the object by the finger.
899 * The total work W is the sum of all dW along the path.
900 *
901 * dW = F*dx, where dx is the piece of path traveled.
902 * Force is change of momentum over time, F = dp/dt = m dv/dt.
903 * Then substituting:
904 * dW = m (dv/dt) * dx = m * v * dv
905 *
906 * Summing along the path, we get:
907 * W = sum(dW) = sum(m * v * dv) = m * sum(v * dv)
908 * Since the mass stays constant, the equation for final velocity is:
909 * vfinal = sqrt(2*sum(v * dv))
910 *
911 * Here,
912 * dv : change of velocity = (v[i+1]-v[i])
913 * dx : change of distance = (x[i+1]-x[i])
914 * dt : change of time = (t[i+1]-t[i])
915 * v : instantaneous velocity = dx/dt
916 *
917 * The final formula is:
918 * vfinal = sqrt(2) * sqrt(sum((v[i]-v[i-1])*|v[i]|)) for all i
919 * The absolute value is needed to properly account for the sign. If the velocity over a
920 * particular segment descreases, then this indicates braking, which means that negative
921 * work was done. So for two positive, but decreasing, velocities, this contribution would be
922 * negative and will cause a smaller final velocity.
923 *
924 * Initial condition
925 * There are two ways to deal with initial condition:
926 * 1) Assume that v(0) = 0, which would mean that the screen is initially at rest.
927 * This is not entirely accurate. We are only taking the past X ms of touch data, where X is
928 * currently equal to 100. However, a touch event that created a fling probably lasted for longer
929 * than that, which would mean that the user has already been interacting with the touchscreen
930 * and it has probably already been moving.
931 * 2) Assume that the touchscreen has already been moving at a certain velocity, calculate this
932 * initial velocity and the equivalent energy, and start with this initial energy.
933 * Consider an example where we have the following data, consisting of 3 points:
934 * time: t0, t1, t2
935 * x : x0, x1, x2
936 * v : 0 , v1, v2
937 * Here is what will happen in each of these scenarios:
938 * 1) By directly applying the formula above with the v(0) = 0 boundary condition, we will get
939 * vfinal = sqrt(2*(|v1|*(v1-v0) + |v2|*(v2-v1))). This can be simplified since v0=0
940 * vfinal = sqrt(2*(|v1|*v1 + |v2|*(v2-v1))) = sqrt(2*(v1^2 + |v2|*(v2 - v1)))
941 * since velocity is a real number
942 * 2) If we treat the screen as already moving, then it must already have an energy (per mass)
943 * equal to 1/2*v1^2. Then the initial energy should be 1/2*v1*2, and only the second segment
944 * will contribute to the total kinetic energy (since we can effectively consider that v0=v1).
945 * This will give the following expression for the final velocity:
946 * vfinal = sqrt(2*(1/2*v1^2 + |v2|*(v2-v1)))
947 * This analysis can be generalized to an arbitrary number of samples.
948 *
949 *
950 * Comparing the two equations above, we see that the only mathematical difference
951 * is the factor of 1/2 in front of the first velocity term.
952 * This boundary condition would allow for the "proper" calculation of the case when all of the
953 * samples are equally spaced in time and distance, which should suggest a constant velocity.
954 *
955 * Note that approach 2) is sensitive to the proper ordering of the data in time, since
956 * the boundary condition must be applied to the oldest sample to be accurate.
957 */
Siarhei Vishniakou97b5e182017-09-01 13:52:33 -0700958static float kineticEnergyToVelocity(float work) {
959 static constexpr float sqrt2 = 1.41421356237;
960 return (work < 0 ? -1.0 : 1.0) * sqrtf(fabsf(work)) * sqrt2;
961}
962
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -0700963static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t count,
964 bool deltaValues) {
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +0100965 // The input should be in reversed time order (most recent sample at index i=0)
966 // t[i] is in nanoseconds, but due to FP arithmetic, convert to seconds inside this function
Siarhei Vishniakou6de8f5e2018-03-02 18:48:15 -0800967 static constexpr float SECONDS_PER_NANO = 1E-9;
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +0100968
969 if (count < 2) {
970 return 0; // if 0 or 1 points, velocity is zero
971 }
972 if (t[1] > t[0]) { // Algorithm will still work, but not perfectly
973 ALOGE("Samples provided to calculateImpulseVelocity in the wrong order");
974 }
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -0700975
976 // If the data values are delta values, we do not have to calculate deltas here.
977 // We can use the delta values directly, along with the calculated time deltas.
978 // Since the data value input is in reversed time order:
979 // [a] for non-delta inputs, instantenous velocity = (x[i] - x[i-1])/(t[i] - t[i-1])
980 // [b] for delta inputs, instantenous velocity = -x[i-1]/(t[i] - t[i - 1])
981 // e.g., let the non-delta values are: V = [2, 3, 7], the equivalent deltas are D = [2, 1, 4].
982 // Since the input is in reversed time order, the input values for this function would be
983 // V'=[7, 3, 2] and D'=[4, 1, 2] for the non-delta and delta values, respectively.
984 //
985 // The equivalent of {(V'[2] - V'[1]) = 2 - 3 = -1} would be {-D'[1] = -1}
986 // Similarly, the equivalent of {(V'[1] - V'[0]) = 3 - 7 = -4} would be {-D'[0] = -4}
987
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +0100988 if (count == 2) { // if 2 points, basic linear calculation
989 if (t[1] == t[0]) {
990 ALOGE("Events have identical time stamps t=%" PRId64 ", setting velocity = 0", t[0]);
991 return 0;
992 }
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -0700993 const float deltaX = deltaValues ? -x[0] : x[1] - x[0];
994 return deltaX / (SECONDS_PER_NANO * (t[1] - t[0]));
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +0100995 }
996 // Guaranteed to have at least 3 points here
997 float work = 0;
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +0100998 for (size_t i = count - 1; i > 0 ; i--) { // start with the oldest sample and go forward in time
999 if (t[i] == t[i-1]) {
1000 ALOGE("Events have identical time stamps t=%" PRId64 ", skipping sample", t[i]);
1001 continue;
1002 }
Siarhei Vishniakou97b5e182017-09-01 13:52:33 -07001003 float vprev = kineticEnergyToVelocity(work); // v[i-1]
Yeabkal Wubshit0bb5e592022-09-14 01:22:28 -07001004 const float deltaX = deltaValues ? -x[i-1] : x[i] - x[i-1];
1005 float vcurr = deltaX / (SECONDS_PER_NANO * (t[i] - t[i-1])); // v[i]
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001006 work += (vcurr - vprev) * fabsf(vcurr);
1007 if (i == count - 1) {
1008 work *= 0.5; // initial condition, case 2) above
1009 }
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001010 }
Siarhei Vishniakou97b5e182017-09-01 13:52:33 -07001011 return kineticEnergyToVelocity(work);
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001012}
1013
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -08001014std::optional<float> ImpulseVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
Siarhei Vishniakou8d232032023-01-11 08:17:21 -08001015 const auto movementIt = mMovements.find(pointerId);
1016 if (movementIt == mMovements.end()) {
1017 return std::nullopt; // no data
1018 }
1019
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -08001020 const RingBuffer<Movement>& movements = movementIt->second;
1021 const size_t size = movements.size();
1022 if (size == 0) {
1023 return std::nullopt; // no data
1024 }
1025
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001026 // Iterate over movement samples in reverse time order and collect samples.
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +00001027 float positions[HISTORY_SIZE];
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001028 nsecs_t time[HISTORY_SIZE];
1029 size_t m = 0; // number of points that will be used for fitting
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -08001030 const Movement& newestMovement = movements[size - 1];
1031 for (ssize_t i = size - 1; i >= 0; i--) {
1032 const Movement& movement = movements[i];
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001033
1034 nsecs_t age = newestMovement.eventTime - movement.eventTime;
1035 if (age > HORIZON) {
1036 break;
1037 }
1038
Siarhei Vishniakou8d232032023-01-11 08:17:21 -08001039 positions[m] = movement.position;
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001040 time[m] = movement.eventTime;
Yeabkal Wubshit64f090f2023-03-03 17:35:11 -08001041 m++;
1042 }
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001043
1044 if (m == 0) {
Siarhei Vishniakou657a1732023-01-12 11:58:52 -08001045 return std::nullopt; // no data
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001046 }
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +00001047
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -08001048 const float velocity = calculateImpulseVelocity(time, positions, m, mDeltaValues);
1049 ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", velocity);
Siarhei Vishniakou9f26fc32022-06-17 22:13:57 +00001050
Siarhei Vishniakou276467b2022-03-17 09:43:28 -07001051 if (DEBUG_IMPULSE) {
1052 // TODO(b/134179997): delete this block once the switch to 'impulse' is complete.
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +00001053 // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons.
1054 // X axis chosen arbitrarily for velocity comparisons.
Siarhei Vishniakou276467b2022-03-17 09:43:28 -07001055 VelocityTracker lsq2(VelocityTracker::Strategy::LSQ2);
Siarhei Vishniakou276467b2022-03-17 09:43:28 -07001056 for (ssize_t i = m - 1; i >= 0; i--) {
Siarhei Vishniakou8d232032023-01-11 08:17:21 -08001057 lsq2.addMovement(time[i], pointerId, AMOTION_EVENT_AXIS_X, positions[i]);
Siarhei Vishniakou276467b2022-03-17 09:43:28 -07001058 }
Siarhei Vishniakou8d232032023-01-11 08:17:21 -08001059 std::optional<float> v = lsq2.getVelocity(AMOTION_EVENT_AXIS_X, pointerId);
Yeabkal Wubshit384ab0f2022-09-09 16:39:18 +00001060 if (v) {
1061 ALOGD("lsq2 velocity: %.1f", *v);
Siarhei Vishniakou276467b2022-03-17 09:43:28 -07001062 } else {
1063 ALOGD("lsq2 velocity: could not compute velocity");
1064 }
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -07001065 }
Yeabkal Wubshit9b4443f2023-02-23 23:35:07 -08001066 return velocity;
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001067}
1068
Jeff Brown5912f952013-07-01 19:10:31 -07001069} // namespace android