1 /* 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 the progress of the algorithm itself. 24 #define DEBUG_STRATEGY 0 25 26 #include <array> 27 #include <inttypes.h> 28 #include <limits.h> 29 #include <math.h> 30 #include <optional> 31 32 #include <android-base/stringprintf.h> 33 #include <cutils/properties.h> 34 #include <input/VelocityTracker.h> 35 #include <utils/BitSet.h> 36 #include <utils/Timers.h> 37 38 namespace android { 39 40 // Nanoseconds per milliseconds. 41 static const nsecs_t NANOS_PER_MS = 1000000; 42 43 // Threshold for determining that a pointer has stopped moving. 44 // Some input devices do not send ACTION_MOVE events in the case where a pointer has 45 // stopped. We need to detect this case so that we can accurately predict the 46 // velocity after the pointer starts moving again. 47 static const nsecs_t ASSUME_POINTER_STOPPED_TIME = 40 * NANOS_PER_MS; 48 49 50 static float vectorDot(const float* a, const float* b, uint32_t m) { 51 float r = 0; 52 for (size_t i = 0; i < m; i++) { 53 r += *(a++) * *(b++); 54 } 55 return r; 56 } 57 58 static float vectorNorm(const float* a, uint32_t m) { 59 float r = 0; 60 for (size_t i = 0; i < m; i++) { 61 float t = *(a++); 62 r += t * t; 63 } 64 return sqrtf(r); 65 } 66 67 #if DEBUG_STRATEGY || DEBUG_VELOCITY 68 static std::string vectorToString(const float* a, uint32_t m) { 69 std::string str; 70 str += "["; 71 for (size_t i = 0; i < m; i++) { 72 if (i) { 73 str += ","; 74 } 75 str += android::base::StringPrintf(" %f", *(a++)); 76 } 77 str += " ]"; 78 return str; 79 } 80 #endif 81 82 #if DEBUG_STRATEGY 83 static std::string matrixToString(const float* a, uint32_t m, uint32_t n, bool rowMajor) { 84 std::string str; 85 str = "["; 86 for (size_t i = 0; i < m; i++) { 87 if (i) { 88 str += ","; 89 } 90 str += " ["; 91 for (size_t j = 0; j < n; j++) { 92 if (j) { 93 str += ","; 94 } 95 str += android::base::StringPrintf(" %f", a[rowMajor ? i * n + j : j * m + i]); 96 } 97 str += " ]"; 98 } 99 str += " ]"; 100 return str; 101 } 102 #endif 103 104 105 // --- VelocityTracker --- 106 107 // The default velocity tracker strategy. 108 // Although other strategies are available for testing and comparison purposes, 109 // this is the strategy that applications will actually use. Be very careful 110 // when adjusting the default strategy because it can dramatically affect 111 // (often in a bad way) the user experience. 112 const char* VelocityTracker::DEFAULT_STRATEGY = "lsq2"; 113 114 VelocityTracker::VelocityTracker(const char* strategy) : 115 mLastEventTime(0), mCurrentPointerIdBits(0), mActivePointerId(-1) { 116 char value[PROPERTY_VALUE_MAX]; 117 118 // Allow the default strategy to be overridden using a system property for debugging. 119 if (!strategy) { 120 int length = property_get("persist.input.velocitytracker.strategy", value, nullptr); 121 if (length > 0) { 122 strategy = value; 123 } else { 124 strategy = DEFAULT_STRATEGY; 125 } 126 } 127 128 // Configure the strategy. 129 if (!configureStrategy(strategy)) { 130 ALOGD("Unrecognized velocity tracker strategy name '%s'.", strategy); 131 if (!configureStrategy(DEFAULT_STRATEGY)) { 132 LOG_ALWAYS_FATAL("Could not create the default velocity tracker strategy '%s'!", 133 strategy); 134 } 135 } 136 } 137 138 VelocityTracker::~VelocityTracker() { 139 delete mStrategy; 140 } 141 142 bool VelocityTracker::configureStrategy(const char* strategy) { 143 mStrategy = createStrategy(strategy); 144 return mStrategy != nullptr; 145 } 146 147 VelocityTrackerStrategy* VelocityTracker::createStrategy(const char* strategy) { 148 if (!strcmp("impulse", strategy)) { 149 // Physical model of pushing an object. Quality: VERY GOOD. 150 // Works with duplicate coordinates, unclean finger liftoff. 151 return new ImpulseVelocityTrackerStrategy(); 152 } 153 if (!strcmp("lsq1", strategy)) { 154 // 1st order least squares. Quality: POOR. 155 // Frequently underfits the touch data especially when the finger accelerates 156 // or changes direction. Often underestimates velocity. The direction 157 // is overly influenced by historical touch points. 158 return new LeastSquaresVelocityTrackerStrategy(1); 159 } 160 if (!strcmp("lsq2", strategy)) { 161 // 2nd order least squares. Quality: VERY GOOD. 162 // Pretty much ideal, but can be confused by certain kinds of touch data, 163 // particularly if the panel has a tendency to generate delayed, 164 // duplicate or jittery touch coordinates when the finger is released. 165 return new LeastSquaresVelocityTrackerStrategy(2); 166 } 167 if (!strcmp("lsq3", strategy)) { 168 // 3rd order least squares. Quality: UNUSABLE. 169 // Frequently overfits the touch data yielding wildly divergent estimates 170 // of the velocity when the finger is released. 171 return new LeastSquaresVelocityTrackerStrategy(3); 172 } 173 if (!strcmp("wlsq2-delta", strategy)) { 174 // 2nd order weighted least squares, delta weighting. Quality: EXPERIMENTAL 175 return new LeastSquaresVelocityTrackerStrategy(2, 176 LeastSquaresVelocityTrackerStrategy::WEIGHTING_DELTA); 177 } 178 if (!strcmp("wlsq2-central", strategy)) { 179 // 2nd order weighted least squares, central weighting. Quality: EXPERIMENTAL 180 return new LeastSquaresVelocityTrackerStrategy(2, 181 LeastSquaresVelocityTrackerStrategy::WEIGHTING_CENTRAL); 182 } 183 if (!strcmp("wlsq2-recent", strategy)) { 184 // 2nd order weighted least squares, recent weighting. Quality: EXPERIMENTAL 185 return new LeastSquaresVelocityTrackerStrategy(2, 186 LeastSquaresVelocityTrackerStrategy::WEIGHTING_RECENT); 187 } 188 if (!strcmp("int1", strategy)) { 189 // 1st order integrating filter. Quality: GOOD. 190 // Not as good as 'lsq2' because it cannot estimate acceleration but it is 191 // more tolerant of errors. Like 'lsq1', this strategy tends to underestimate 192 // the velocity of a fling but this strategy tends to respond to changes in 193 // direction more quickly and accurately. 194 return new IntegratingVelocityTrackerStrategy(1); 195 } 196 if (!strcmp("int2", strategy)) { 197 // 2nd order integrating filter. Quality: EXPERIMENTAL. 198 // For comparison purposes only. Unlike 'int1' this strategy can compensate 199 // for acceleration but it typically overestimates the effect. 200 return new IntegratingVelocityTrackerStrategy(2); 201 } 202 if (!strcmp("legacy", strategy)) { 203 // Legacy velocity tracker algorithm. Quality: POOR. 204 // For comparison purposes only. This algorithm is strongly influenced by 205 // old data points, consistently underestimates velocity and takes a very long 206 // time to adjust to changes in direction. 207 return new LegacyVelocityTrackerStrategy(); 208 } 209 return nullptr; 210 } 211 212 void VelocityTracker::clear() { 213 mCurrentPointerIdBits.clear(); 214 mActivePointerId = -1; 215 216 mStrategy->clear(); 217 } 218 219 void VelocityTracker::clearPointers(BitSet32 idBits) { 220 BitSet32 remainingIdBits(mCurrentPointerIdBits.value & ~idBits.value); 221 mCurrentPointerIdBits = remainingIdBits; 222 223 if (mActivePointerId >= 0 && idBits.hasBit(mActivePointerId)) { 224 mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1; 225 } 226 227 mStrategy->clearPointers(idBits); 228 } 229 230 void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions) { 231 while (idBits.count() > MAX_POINTERS) { 232 idBits.clearLastMarkedBit(); 233 } 234 235 if ((mCurrentPointerIdBits.value & idBits.value) 236 && eventTime >= mLastEventTime + ASSUME_POINTER_STOPPED_TIME) { 237 #if DEBUG_VELOCITY 238 ALOGD("VelocityTracker: stopped for %0.3f ms, clearing state.", 239 (eventTime - mLastEventTime) * 0.000001f); 240 #endif 241 // We have not received any movements for too long. Assume that all pointers 242 // have stopped. 243 mStrategy->clear(); 244 } 245 mLastEventTime = eventTime; 246 247 mCurrentPointerIdBits = idBits; 248 if (mActivePointerId < 0 || !idBits.hasBit(mActivePointerId)) { 249 mActivePointerId = idBits.isEmpty() ? -1 : idBits.firstMarkedBit(); 250 } 251 252 mStrategy->addMovement(eventTime, idBits, positions); 253 254 #if DEBUG_VELOCITY 255 ALOGD("VelocityTracker: addMovement eventTime=%" PRId64 ", idBits=0x%08x, activePointerId=%d", 256 eventTime, idBits.value, mActivePointerId); 257 for (BitSet32 iterBits(idBits); !iterBits.isEmpty(); ) { 258 uint32_t id = iterBits.firstMarkedBit(); 259 uint32_t index = idBits.getIndexOfBit(id); 260 iterBits.clearBit(id); 261 Estimator estimator; 262 getEstimator(id, &estimator); 263 ALOGD(" %d: position (%0.3f, %0.3f), " 264 "estimator (degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f)", 265 id, positions[index].x, positions[index].y, 266 int(estimator.degree), 267 vectorToString(estimator.xCoeff, estimator.degree + 1).c_str(), 268 vectorToString(estimator.yCoeff, estimator.degree + 1).c_str(), 269 estimator.confidence); 270 } 271 #endif 272 } 273 274 void VelocityTracker::addMovement(const MotionEvent* event) { 275 int32_t actionMasked = event->getActionMasked(); 276 277 switch (actionMasked) { 278 case AMOTION_EVENT_ACTION_DOWN: 279 case AMOTION_EVENT_ACTION_HOVER_ENTER: 280 // Clear all pointers on down before adding the new movement. 281 clear(); 282 break; 283 case AMOTION_EVENT_ACTION_POINTER_DOWN: { 284 // Start a new movement trace for a pointer that just went down. 285 // We do this on down instead of on up because the client may want to query the 286 // final velocity for a pointer that just went up. 287 BitSet32 downIdBits; 288 downIdBits.markBit(event->getPointerId(event->getActionIndex())); 289 clearPointers(downIdBits); 290 break; 291 } 292 case AMOTION_EVENT_ACTION_MOVE: 293 case AMOTION_EVENT_ACTION_HOVER_MOVE: 294 break; 295 default: 296 // Ignore all other actions because they do not convey any new information about 297 // pointer movement. We also want to preserve the last known velocity of the pointers. 298 // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position 299 // of the pointers that went up. ACTION_POINTER_UP does include the new position of 300 // pointers that remained down but we will also receive an ACTION_MOVE with this 301 // information if any of them actually moved. Since we don't know how many pointers 302 // will be going up at once it makes sense to just wait for the following ACTION_MOVE 303 // before adding the movement. 304 return; 305 } 306 307 size_t pointerCount = event->getPointerCount(); 308 if (pointerCount > MAX_POINTERS) { 309 pointerCount = MAX_POINTERS; 310 } 311 312 BitSet32 idBits; 313 for (size_t i = 0; i < pointerCount; i++) { 314 idBits.markBit(event->getPointerId(i)); 315 } 316 317 uint32_t pointerIndex[MAX_POINTERS]; 318 for (size_t i = 0; i < pointerCount; i++) { 319 pointerIndex[i] = idBits.getIndexOfBit(event->getPointerId(i)); 320 } 321 322 nsecs_t eventTime; 323 Position positions[pointerCount]; 324 325 size_t historySize = event->getHistorySize(); 326 for (size_t h = 0; h < historySize; h++) { 327 eventTime = event->getHistoricalEventTime(h); 328 for (size_t i = 0; i < pointerCount; i++) { 329 uint32_t index = pointerIndex[i]; 330 positions[index].x = event->getHistoricalX(i, h); 331 positions[index].y = event->getHistoricalY(i, h); 332 } 333 addMovement(eventTime, idBits, positions); 334 } 335 336 eventTime = event->getEventTime(); 337 for (size_t i = 0; i < pointerCount; i++) { 338 uint32_t index = pointerIndex[i]; 339 positions[index].x = event->getX(i); 340 positions[index].y = event->getY(i); 341 } 342 addMovement(eventTime, idBits, positions); 343 } 344 345 bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const { 346 Estimator estimator; 347 if (getEstimator(id, &estimator) && estimator.degree >= 1) { 348 *outVx = estimator.xCoeff[1]; 349 *outVy = estimator.yCoeff[1]; 350 return true; 351 } 352 *outVx = 0; 353 *outVy = 0; 354 return false; 355 } 356 357 bool VelocityTracker::getEstimator(uint32_t id, Estimator* outEstimator) const { 358 return mStrategy->getEstimator(id, outEstimator); 359 } 360 361 362 // --- LeastSquaresVelocityTrackerStrategy --- 363 364 LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy( 365 uint32_t degree, Weighting weighting) : 366 mDegree(degree), mWeighting(weighting) { 367 clear(); 368 } 369 370 LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() { 371 } 372 373 void LeastSquaresVelocityTrackerStrategy::clear() { 374 mIndex = 0; 375 mMovements[0].idBits.clear(); 376 } 377 378 void LeastSquaresVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { 379 BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value); 380 mMovements[mIndex].idBits = remainingIdBits; 381 } 382 383 void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, 384 const VelocityTracker::Position* positions) { 385 if (mMovements[mIndex].eventTime != eventTime) { 386 // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates 387 // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include 388 // the new pointer. If the eventtimes for both events are identical, just update the data 389 // for this time. 390 // We only compare against the last value, as it is likely that addMovement is called 391 // in chronological order as events occur. 392 mIndex++; 393 } 394 if (mIndex == HISTORY_SIZE) { 395 mIndex = 0; 396 } 397 398 Movement& movement = mMovements[mIndex]; 399 movement.eventTime = eventTime; 400 movement.idBits = idBits; 401 uint32_t count = idBits.count(); 402 for (uint32_t i = 0; i < count; i++) { 403 movement.positions[i] = positions[i]; 404 } 405 } 406 407 /** 408 * Solves a linear least squares problem to obtain a N degree polynomial that fits 409 * the specified input data as nearly as possible. 410 * 411 * Returns true if a solution is found, false otherwise. 412 * 413 * The input consists of two vectors of data points X and Y with indices 0..m-1 414 * along with a weight vector W of the same size. 415 * 416 * The output is a vector B with indices 0..n that describes a polynomial 417 * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1] X[i] 418 * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is minimized. 419 * 420 * Accordingly, the weight vector W should be initialized by the caller with the 421 * reciprocal square root of the variance of the error in each input data point. 422 * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 / stddev(Y[i]). 423 * The weights express the relative importance of each data point. If the weights are 424 * all 1, then the data points are considered to be of equal importance when fitting 425 * the polynomial. It is a good idea to choose weights that diminish the importance 426 * of data points that may have higher than usual error margins. 427 * 428 * Errors among data points are assumed to be independent. W is represented here 429 * as a vector although in the literature it is typically taken to be a diagonal matrix. 430 * 431 * That is to say, the function that generated the input data can be approximated 432 * by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n. 433 * 434 * The coefficient of determination (R^2) is also returned to describe the goodness 435 * of fit of the model for the given data. It is a value between 0 and 1, where 1 436 * indicates perfect correspondence. 437 * 438 * This function first expands the X vector to a m by n matrix A such that 439 * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then 440 * multiplies it by w[i]./ 441 * 442 * Then it calculates the QR decomposition of A yielding an m by m orthonormal matrix Q 443 * and an m by n upper triangular matrix R. Because R is upper triangular (lower 444 * part is all zeroes), we can simplify the decomposition into an m by n matrix 445 * Q1 and a n by n matrix R1 such that A = Q1 R1. 446 * 447 * Finally we solve the system of linear equations given by R1 B = (Qtranspose W Y) 448 * to find B. 449 * 450 * For efficiency, we lay out A and Q column-wise in memory because we frequently 451 * operate on the column vectors. Conversely, we lay out R row-wise. 452 * 453 * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares 454 * http://en.wikipedia.org/wiki/Gram-Schmidt 455 */ 456 static bool solveLeastSquares(const float* x, const float* y, 457 const float* w, uint32_t m, uint32_t n, float* outB, float* outDet) { 458 #if DEBUG_STRATEGY 459 ALOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n), 460 vectorToString(x, m).c_str(), vectorToString(y, m).c_str(), 461 vectorToString(w, m).c_str()); 462 #endif 463 464 // Expand the X vector to a matrix A, pre-multiplied by the weights. 465 float a[n][m]; // column-major order 466 for (uint32_t h = 0; h < m; h++) { 467 a[0][h] = w[h]; 468 for (uint32_t i = 1; i < n; i++) { 469 a[i][h] = a[i - 1][h] * x[h]; 470 } 471 } 472 #if DEBUG_STRATEGY 473 ALOGD(" - a=%s", matrixToString(&a[0][0], m, n, false /*rowMajor*/).c_str()); 474 #endif 475 476 // Apply the Gram-Schmidt process to A to obtain its QR decomposition. 477 float q[n][m]; // orthonormal basis, column-major order 478 float r[n][n]; // upper triangular matrix, row-major order 479 for (uint32_t j = 0; j < n; j++) { 480 for (uint32_t h = 0; h < m; h++) { 481 q[j][h] = a[j][h]; 482 } 483 for (uint32_t i = 0; i < j; i++) { 484 float dot = vectorDot(&q[j][0], &q[i][0], m); 485 for (uint32_t h = 0; h < m; h++) { 486 q[j][h] -= dot * q[i][h]; 487 } 488 } 489 490 float norm = vectorNorm(&q[j][0], m); 491 if (norm < 0.000001f) { 492 // vectors are linearly dependent or zero so no solution 493 #if DEBUG_STRATEGY 494 ALOGD(" - no solution, norm=%f", norm); 495 #endif 496 return false; 497 } 498 499 float invNorm = 1.0f / norm; 500 for (uint32_t h = 0; h < m; h++) { 501 q[j][h] *= invNorm; 502 } 503 for (uint32_t i = 0; i < n; i++) { 504 r[j][i] = i < j ? 0 : vectorDot(&q[j][0], &a[i][0], m); 505 } 506 } 507 #if DEBUG_STRATEGY 508 ALOGD(" - q=%s", matrixToString(&q[0][0], m, n, false /*rowMajor*/).c_str()); 509 ALOGD(" - r=%s", matrixToString(&r[0][0], n, n, true /*rowMajor*/).c_str()); 510 511 // calculate QR, if we factored A correctly then QR should equal A 512 float qr[n][m]; 513 for (uint32_t h = 0; h < m; h++) { 514 for (uint32_t i = 0; i < n; i++) { 515 qr[i][h] = 0; 516 for (uint32_t j = 0; j < n; j++) { 517 qr[i][h] += q[j][h] * r[j][i]; 518 } 519 } 520 } 521 ALOGD(" - qr=%s", matrixToString(&qr[0][0], m, n, false /*rowMajor*/).c_str()); 522 #endif 523 524 // Solve R B = Qt W Y to find B. This is easy because R is upper triangular. 525 // We just work from bottom-right to top-left calculating B's coefficients. 526 float wy[m]; 527 for (uint32_t h = 0; h < m; h++) { 528 wy[h] = y[h] * w[h]; 529 } 530 for (uint32_t i = n; i != 0; ) { 531 i--; 532 outB[i] = vectorDot(&q[i][0], wy, m); 533 for (uint32_t j = n - 1; j > i; j--) { 534 outB[i] -= r[i][j] * outB[j]; 535 } 536 outB[i] /= r[i][i]; 537 } 538 #if DEBUG_STRATEGY 539 ALOGD(" - b=%s", vectorToString(outB, n).c_str()); 540 #endif 541 542 // Calculate the coefficient of determination as 1 - (SSerr / SStot) where 543 // SSerr is the residual sum of squares (variance of the error), 544 // and SStot is the total sum of squares (variance of the data) where each 545 // has been weighted. 546 float ymean = 0; 547 for (uint32_t h = 0; h < m; h++) { 548 ymean += y[h]; 549 } 550 ymean /= m; 551 552 float sserr = 0; 553 float sstot = 0; 554 for (uint32_t h = 0; h < m; h++) { 555 float err = y[h] - outB[0]; 556 float term = 1; 557 for (uint32_t i = 1; i < n; i++) { 558 term *= x[h]; 559 err -= term * outB[i]; 560 } 561 sserr += w[h] * w[h] * err * err; 562 float var = y[h] - ymean; 563 sstot += w[h] * w[h] * var * var; 564 } 565 *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1; 566 #if DEBUG_STRATEGY 567 ALOGD(" - sserr=%f", sserr); 568 ALOGD(" - sstot=%f", sstot); 569 ALOGD(" - det=%f", *outDet); 570 #endif 571 return true; 572 } 573 574 /* 575 * Optimized unweighted second-order least squares fit. About 2x speed improvement compared to 576 * the default implementation 577 */ 578 static std::optional<std::array<float, 3>> solveUnweightedLeastSquaresDeg2( 579 const float* x, const float* y, size_t count) { 580 // Solving y = a*x^2 + b*x + c 581 float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0; 582 583 for (size_t i = 0; i < count; i++) { 584 float xi = x[i]; 585 float yi = y[i]; 586 float xi2 = xi*xi; 587 float xi3 = xi2*xi; 588 float xi4 = xi3*xi; 589 float xiyi = xi*yi; 590 float xi2yi = xi2*yi; 591 592 sxi += xi; 593 sxi2 += xi2; 594 sxiyi += xiyi; 595 sxi2yi += xi2yi; 596 syi += yi; 597 sxi3 += xi3; 598 sxi4 += xi4; 599 } 600 601 float Sxx = sxi2 - sxi*sxi / count; 602 float Sxy = sxiyi - sxi*syi / count; 603 float Sxx2 = sxi3 - sxi*sxi2 / count; 604 float Sx2y = sxi2yi - sxi2*syi / count; 605 float Sx2x2 = sxi4 - sxi2*sxi2 / count; 606 607 float denominator = Sxx*Sx2x2 - Sxx2*Sxx2; 608 if (denominator == 0) { 609 ALOGW("division by 0 when computing velocity, Sxx=%f, Sx2x2=%f, Sxx2=%f", Sxx, Sx2x2, Sxx2); 610 return std::nullopt; 611 } 612 // Compute a 613 float numerator = Sx2y*Sxx - Sxy*Sxx2; 614 float a = numerator / denominator; 615 616 // Compute b 617 numerator = Sxy*Sx2x2 - Sx2y*Sxx2; 618 float b = numerator / denominator; 619 620 // Compute c 621 float c = syi/count - b * sxi/count - a * sxi2/count; 622 623 return std::make_optional(std::array<float, 3>({c, b, a})); 624 } 625 626 bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id, 627 VelocityTracker::Estimator* outEstimator) const { 628 outEstimator->clear(); 629 630 // Iterate over movement samples in reverse time order and collect samples. 631 float x[HISTORY_SIZE]; 632 float y[HISTORY_SIZE]; 633 float w[HISTORY_SIZE]; 634 float time[HISTORY_SIZE]; 635 uint32_t m = 0; 636 uint32_t index = mIndex; 637 const Movement& newestMovement = mMovements[mIndex]; 638 do { 639 const Movement& movement = mMovements[index]; 640 if (!movement.idBits.hasBit(id)) { 641 break; 642 } 643 644 nsecs_t age = newestMovement.eventTime - movement.eventTime; 645 if (age > HORIZON) { 646 break; 647 } 648 649 const VelocityTracker::Position& position = movement.getPosition(id); 650 x[m] = position.x; 651 y[m] = position.y; 652 w[m] = chooseWeight(index); 653 time[m] = -age * 0.000000001f; 654 index = (index == 0 ? HISTORY_SIZE : index) - 1; 655 } while (++m < HISTORY_SIZE); 656 657 if (m == 0) { 658 return false; // no data 659 } 660 661 // Calculate a least squares polynomial fit. 662 uint32_t degree = mDegree; 663 if (degree > m - 1) { 664 degree = m - 1; 665 } 666 667 if (degree == 2 && mWeighting == WEIGHTING_NONE) { 668 // Optimize unweighted, quadratic polynomial fit 669 std::optional<std::array<float, 3>> xCoeff = solveUnweightedLeastSquaresDeg2(time, x, m); 670 std::optional<std::array<float, 3>> yCoeff = solveUnweightedLeastSquaresDeg2(time, y, m); 671 if (xCoeff && yCoeff) { 672 outEstimator->time = newestMovement.eventTime; 673 outEstimator->degree = 2; 674 outEstimator->confidence = 1; 675 for (size_t i = 0; i <= outEstimator->degree; i++) { 676 outEstimator->xCoeff[i] = (*xCoeff)[i]; 677 outEstimator->yCoeff[i] = (*yCoeff)[i]; 678 } 679 return true; 680 } 681 } else if (degree >= 1) { 682 // General case for an Nth degree polynomial fit 683 float xdet, ydet; 684 uint32_t n = degree + 1; 685 if (solveLeastSquares(time, x, w, m, n, outEstimator->xCoeff, &xdet) 686 && solveLeastSquares(time, y, w, m, n, outEstimator->yCoeff, &ydet)) { 687 outEstimator->time = newestMovement.eventTime; 688 outEstimator->degree = degree; 689 outEstimator->confidence = xdet * ydet; 690 #if DEBUG_STRATEGY 691 ALOGD("estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f", 692 int(outEstimator->degree), 693 vectorToString(outEstimator->xCoeff, n).c_str(), 694 vectorToString(outEstimator->yCoeff, n).c_str(), 695 outEstimator->confidence); 696 #endif 697 return true; 698 } 699 } 700 701 // No velocity data available for this pointer, but we do have its current position. 702 outEstimator->xCoeff[0] = x[0]; 703 outEstimator->yCoeff[0] = y[0]; 704 outEstimator->time = newestMovement.eventTime; 705 outEstimator->degree = 0; 706 outEstimator->confidence = 1; 707 return true; 708 } 709 710 float LeastSquaresVelocityTrackerStrategy::chooseWeight(uint32_t index) const { 711 switch (mWeighting) { 712 case WEIGHTING_DELTA: { 713 // Weight points based on how much time elapsed between them and the next 714 // point so that points that "cover" a shorter time span are weighed less. 715 // delta 0ms: 0.5 716 // delta 10ms: 1.0 717 if (index == mIndex) { 718 return 1.0f; 719 } 720 uint32_t nextIndex = (index + 1) % HISTORY_SIZE; 721 float deltaMillis = (mMovements[nextIndex].eventTime- mMovements[index].eventTime) 722 * 0.000001f; 723 if (deltaMillis < 0) { 724 return 0.5f; 725 } 726 if (deltaMillis < 10) { 727 return 0.5f + deltaMillis * 0.05; 728 } 729 return 1.0f; 730 } 731 732 case WEIGHTING_CENTRAL: { 733 // Weight points based on their age, weighing very recent and very old points less. 734 // age 0ms: 0.5 735 // age 10ms: 1.0 736 // age 50ms: 1.0 737 // age 60ms: 0.5 738 float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime) 739 * 0.000001f; 740 if (ageMillis < 0) { 741 return 0.5f; 742 } 743 if (ageMillis < 10) { 744 return 0.5f + ageMillis * 0.05; 745 } 746 if (ageMillis < 50) { 747 return 1.0f; 748 } 749 if (ageMillis < 60) { 750 return 0.5f + (60 - ageMillis) * 0.05; 751 } 752 return 0.5f; 753 } 754 755 case WEIGHTING_RECENT: { 756 // Weight points based on their age, weighing older points less. 757 // age 0ms: 1.0 758 // age 50ms: 1.0 759 // age 100ms: 0.5 760 float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime) 761 * 0.000001f; 762 if (ageMillis < 50) { 763 return 1.0f; 764 } 765 if (ageMillis < 100) { 766 return 0.5f + (100 - ageMillis) * 0.01f; 767 } 768 return 0.5f; 769 } 770 771 case WEIGHTING_NONE: 772 default: 773 return 1.0f; 774 } 775 } 776 777 778 // --- IntegratingVelocityTrackerStrategy --- 779 780 IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(uint32_t degree) : 781 mDegree(degree) { 782 } 783 784 IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() { 785 } 786 787 void IntegratingVelocityTrackerStrategy::clear() { 788 mPointerIdBits.clear(); 789 } 790 791 void IntegratingVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { 792 mPointerIdBits.value &= ~idBits.value; 793 } 794 795 void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, 796 const VelocityTracker::Position* positions) { 797 uint32_t index = 0; 798 for (BitSet32 iterIdBits(idBits); !iterIdBits.isEmpty();) { 799 uint32_t id = iterIdBits.clearFirstMarkedBit(); 800 State& state = mPointerState[id]; 801 const VelocityTracker::Position& position = positions[index++]; 802 if (mPointerIdBits.hasBit(id)) { 803 updateState(state, eventTime, position.x, position.y); 804 } else { 805 initState(state, eventTime, position.x, position.y); 806 } 807 } 808 809 mPointerIdBits = idBits; 810 } 811 812 bool IntegratingVelocityTrackerStrategy::getEstimator(uint32_t id, 813 VelocityTracker::Estimator* outEstimator) const { 814 outEstimator->clear(); 815 816 if (mPointerIdBits.hasBit(id)) { 817 const State& state = mPointerState[id]; 818 populateEstimator(state, outEstimator); 819 return true; 820 } 821 822 return false; 823 } 824 825 void IntegratingVelocityTrackerStrategy::initState(State& state, 826 nsecs_t eventTime, float xpos, float ypos) const { 827 state.updateTime = eventTime; 828 state.degree = 0; 829 830 state.xpos = xpos; 831 state.xvel = 0; 832 state.xaccel = 0; 833 state.ypos = ypos; 834 state.yvel = 0; 835 state.yaccel = 0; 836 } 837 838 void IntegratingVelocityTrackerStrategy::updateState(State& state, 839 nsecs_t eventTime, float xpos, float ypos) const { 840 const nsecs_t MIN_TIME_DELTA = 2 * NANOS_PER_MS; 841 const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds 842 843 if (eventTime <= state.updateTime + MIN_TIME_DELTA) { 844 return; 845 } 846 847 float dt = (eventTime - state.updateTime) * 0.000000001f; 848 state.updateTime = eventTime; 849 850 float xvel = (xpos - state.xpos) / dt; 851 float yvel = (ypos - state.ypos) / dt; 852 if (state.degree == 0) { 853 state.xvel = xvel; 854 state.yvel = yvel; 855 state.degree = 1; 856 } else { 857 float alpha = dt / (FILTER_TIME_CONSTANT + dt); 858 if (mDegree == 1) { 859 state.xvel += (xvel - state.xvel) * alpha; 860 state.yvel += (yvel - state.yvel) * alpha; 861 } else { 862 float xaccel = (xvel - state.xvel) / dt; 863 float yaccel = (yvel - state.yvel) / dt; 864 if (state.degree == 1) { 865 state.xaccel = xaccel; 866 state.yaccel = yaccel; 867 state.degree = 2; 868 } else { 869 state.xaccel += (xaccel - state.xaccel) * alpha; 870 state.yaccel += (yaccel - state.yaccel) * alpha; 871 } 872 state.xvel += (state.xaccel * dt) * alpha; 873 state.yvel += (state.yaccel * dt) * alpha; 874 } 875 } 876 state.xpos = xpos; 877 state.ypos = ypos; 878 } 879 880 void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state, 881 VelocityTracker::Estimator* outEstimator) const { 882 outEstimator->time = state.updateTime; 883 outEstimator->confidence = 1.0f; 884 outEstimator->degree = state.degree; 885 outEstimator->xCoeff[0] = state.xpos; 886 outEstimator->xCoeff[1] = state.xvel; 887 outEstimator->xCoeff[2] = state.xaccel / 2; 888 outEstimator->yCoeff[0] = state.ypos; 889 outEstimator->yCoeff[1] = state.yvel; 890 outEstimator->yCoeff[2] = state.yaccel / 2; 891 } 892 893 894 // --- LegacyVelocityTrackerStrategy --- 895 896 LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() { 897 clear(); 898 } 899 900 LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() { 901 } 902 903 void LegacyVelocityTrackerStrategy::clear() { 904 mIndex = 0; 905 mMovements[0].idBits.clear(); 906 } 907 908 void LegacyVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { 909 BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value); 910 mMovements[mIndex].idBits = remainingIdBits; 911 } 912 913 void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, 914 const VelocityTracker::Position* positions) { 915 if (++mIndex == HISTORY_SIZE) { 916 mIndex = 0; 917 } 918 919 Movement& movement = mMovements[mIndex]; 920 movement.eventTime = eventTime; 921 movement.idBits = idBits; 922 uint32_t count = idBits.count(); 923 for (uint32_t i = 0; i < count; i++) { 924 movement.positions[i] = positions[i]; 925 } 926 } 927 928 bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id, 929 VelocityTracker::Estimator* outEstimator) const { 930 outEstimator->clear(); 931 932 const Movement& newestMovement = mMovements[mIndex]; 933 if (!newestMovement.idBits.hasBit(id)) { 934 return false; // no data 935 } 936 937 // Find the oldest sample that contains the pointer and that is not older than HORIZON. 938 nsecs_t minTime = newestMovement.eventTime - HORIZON; 939 uint32_t oldestIndex = mIndex; 940 uint32_t numTouches = 1; 941 do { 942 uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1; 943 const Movement& nextOldestMovement = mMovements[nextOldestIndex]; 944 if (!nextOldestMovement.idBits.hasBit(id) 945 || nextOldestMovement.eventTime < minTime) { 946 break; 947 } 948 oldestIndex = nextOldestIndex; 949 } while (++numTouches < HISTORY_SIZE); 950 951 // Calculate an exponentially weighted moving average of the velocity estimate 952 // at different points in time measured relative to the oldest sample. 953 // This is essentially an IIR filter. Newer samples are weighted more heavily 954 // than older samples. Samples at equal time points are weighted more or less 955 // equally. 956 // 957 // One tricky problem is that the sample data may be poorly conditioned. 958 // Sometimes samples arrive very close together in time which can cause us to 959 // overestimate the velocity at that time point. Most samples might be measured 960 // 16ms apart but some consecutive samples could be only 0.5sm apart because 961 // the hardware or driver reports them irregularly or in bursts. 962 float accumVx = 0; 963 float accumVy = 0; 964 uint32_t index = oldestIndex; 965 uint32_t samplesUsed = 0; 966 const Movement& oldestMovement = mMovements[oldestIndex]; 967 const VelocityTracker::Position& oldestPosition = oldestMovement.getPosition(id); 968 nsecs_t lastDuration = 0; 969 970 while (numTouches-- > 1) { 971 if (++index == HISTORY_SIZE) { 972 index = 0; 973 } 974 const Movement& movement = mMovements[index]; 975 nsecs_t duration = movement.eventTime - oldestMovement.eventTime; 976 977 // If the duration between samples is small, we may significantly overestimate 978 // the velocity. Consequently, we impose a minimum duration constraint on the 979 // samples that we include in the calculation. 980 if (duration >= MIN_DURATION) { 981 const VelocityTracker::Position& position = movement.getPosition(id); 982 float scale = 1000000000.0f / duration; // one over time delta in seconds 983 float vx = (position.x - oldestPosition.x) * scale; 984 float vy = (position.y - oldestPosition.y) * scale; 985 accumVx = (accumVx * lastDuration + vx * duration) / (duration + lastDuration); 986 accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration); 987 lastDuration = duration; 988 samplesUsed += 1; 989 } 990 } 991 992 // Report velocity. 993 const VelocityTracker::Position& newestPosition = newestMovement.getPosition(id); 994 outEstimator->time = newestMovement.eventTime; 995 outEstimator->confidence = 1; 996 outEstimator->xCoeff[0] = newestPosition.x; 997 outEstimator->yCoeff[0] = newestPosition.y; 998 if (samplesUsed) { 999 outEstimator->xCoeff[1] = accumVx; 1000 outEstimator->yCoeff[1] = accumVy; 1001 outEstimator->degree = 1; 1002 } else { 1003 outEstimator->degree = 0; 1004 } 1005 return true; 1006 } 1007 1008 // --- ImpulseVelocityTrackerStrategy --- 1009 1010 ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy() { 1011 clear(); 1012 } 1013 1014 ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() { 1015 } 1016 1017 void ImpulseVelocityTrackerStrategy::clear() { 1018 mIndex = 0; 1019 mMovements[0].idBits.clear(); 1020 } 1021 1022 void ImpulseVelocityTrackerStrategy::clearPointers(BitSet32 idBits) { 1023 BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value); 1024 mMovements[mIndex].idBits = remainingIdBits; 1025 } 1026 1027 void ImpulseVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits, 1028 const VelocityTracker::Position* positions) { 1029 if (mMovements[mIndex].eventTime != eventTime) { 1030 // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates 1031 // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include 1032 // the new pointer. If the eventtimes for both events are identical, just update the data 1033 // for this time. 1034 // We only compare against the last value, as it is likely that addMovement is called 1035 // in chronological order as events occur. 1036 mIndex++; 1037 } 1038 if (mIndex == HISTORY_SIZE) { 1039 mIndex = 0; 1040 } 1041 1042 Movement& movement = mMovements[mIndex]; 1043 movement.eventTime = eventTime; 1044 movement.idBits = idBits; 1045 uint32_t count = idBits.count(); 1046 for (uint32_t i = 0; i < count; i++) { 1047 movement.positions[i] = positions[i]; 1048 } 1049 } 1050 1051 /** 1052 * Calculate the total impulse provided to the screen and the resulting velocity. 1053 * 1054 * The touchscreen is modeled as a physical object. 1055 * Initial condition is discussed below, but for now suppose that v(t=0) = 0 1056 * 1057 * The kinetic energy of the object at the release is E=0.5*m*v^2 1058 * Then vfinal = sqrt(2E/m). The goal is to calculate E. 1059 * 1060 * The kinetic energy at the release is equal to the total work done on the object by the finger. 1061 * The total work W is the sum of all dW along the path. 1062 * 1063 * dW = F*dx, where dx is the piece of path traveled. 1064 * Force is change of momentum over time, F = dp/dt = m dv/dt. 1065 * Then substituting: 1066 * dW = m (dv/dt) * dx = m * v * dv 1067 * 1068 * Summing along the path, we get: 1069 * W = sum(dW) = sum(m * v * dv) = m * sum(v * dv) 1070 * Since the mass stays constant, the equation for final velocity is: 1071 * vfinal = sqrt(2*sum(v * dv)) 1072 * 1073 * Here, 1074 * dv : change of velocity = (v[i+1]-v[i]) 1075 * dx : change of distance = (x[i+1]-x[i]) 1076 * dt : change of time = (t[i+1]-t[i]) 1077 * v : instantaneous velocity = dx/dt 1078 * 1079 * The final formula is: 1080 * vfinal = sqrt(2) * sqrt(sum((v[i]-v[i-1])*|v[i]|)) for all i 1081 * The absolute value is needed to properly account for the sign. If the velocity over a 1082 * particular segment descreases, then this indicates braking, which means that negative 1083 * work was done. So for two positive, but decreasing, velocities, this contribution would be 1084 * negative and will cause a smaller final velocity. 1085 * 1086 * Initial condition 1087 * There are two ways to deal with initial condition: 1088 * 1) Assume that v(0) = 0, which would mean that the screen is initially at rest. 1089 * This is not entirely accurate. We are only taking the past X ms of touch data, where X is 1090 * currently equal to 100. However, a touch event that created a fling probably lasted for longer 1091 * than that, which would mean that the user has already been interacting with the touchscreen 1092 * and it has probably already been moving. 1093 * 2) Assume that the touchscreen has already been moving at a certain velocity, calculate this 1094 * initial velocity and the equivalent energy, and start with this initial energy. 1095 * Consider an example where we have the following data, consisting of 3 points: 1096 * time: t0, t1, t2 1097 * x : x0, x1, x2 1098 * v : 0 , v1, v2 1099 * Here is what will happen in each of these scenarios: 1100 * 1) By directly applying the formula above with the v(0) = 0 boundary condition, we will get 1101 * vfinal = sqrt(2*(|v1|*(v1-v0) + |v2|*(v2-v1))). This can be simplified since v0=0 1102 * vfinal = sqrt(2*(|v1|*v1 + |v2|*(v2-v1))) = sqrt(2*(v1^2 + |v2|*(v2 - v1))) 1103 * since velocity is a real number 1104 * 2) If we treat the screen as already moving, then it must already have an energy (per mass) 1105 * equal to 1/2*v1^2. Then the initial energy should be 1/2*v1*2, and only the second segment 1106 * will contribute to the total kinetic energy (since we can effectively consider that v0=v1). 1107 * This will give the following expression for the final velocity: 1108 * vfinal = sqrt(2*(1/2*v1^2 + |v2|*(v2-v1))) 1109 * This analysis can be generalized to an arbitrary number of samples. 1110 * 1111 * 1112 * Comparing the two equations above, we see that the only mathematical difference 1113 * is the factor of 1/2 in front of the first velocity term. 1114 * This boundary condition would allow for the "proper" calculation of the case when all of the 1115 * samples are equally spaced in time and distance, which should suggest a constant velocity. 1116 * 1117 * Note that approach 2) is sensitive to the proper ordering of the data in time, since 1118 * the boundary condition must be applied to the oldest sample to be accurate. 1119 */ 1120 static float kineticEnergyToVelocity(float work) { 1121 static constexpr float sqrt2 = 1.41421356237; 1122 return (work < 0 ? -1.0 : 1.0) * sqrtf(fabsf(work)) * sqrt2; 1123 } 1124 1125 static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t count) { 1126 // The input should be in reversed time order (most recent sample at index i=0) 1127 // t[i] is in nanoseconds, but due to FP arithmetic, convert to seconds inside this function 1128 static constexpr float SECONDS_PER_NANO = 1E-9; 1129 1130 if (count < 2) { 1131 return 0; // if 0 or 1 points, velocity is zero 1132 } 1133 if (t[1] > t[0]) { // Algorithm will still work, but not perfectly 1134 ALOGE("Samples provided to calculateImpulseVelocity in the wrong order"); 1135 } 1136 if (count == 2) { // if 2 points, basic linear calculation 1137 if (t[1] == t[0]) { 1138 ALOGE("Events have identical time stamps t=%" PRId64 ", setting velocity = 0", t[0]); 1139 return 0; 1140 } 1141 return (x[1] - x[0]) / (SECONDS_PER_NANO * (t[1] - t[0])); 1142 } 1143 // Guaranteed to have at least 3 points here 1144 float work = 0; 1145 for (size_t i = count - 1; i > 0 ; i--) { // start with the oldest sample and go forward in time 1146 if (t[i] == t[i-1]) { 1147 ALOGE("Events have identical time stamps t=%" PRId64 ", skipping sample", t[i]); 1148 continue; 1149 } 1150 float vprev = kineticEnergyToVelocity(work); // v[i-1] 1151 float vcurr = (x[i] - x[i-1]) / (SECONDS_PER_NANO * (t[i] - t[i-1])); // v[i] 1152 work += (vcurr - vprev) * fabsf(vcurr); 1153 if (i == count - 1) { 1154 work *= 0.5; // initial condition, case 2) above 1155 } 1156 } 1157 return kineticEnergyToVelocity(work); 1158 } 1159 1160 bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id, 1161 VelocityTracker::Estimator* outEstimator) const { 1162 outEstimator->clear(); 1163 1164 // Iterate over movement samples in reverse time order and collect samples. 1165 float x[HISTORY_SIZE]; 1166 float y[HISTORY_SIZE]; 1167 nsecs_t time[HISTORY_SIZE]; 1168 size_t m = 0; // number of points that will be used for fitting 1169 size_t index = mIndex; 1170 const Movement& newestMovement = mMovements[mIndex]; 1171 do { 1172 const Movement& movement = mMovements[index]; 1173 if (!movement.idBits.hasBit(id)) { 1174 break; 1175 } 1176 1177 nsecs_t age = newestMovement.eventTime - movement.eventTime; 1178 if (age > HORIZON) { 1179 break; 1180 } 1181 1182 const VelocityTracker::Position& position = movement.getPosition(id); 1183 x[m] = position.x; 1184 y[m] = position.y; 1185 time[m] = movement.eventTime; 1186 index = (index == 0 ? HISTORY_SIZE : index) - 1; 1187 } while (++m < HISTORY_SIZE); 1188 1189 if (m == 0) { 1190 return false; // no data 1191 } 1192 outEstimator->xCoeff[0] = 0; 1193 outEstimator->yCoeff[0] = 0; 1194 outEstimator->xCoeff[1] = calculateImpulseVelocity(time, x, m); 1195 outEstimator->yCoeff[1] = calculateImpulseVelocity(time, y, m); 1196 outEstimator->xCoeff[2] = 0; 1197 outEstimator->yCoeff[2] = 0; 1198 outEstimator->time = newestMovement.eventTime; 1199 outEstimator->degree = 2; // similar results to 2nd degree fit 1200 outEstimator->confidence = 1; 1201 #if DEBUG_STRATEGY 1202 ALOGD("velocity: (%f, %f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]); 1203 #endif 1204 return true; 1205 } 1206 1207 } // namespace android 1208