1*38e8c45fSAndroid Build Coastguard Worker /*
2*38e8c45fSAndroid Build Coastguard Worker * Copyright (C) 2012 The Android Open Source Project
3*38e8c45fSAndroid Build Coastguard Worker *
4*38e8c45fSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License");
5*38e8c45fSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License.
6*38e8c45fSAndroid Build Coastguard Worker * You may obtain a copy of the License at
7*38e8c45fSAndroid Build Coastguard Worker *
8*38e8c45fSAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0
9*38e8c45fSAndroid Build Coastguard Worker *
10*38e8c45fSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software
11*38e8c45fSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS,
12*38e8c45fSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*38e8c45fSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and
14*38e8c45fSAndroid Build Coastguard Worker * limitations under the License.
15*38e8c45fSAndroid Build Coastguard Worker */
16*38e8c45fSAndroid Build Coastguard Worker
17*38e8c45fSAndroid Build Coastguard Worker #define LOG_TAG "VelocityTracker"
18*38e8c45fSAndroid Build Coastguard Worker
19*38e8c45fSAndroid Build Coastguard Worker #include <android-base/logging.h>
20*38e8c45fSAndroid Build Coastguard Worker #include <ftl/enum.h>
21*38e8c45fSAndroid Build Coastguard Worker #include <inttypes.h>
22*38e8c45fSAndroid Build Coastguard Worker #include <limits.h>
23*38e8c45fSAndroid Build Coastguard Worker #include <math.h>
24*38e8c45fSAndroid Build Coastguard Worker #include <array>
25*38e8c45fSAndroid Build Coastguard Worker #include <optional>
26*38e8c45fSAndroid Build Coastguard Worker
27*38e8c45fSAndroid Build Coastguard Worker #include <input/PrintTools.h>
28*38e8c45fSAndroid Build Coastguard Worker #include <input/VelocityTracker.h>
29*38e8c45fSAndroid Build Coastguard Worker #include <utils/BitSet.h>
30*38e8c45fSAndroid Build Coastguard Worker #include <utils/Timers.h>
31*38e8c45fSAndroid Build Coastguard Worker
32*38e8c45fSAndroid Build Coastguard Worker using std::literals::chrono_literals::operator""ms;
33*38e8c45fSAndroid Build Coastguard Worker
34*38e8c45fSAndroid Build Coastguard Worker namespace android {
35*38e8c45fSAndroid Build Coastguard Worker
36*38e8c45fSAndroid Build Coastguard Worker /**
37*38e8c45fSAndroid Build Coastguard Worker * Log debug messages about velocity tracking.
38*38e8c45fSAndroid Build Coastguard Worker * Enable this via "adb shell setprop log.tag.VelocityTrackerVelocity DEBUG" (requires restart)
39*38e8c45fSAndroid Build Coastguard Worker */
40*38e8c45fSAndroid Build Coastguard Worker const bool DEBUG_VELOCITY =
41*38e8c45fSAndroid Build Coastguard Worker __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Velocity", ANDROID_LOG_INFO);
42*38e8c45fSAndroid Build Coastguard Worker
43*38e8c45fSAndroid Build Coastguard Worker /**
44*38e8c45fSAndroid Build Coastguard Worker * Log debug messages about the progress of the algorithm itself.
45*38e8c45fSAndroid Build Coastguard Worker * Enable this via "adb shell setprop log.tag.VelocityTrackerStrategy DEBUG" (requires restart)
46*38e8c45fSAndroid Build Coastguard Worker */
47*38e8c45fSAndroid Build Coastguard Worker const bool DEBUG_STRATEGY =
48*38e8c45fSAndroid Build Coastguard Worker __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Strategy", ANDROID_LOG_INFO);
49*38e8c45fSAndroid Build Coastguard Worker
50*38e8c45fSAndroid Build Coastguard Worker /**
51*38e8c45fSAndroid Build Coastguard Worker * Log debug messages about the 'impulse' strategy.
52*38e8c45fSAndroid Build Coastguard Worker * Enable this via "adb shell setprop log.tag.VelocityTrackerImpulse DEBUG" (requires restart)
53*38e8c45fSAndroid Build Coastguard Worker */
54*38e8c45fSAndroid Build Coastguard Worker const bool DEBUG_IMPULSE =
55*38e8c45fSAndroid Build Coastguard Worker __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Impulse", ANDROID_LOG_INFO);
56*38e8c45fSAndroid Build Coastguard Worker
57*38e8c45fSAndroid Build Coastguard Worker // Nanoseconds per milliseconds.
58*38e8c45fSAndroid Build Coastguard Worker static const nsecs_t NANOS_PER_MS = 1000000;
59*38e8c45fSAndroid Build Coastguard Worker
60*38e8c45fSAndroid Build Coastguard Worker // Seconds per nanosecond.
61*38e8c45fSAndroid Build Coastguard Worker static const float SECONDS_PER_NANO = 1E-9;
62*38e8c45fSAndroid Build Coastguard Worker
63*38e8c45fSAndroid Build Coastguard Worker // All axes supported for velocity tracking, mapped to their default strategies.
64*38e8c45fSAndroid Build Coastguard Worker // Although other strategies are available for testing and comparison purposes,
65*38e8c45fSAndroid Build Coastguard Worker // the default strategy is the one that applications will actually use. Be very careful
66*38e8c45fSAndroid Build Coastguard Worker // when adjusting the default strategy because it can dramatically affect
67*38e8c45fSAndroid Build Coastguard Worker // (often in a bad way) the user experience.
68*38e8c45fSAndroid Build Coastguard Worker static const std::map<int32_t, VelocityTracker::Strategy> DEFAULT_STRATEGY_BY_AXIS =
69*38e8c45fSAndroid Build Coastguard Worker {{AMOTION_EVENT_AXIS_X, VelocityTracker::Strategy::LSQ2},
70*38e8c45fSAndroid Build Coastguard Worker {AMOTION_EVENT_AXIS_Y, VelocityTracker::Strategy::LSQ2},
71*38e8c45fSAndroid Build Coastguard Worker {AMOTION_EVENT_AXIS_SCROLL, VelocityTracker::Strategy::IMPULSE}};
72*38e8c45fSAndroid Build Coastguard Worker
73*38e8c45fSAndroid Build Coastguard Worker // Axes specifying location on a 2D plane (i.e. X and Y).
74*38e8c45fSAndroid Build Coastguard Worker static const std::set<int32_t> PLANAR_AXES = {AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y};
75*38e8c45fSAndroid Build Coastguard Worker
76*38e8c45fSAndroid Build Coastguard Worker // Axes whose motion values are differential values (i.e. deltas).
77*38e8c45fSAndroid Build Coastguard Worker static const std::set<int32_t> DIFFERENTIAL_AXES = {AMOTION_EVENT_AXIS_SCROLL};
78*38e8c45fSAndroid Build Coastguard Worker
79*38e8c45fSAndroid Build Coastguard Worker // Threshold for determining that a pointer has stopped moving.
80*38e8c45fSAndroid Build Coastguard Worker // Some input devices do not send ACTION_MOVE events in the case where a pointer has
81*38e8c45fSAndroid Build Coastguard Worker // stopped. We need to detect this case so that we can accurately predict the
82*38e8c45fSAndroid Build Coastguard Worker // velocity after the pointer starts moving again.
83*38e8c45fSAndroid Build Coastguard Worker static const std::chrono::duration ASSUME_POINTER_STOPPED_TIME = 40ms;
84*38e8c45fSAndroid Build Coastguard Worker
toString(std::chrono::nanoseconds t)85*38e8c45fSAndroid Build Coastguard Worker static std::string toString(std::chrono::nanoseconds t) {
86*38e8c45fSAndroid Build Coastguard Worker std::stringstream stream;
87*38e8c45fSAndroid Build Coastguard Worker stream.precision(1);
88*38e8c45fSAndroid Build Coastguard Worker stream << std::fixed << std::chrono::duration<float, std::milli>(t).count() << " ms";
89*38e8c45fSAndroid Build Coastguard Worker return stream.str();
90*38e8c45fSAndroid Build Coastguard Worker }
91*38e8c45fSAndroid Build Coastguard Worker
vectorDot(const float * a,const float * b,uint32_t m)92*38e8c45fSAndroid Build Coastguard Worker static float vectorDot(const float* a, const float* b, uint32_t m) {
93*38e8c45fSAndroid Build Coastguard Worker float r = 0;
94*38e8c45fSAndroid Build Coastguard Worker for (size_t i = 0; i < m; i++) {
95*38e8c45fSAndroid Build Coastguard Worker r += *(a++) * *(b++);
96*38e8c45fSAndroid Build Coastguard Worker }
97*38e8c45fSAndroid Build Coastguard Worker return r;
98*38e8c45fSAndroid Build Coastguard Worker }
99*38e8c45fSAndroid Build Coastguard Worker
vectorNorm(const float * a,uint32_t m)100*38e8c45fSAndroid Build Coastguard Worker static float vectorNorm(const float* a, uint32_t m) {
101*38e8c45fSAndroid Build Coastguard Worker float r = 0;
102*38e8c45fSAndroid Build Coastguard Worker for (size_t i = 0; i < m; i++) {
103*38e8c45fSAndroid Build Coastguard Worker float t = *(a++);
104*38e8c45fSAndroid Build Coastguard Worker r += t * t;
105*38e8c45fSAndroid Build Coastguard Worker }
106*38e8c45fSAndroid Build Coastguard Worker return sqrtf(r);
107*38e8c45fSAndroid Build Coastguard Worker }
108*38e8c45fSAndroid Build Coastguard Worker
vectorToString(const float * a,uint32_t m)109*38e8c45fSAndroid Build Coastguard Worker static std::string vectorToString(const float* a, uint32_t m) {
110*38e8c45fSAndroid Build Coastguard Worker std::string str;
111*38e8c45fSAndroid Build Coastguard Worker str += "[";
112*38e8c45fSAndroid Build Coastguard Worker for (size_t i = 0; i < m; i++) {
113*38e8c45fSAndroid Build Coastguard Worker if (i) {
114*38e8c45fSAndroid Build Coastguard Worker str += ",";
115*38e8c45fSAndroid Build Coastguard Worker }
116*38e8c45fSAndroid Build Coastguard Worker str += android::base::StringPrintf(" %f", *(a++));
117*38e8c45fSAndroid Build Coastguard Worker }
118*38e8c45fSAndroid Build Coastguard Worker str += " ]";
119*38e8c45fSAndroid Build Coastguard Worker return str;
120*38e8c45fSAndroid Build Coastguard Worker }
121*38e8c45fSAndroid Build Coastguard Worker
vectorToString(const std::vector<float> & v)122*38e8c45fSAndroid Build Coastguard Worker static std::string vectorToString(const std::vector<float>& v) {
123*38e8c45fSAndroid Build Coastguard Worker return vectorToString(v.data(), v.size());
124*38e8c45fSAndroid Build Coastguard Worker }
125*38e8c45fSAndroid Build Coastguard Worker
matrixToString(const float * a,uint32_t m,uint32_t n,bool rowMajor)126*38e8c45fSAndroid Build Coastguard Worker static std::string matrixToString(const float* a, uint32_t m, uint32_t n, bool rowMajor) {
127*38e8c45fSAndroid Build Coastguard Worker std::string str;
128*38e8c45fSAndroid Build Coastguard Worker str = "[";
129*38e8c45fSAndroid Build Coastguard Worker for (size_t i = 0; i < m; i++) {
130*38e8c45fSAndroid Build Coastguard Worker if (i) {
131*38e8c45fSAndroid Build Coastguard Worker str += ",";
132*38e8c45fSAndroid Build Coastguard Worker }
133*38e8c45fSAndroid Build Coastguard Worker str += " [";
134*38e8c45fSAndroid Build Coastguard Worker for (size_t j = 0; j < n; j++) {
135*38e8c45fSAndroid Build Coastguard Worker if (j) {
136*38e8c45fSAndroid Build Coastguard Worker str += ",";
137*38e8c45fSAndroid Build Coastguard Worker }
138*38e8c45fSAndroid Build Coastguard Worker str += android::base::StringPrintf(" %f", a[rowMajor ? i * n + j : j * m + i]);
139*38e8c45fSAndroid Build Coastguard Worker }
140*38e8c45fSAndroid Build Coastguard Worker str += " ]";
141*38e8c45fSAndroid Build Coastguard Worker }
142*38e8c45fSAndroid Build Coastguard Worker str += " ]";
143*38e8c45fSAndroid Build Coastguard Worker return str;
144*38e8c45fSAndroid Build Coastguard Worker }
145*38e8c45fSAndroid Build Coastguard Worker
146*38e8c45fSAndroid Build Coastguard Worker
147*38e8c45fSAndroid Build Coastguard Worker // --- VelocityTracker ---
148*38e8c45fSAndroid Build Coastguard Worker
VelocityTracker(const Strategy strategy)149*38e8c45fSAndroid Build Coastguard Worker VelocityTracker::VelocityTracker(const Strategy strategy)
150*38e8c45fSAndroid Build Coastguard Worker : mLastEventTime(0), mCurrentPointerIdBits(0), mOverrideStrategy(strategy) {}
151*38e8c45fSAndroid Build Coastguard Worker
isAxisSupported(int32_t axis)152*38e8c45fSAndroid Build Coastguard Worker bool VelocityTracker::isAxisSupported(int32_t axis) {
153*38e8c45fSAndroid Build Coastguard Worker return DEFAULT_STRATEGY_BY_AXIS.find(axis) != DEFAULT_STRATEGY_BY_AXIS.end();
154*38e8c45fSAndroid Build Coastguard Worker }
155*38e8c45fSAndroid Build Coastguard Worker
configureStrategy(int32_t axis)156*38e8c45fSAndroid Build Coastguard Worker void VelocityTracker::configureStrategy(int32_t axis) {
157*38e8c45fSAndroid Build Coastguard Worker const bool isDifferentialAxis = DIFFERENTIAL_AXES.find(axis) != DIFFERENTIAL_AXES.end();
158*38e8c45fSAndroid Build Coastguard Worker if (isDifferentialAxis || mOverrideStrategy == VelocityTracker::Strategy::DEFAULT) {
159*38e8c45fSAndroid Build Coastguard Worker // Do not allow overrides of strategies for differential axes, for now.
160*38e8c45fSAndroid Build Coastguard Worker mConfiguredStrategies[axis] = createStrategy(DEFAULT_STRATEGY_BY_AXIS.at(axis),
161*38e8c45fSAndroid Build Coastguard Worker /*deltaValues=*/isDifferentialAxis);
162*38e8c45fSAndroid Build Coastguard Worker } else {
163*38e8c45fSAndroid Build Coastguard Worker mConfiguredStrategies[axis] = createStrategy(mOverrideStrategy, /*deltaValues=*/false);
164*38e8c45fSAndroid Build Coastguard Worker }
165*38e8c45fSAndroid Build Coastguard Worker }
166*38e8c45fSAndroid Build Coastguard Worker
createStrategy(VelocityTracker::Strategy strategy,bool deltaValues)167*38e8c45fSAndroid Build Coastguard Worker std::unique_ptr<VelocityTrackerStrategy> VelocityTracker::createStrategy(
168*38e8c45fSAndroid Build Coastguard Worker VelocityTracker::Strategy strategy, bool deltaValues) {
169*38e8c45fSAndroid Build Coastguard Worker switch (strategy) {
170*38e8c45fSAndroid Build Coastguard Worker case VelocityTracker::Strategy::IMPULSE:
171*38e8c45fSAndroid Build Coastguard Worker ALOGI_IF(DEBUG_STRATEGY, "Initializing impulse strategy");
172*38e8c45fSAndroid Build Coastguard Worker return std::make_unique<ImpulseVelocityTrackerStrategy>(deltaValues);
173*38e8c45fSAndroid Build Coastguard Worker
174*38e8c45fSAndroid Build Coastguard Worker case VelocityTracker::Strategy::LSQ1:
175*38e8c45fSAndroid Build Coastguard Worker return std::make_unique<LeastSquaresVelocityTrackerStrategy>(1);
176*38e8c45fSAndroid Build Coastguard Worker
177*38e8c45fSAndroid Build Coastguard Worker case VelocityTracker::Strategy::LSQ2:
178*38e8c45fSAndroid Build Coastguard Worker ALOGI_IF(DEBUG_STRATEGY && !DEBUG_IMPULSE, "Initializing lsq2 strategy");
179*38e8c45fSAndroid Build Coastguard Worker return std::make_unique<LeastSquaresVelocityTrackerStrategy>(2);
180*38e8c45fSAndroid Build Coastguard Worker
181*38e8c45fSAndroid Build Coastguard Worker case VelocityTracker::Strategy::LSQ3:
182*38e8c45fSAndroid Build Coastguard Worker return std::make_unique<LeastSquaresVelocityTrackerStrategy>(3);
183*38e8c45fSAndroid Build Coastguard Worker
184*38e8c45fSAndroid Build Coastguard Worker case VelocityTracker::Strategy::WLSQ2_DELTA:
185*38e8c45fSAndroid Build Coastguard Worker return std::make_unique<
186*38e8c45fSAndroid Build Coastguard Worker LeastSquaresVelocityTrackerStrategy>(2,
187*38e8c45fSAndroid Build Coastguard Worker LeastSquaresVelocityTrackerStrategy::
188*38e8c45fSAndroid Build Coastguard Worker Weighting::DELTA);
189*38e8c45fSAndroid Build Coastguard Worker case VelocityTracker::Strategy::WLSQ2_CENTRAL:
190*38e8c45fSAndroid Build Coastguard Worker return std::make_unique<
191*38e8c45fSAndroid Build Coastguard Worker LeastSquaresVelocityTrackerStrategy>(2,
192*38e8c45fSAndroid Build Coastguard Worker LeastSquaresVelocityTrackerStrategy::
193*38e8c45fSAndroid Build Coastguard Worker Weighting::CENTRAL);
194*38e8c45fSAndroid Build Coastguard Worker case VelocityTracker::Strategy::WLSQ2_RECENT:
195*38e8c45fSAndroid Build Coastguard Worker return std::make_unique<
196*38e8c45fSAndroid Build Coastguard Worker LeastSquaresVelocityTrackerStrategy>(2,
197*38e8c45fSAndroid Build Coastguard Worker LeastSquaresVelocityTrackerStrategy::
198*38e8c45fSAndroid Build Coastguard Worker Weighting::RECENT);
199*38e8c45fSAndroid Build Coastguard Worker
200*38e8c45fSAndroid Build Coastguard Worker case VelocityTracker::Strategy::INT1:
201*38e8c45fSAndroid Build Coastguard Worker return std::make_unique<IntegratingVelocityTrackerStrategy>(1);
202*38e8c45fSAndroid Build Coastguard Worker
203*38e8c45fSAndroid Build Coastguard Worker case VelocityTracker::Strategy::INT2:
204*38e8c45fSAndroid Build Coastguard Worker return std::make_unique<IntegratingVelocityTrackerStrategy>(2);
205*38e8c45fSAndroid Build Coastguard Worker
206*38e8c45fSAndroid Build Coastguard Worker case VelocityTracker::Strategy::LEGACY:
207*38e8c45fSAndroid Build Coastguard Worker return std::make_unique<LegacyVelocityTrackerStrategy>();
208*38e8c45fSAndroid Build Coastguard Worker
209*38e8c45fSAndroid Build Coastguard Worker default:
210*38e8c45fSAndroid Build Coastguard Worker break;
211*38e8c45fSAndroid Build Coastguard Worker }
212*38e8c45fSAndroid Build Coastguard Worker LOG(FATAL) << "Invalid strategy: " << ftl::enum_string(strategy)
213*38e8c45fSAndroid Build Coastguard Worker << ", deltaValues = " << deltaValues;
214*38e8c45fSAndroid Build Coastguard Worker
215*38e8c45fSAndroid Build Coastguard Worker return nullptr;
216*38e8c45fSAndroid Build Coastguard Worker }
217*38e8c45fSAndroid Build Coastguard Worker
clear()218*38e8c45fSAndroid Build Coastguard Worker void VelocityTracker::clear() {
219*38e8c45fSAndroid Build Coastguard Worker mCurrentPointerIdBits.clear();
220*38e8c45fSAndroid Build Coastguard Worker mActivePointerId = std::nullopt;
221*38e8c45fSAndroid Build Coastguard Worker mConfiguredStrategies.clear();
222*38e8c45fSAndroid Build Coastguard Worker }
223*38e8c45fSAndroid Build Coastguard Worker
clearPointer(int32_t pointerId)224*38e8c45fSAndroid Build Coastguard Worker void VelocityTracker::clearPointer(int32_t pointerId) {
225*38e8c45fSAndroid Build Coastguard Worker mCurrentPointerIdBits.clearBit(pointerId);
226*38e8c45fSAndroid Build Coastguard Worker
227*38e8c45fSAndroid Build Coastguard Worker if (mActivePointerId && *mActivePointerId == pointerId) {
228*38e8c45fSAndroid Build Coastguard Worker // The active pointer id is being removed. Mark it invalid and try to find a new one
229*38e8c45fSAndroid Build Coastguard Worker // from the remaining pointers.
230*38e8c45fSAndroid Build Coastguard Worker mActivePointerId = std::nullopt;
231*38e8c45fSAndroid Build Coastguard Worker if (!mCurrentPointerIdBits.isEmpty()) {
232*38e8c45fSAndroid Build Coastguard Worker mActivePointerId = mCurrentPointerIdBits.firstMarkedBit();
233*38e8c45fSAndroid Build Coastguard Worker }
234*38e8c45fSAndroid Build Coastguard Worker }
235*38e8c45fSAndroid Build Coastguard Worker
236*38e8c45fSAndroid Build Coastguard Worker for (const auto& [_, strategy] : mConfiguredStrategies) {
237*38e8c45fSAndroid Build Coastguard Worker strategy->clearPointer(pointerId);
238*38e8c45fSAndroid Build Coastguard Worker }
239*38e8c45fSAndroid Build Coastguard Worker }
240*38e8c45fSAndroid Build Coastguard Worker
addMovement(nsecs_t eventTime,int32_t pointerId,int32_t axis,float position)241*38e8c45fSAndroid Build Coastguard Worker void VelocityTracker::addMovement(nsecs_t eventTime, int32_t pointerId, int32_t axis,
242*38e8c45fSAndroid Build Coastguard Worker float position) {
243*38e8c45fSAndroid Build Coastguard Worker if (pointerId < 0 || pointerId > MAX_POINTER_ID) {
244*38e8c45fSAndroid Build Coastguard Worker LOG(FATAL) << "Invalid pointer ID " << pointerId << " for axis "
245*38e8c45fSAndroid Build Coastguard Worker << MotionEvent::getLabel(axis);
246*38e8c45fSAndroid Build Coastguard Worker }
247*38e8c45fSAndroid Build Coastguard Worker
248*38e8c45fSAndroid Build Coastguard Worker if (mCurrentPointerIdBits.hasBit(pointerId) &&
249*38e8c45fSAndroid Build Coastguard Worker std::chrono::nanoseconds(eventTime - mLastEventTime) > ASSUME_POINTER_STOPPED_TIME) {
250*38e8c45fSAndroid Build Coastguard Worker ALOGD_IF(DEBUG_VELOCITY, "VelocityTracker: stopped for %s, clearing state.",
251*38e8c45fSAndroid Build Coastguard Worker toString(std::chrono::nanoseconds(eventTime - mLastEventTime)).c_str());
252*38e8c45fSAndroid Build Coastguard Worker
253*38e8c45fSAndroid Build Coastguard Worker // We have not received any movements for too long. Assume that all pointers
254*38e8c45fSAndroid Build Coastguard Worker // have stopped.
255*38e8c45fSAndroid Build Coastguard Worker mConfiguredStrategies.clear();
256*38e8c45fSAndroid Build Coastguard Worker }
257*38e8c45fSAndroid Build Coastguard Worker mLastEventTime = eventTime;
258*38e8c45fSAndroid Build Coastguard Worker
259*38e8c45fSAndroid Build Coastguard Worker mCurrentPointerIdBits.markBit(pointerId);
260*38e8c45fSAndroid Build Coastguard Worker if (!mActivePointerId) {
261*38e8c45fSAndroid Build Coastguard Worker // Let this be the new active pointer if no active pointer is currently set
262*38e8c45fSAndroid Build Coastguard Worker mActivePointerId = pointerId;
263*38e8c45fSAndroid Build Coastguard Worker }
264*38e8c45fSAndroid Build Coastguard Worker
265*38e8c45fSAndroid Build Coastguard Worker if (mConfiguredStrategies.find(axis) == mConfiguredStrategies.end()) {
266*38e8c45fSAndroid Build Coastguard Worker configureStrategy(axis);
267*38e8c45fSAndroid Build Coastguard Worker }
268*38e8c45fSAndroid Build Coastguard Worker mConfiguredStrategies[axis]->addMovement(eventTime, pointerId, position);
269*38e8c45fSAndroid Build Coastguard Worker
270*38e8c45fSAndroid Build Coastguard Worker if (DEBUG_VELOCITY) {
271*38e8c45fSAndroid Build Coastguard Worker LOG(INFO) << "VelocityTracker: addMovement axis=" << MotionEvent::getLabel(axis)
272*38e8c45fSAndroid Build Coastguard Worker << ", eventTime=" << eventTime << ", pointerId=" << pointerId
273*38e8c45fSAndroid Build Coastguard Worker << ", activePointerId=" << toString(mActivePointerId) << ", position=" << position
274*38e8c45fSAndroid Build Coastguard Worker << ", velocity=" << toString(getVelocity(axis, pointerId));
275*38e8c45fSAndroid Build Coastguard Worker }
276*38e8c45fSAndroid Build Coastguard Worker }
277*38e8c45fSAndroid Build Coastguard Worker
addMovement(const MotionEvent & event)278*38e8c45fSAndroid Build Coastguard Worker void VelocityTracker::addMovement(const MotionEvent& event) {
279*38e8c45fSAndroid Build Coastguard Worker // Stores data about which axes to process based on the incoming motion event.
280*38e8c45fSAndroid Build Coastguard Worker std::set<int32_t> axesToProcess;
281*38e8c45fSAndroid Build Coastguard Worker int32_t actionMasked = event.getActionMasked();
282*38e8c45fSAndroid Build Coastguard Worker
283*38e8c45fSAndroid Build Coastguard Worker switch (actionMasked) {
284*38e8c45fSAndroid Build Coastguard Worker case AMOTION_EVENT_ACTION_DOWN:
285*38e8c45fSAndroid Build Coastguard Worker case AMOTION_EVENT_ACTION_HOVER_ENTER:
286*38e8c45fSAndroid Build Coastguard Worker // Clear all pointers on down before adding the new movement.
287*38e8c45fSAndroid Build Coastguard Worker clear();
288*38e8c45fSAndroid Build Coastguard Worker axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
289*38e8c45fSAndroid Build Coastguard Worker break;
290*38e8c45fSAndroid Build Coastguard Worker case AMOTION_EVENT_ACTION_POINTER_DOWN: {
291*38e8c45fSAndroid Build Coastguard Worker // Start a new movement trace for a pointer that just went down.
292*38e8c45fSAndroid Build Coastguard Worker // We do this on down instead of on up because the client may want to query the
293*38e8c45fSAndroid Build Coastguard Worker // final velocity for a pointer that just went up.
294*38e8c45fSAndroid Build Coastguard Worker clearPointer(event.getPointerId(event.getActionIndex()));
295*38e8c45fSAndroid Build Coastguard Worker axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
296*38e8c45fSAndroid Build Coastguard Worker break;
297*38e8c45fSAndroid Build Coastguard Worker }
298*38e8c45fSAndroid Build Coastguard Worker case AMOTION_EVENT_ACTION_MOVE:
299*38e8c45fSAndroid Build Coastguard Worker case AMOTION_EVENT_ACTION_HOVER_MOVE:
300*38e8c45fSAndroid Build Coastguard Worker axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
301*38e8c45fSAndroid Build Coastguard Worker break;
302*38e8c45fSAndroid Build Coastguard Worker case AMOTION_EVENT_ACTION_POINTER_UP:
303*38e8c45fSAndroid Build Coastguard Worker if (event.getFlags() & AMOTION_EVENT_FLAG_CANCELED) {
304*38e8c45fSAndroid Build Coastguard Worker clearPointer(event.getPointerId(event.getActionIndex()));
305*38e8c45fSAndroid Build Coastguard Worker return;
306*38e8c45fSAndroid Build Coastguard Worker }
307*38e8c45fSAndroid Build Coastguard Worker // Continue to ACTION_UP to ensure that the POINTER_STOPPED logic is triggered.
308*38e8c45fSAndroid Build Coastguard Worker [[fallthrough]];
309*38e8c45fSAndroid Build Coastguard Worker case AMOTION_EVENT_ACTION_UP: {
310*38e8c45fSAndroid Build Coastguard Worker std::chrono::nanoseconds delaySinceLastEvent(event.getEventTime() - mLastEventTime);
311*38e8c45fSAndroid Build Coastguard Worker if (delaySinceLastEvent > ASSUME_POINTER_STOPPED_TIME) {
312*38e8c45fSAndroid Build Coastguard Worker ALOGD_IF(DEBUG_VELOCITY,
313*38e8c45fSAndroid Build Coastguard Worker "VelocityTracker: stopped for %s, clearing state upon pointer liftoff.",
314*38e8c45fSAndroid Build Coastguard Worker toString(delaySinceLastEvent).c_str());
315*38e8c45fSAndroid Build Coastguard Worker // We have not received any movements for too long. Assume that all pointers
316*38e8c45fSAndroid Build Coastguard Worker // have stopped.
317*38e8c45fSAndroid Build Coastguard Worker for (int32_t axis : PLANAR_AXES) {
318*38e8c45fSAndroid Build Coastguard Worker mConfiguredStrategies.erase(axis);
319*38e8c45fSAndroid Build Coastguard Worker }
320*38e8c45fSAndroid Build Coastguard Worker }
321*38e8c45fSAndroid Build Coastguard Worker // These actions because they do not convey any new information about
322*38e8c45fSAndroid Build Coastguard Worker // pointer movement. We also want to preserve the last known velocity of the pointers.
323*38e8c45fSAndroid Build Coastguard Worker // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position
324*38e8c45fSAndroid Build Coastguard Worker // of the pointers that went up. ACTION_POINTER_UP does include the new position of
325*38e8c45fSAndroid Build Coastguard Worker // pointers that remained down but we will also receive an ACTION_MOVE with this
326*38e8c45fSAndroid Build Coastguard Worker // information if any of them actually moved. Since we don't know how many pointers
327*38e8c45fSAndroid Build Coastguard Worker // will be going up at once it makes sense to just wait for the following ACTION_MOVE
328*38e8c45fSAndroid Build Coastguard Worker // before adding the movement.
329*38e8c45fSAndroid Build Coastguard Worker return;
330*38e8c45fSAndroid Build Coastguard Worker }
331*38e8c45fSAndroid Build Coastguard Worker case AMOTION_EVENT_ACTION_SCROLL:
332*38e8c45fSAndroid Build Coastguard Worker axesToProcess.insert(AMOTION_EVENT_AXIS_SCROLL);
333*38e8c45fSAndroid Build Coastguard Worker break;
334*38e8c45fSAndroid Build Coastguard Worker case AMOTION_EVENT_ACTION_CANCEL: {
335*38e8c45fSAndroid Build Coastguard Worker clear();
336*38e8c45fSAndroid Build Coastguard Worker return;
337*38e8c45fSAndroid Build Coastguard Worker }
338*38e8c45fSAndroid Build Coastguard Worker
339*38e8c45fSAndroid Build Coastguard Worker default:
340*38e8c45fSAndroid Build Coastguard Worker // Ignore all other actions.
341*38e8c45fSAndroid Build Coastguard Worker return;
342*38e8c45fSAndroid Build Coastguard Worker }
343*38e8c45fSAndroid Build Coastguard Worker
344*38e8c45fSAndroid Build Coastguard Worker const size_t historySize = event.getHistorySize();
345*38e8c45fSAndroid Build Coastguard Worker for (size_t h = 0; h <= historySize; h++) {
346*38e8c45fSAndroid Build Coastguard Worker const nsecs_t eventTime = event.getHistoricalEventTime(h);
347*38e8c45fSAndroid Build Coastguard Worker for (size_t i = 0; i < event.getPointerCount(); i++) {
348*38e8c45fSAndroid Build Coastguard Worker if (event.isResampled(i, h)) {
349*38e8c45fSAndroid Build Coastguard Worker continue; // skip resampled samples
350*38e8c45fSAndroid Build Coastguard Worker }
351*38e8c45fSAndroid Build Coastguard Worker const int32_t pointerId = event.getPointerId(i);
352*38e8c45fSAndroid Build Coastguard Worker for (int32_t axis : axesToProcess) {
353*38e8c45fSAndroid Build Coastguard Worker const float position = event.getHistoricalAxisValue(axis, i, h);
354*38e8c45fSAndroid Build Coastguard Worker addMovement(eventTime, pointerId, axis, position);
355*38e8c45fSAndroid Build Coastguard Worker }
356*38e8c45fSAndroid Build Coastguard Worker }
357*38e8c45fSAndroid Build Coastguard Worker }
358*38e8c45fSAndroid Build Coastguard Worker }
359*38e8c45fSAndroid Build Coastguard Worker
getVelocity(int32_t axis,int32_t pointerId) const360*38e8c45fSAndroid Build Coastguard Worker std::optional<float> VelocityTracker::getVelocity(int32_t axis, int32_t pointerId) const {
361*38e8c45fSAndroid Build Coastguard Worker const auto& it = mConfiguredStrategies.find(axis);
362*38e8c45fSAndroid Build Coastguard Worker if (it != mConfiguredStrategies.end()) {
363*38e8c45fSAndroid Build Coastguard Worker return it->second->getVelocity(pointerId);
364*38e8c45fSAndroid Build Coastguard Worker }
365*38e8c45fSAndroid Build Coastguard Worker return {};
366*38e8c45fSAndroid Build Coastguard Worker }
367*38e8c45fSAndroid Build Coastguard Worker
getComputedVelocity(int32_t units,float maxVelocity)368*38e8c45fSAndroid Build Coastguard Worker VelocityTracker::ComputedVelocity VelocityTracker::getComputedVelocity(int32_t units,
369*38e8c45fSAndroid Build Coastguard Worker float maxVelocity) {
370*38e8c45fSAndroid Build Coastguard Worker ComputedVelocity computedVelocity;
371*38e8c45fSAndroid Build Coastguard Worker for (const auto& [axis, _] : mConfiguredStrategies) {
372*38e8c45fSAndroid Build Coastguard Worker BitSet32 copyIdBits = BitSet32(mCurrentPointerIdBits);
373*38e8c45fSAndroid Build Coastguard Worker while (!copyIdBits.isEmpty()) {
374*38e8c45fSAndroid Build Coastguard Worker uint32_t id = copyIdBits.clearFirstMarkedBit();
375*38e8c45fSAndroid Build Coastguard Worker std::optional<float> velocity = getVelocity(axis, id);
376*38e8c45fSAndroid Build Coastguard Worker if (velocity) {
377*38e8c45fSAndroid Build Coastguard Worker float adjustedVelocity =
378*38e8c45fSAndroid Build Coastguard Worker std::clamp(*velocity * units / 1000, -maxVelocity, maxVelocity);
379*38e8c45fSAndroid Build Coastguard Worker computedVelocity.addVelocity(axis, id, adjustedVelocity);
380*38e8c45fSAndroid Build Coastguard Worker }
381*38e8c45fSAndroid Build Coastguard Worker }
382*38e8c45fSAndroid Build Coastguard Worker }
383*38e8c45fSAndroid Build Coastguard Worker return computedVelocity;
384*38e8c45fSAndroid Build Coastguard Worker }
385*38e8c45fSAndroid Build Coastguard Worker
AccumulatingVelocityTrackerStrategy(nsecs_t horizonNanos,bool maintainHorizonDuringAdd)386*38e8c45fSAndroid Build Coastguard Worker AccumulatingVelocityTrackerStrategy::AccumulatingVelocityTrackerStrategy(
387*38e8c45fSAndroid Build Coastguard Worker nsecs_t horizonNanos, bool maintainHorizonDuringAdd)
388*38e8c45fSAndroid Build Coastguard Worker : mHorizonNanos(horizonNanos), mMaintainHorizonDuringAdd(maintainHorizonDuringAdd) {}
389*38e8c45fSAndroid Build Coastguard Worker
clearPointer(int32_t pointerId)390*38e8c45fSAndroid Build Coastguard Worker void AccumulatingVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
391*38e8c45fSAndroid Build Coastguard Worker mMovements.erase(pointerId);
392*38e8c45fSAndroid Build Coastguard Worker }
393*38e8c45fSAndroid Build Coastguard Worker
addMovement(nsecs_t eventTime,int32_t pointerId,float position)394*38e8c45fSAndroid Build Coastguard Worker void AccumulatingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
395*38e8c45fSAndroid Build Coastguard Worker float position) {
396*38e8c45fSAndroid Build Coastguard Worker auto [ringBufferIt, _] = mMovements.try_emplace(pointerId, HISTORY_SIZE);
397*38e8c45fSAndroid Build Coastguard Worker RingBuffer<Movement>& movements = ringBufferIt->second;
398*38e8c45fSAndroid Build Coastguard Worker const size_t size = movements.size();
399*38e8c45fSAndroid Build Coastguard Worker
400*38e8c45fSAndroid Build Coastguard Worker if (size != 0 && movements[size - 1].eventTime == eventTime) {
401*38e8c45fSAndroid Build Coastguard Worker // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
402*38e8c45fSAndroid Build Coastguard Worker // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
403*38e8c45fSAndroid Build Coastguard Worker // the new pointer. If the eventtimes for both events are identical, just update the data
404*38e8c45fSAndroid Build Coastguard Worker // for this time (i.e. pop out the last element, and insert the updated movement).
405*38e8c45fSAndroid Build Coastguard Worker // We only compare against the last value, as it is likely that addMovement is called
406*38e8c45fSAndroid Build Coastguard Worker // in chronological order as events occur.
407*38e8c45fSAndroid Build Coastguard Worker movements.popBack();
408*38e8c45fSAndroid Build Coastguard Worker }
409*38e8c45fSAndroid Build Coastguard Worker
410*38e8c45fSAndroid Build Coastguard Worker movements.pushBack({eventTime, position});
411*38e8c45fSAndroid Build Coastguard Worker
412*38e8c45fSAndroid Build Coastguard Worker // Clear movements that do not fall within `mHorizonNanos` of the latest movement.
413*38e8c45fSAndroid Build Coastguard Worker // Note that, if in the future we decide to use more movements (i.e. increase HISTORY_SIZE),
414*38e8c45fSAndroid Build Coastguard Worker // we can consider making this step binary-search based, which will give us some improvement.
415*38e8c45fSAndroid Build Coastguard Worker if (mMaintainHorizonDuringAdd) {
416*38e8c45fSAndroid Build Coastguard Worker while (eventTime - movements[0].eventTime > mHorizonNanos) {
417*38e8c45fSAndroid Build Coastguard Worker movements.popFront();
418*38e8c45fSAndroid Build Coastguard Worker }
419*38e8c45fSAndroid Build Coastguard Worker }
420*38e8c45fSAndroid Build Coastguard Worker }
421*38e8c45fSAndroid Build Coastguard Worker
422*38e8c45fSAndroid Build Coastguard Worker // --- LeastSquaresVelocityTrackerStrategy ---
423*38e8c45fSAndroid Build Coastguard Worker
LeastSquaresVelocityTrackerStrategy(uint32_t degree,Weighting weighting)424*38e8c45fSAndroid Build Coastguard Worker LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(uint32_t degree,
425*38e8c45fSAndroid Build Coastguard Worker Weighting weighting)
426*38e8c45fSAndroid Build Coastguard Worker : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/,
427*38e8c45fSAndroid Build Coastguard Worker true /*maintainHorizonDuringAdd*/),
428*38e8c45fSAndroid Build Coastguard Worker mDegree(degree),
429*38e8c45fSAndroid Build Coastguard Worker mWeighting(weighting) {}
430*38e8c45fSAndroid Build Coastguard Worker
~LeastSquaresVelocityTrackerStrategy()431*38e8c45fSAndroid Build Coastguard Worker LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {}
432*38e8c45fSAndroid Build Coastguard Worker
433*38e8c45fSAndroid Build Coastguard Worker /**
434*38e8c45fSAndroid Build Coastguard Worker * Solves a linear least squares problem to obtain a N degree polynomial that fits
435*38e8c45fSAndroid Build Coastguard Worker * the specified input data as nearly as possible.
436*38e8c45fSAndroid Build Coastguard Worker *
437*38e8c45fSAndroid Build Coastguard Worker * Returns true if a solution is found, false otherwise.
438*38e8c45fSAndroid Build Coastguard Worker *
439*38e8c45fSAndroid Build Coastguard Worker * The input consists of two vectors of data points X and Y with indices 0..m-1
440*38e8c45fSAndroid Build Coastguard Worker * along with a weight vector W of the same size.
441*38e8c45fSAndroid Build Coastguard Worker *
442*38e8c45fSAndroid Build Coastguard Worker * The output is a vector B with indices 0..n that describes a polynomial
443*38e8c45fSAndroid Build Coastguard Worker * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1] X[i]
444*38e8c45fSAndroid Build Coastguard Worker * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is minimized.
445*38e8c45fSAndroid Build Coastguard Worker *
446*38e8c45fSAndroid Build Coastguard Worker * Accordingly, the weight vector W should be initialized by the caller with the
447*38e8c45fSAndroid Build Coastguard Worker * reciprocal square root of the variance of the error in each input data point.
448*38e8c45fSAndroid Build Coastguard Worker * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 / stddev(Y[i]).
449*38e8c45fSAndroid Build Coastguard Worker * The weights express the relative importance of each data point. If the weights are
450*38e8c45fSAndroid Build Coastguard Worker * all 1, then the data points are considered to be of equal importance when fitting
451*38e8c45fSAndroid Build Coastguard Worker * the polynomial. It is a good idea to choose weights that diminish the importance
452*38e8c45fSAndroid Build Coastguard Worker * of data points that may have higher than usual error margins.
453*38e8c45fSAndroid Build Coastguard Worker *
454*38e8c45fSAndroid Build Coastguard Worker * Errors among data points are assumed to be independent. W is represented here
455*38e8c45fSAndroid Build Coastguard Worker * as a vector although in the literature it is typically taken to be a diagonal matrix.
456*38e8c45fSAndroid Build Coastguard Worker *
457*38e8c45fSAndroid Build Coastguard Worker * That is to say, the function that generated the input data can be approximated
458*38e8c45fSAndroid Build Coastguard Worker * by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n.
459*38e8c45fSAndroid Build Coastguard Worker *
460*38e8c45fSAndroid Build Coastguard Worker * The coefficient of determination (R^2) is also returned to describe the goodness
461*38e8c45fSAndroid Build Coastguard Worker * of fit of the model for the given data. It is a value between 0 and 1, where 1
462*38e8c45fSAndroid Build Coastguard Worker * indicates perfect correspondence.
463*38e8c45fSAndroid Build Coastguard Worker *
464*38e8c45fSAndroid Build Coastguard Worker * This function first expands the X vector to a m by n matrix A such that
465*38e8c45fSAndroid Build Coastguard Worker * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then
466*38e8c45fSAndroid Build Coastguard Worker * multiplies it by w[i]./
467*38e8c45fSAndroid Build Coastguard Worker *
468*38e8c45fSAndroid Build Coastguard Worker * Then it calculates the QR decomposition of A yielding an m by m orthonormal matrix Q
469*38e8c45fSAndroid Build Coastguard Worker * and an m by n upper triangular matrix R. Because R is upper triangular (lower
470*38e8c45fSAndroid Build Coastguard Worker * part is all zeroes), we can simplify the decomposition into an m by n matrix
471*38e8c45fSAndroid Build Coastguard Worker * Q1 and a n by n matrix R1 such that A = Q1 R1.
472*38e8c45fSAndroid Build Coastguard Worker *
473*38e8c45fSAndroid Build Coastguard Worker * Finally we solve the system of linear equations given by R1 B = (Qtranspose W Y)
474*38e8c45fSAndroid Build Coastguard Worker * to find B.
475*38e8c45fSAndroid Build Coastguard Worker *
476*38e8c45fSAndroid Build Coastguard Worker * For efficiency, we lay out A and Q column-wise in memory because we frequently
477*38e8c45fSAndroid Build Coastguard Worker * operate on the column vectors. Conversely, we lay out R row-wise.
478*38e8c45fSAndroid Build Coastguard Worker *
479*38e8c45fSAndroid Build Coastguard Worker * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
480*38e8c45fSAndroid Build Coastguard Worker * http://en.wikipedia.org/wiki/Gram-Schmidt
481*38e8c45fSAndroid Build Coastguard Worker */
solveLeastSquares(const std::vector<float> & x,const std::vector<float> & y,const std::vector<float> & w,uint32_t n)482*38e8c45fSAndroid Build Coastguard Worker static std::optional<float> solveLeastSquares(const std::vector<float>& x,
483*38e8c45fSAndroid Build Coastguard Worker const std::vector<float>& y,
484*38e8c45fSAndroid Build Coastguard Worker const std::vector<float>& w, uint32_t n) {
485*38e8c45fSAndroid Build Coastguard Worker const size_t m = x.size();
486*38e8c45fSAndroid Build Coastguard Worker
487*38e8c45fSAndroid Build Coastguard Worker ALOGD_IF(DEBUG_STRATEGY, "solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n),
488*38e8c45fSAndroid Build Coastguard Worker vectorToString(x).c_str(), vectorToString(y).c_str(), vectorToString(w).c_str());
489*38e8c45fSAndroid Build Coastguard Worker
490*38e8c45fSAndroid Build Coastguard Worker LOG_ALWAYS_FATAL_IF(m != y.size() || m != w.size(), "Mismatched vector sizes");
491*38e8c45fSAndroid Build Coastguard Worker
492*38e8c45fSAndroid Build Coastguard Worker // Expand the X vector to a matrix A, pre-multiplied by the weights.
493*38e8c45fSAndroid Build Coastguard Worker float a[n][m]; // column-major order
494*38e8c45fSAndroid Build Coastguard Worker for (uint32_t h = 0; h < m; h++) {
495*38e8c45fSAndroid Build Coastguard Worker a[0][h] = w[h];
496*38e8c45fSAndroid Build Coastguard Worker for (uint32_t i = 1; i < n; i++) {
497*38e8c45fSAndroid Build Coastguard Worker a[i][h] = a[i - 1][h] * x[h];
498*38e8c45fSAndroid Build Coastguard Worker }
499*38e8c45fSAndroid Build Coastguard Worker }
500*38e8c45fSAndroid Build Coastguard Worker
501*38e8c45fSAndroid Build Coastguard Worker ALOGD_IF(DEBUG_STRATEGY, " - a=%s",
502*38e8c45fSAndroid Build Coastguard Worker matrixToString(&a[0][0], m, n, /*rowMajor=*/false).c_str());
503*38e8c45fSAndroid Build Coastguard Worker
504*38e8c45fSAndroid Build Coastguard Worker // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
505*38e8c45fSAndroid Build Coastguard Worker float q[n][m]; // orthonormal basis, column-major order
506*38e8c45fSAndroid Build Coastguard Worker float r[n][n]; // upper triangular matrix, row-major order
507*38e8c45fSAndroid Build Coastguard Worker for (uint32_t j = 0; j < n; j++) {
508*38e8c45fSAndroid Build Coastguard Worker for (uint32_t h = 0; h < m; h++) {
509*38e8c45fSAndroid Build Coastguard Worker q[j][h] = a[j][h];
510*38e8c45fSAndroid Build Coastguard Worker }
511*38e8c45fSAndroid Build Coastguard Worker for (uint32_t i = 0; i < j; i++) {
512*38e8c45fSAndroid Build Coastguard Worker float dot = vectorDot(&q[j][0], &q[i][0], m);
513*38e8c45fSAndroid Build Coastguard Worker for (uint32_t h = 0; h < m; h++) {
514*38e8c45fSAndroid Build Coastguard Worker q[j][h] -= dot * q[i][h];
515*38e8c45fSAndroid Build Coastguard Worker }
516*38e8c45fSAndroid Build Coastguard Worker }
517*38e8c45fSAndroid Build Coastguard Worker
518*38e8c45fSAndroid Build Coastguard Worker float norm = vectorNorm(&q[j][0], m);
519*38e8c45fSAndroid Build Coastguard Worker if (norm < 0.000001f) {
520*38e8c45fSAndroid Build Coastguard Worker // vectors are linearly dependent or zero so no solution
521*38e8c45fSAndroid Build Coastguard Worker ALOGD_IF(DEBUG_STRATEGY, " - no solution, norm=%f", norm);
522*38e8c45fSAndroid Build Coastguard Worker return {};
523*38e8c45fSAndroid Build Coastguard Worker }
524*38e8c45fSAndroid Build Coastguard Worker
525*38e8c45fSAndroid Build Coastguard Worker float invNorm = 1.0f / norm;
526*38e8c45fSAndroid Build Coastguard Worker for (uint32_t h = 0; h < m; h++) {
527*38e8c45fSAndroid Build Coastguard Worker q[j][h] *= invNorm;
528*38e8c45fSAndroid Build Coastguard Worker }
529*38e8c45fSAndroid Build Coastguard Worker for (uint32_t i = 0; i < n; i++) {
530*38e8c45fSAndroid Build Coastguard Worker r[j][i] = i < j ? 0 : vectorDot(&q[j][0], &a[i][0], m);
531*38e8c45fSAndroid Build Coastguard Worker }
532*38e8c45fSAndroid Build Coastguard Worker }
533*38e8c45fSAndroid Build Coastguard Worker if (DEBUG_STRATEGY) {
534*38e8c45fSAndroid Build Coastguard Worker ALOGD(" - q=%s", matrixToString(&q[0][0], m, n, /*rowMajor=*/false).c_str());
535*38e8c45fSAndroid Build Coastguard Worker ALOGD(" - r=%s", matrixToString(&r[0][0], n, n, /*rowMajor=*/true).c_str());
536*38e8c45fSAndroid Build Coastguard Worker
537*38e8c45fSAndroid Build Coastguard Worker // calculate QR, if we factored A correctly then QR should equal A
538*38e8c45fSAndroid Build Coastguard Worker float qr[n][m];
539*38e8c45fSAndroid Build Coastguard Worker for (uint32_t h = 0; h < m; h++) {
540*38e8c45fSAndroid Build Coastguard Worker for (uint32_t i = 0; i < n; i++) {
541*38e8c45fSAndroid Build Coastguard Worker qr[i][h] = 0;
542*38e8c45fSAndroid Build Coastguard Worker for (uint32_t j = 0; j < n; j++) {
543*38e8c45fSAndroid Build Coastguard Worker qr[i][h] += q[j][h] * r[j][i];
544*38e8c45fSAndroid Build Coastguard Worker }
545*38e8c45fSAndroid Build Coastguard Worker }
546*38e8c45fSAndroid Build Coastguard Worker }
547*38e8c45fSAndroid Build Coastguard Worker ALOGD(" - qr=%s", matrixToString(&qr[0][0], m, n, /*rowMajor=*/false).c_str());
548*38e8c45fSAndroid Build Coastguard Worker }
549*38e8c45fSAndroid Build Coastguard Worker
550*38e8c45fSAndroid Build Coastguard Worker // Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
551*38e8c45fSAndroid Build Coastguard Worker // We just work from bottom-right to top-left calculating B's coefficients.
552*38e8c45fSAndroid Build Coastguard Worker float wy[m];
553*38e8c45fSAndroid Build Coastguard Worker for (uint32_t h = 0; h < m; h++) {
554*38e8c45fSAndroid Build Coastguard Worker wy[h] = y[h] * w[h];
555*38e8c45fSAndroid Build Coastguard Worker }
556*38e8c45fSAndroid Build Coastguard Worker std::array<float, VelocityTracker::MAX_DEGREE + 1> outB;
557*38e8c45fSAndroid Build Coastguard Worker for (uint32_t i = n; i != 0; ) {
558*38e8c45fSAndroid Build Coastguard Worker i--;
559*38e8c45fSAndroid Build Coastguard Worker outB[i] = vectorDot(&q[i][0], wy, m);
560*38e8c45fSAndroid Build Coastguard Worker for (uint32_t j = n - 1; j > i; j--) {
561*38e8c45fSAndroid Build Coastguard Worker outB[i] -= r[i][j] * outB[j];
562*38e8c45fSAndroid Build Coastguard Worker }
563*38e8c45fSAndroid Build Coastguard Worker outB[i] /= r[i][i];
564*38e8c45fSAndroid Build Coastguard Worker }
565*38e8c45fSAndroid Build Coastguard Worker
566*38e8c45fSAndroid Build Coastguard Worker ALOGD_IF(DEBUG_STRATEGY, " - b=%s", vectorToString(outB.data(), n).c_str());
567*38e8c45fSAndroid Build Coastguard Worker
568*38e8c45fSAndroid Build Coastguard Worker // Calculate the coefficient of determination as 1 - (SSerr / SStot) where
569*38e8c45fSAndroid Build Coastguard Worker // SSerr is the residual sum of squares (variance of the error),
570*38e8c45fSAndroid Build Coastguard Worker // and SStot is the total sum of squares (variance of the data) where each
571*38e8c45fSAndroid Build Coastguard Worker // has been weighted.
572*38e8c45fSAndroid Build Coastguard Worker float ymean = 0;
573*38e8c45fSAndroid Build Coastguard Worker for (uint32_t h = 0; h < m; h++) {
574*38e8c45fSAndroid Build Coastguard Worker ymean += y[h];
575*38e8c45fSAndroid Build Coastguard Worker }
576*38e8c45fSAndroid Build Coastguard Worker ymean /= m;
577*38e8c45fSAndroid Build Coastguard Worker
578*38e8c45fSAndroid Build Coastguard Worker if (DEBUG_STRATEGY) {
579*38e8c45fSAndroid Build Coastguard Worker float sserr = 0;
580*38e8c45fSAndroid Build Coastguard Worker float sstot = 0;
581*38e8c45fSAndroid Build Coastguard Worker for (uint32_t h = 0; h < m; h++) {
582*38e8c45fSAndroid Build Coastguard Worker float err = y[h] - outB[0];
583*38e8c45fSAndroid Build Coastguard Worker float term = 1;
584*38e8c45fSAndroid Build Coastguard Worker for (uint32_t i = 1; i < n; i++) {
585*38e8c45fSAndroid Build Coastguard Worker term *= x[h];
586*38e8c45fSAndroid Build Coastguard Worker err -= term * outB[i];
587*38e8c45fSAndroid Build Coastguard Worker }
588*38e8c45fSAndroid Build Coastguard Worker sserr += w[h] * w[h] * err * err;
589*38e8c45fSAndroid Build Coastguard Worker float var = y[h] - ymean;
590*38e8c45fSAndroid Build Coastguard Worker sstot += w[h] * w[h] * var * var;
591*38e8c45fSAndroid Build Coastguard Worker }
592*38e8c45fSAndroid Build Coastguard Worker ALOGD(" - sserr=%f", sserr);
593*38e8c45fSAndroid Build Coastguard Worker ALOGD(" - sstot=%f", sstot);
594*38e8c45fSAndroid Build Coastguard Worker }
595*38e8c45fSAndroid Build Coastguard Worker
596*38e8c45fSAndroid Build Coastguard Worker return outB[1];
597*38e8c45fSAndroid Build Coastguard Worker }
598*38e8c45fSAndroid Build Coastguard Worker
599*38e8c45fSAndroid Build Coastguard Worker /*
600*38e8c45fSAndroid Build Coastguard Worker * Optimized unweighted second-order least squares fit. About 2x speed improvement compared to
601*38e8c45fSAndroid Build Coastguard Worker * the default implementation
602*38e8c45fSAndroid Build Coastguard Worker */
solveUnweightedLeastSquaresDeg2(const RingBuffer<Movement> & movements) const603*38e8c45fSAndroid Build Coastguard Worker std::optional<float> LeastSquaresVelocityTrackerStrategy::solveUnweightedLeastSquaresDeg2(
604*38e8c45fSAndroid Build Coastguard Worker const RingBuffer<Movement>& movements) const {
605*38e8c45fSAndroid Build Coastguard Worker // Solving y = a*x^2 + b*x + c, where
606*38e8c45fSAndroid Build Coastguard Worker // - "x" is age (i.e. duration since latest movement) of the movemnets
607*38e8c45fSAndroid Build Coastguard Worker // - "y" is positions of the movements.
608*38e8c45fSAndroid Build Coastguard Worker float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0;
609*38e8c45fSAndroid Build Coastguard Worker
610*38e8c45fSAndroid Build Coastguard Worker const size_t count = movements.size();
611*38e8c45fSAndroid Build Coastguard Worker const Movement& newestMovement = movements[count - 1];
612*38e8c45fSAndroid Build Coastguard Worker for (size_t i = 0; i < count; i++) {
613*38e8c45fSAndroid Build Coastguard Worker const Movement& movement = movements[i];
614*38e8c45fSAndroid Build Coastguard Worker nsecs_t age = newestMovement.eventTime - movement.eventTime;
615*38e8c45fSAndroid Build Coastguard Worker float xi = -age * SECONDS_PER_NANO;
616*38e8c45fSAndroid Build Coastguard Worker float yi = movement.position;
617*38e8c45fSAndroid Build Coastguard Worker
618*38e8c45fSAndroid Build Coastguard Worker float xi2 = xi*xi;
619*38e8c45fSAndroid Build Coastguard Worker float xi3 = xi2*xi;
620*38e8c45fSAndroid Build Coastguard Worker float xi4 = xi3*xi;
621*38e8c45fSAndroid Build Coastguard Worker float xiyi = xi*yi;
622*38e8c45fSAndroid Build Coastguard Worker float xi2yi = xi2*yi;
623*38e8c45fSAndroid Build Coastguard Worker
624*38e8c45fSAndroid Build Coastguard Worker sxi += xi;
625*38e8c45fSAndroid Build Coastguard Worker sxi2 += xi2;
626*38e8c45fSAndroid Build Coastguard Worker sxiyi += xiyi;
627*38e8c45fSAndroid Build Coastguard Worker sxi2yi += xi2yi;
628*38e8c45fSAndroid Build Coastguard Worker syi += yi;
629*38e8c45fSAndroid Build Coastguard Worker sxi3 += xi3;
630*38e8c45fSAndroid Build Coastguard Worker sxi4 += xi4;
631*38e8c45fSAndroid Build Coastguard Worker }
632*38e8c45fSAndroid Build Coastguard Worker
633*38e8c45fSAndroid Build Coastguard Worker float Sxx = sxi2 - sxi*sxi / count;
634*38e8c45fSAndroid Build Coastguard Worker float Sxy = sxiyi - sxi*syi / count;
635*38e8c45fSAndroid Build Coastguard Worker float Sxx2 = sxi3 - sxi*sxi2 / count;
636*38e8c45fSAndroid Build Coastguard Worker float Sx2y = sxi2yi - sxi2*syi / count;
637*38e8c45fSAndroid Build Coastguard Worker float Sx2x2 = sxi4 - sxi2*sxi2 / count;
638*38e8c45fSAndroid Build Coastguard Worker
639*38e8c45fSAndroid Build Coastguard Worker float denominator = Sxx*Sx2x2 - Sxx2*Sxx2;
640*38e8c45fSAndroid Build Coastguard Worker if (denominator == 0) {
641*38e8c45fSAndroid Build Coastguard Worker ALOGW("division by 0 when computing velocity, Sxx=%f, Sx2x2=%f, Sxx2=%f", Sxx, Sx2x2, Sxx2);
642*38e8c45fSAndroid Build Coastguard Worker return std::nullopt;
643*38e8c45fSAndroid Build Coastguard Worker }
644*38e8c45fSAndroid Build Coastguard Worker
645*38e8c45fSAndroid Build Coastguard Worker return (Sxy * Sx2x2 - Sx2y * Sxx2) / denominator;
646*38e8c45fSAndroid Build Coastguard Worker }
647*38e8c45fSAndroid Build Coastguard Worker
getVelocity(int32_t pointerId) const648*38e8c45fSAndroid Build Coastguard Worker std::optional<float> LeastSquaresVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
649*38e8c45fSAndroid Build Coastguard Worker const auto movementIt = mMovements.find(pointerId);
650*38e8c45fSAndroid Build Coastguard Worker if (movementIt == mMovements.end()) {
651*38e8c45fSAndroid Build Coastguard Worker return std::nullopt; // no data
652*38e8c45fSAndroid Build Coastguard Worker }
653*38e8c45fSAndroid Build Coastguard Worker
654*38e8c45fSAndroid Build Coastguard Worker const RingBuffer<Movement>& movements = movementIt->second;
655*38e8c45fSAndroid Build Coastguard Worker const size_t size = movements.size();
656*38e8c45fSAndroid Build Coastguard Worker if (size == 0) {
657*38e8c45fSAndroid Build Coastguard Worker return std::nullopt; // no data
658*38e8c45fSAndroid Build Coastguard Worker }
659*38e8c45fSAndroid Build Coastguard Worker
660*38e8c45fSAndroid Build Coastguard Worker uint32_t degree = mDegree;
661*38e8c45fSAndroid Build Coastguard Worker if (degree > size - 1) {
662*38e8c45fSAndroid Build Coastguard Worker degree = size - 1;
663*38e8c45fSAndroid Build Coastguard Worker }
664*38e8c45fSAndroid Build Coastguard Worker
665*38e8c45fSAndroid Build Coastguard Worker if (degree <= 0) {
666*38e8c45fSAndroid Build Coastguard Worker return std::nullopt;
667*38e8c45fSAndroid Build Coastguard Worker }
668*38e8c45fSAndroid Build Coastguard Worker
669*38e8c45fSAndroid Build Coastguard Worker if (degree == 2 && mWeighting == Weighting::NONE) {
670*38e8c45fSAndroid Build Coastguard Worker // Optimize unweighted, quadratic polynomial fit
671*38e8c45fSAndroid Build Coastguard Worker return solveUnweightedLeastSquaresDeg2(movements);
672*38e8c45fSAndroid Build Coastguard Worker }
673*38e8c45fSAndroid Build Coastguard Worker
674*38e8c45fSAndroid Build Coastguard Worker // Iterate over movement samples in reverse time order and collect samples.
675*38e8c45fSAndroid Build Coastguard Worker std::vector<float> positions;
676*38e8c45fSAndroid Build Coastguard Worker std::vector<float> w;
677*38e8c45fSAndroid Build Coastguard Worker std::vector<float> time;
678*38e8c45fSAndroid Build Coastguard Worker
679*38e8c45fSAndroid Build Coastguard Worker const Movement& newestMovement = movements[size - 1];
680*38e8c45fSAndroid Build Coastguard Worker for (ssize_t i = size - 1; i >= 0; i--) {
681*38e8c45fSAndroid Build Coastguard Worker const Movement& movement = movements[i];
682*38e8c45fSAndroid Build Coastguard Worker nsecs_t age = newestMovement.eventTime - movement.eventTime;
683*38e8c45fSAndroid Build Coastguard Worker positions.push_back(movement.position);
684*38e8c45fSAndroid Build Coastguard Worker w.push_back(chooseWeight(pointerId, i));
685*38e8c45fSAndroid Build Coastguard Worker time.push_back(-age * 0.000000001f);
686*38e8c45fSAndroid Build Coastguard Worker }
687*38e8c45fSAndroid Build Coastguard Worker
688*38e8c45fSAndroid Build Coastguard Worker // General case for an Nth degree polynomial fit
689*38e8c45fSAndroid Build Coastguard Worker return solveLeastSquares(time, positions, w, degree + 1);
690*38e8c45fSAndroid Build Coastguard Worker }
691*38e8c45fSAndroid Build Coastguard Worker
chooseWeight(int32_t pointerId,uint32_t index) const692*38e8c45fSAndroid Build Coastguard Worker float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint32_t index) const {
693*38e8c45fSAndroid Build Coastguard Worker const RingBuffer<Movement>& movements = mMovements.at(pointerId);
694*38e8c45fSAndroid Build Coastguard Worker const size_t size = movements.size();
695*38e8c45fSAndroid Build Coastguard Worker switch (mWeighting) {
696*38e8c45fSAndroid Build Coastguard Worker case Weighting::DELTA: {
697*38e8c45fSAndroid Build Coastguard Worker // Weight points based on how much time elapsed between them and the next
698*38e8c45fSAndroid Build Coastguard Worker // point so that points that "cover" a shorter time span are weighed less.
699*38e8c45fSAndroid Build Coastguard Worker // delta 0ms: 0.5
700*38e8c45fSAndroid Build Coastguard Worker // delta 10ms: 1.0
701*38e8c45fSAndroid Build Coastguard Worker if (index == size - 1) {
702*38e8c45fSAndroid Build Coastguard Worker return 1.0f;
703*38e8c45fSAndroid Build Coastguard Worker }
704*38e8c45fSAndroid Build Coastguard Worker float deltaMillis =
705*38e8c45fSAndroid Build Coastguard Worker (movements[index + 1].eventTime - movements[index].eventTime) * 0.000001f;
706*38e8c45fSAndroid Build Coastguard Worker if (deltaMillis < 0) {
707*38e8c45fSAndroid Build Coastguard Worker return 0.5f;
708*38e8c45fSAndroid Build Coastguard Worker }
709*38e8c45fSAndroid Build Coastguard Worker if (deltaMillis < 10) {
710*38e8c45fSAndroid Build Coastguard Worker return 0.5f + deltaMillis * 0.05;
711*38e8c45fSAndroid Build Coastguard Worker }
712*38e8c45fSAndroid Build Coastguard Worker return 1.0f;
713*38e8c45fSAndroid Build Coastguard Worker }
714*38e8c45fSAndroid Build Coastguard Worker
715*38e8c45fSAndroid Build Coastguard Worker case Weighting::CENTRAL: {
716*38e8c45fSAndroid Build Coastguard Worker // Weight points based on their age, weighing very recent and very old points less.
717*38e8c45fSAndroid Build Coastguard Worker // age 0ms: 0.5
718*38e8c45fSAndroid Build Coastguard Worker // age 10ms: 1.0
719*38e8c45fSAndroid Build Coastguard Worker // age 50ms: 1.0
720*38e8c45fSAndroid Build Coastguard Worker // age 60ms: 0.5
721*38e8c45fSAndroid Build Coastguard Worker float ageMillis =
722*38e8c45fSAndroid Build Coastguard Worker (movements[size - 1].eventTime - movements[index].eventTime) * 0.000001f;
723*38e8c45fSAndroid Build Coastguard Worker if (ageMillis < 0) {
724*38e8c45fSAndroid Build Coastguard Worker return 0.5f;
725*38e8c45fSAndroid Build Coastguard Worker }
726*38e8c45fSAndroid Build Coastguard Worker if (ageMillis < 10) {
727*38e8c45fSAndroid Build Coastguard Worker return 0.5f + ageMillis * 0.05;
728*38e8c45fSAndroid Build Coastguard Worker }
729*38e8c45fSAndroid Build Coastguard Worker if (ageMillis < 50) {
730*38e8c45fSAndroid Build Coastguard Worker return 1.0f;
731*38e8c45fSAndroid Build Coastguard Worker }
732*38e8c45fSAndroid Build Coastguard Worker if (ageMillis < 60) {
733*38e8c45fSAndroid Build Coastguard Worker return 0.5f + (60 - ageMillis) * 0.05;
734*38e8c45fSAndroid Build Coastguard Worker }
735*38e8c45fSAndroid Build Coastguard Worker return 0.5f;
736*38e8c45fSAndroid Build Coastguard Worker }
737*38e8c45fSAndroid Build Coastguard Worker
738*38e8c45fSAndroid Build Coastguard Worker case Weighting::RECENT: {
739*38e8c45fSAndroid Build Coastguard Worker // Weight points based on their age, weighing older points less.
740*38e8c45fSAndroid Build Coastguard Worker // age 0ms: 1.0
741*38e8c45fSAndroid Build Coastguard Worker // age 50ms: 1.0
742*38e8c45fSAndroid Build Coastguard Worker // age 100ms: 0.5
743*38e8c45fSAndroid Build Coastguard Worker float ageMillis =
744*38e8c45fSAndroid Build Coastguard Worker (movements[size - 1].eventTime - movements[index].eventTime) * 0.000001f;
745*38e8c45fSAndroid Build Coastguard Worker if (ageMillis < 50) {
746*38e8c45fSAndroid Build Coastguard Worker return 1.0f;
747*38e8c45fSAndroid Build Coastguard Worker }
748*38e8c45fSAndroid Build Coastguard Worker if (ageMillis < 100) {
749*38e8c45fSAndroid Build Coastguard Worker return 0.5f + (100 - ageMillis) * 0.01f;
750*38e8c45fSAndroid Build Coastguard Worker }
751*38e8c45fSAndroid Build Coastguard Worker return 0.5f;
752*38e8c45fSAndroid Build Coastguard Worker }
753*38e8c45fSAndroid Build Coastguard Worker
754*38e8c45fSAndroid Build Coastguard Worker case Weighting::NONE:
755*38e8c45fSAndroid Build Coastguard Worker return 1.0f;
756*38e8c45fSAndroid Build Coastguard Worker }
757*38e8c45fSAndroid Build Coastguard Worker }
758*38e8c45fSAndroid Build Coastguard Worker
759*38e8c45fSAndroid Build Coastguard Worker // --- IntegratingVelocityTrackerStrategy ---
760*38e8c45fSAndroid Build Coastguard Worker
IntegratingVelocityTrackerStrategy(uint32_t degree)761*38e8c45fSAndroid Build Coastguard Worker IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(uint32_t degree) :
762*38e8c45fSAndroid Build Coastguard Worker mDegree(degree) {
763*38e8c45fSAndroid Build Coastguard Worker }
764*38e8c45fSAndroid Build Coastguard Worker
~IntegratingVelocityTrackerStrategy()765*38e8c45fSAndroid Build Coastguard Worker IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {
766*38e8c45fSAndroid Build Coastguard Worker }
767*38e8c45fSAndroid Build Coastguard Worker
clearPointer(int32_t pointerId)768*38e8c45fSAndroid Build Coastguard Worker void IntegratingVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
769*38e8c45fSAndroid Build Coastguard Worker mPointerIdBits.clearBit(pointerId);
770*38e8c45fSAndroid Build Coastguard Worker }
771*38e8c45fSAndroid Build Coastguard Worker
addMovement(nsecs_t eventTime,int32_t pointerId,float position)772*38e8c45fSAndroid Build Coastguard Worker void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
773*38e8c45fSAndroid Build Coastguard Worker float position) {
774*38e8c45fSAndroid Build Coastguard Worker State& state = mPointerState[pointerId];
775*38e8c45fSAndroid Build Coastguard Worker if (mPointerIdBits.hasBit(pointerId)) {
776*38e8c45fSAndroid Build Coastguard Worker updateState(state, eventTime, position);
777*38e8c45fSAndroid Build Coastguard Worker } else {
778*38e8c45fSAndroid Build Coastguard Worker initState(state, eventTime, position);
779*38e8c45fSAndroid Build Coastguard Worker }
780*38e8c45fSAndroid Build Coastguard Worker
781*38e8c45fSAndroid Build Coastguard Worker mPointerIdBits.markBit(pointerId);
782*38e8c45fSAndroid Build Coastguard Worker }
783*38e8c45fSAndroid Build Coastguard Worker
getVelocity(int32_t pointerId) const784*38e8c45fSAndroid Build Coastguard Worker std::optional<float> IntegratingVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
785*38e8c45fSAndroid Build Coastguard Worker if (mPointerIdBits.hasBit(pointerId)) {
786*38e8c45fSAndroid Build Coastguard Worker return mPointerState[pointerId].vel;
787*38e8c45fSAndroid Build Coastguard Worker }
788*38e8c45fSAndroid Build Coastguard Worker
789*38e8c45fSAndroid Build Coastguard Worker return std::nullopt;
790*38e8c45fSAndroid Build Coastguard Worker }
791*38e8c45fSAndroid Build Coastguard Worker
initState(State & state,nsecs_t eventTime,float pos) const792*38e8c45fSAndroid Build Coastguard Worker void IntegratingVelocityTrackerStrategy::initState(State& state, nsecs_t eventTime,
793*38e8c45fSAndroid Build Coastguard Worker float pos) const {
794*38e8c45fSAndroid Build Coastguard Worker state.updateTime = eventTime;
795*38e8c45fSAndroid Build Coastguard Worker state.degree = 0;
796*38e8c45fSAndroid Build Coastguard Worker
797*38e8c45fSAndroid Build Coastguard Worker state.pos = pos;
798*38e8c45fSAndroid Build Coastguard Worker state.accel = 0;
799*38e8c45fSAndroid Build Coastguard Worker state.vel = 0;
800*38e8c45fSAndroid Build Coastguard Worker }
801*38e8c45fSAndroid Build Coastguard Worker
updateState(State & state,nsecs_t eventTime,float pos) const802*38e8c45fSAndroid Build Coastguard Worker void IntegratingVelocityTrackerStrategy::updateState(State& state, nsecs_t eventTime,
803*38e8c45fSAndroid Build Coastguard Worker float pos) const {
804*38e8c45fSAndroid Build Coastguard Worker const nsecs_t MIN_TIME_DELTA = 2 * NANOS_PER_MS;
805*38e8c45fSAndroid Build Coastguard Worker const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds
806*38e8c45fSAndroid Build Coastguard Worker
807*38e8c45fSAndroid Build Coastguard Worker if (eventTime <= state.updateTime + MIN_TIME_DELTA) {
808*38e8c45fSAndroid Build Coastguard Worker return;
809*38e8c45fSAndroid Build Coastguard Worker }
810*38e8c45fSAndroid Build Coastguard Worker
811*38e8c45fSAndroid Build Coastguard Worker float dt = (eventTime - state.updateTime) * 0.000000001f;
812*38e8c45fSAndroid Build Coastguard Worker state.updateTime = eventTime;
813*38e8c45fSAndroid Build Coastguard Worker
814*38e8c45fSAndroid Build Coastguard Worker float vel = (pos - state.pos) / dt;
815*38e8c45fSAndroid Build Coastguard Worker if (state.degree == 0) {
816*38e8c45fSAndroid Build Coastguard Worker state.vel = vel;
817*38e8c45fSAndroid Build Coastguard Worker state.degree = 1;
818*38e8c45fSAndroid Build Coastguard Worker } else {
819*38e8c45fSAndroid Build Coastguard Worker float alpha = dt / (FILTER_TIME_CONSTANT + dt);
820*38e8c45fSAndroid Build Coastguard Worker if (mDegree == 1) {
821*38e8c45fSAndroid Build Coastguard Worker state.vel += (vel - state.vel) * alpha;
822*38e8c45fSAndroid Build Coastguard Worker } else {
823*38e8c45fSAndroid Build Coastguard Worker float accel = (vel - state.vel) / dt;
824*38e8c45fSAndroid Build Coastguard Worker if (state.degree == 1) {
825*38e8c45fSAndroid Build Coastguard Worker state.accel = accel;
826*38e8c45fSAndroid Build Coastguard Worker state.degree = 2;
827*38e8c45fSAndroid Build Coastguard Worker } else {
828*38e8c45fSAndroid Build Coastguard Worker state.accel += (accel - state.accel) * alpha;
829*38e8c45fSAndroid Build Coastguard Worker }
830*38e8c45fSAndroid Build Coastguard Worker state.vel += (state.accel * dt) * alpha;
831*38e8c45fSAndroid Build Coastguard Worker }
832*38e8c45fSAndroid Build Coastguard Worker }
833*38e8c45fSAndroid Build Coastguard Worker state.pos = pos;
834*38e8c45fSAndroid Build Coastguard Worker }
835*38e8c45fSAndroid Build Coastguard Worker
836*38e8c45fSAndroid Build Coastguard Worker // --- LegacyVelocityTrackerStrategy ---
837*38e8c45fSAndroid Build Coastguard Worker
LegacyVelocityTrackerStrategy()838*38e8c45fSAndroid Build Coastguard Worker LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy()
839*38e8c45fSAndroid Build Coastguard Worker : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/,
840*38e8c45fSAndroid Build Coastguard Worker false /*maintainHorizonDuringAdd*/) {}
841*38e8c45fSAndroid Build Coastguard Worker
~LegacyVelocityTrackerStrategy()842*38e8c45fSAndroid Build Coastguard Worker LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() {
843*38e8c45fSAndroid Build Coastguard Worker }
844*38e8c45fSAndroid Build Coastguard Worker
getVelocity(int32_t pointerId) const845*38e8c45fSAndroid Build Coastguard Worker std::optional<float> LegacyVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
846*38e8c45fSAndroid Build Coastguard Worker const auto movementIt = mMovements.find(pointerId);
847*38e8c45fSAndroid Build Coastguard Worker if (movementIt == mMovements.end()) {
848*38e8c45fSAndroid Build Coastguard Worker return std::nullopt; // no data
849*38e8c45fSAndroid Build Coastguard Worker }
850*38e8c45fSAndroid Build Coastguard Worker
851*38e8c45fSAndroid Build Coastguard Worker const RingBuffer<Movement>& movements = movementIt->second;
852*38e8c45fSAndroid Build Coastguard Worker const size_t size = movements.size();
853*38e8c45fSAndroid Build Coastguard Worker if (size == 0) {
854*38e8c45fSAndroid Build Coastguard Worker return std::nullopt; // no data
855*38e8c45fSAndroid Build Coastguard Worker }
856*38e8c45fSAndroid Build Coastguard Worker
857*38e8c45fSAndroid Build Coastguard Worker const Movement& newestMovement = movements[size - 1];
858*38e8c45fSAndroid Build Coastguard Worker
859*38e8c45fSAndroid Build Coastguard Worker // Find the oldest sample that contains the pointer and that is not older than HORIZON.
860*38e8c45fSAndroid Build Coastguard Worker nsecs_t minTime = newestMovement.eventTime - HORIZON;
861*38e8c45fSAndroid Build Coastguard Worker uint32_t oldestIndex = size - 1;
862*38e8c45fSAndroid Build Coastguard Worker for (ssize_t i = size - 1; i >= 0; i--) {
863*38e8c45fSAndroid Build Coastguard Worker const Movement& nextOldestMovement = movements[i];
864*38e8c45fSAndroid Build Coastguard Worker if (nextOldestMovement.eventTime < minTime) {
865*38e8c45fSAndroid Build Coastguard Worker break;
866*38e8c45fSAndroid Build Coastguard Worker }
867*38e8c45fSAndroid Build Coastguard Worker oldestIndex = i;
868*38e8c45fSAndroid Build Coastguard Worker }
869*38e8c45fSAndroid Build Coastguard Worker
870*38e8c45fSAndroid Build Coastguard Worker // Calculate an exponentially weighted moving average of the velocity estimate
871*38e8c45fSAndroid Build Coastguard Worker // at different points in time measured relative to the oldest sample.
872*38e8c45fSAndroid Build Coastguard Worker // This is essentially an IIR filter. Newer samples are weighted more heavily
873*38e8c45fSAndroid Build Coastguard Worker // than older samples. Samples at equal time points are weighted more or less
874*38e8c45fSAndroid Build Coastguard Worker // equally.
875*38e8c45fSAndroid Build Coastguard Worker //
876*38e8c45fSAndroid Build Coastguard Worker // One tricky problem is that the sample data may be poorly conditioned.
877*38e8c45fSAndroid Build Coastguard Worker // Sometimes samples arrive very close together in time which can cause us to
878*38e8c45fSAndroid Build Coastguard Worker // overestimate the velocity at that time point. Most samples might be measured
879*38e8c45fSAndroid Build Coastguard Worker // 16ms apart but some consecutive samples could be only 0.5sm apart because
880*38e8c45fSAndroid Build Coastguard Worker // the hardware or driver reports them irregularly or in bursts.
881*38e8c45fSAndroid Build Coastguard Worker float accumV = 0;
882*38e8c45fSAndroid Build Coastguard Worker uint32_t samplesUsed = 0;
883*38e8c45fSAndroid Build Coastguard Worker const Movement& oldestMovement = movements[oldestIndex];
884*38e8c45fSAndroid Build Coastguard Worker float oldestPosition = oldestMovement.position;
885*38e8c45fSAndroid Build Coastguard Worker nsecs_t lastDuration = 0;
886*38e8c45fSAndroid Build Coastguard Worker
887*38e8c45fSAndroid Build Coastguard Worker for (size_t i = oldestIndex; i < size; i++) {
888*38e8c45fSAndroid Build Coastguard Worker const Movement& movement = movements[i];
889*38e8c45fSAndroid Build Coastguard Worker nsecs_t duration = movement.eventTime - oldestMovement.eventTime;
890*38e8c45fSAndroid Build Coastguard Worker
891*38e8c45fSAndroid Build Coastguard Worker // If the duration between samples is small, we may significantly overestimate
892*38e8c45fSAndroid Build Coastguard Worker // the velocity. Consequently, we impose a minimum duration constraint on the
893*38e8c45fSAndroid Build Coastguard Worker // samples that we include in the calculation.
894*38e8c45fSAndroid Build Coastguard Worker if (duration >= MIN_DURATION) {
895*38e8c45fSAndroid Build Coastguard Worker float position = movement.position;
896*38e8c45fSAndroid Build Coastguard Worker float scale = 1000000000.0f / duration; // one over time delta in seconds
897*38e8c45fSAndroid Build Coastguard Worker float v = (position - oldestPosition) * scale;
898*38e8c45fSAndroid Build Coastguard Worker accumV = (accumV * lastDuration + v * duration) / (duration + lastDuration);
899*38e8c45fSAndroid Build Coastguard Worker lastDuration = duration;
900*38e8c45fSAndroid Build Coastguard Worker samplesUsed += 1;
901*38e8c45fSAndroid Build Coastguard Worker }
902*38e8c45fSAndroid Build Coastguard Worker }
903*38e8c45fSAndroid Build Coastguard Worker
904*38e8c45fSAndroid Build Coastguard Worker if (samplesUsed) {
905*38e8c45fSAndroid Build Coastguard Worker return accumV;
906*38e8c45fSAndroid Build Coastguard Worker }
907*38e8c45fSAndroid Build Coastguard Worker return std::nullopt;
908*38e8c45fSAndroid Build Coastguard Worker }
909*38e8c45fSAndroid Build Coastguard Worker
910*38e8c45fSAndroid Build Coastguard Worker // --- ImpulseVelocityTrackerStrategy ---
911*38e8c45fSAndroid Build Coastguard Worker
ImpulseVelocityTrackerStrategy(bool deltaValues)912*38e8c45fSAndroid Build Coastguard Worker ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy(bool deltaValues)
913*38e8c45fSAndroid Build Coastguard Worker : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/,
914*38e8c45fSAndroid Build Coastguard Worker true /*maintainHorizonDuringAdd*/),
915*38e8c45fSAndroid Build Coastguard Worker mDeltaValues(deltaValues) {}
916*38e8c45fSAndroid Build Coastguard Worker
~ImpulseVelocityTrackerStrategy()917*38e8c45fSAndroid Build Coastguard Worker ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() {
918*38e8c45fSAndroid Build Coastguard Worker }
919*38e8c45fSAndroid Build Coastguard Worker
920*38e8c45fSAndroid Build Coastguard Worker /**
921*38e8c45fSAndroid Build Coastguard Worker * Calculate the total impulse provided to the screen and the resulting velocity.
922*38e8c45fSAndroid Build Coastguard Worker *
923*38e8c45fSAndroid Build Coastguard Worker * The touchscreen is modeled as a physical object.
924*38e8c45fSAndroid Build Coastguard Worker * Initial condition is discussed below, but for now suppose that v(t=0) = 0
925*38e8c45fSAndroid Build Coastguard Worker *
926*38e8c45fSAndroid Build Coastguard Worker * The kinetic energy of the object at the release is E=0.5*m*v^2
927*38e8c45fSAndroid Build Coastguard Worker * Then vfinal = sqrt(2E/m). The goal is to calculate E.
928*38e8c45fSAndroid Build Coastguard Worker *
929*38e8c45fSAndroid Build Coastguard Worker * The kinetic energy at the release is equal to the total work done on the object by the finger.
930*38e8c45fSAndroid Build Coastguard Worker * The total work W is the sum of all dW along the path.
931*38e8c45fSAndroid Build Coastguard Worker *
932*38e8c45fSAndroid Build Coastguard Worker * dW = F*dx, where dx is the piece of path traveled.
933*38e8c45fSAndroid Build Coastguard Worker * Force is change of momentum over time, F = dp/dt = m dv/dt.
934*38e8c45fSAndroid Build Coastguard Worker * Then substituting:
935*38e8c45fSAndroid Build Coastguard Worker * dW = m (dv/dt) * dx = m * v * dv
936*38e8c45fSAndroid Build Coastguard Worker *
937*38e8c45fSAndroid Build Coastguard Worker * Summing along the path, we get:
938*38e8c45fSAndroid Build Coastguard Worker * W = sum(dW) = sum(m * v * dv) = m * sum(v * dv)
939*38e8c45fSAndroid Build Coastguard Worker * Since the mass stays constant, the equation for final velocity is:
940*38e8c45fSAndroid Build Coastguard Worker * vfinal = sqrt(2*sum(v * dv))
941*38e8c45fSAndroid Build Coastguard Worker *
942*38e8c45fSAndroid Build Coastguard Worker * Here,
943*38e8c45fSAndroid Build Coastguard Worker * dv : change of velocity = (v[i+1]-v[i])
944*38e8c45fSAndroid Build Coastguard Worker * dx : change of distance = (x[i+1]-x[i])
945*38e8c45fSAndroid Build Coastguard Worker * dt : change of time = (t[i+1]-t[i])
946*38e8c45fSAndroid Build Coastguard Worker * v : instantaneous velocity = dx/dt
947*38e8c45fSAndroid Build Coastguard Worker *
948*38e8c45fSAndroid Build Coastguard Worker * The final formula is:
949*38e8c45fSAndroid Build Coastguard Worker * vfinal = sqrt(2) * sqrt(sum((v[i]-v[i-1])*|v[i]|)) for all i
950*38e8c45fSAndroid Build Coastguard Worker * The absolute value is needed to properly account for the sign. If the velocity over a
951*38e8c45fSAndroid Build Coastguard Worker * particular segment descreases, then this indicates braking, which means that negative
952*38e8c45fSAndroid Build Coastguard Worker * work was done. So for two positive, but decreasing, velocities, this contribution would be
953*38e8c45fSAndroid Build Coastguard Worker * negative and will cause a smaller final velocity.
954*38e8c45fSAndroid Build Coastguard Worker *
955*38e8c45fSAndroid Build Coastguard Worker * Initial condition
956*38e8c45fSAndroid Build Coastguard Worker * There are two ways to deal with initial condition:
957*38e8c45fSAndroid Build Coastguard Worker * 1) Assume that v(0) = 0, which would mean that the screen is initially at rest.
958*38e8c45fSAndroid Build Coastguard Worker * This is not entirely accurate. We are only taking the past X ms of touch data, where X is
959*38e8c45fSAndroid Build Coastguard Worker * currently equal to 100. However, a touch event that created a fling probably lasted for longer
960*38e8c45fSAndroid Build Coastguard Worker * than that, which would mean that the user has already been interacting with the touchscreen
961*38e8c45fSAndroid Build Coastguard Worker * and it has probably already been moving.
962*38e8c45fSAndroid Build Coastguard Worker * 2) Assume that the touchscreen has already been moving at a certain velocity, calculate this
963*38e8c45fSAndroid Build Coastguard Worker * initial velocity and the equivalent energy, and start with this initial energy.
964*38e8c45fSAndroid Build Coastguard Worker * Consider an example where we have the following data, consisting of 3 points:
965*38e8c45fSAndroid Build Coastguard Worker * time: t0, t1, t2
966*38e8c45fSAndroid Build Coastguard Worker * x : x0, x1, x2
967*38e8c45fSAndroid Build Coastguard Worker * v : 0 , v1, v2
968*38e8c45fSAndroid Build Coastguard Worker * Here is what will happen in each of these scenarios:
969*38e8c45fSAndroid Build Coastguard Worker * 1) By directly applying the formula above with the v(0) = 0 boundary condition, we will get
970*38e8c45fSAndroid Build Coastguard Worker * vfinal = sqrt(2*(|v1|*(v1-v0) + |v2|*(v2-v1))). This can be simplified since v0=0
971*38e8c45fSAndroid Build Coastguard Worker * vfinal = sqrt(2*(|v1|*v1 + |v2|*(v2-v1))) = sqrt(2*(v1^2 + |v2|*(v2 - v1)))
972*38e8c45fSAndroid Build Coastguard Worker * since velocity is a real number
973*38e8c45fSAndroid Build Coastguard Worker * 2) If we treat the screen as already moving, then it must already have an energy (per mass)
974*38e8c45fSAndroid Build Coastguard Worker * equal to 1/2*v1^2. Then the initial energy should be 1/2*v1*2, and only the second segment
975*38e8c45fSAndroid Build Coastguard Worker * will contribute to the total kinetic energy (since we can effectively consider that v0=v1).
976*38e8c45fSAndroid Build Coastguard Worker * This will give the following expression for the final velocity:
977*38e8c45fSAndroid Build Coastguard Worker * vfinal = sqrt(2*(1/2*v1^2 + |v2|*(v2-v1)))
978*38e8c45fSAndroid Build Coastguard Worker * This analysis can be generalized to an arbitrary number of samples.
979*38e8c45fSAndroid Build Coastguard Worker *
980*38e8c45fSAndroid Build Coastguard Worker *
981*38e8c45fSAndroid Build Coastguard Worker * Comparing the two equations above, we see that the only mathematical difference
982*38e8c45fSAndroid Build Coastguard Worker * is the factor of 1/2 in front of the first velocity term.
983*38e8c45fSAndroid Build Coastguard Worker * This boundary condition would allow for the "proper" calculation of the case when all of the
984*38e8c45fSAndroid Build Coastguard Worker * samples are equally spaced in time and distance, which should suggest a constant velocity.
985*38e8c45fSAndroid Build Coastguard Worker *
986*38e8c45fSAndroid Build Coastguard Worker * Note that approach 2) is sensitive to the proper ordering of the data in time, since
987*38e8c45fSAndroid Build Coastguard Worker * the boundary condition must be applied to the oldest sample to be accurate.
988*38e8c45fSAndroid Build Coastguard Worker */
kineticEnergyToVelocity(float work)989*38e8c45fSAndroid Build Coastguard Worker static float kineticEnergyToVelocity(float work) {
990*38e8c45fSAndroid Build Coastguard Worker static constexpr float sqrt2 = 1.41421356237;
991*38e8c45fSAndroid Build Coastguard Worker return (work < 0 ? -1.0 : 1.0) * sqrtf(fabsf(work)) * sqrt2;
992*38e8c45fSAndroid Build Coastguard Worker }
993*38e8c45fSAndroid Build Coastguard Worker
getVelocity(int32_t pointerId) const994*38e8c45fSAndroid Build Coastguard Worker std::optional<float> ImpulseVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
995*38e8c45fSAndroid Build Coastguard Worker const auto movementIt = mMovements.find(pointerId);
996*38e8c45fSAndroid Build Coastguard Worker if (movementIt == mMovements.end()) {
997*38e8c45fSAndroid Build Coastguard Worker return std::nullopt; // no data
998*38e8c45fSAndroid Build Coastguard Worker }
999*38e8c45fSAndroid Build Coastguard Worker
1000*38e8c45fSAndroid Build Coastguard Worker const RingBuffer<Movement>& movements = movementIt->second;
1001*38e8c45fSAndroid Build Coastguard Worker const size_t size = movements.size();
1002*38e8c45fSAndroid Build Coastguard Worker if (size == 0) {
1003*38e8c45fSAndroid Build Coastguard Worker return std::nullopt; // no data
1004*38e8c45fSAndroid Build Coastguard Worker }
1005*38e8c45fSAndroid Build Coastguard Worker
1006*38e8c45fSAndroid Build Coastguard Worker float work = 0;
1007*38e8c45fSAndroid Build Coastguard Worker for (size_t i = 0; i < size - 1; i++) {
1008*38e8c45fSAndroid Build Coastguard Worker const Movement& mvt = movements[i];
1009*38e8c45fSAndroid Build Coastguard Worker const Movement& nextMvt = movements[i + 1];
1010*38e8c45fSAndroid Build Coastguard Worker
1011*38e8c45fSAndroid Build Coastguard Worker float vprev = kineticEnergyToVelocity(work);
1012*38e8c45fSAndroid Build Coastguard Worker float delta = mDeltaValues ? nextMvt.position : nextMvt.position - mvt.position;
1013*38e8c45fSAndroid Build Coastguard Worker float vcurr = delta / (SECONDS_PER_NANO * (nextMvt.eventTime - mvt.eventTime));
1014*38e8c45fSAndroid Build Coastguard Worker work += (vcurr - vprev) * fabsf(vcurr);
1015*38e8c45fSAndroid Build Coastguard Worker
1016*38e8c45fSAndroid Build Coastguard Worker if (i == 0) {
1017*38e8c45fSAndroid Build Coastguard Worker work *= 0.5; // initial condition, case 2) above
1018*38e8c45fSAndroid Build Coastguard Worker }
1019*38e8c45fSAndroid Build Coastguard Worker }
1020*38e8c45fSAndroid Build Coastguard Worker
1021*38e8c45fSAndroid Build Coastguard Worker const float velocity = kineticEnergyToVelocity(work);
1022*38e8c45fSAndroid Build Coastguard Worker ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", velocity);
1023*38e8c45fSAndroid Build Coastguard Worker
1024*38e8c45fSAndroid Build Coastguard Worker if (DEBUG_IMPULSE) {
1025*38e8c45fSAndroid Build Coastguard Worker // TODO(b/134179997): delete this block once the switch to 'impulse' is complete.
1026*38e8c45fSAndroid Build Coastguard Worker // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons.
1027*38e8c45fSAndroid Build Coastguard Worker // X axis chosen arbitrarily for velocity comparisons.
1028*38e8c45fSAndroid Build Coastguard Worker VelocityTracker lsq2(VelocityTracker::Strategy::LSQ2);
1029*38e8c45fSAndroid Build Coastguard Worker for (size_t i = 0; i < size; i++) {
1030*38e8c45fSAndroid Build Coastguard Worker const Movement& mvt = movements[i];
1031*38e8c45fSAndroid Build Coastguard Worker lsq2.addMovement(mvt.eventTime, pointerId, AMOTION_EVENT_AXIS_X, mvt.position);
1032*38e8c45fSAndroid Build Coastguard Worker }
1033*38e8c45fSAndroid Build Coastguard Worker std::optional<float> v = lsq2.getVelocity(AMOTION_EVENT_AXIS_X, pointerId);
1034*38e8c45fSAndroid Build Coastguard Worker if (v) {
1035*38e8c45fSAndroid Build Coastguard Worker ALOGD("lsq2 velocity: %.1f", *v);
1036*38e8c45fSAndroid Build Coastguard Worker } else {
1037*38e8c45fSAndroid Build Coastguard Worker ALOGD("lsq2 velocity: could not compute velocity");
1038*38e8c45fSAndroid Build Coastguard Worker }
1039*38e8c45fSAndroid Build Coastguard Worker }
1040*38e8c45fSAndroid Build Coastguard Worker return velocity;
1041*38e8c45fSAndroid Build Coastguard Worker }
1042*38e8c45fSAndroid Build Coastguard Worker
1043*38e8c45fSAndroid Build Coastguard Worker } // namespace android
1044