blob: 5dbafd8ebafb27724e816aa49b91083925df9323 [file] [log] [blame]
Jeff Brown8a90e6e2012-05-11 12:24:35 -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"
18//#define LOG_NDEBUG 0
19
20// Log debug messages about velocity tracking.
21#define DEBUG_VELOCITY 0
22
23// Log debug messages about least squares fitting.
24#define DEBUG_LEAST_SQUARES 0
25
26#include <math.h>
27#include <limits.h>
28
29#include <androidfw/VelocityTracker.h>
30#include <utils/BitSet.h>
31#include <utils/String8.h>
32#include <utils/Timers.h>
33
34namespace android {
35
Jeff Brown90729402012-05-14 18:46:18 -070036// Nanoseconds per milliseconds.
37static const nsecs_t NANOS_PER_MS = 1000000;
38
39// Threshold for determining that a pointer has stopped moving.
40// Some input devices do not send ACTION_MOVE events in the case where a pointer has
41// stopped. We need to detect this case so that we can accurately predict the
42// velocity after the pointer starts moving again.
43static const nsecs_t ASSUME_POINTER_STOPPED_TIME = 40 * NANOS_PER_MS;
44
45
Jeff Brown85bd0d62012-05-13 15:30:42 -070046static float vectorDot(const float* a, const float* b, uint32_t m) {
Jeff Brown8a90e6e2012-05-11 12:24:35 -070047 float r = 0;
48 while (m--) {
49 r += *(a++) * *(b++);
50 }
51 return r;
52}
53
Jeff Brown85bd0d62012-05-13 15:30:42 -070054static float vectorNorm(const float* a, uint32_t m) {
Jeff Brown8a90e6e2012-05-11 12:24:35 -070055 float r = 0;
56 while (m--) {
57 float t = *(a++);
58 r += t * t;
59 }
60 return sqrtf(r);
61}
62
63#if DEBUG_LEAST_SQUARES || DEBUG_VELOCITY
64static String8 vectorToString(const float* a, uint32_t m) {
65 String8 str;
66 str.append("[");
67 while (m--) {
68 str.appendFormat(" %f", *(a++));
69 if (m) {
70 str.append(",");
71 }
72 }
73 str.append(" ]");
74 return str;
75}
76
77static String8 matrixToString(const float* a, uint32_t m, uint32_t n, bool rowMajor) {
78 String8 str;
79 str.append("[");
80 for (size_t i = 0; i < m; i++) {
81 if (i) {
82 str.append(",");
83 }
84 str.append(" [");
85 for (size_t j = 0; j < n; j++) {
86 if (j) {
87 str.append(",");
88 }
89 str.appendFormat(" %f", a[rowMajor ? i * n + j : j * m + i]);
90 }
91 str.append(" ]");
92 }
93 str.append(" ]");
94 return str;
95}
96#endif
97
Jeff Brown85bd0d62012-05-13 15:30:42 -070098
99// --- VelocityTracker ---
100
101VelocityTracker::VelocityTracker() :
Jeff Brown90729402012-05-14 18:46:18 -0700102 mLastEventTime(0), mCurrentPointerIdBits(0), mActivePointerId(-1),
Jeff Brown85bd0d62012-05-13 15:30:42 -0700103 mStrategy(new LeastSquaresVelocityTrackerStrategy()) {
104}
105
Jeff Brown85bd0d62012-05-13 15:30:42 -0700106VelocityTracker::~VelocityTracker() {
107 delete mStrategy;
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700108}
109
110void VelocityTracker::clear() {
Jeff Brown85bd0d62012-05-13 15:30:42 -0700111 mCurrentPointerIdBits.clear();
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700112 mActivePointerId = -1;
Jeff Brown85bd0d62012-05-13 15:30:42 -0700113
114 mStrategy->clear();
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700115}
116
117void VelocityTracker::clearPointers(BitSet32 idBits) {
Jeff Brown85bd0d62012-05-13 15:30:42 -0700118 BitSet32 remainingIdBits(mCurrentPointerIdBits.value & ~idBits.value);
119 mCurrentPointerIdBits = remainingIdBits;
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700120
121 if (mActivePointerId >= 0 && idBits.hasBit(mActivePointerId)) {
122 mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1;
123 }
Jeff Brown85bd0d62012-05-13 15:30:42 -0700124
125 mStrategy->clearPointers(idBits);
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700126}
127
128void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions) {
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700129 while (idBits.count() > MAX_POINTERS) {
130 idBits.clearLastMarkedBit();
131 }
132
Jeff Brown90729402012-05-14 18:46:18 -0700133 if ((mCurrentPointerIdBits.value & idBits.value)
134 && eventTime >= mLastEventTime + ASSUME_POINTER_STOPPED_TIME) {
135#if DEBUG_VELOCITY
136 ALOGD("VelocityTracker: stopped for %0.3f ms, clearing state.",
137 (eventTime - mLastEventTime) * 0.000001f);
138#endif
139 // We have not received any movements for too long. Assume that all pointers
140 // have stopped.
141 mStrategy->clear();
142 }
143 mLastEventTime = eventTime;
144
Jeff Brown85bd0d62012-05-13 15:30:42 -0700145 mCurrentPointerIdBits = idBits;
146 if (mActivePointerId < 0 || !idBits.hasBit(mActivePointerId)) {
147 mActivePointerId = idBits.isEmpty() ? -1 : idBits.firstMarkedBit();
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700148 }
149
Jeff Brown85bd0d62012-05-13 15:30:42 -0700150 mStrategy->addMovement(eventTime, idBits, positions);
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700151
152#if DEBUG_VELOCITY
153 ALOGD("VelocityTracker: addMovement eventTime=%lld, idBits=0x%08x, activePointerId=%d",
154 eventTime, idBits.value, mActivePointerId);
155 for (BitSet32 iterBits(idBits); !iterBits.isEmpty(); ) {
156 uint32_t id = iterBits.firstMarkedBit();
157 uint32_t index = idBits.getIndexOfBit(id);
158 iterBits.clearBit(id);
159 Estimator estimator;
Jeff Brown85bd0d62012-05-13 15:30:42 -0700160 getEstimator(id, &estimator);
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700161 ALOGD(" %d: position (%0.3f, %0.3f), "
162 "estimator (degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f)",
163 id, positions[index].x, positions[index].y,
164 int(estimator.degree),
Jeff Browndcab1902012-05-14 18:44:17 -0700165 vectorToString(estimator.xCoeff, estimator.degree + 1).string(),
166 vectorToString(estimator.yCoeff, estimator.degree + 1).string(),
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700167 estimator.confidence);
168 }
169#endif
170}
171
172void VelocityTracker::addMovement(const MotionEvent* event) {
173 int32_t actionMasked = event->getActionMasked();
174
175 switch (actionMasked) {
176 case AMOTION_EVENT_ACTION_DOWN:
177 case AMOTION_EVENT_ACTION_HOVER_ENTER:
178 // Clear all pointers on down before adding the new movement.
179 clear();
180 break;
181 case AMOTION_EVENT_ACTION_POINTER_DOWN: {
182 // Start a new movement trace for a pointer that just went down.
183 // We do this on down instead of on up because the client may want to query the
184 // final velocity for a pointer that just went up.
185 BitSet32 downIdBits;
186 downIdBits.markBit(event->getPointerId(event->getActionIndex()));
187 clearPointers(downIdBits);
188 break;
189 }
190 case AMOTION_EVENT_ACTION_MOVE:
191 case AMOTION_EVENT_ACTION_HOVER_MOVE:
192 break;
193 default:
194 // Ignore all other actions because they do not convey any new information about
195 // pointer movement. We also want to preserve the last known velocity of the pointers.
196 // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position
197 // of the pointers that went up. ACTION_POINTER_UP does include the new position of
198 // pointers that remained down but we will also receive an ACTION_MOVE with this
199 // information if any of them actually moved. Since we don't know how many pointers
200 // will be going up at once it makes sense to just wait for the following ACTION_MOVE
201 // before adding the movement.
202 return;
203 }
204
205 size_t pointerCount = event->getPointerCount();
206 if (pointerCount > MAX_POINTERS) {
207 pointerCount = MAX_POINTERS;
208 }
209
210 BitSet32 idBits;
211 for (size_t i = 0; i < pointerCount; i++) {
212 idBits.markBit(event->getPointerId(i));
213 }
214
Jeff Browndcab1902012-05-14 18:44:17 -0700215 uint32_t pointerIndex[MAX_POINTERS];
216 for (size_t i = 0; i < pointerCount; i++) {
217 pointerIndex[i] = idBits.getIndexOfBit(event->getPointerId(i));
218 }
219
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700220 nsecs_t eventTime;
221 Position positions[pointerCount];
222
223 size_t historySize = event->getHistorySize();
224 for (size_t h = 0; h < historySize; h++) {
225 eventTime = event->getHistoricalEventTime(h);
226 for (size_t i = 0; i < pointerCount; i++) {
Jeff Browndcab1902012-05-14 18:44:17 -0700227 uint32_t index = pointerIndex[i];
228 positions[index].x = event->getHistoricalX(i, h);
229 positions[index].y = event->getHistoricalY(i, h);
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700230 }
231 addMovement(eventTime, idBits, positions);
232 }
233
234 eventTime = event->getEventTime();
235 for (size_t i = 0; i < pointerCount; i++) {
Jeff Browndcab1902012-05-14 18:44:17 -0700236 uint32_t index = pointerIndex[i];
237 positions[index].x = event->getX(i);
238 positions[index].y = event->getY(i);
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700239 }
240 addMovement(eventTime, idBits, positions);
241}
242
Jeff Brown85bd0d62012-05-13 15:30:42 -0700243bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const {
244 Estimator estimator;
245 if (getEstimator(id, &estimator) && estimator.degree >= 1) {
246 *outVx = estimator.xCoeff[1];
247 *outVy = estimator.yCoeff[1];
248 return true;
249 }
250 *outVx = 0;
251 *outVy = 0;
252 return false;
253}
254
255bool VelocityTracker::getEstimator(uint32_t id, Estimator* outEstimator) const {
256 return mStrategy->getEstimator(id, outEstimator);
257}
258
259
260// --- LeastSquaresVelocityTrackerStrategy ---
261
262const uint32_t LeastSquaresVelocityTrackerStrategy::DEGREE;
263const nsecs_t LeastSquaresVelocityTrackerStrategy::HORIZON;
264const uint32_t LeastSquaresVelocityTrackerStrategy::HISTORY_SIZE;
265
266LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy() {
267 clear();
268}
269
270LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {
271}
272
273void LeastSquaresVelocityTrackerStrategy::clear() {
274 mIndex = 0;
275 mMovements[0].idBits.clear();
276}
277
278void LeastSquaresVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
279 BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
280 mMovements[mIndex].idBits = remainingIdBits;
281}
282
283void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
284 const VelocityTracker::Position* positions) {
285 if (++mIndex == HISTORY_SIZE) {
286 mIndex = 0;
287 }
288
289 Movement& movement = mMovements[mIndex];
290 movement.eventTime = eventTime;
291 movement.idBits = idBits;
292 uint32_t count = idBits.count();
293 for (uint32_t i = 0; i < count; i++) {
294 movement.positions[i] = positions[i];
295 }
296}
297
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700298/**
299 * Solves a linear least squares problem to obtain a N degree polynomial that fits
300 * the specified input data as nearly as possible.
301 *
302 * Returns true if a solution is found, false otherwise.
303 *
304 * The input consists of two vectors of data points X and Y with indices 0..m-1.
305 * The output is a vector B with indices 0..n-1 that describes a polynomial
306 * that fits the data, such the sum of abs(Y[i] - (B[0] + B[1] X[i] + B[2] X[i]^2 ... B[n] X[i]^n))
307 * for all i between 0 and m-1 is minimized.
308 *
309 * That is to say, the function that generated the input data can be approximated
310 * by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n.
311 *
312 * The coefficient of determination (R^2) is also returned to describe the goodness
313 * of fit of the model for the given data. It is a value between 0 and 1, where 1
314 * indicates perfect correspondence.
315 *
316 * This function first expands the X vector to a m by n matrix A such that
317 * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n.
318 *
319 * Then it calculates the QR decomposition of A yielding an m by m orthonormal matrix Q
320 * and an m by n upper triangular matrix R. Because R is upper triangular (lower
321 * part is all zeroes), we can simplify the decomposition into an m by n matrix
322 * Q1 and a n by n matrix R1 such that A = Q1 R1.
323 *
324 * Finally we solve the system of linear equations given by R1 B = (Qtranspose Y)
325 * to find B.
326 *
327 * For efficiency, we lay out A and Q column-wise in memory because we frequently
328 * operate on the column vectors. Conversely, we lay out R row-wise.
329 *
330 * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
331 * http://en.wikipedia.org/wiki/Gram-Schmidt
332 */
333static bool solveLeastSquares(const float* x, const float* y, uint32_t m, uint32_t n,
334 float* outB, float* outDet) {
335#if DEBUG_LEAST_SQUARES
336 ALOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s", int(m), int(n),
337 vectorToString(x, m).string(), vectorToString(y, m).string());
338#endif
339
340 // Expand the X vector to a matrix A.
341 float a[n][m]; // column-major order
342 for (uint32_t h = 0; h < m; h++) {
343 a[0][h] = 1;
344 for (uint32_t i = 1; i < n; i++) {
345 a[i][h] = a[i - 1][h] * x[h];
346 }
347 }
348#if DEBUG_LEAST_SQUARES
349 ALOGD(" - a=%s", matrixToString(&a[0][0], m, n, false /*rowMajor*/).string());
350#endif
351
352 // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
353 float q[n][m]; // orthonormal basis, column-major order
354 float r[n][n]; // upper triangular matrix, row-major order
355 for (uint32_t j = 0; j < n; j++) {
356 for (uint32_t h = 0; h < m; h++) {
357 q[j][h] = a[j][h];
358 }
359 for (uint32_t i = 0; i < j; i++) {
360 float dot = vectorDot(&q[j][0], &q[i][0], m);
361 for (uint32_t h = 0; h < m; h++) {
362 q[j][h] -= dot * q[i][h];
363 }
364 }
365
366 float norm = vectorNorm(&q[j][0], m);
367 if (norm < 0.000001f) {
368 // vectors are linearly dependent or zero so no solution
369#if DEBUG_LEAST_SQUARES
370 ALOGD(" - no solution, norm=%f", norm);
371#endif
372 return false;
373 }
374
375 float invNorm = 1.0f / norm;
376 for (uint32_t h = 0; h < m; h++) {
377 q[j][h] *= invNorm;
378 }
379 for (uint32_t i = 0; i < n; i++) {
380 r[j][i] = i < j ? 0 : vectorDot(&q[j][0], &a[i][0], m);
381 }
382 }
383#if DEBUG_LEAST_SQUARES
384 ALOGD(" - q=%s", matrixToString(&q[0][0], m, n, false /*rowMajor*/).string());
385 ALOGD(" - r=%s", matrixToString(&r[0][0], n, n, true /*rowMajor*/).string());
386
387 // calculate QR, if we factored A correctly then QR should equal A
388 float qr[n][m];
389 for (uint32_t h = 0; h < m; h++) {
390 for (uint32_t i = 0; i < n; i++) {
391 qr[i][h] = 0;
392 for (uint32_t j = 0; j < n; j++) {
393 qr[i][h] += q[j][h] * r[j][i];
394 }
395 }
396 }
397 ALOGD(" - qr=%s", matrixToString(&qr[0][0], m, n, false /*rowMajor*/).string());
398#endif
399
400 // Solve R B = Qt Y to find B. This is easy because R is upper triangular.
401 // We just work from bottom-right to top-left calculating B's coefficients.
402 for (uint32_t i = n; i-- != 0; ) {
403 outB[i] = vectorDot(&q[i][0], y, m);
404 for (uint32_t j = n - 1; j > i; j--) {
405 outB[i] -= r[i][j] * outB[j];
406 }
407 outB[i] /= r[i][i];
408 }
409#if DEBUG_LEAST_SQUARES
410 ALOGD(" - b=%s", vectorToString(outB, n).string());
411#endif
412
413 // Calculate the coefficient of determination as 1 - (SSerr / SStot) where
414 // SSerr is the residual sum of squares (squared variance of the error),
415 // and SStot is the total sum of squares (squared variance of the data).
416 float ymean = 0;
417 for (uint32_t h = 0; h < m; h++) {
418 ymean += y[h];
419 }
420 ymean /= m;
421
422 float sserr = 0;
423 float sstot = 0;
424 for (uint32_t h = 0; h < m; h++) {
425 float err = y[h] - outB[0];
426 float term = 1;
427 for (uint32_t i = 1; i < n; i++) {
428 term *= x[h];
429 err -= term * outB[i];
430 }
431 sserr += err * err;
432 float var = y[h] - ymean;
433 sstot += var * var;
434 }
435 *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1;
436#if DEBUG_LEAST_SQUARES
437 ALOGD(" - sserr=%f", sserr);
438 ALOGD(" - sstot=%f", sstot);
439 ALOGD(" - det=%f", *outDet);
440#endif
441 return true;
442}
443
Jeff Brown85bd0d62012-05-13 15:30:42 -0700444bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id,
445 VelocityTracker::Estimator* outEstimator) const {
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700446 outEstimator->clear();
447
448 // Iterate over movement samples in reverse time order and collect samples.
449 float x[HISTORY_SIZE];
450 float y[HISTORY_SIZE];
451 float time[HISTORY_SIZE];
452 uint32_t m = 0;
453 uint32_t index = mIndex;
454 const Movement& newestMovement = mMovements[mIndex];
455 do {
456 const Movement& movement = mMovements[index];
457 if (!movement.idBits.hasBit(id)) {
458 break;
459 }
460
461 nsecs_t age = newestMovement.eventTime - movement.eventTime;
Jeff Brown85bd0d62012-05-13 15:30:42 -0700462 if (age > HORIZON) {
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700463 break;
464 }
465
Jeff Brown85bd0d62012-05-13 15:30:42 -0700466 const VelocityTracker::Position& position = movement.getPosition(id);
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700467 x[m] = position.x;
468 y[m] = position.y;
469 time[m] = -age * 0.000000001f;
470 index = (index == 0 ? HISTORY_SIZE : index) - 1;
471 } while (++m < HISTORY_SIZE);
472
473 if (m == 0) {
474 return false; // no data
475 }
476
477 // Calculate a least squares polynomial fit.
Jeff Brown85bd0d62012-05-13 15:30:42 -0700478 uint32_t degree = DEGREE;
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700479 if (degree > m - 1) {
480 degree = m - 1;
481 }
482 if (degree >= 1) {
483 float xdet, ydet;
484 uint32_t n = degree + 1;
485 if (solveLeastSquares(time, x, m, n, outEstimator->xCoeff, &xdet)
486 && solveLeastSquares(time, y, m, n, outEstimator->yCoeff, &ydet)) {
Jeff Brown90729402012-05-14 18:46:18 -0700487 outEstimator->time = newestMovement.eventTime;
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700488 outEstimator->degree = degree;
489 outEstimator->confidence = xdet * ydet;
490#if DEBUG_LEAST_SQUARES
491 ALOGD("estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f",
492 int(outEstimator->degree),
493 vectorToString(outEstimator->xCoeff, n).string(),
494 vectorToString(outEstimator->yCoeff, n).string(),
495 outEstimator->confidence);
496#endif
497 return true;
498 }
499 }
500
501 // No velocity data available for this pointer, but we do have its current position.
502 outEstimator->xCoeff[0] = x[0];
503 outEstimator->yCoeff[0] = y[0];
Jeff Brown90729402012-05-14 18:46:18 -0700504 outEstimator->time = newestMovement.eventTime;
Jeff Brown8a90e6e2012-05-11 12:24:35 -0700505 outEstimator->degree = 0;
506 outEstimator->confidence = 1;
507 return true;
508}
509
510} // namespace android