Tweak VelocityTracker.

Bug: 5265529

Calculate the velocity using the most recent touch sample as the
point of reference instead of the oldest.  This change more heavily
weights recent touch samples and reduces the sample time window
used for calculation.  This significantly improves the accuracy
of fling gesture detection.

Change-Id: Ib1940933e786e5f6a731552a99bcd9400741d55f
This commit is contained in:
Jeff Brown 2011-09-09 15:39:35 -07:00
parent 5904d3ed17
commit 137c3c5495
2 changed files with 35 additions and 63 deletions

View File

@ -660,15 +660,19 @@ private:
static const uint32_t HISTORY_SIZE = 10; static const uint32_t HISTORY_SIZE = 10;
// Oldest sample to consider when calculating the velocity. // Oldest sample to consider when calculating the velocity.
static const nsecs_t MAX_AGE = 200 * 1000000; // 200 ms static const nsecs_t MAX_AGE = 100 * 1000000; // 100 ms
// The minimum duration between samples when estimating velocity. // The minimum duration between samples when estimating velocity.
static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms static const nsecs_t MIN_DURATION = 5 * 1000000; // 5 ms
struct Movement { struct Movement {
nsecs_t eventTime; nsecs_t eventTime;
BitSet32 idBits; BitSet32 idBits;
Position positions[MAX_POINTERS]; Position positions[MAX_POINTERS];
inline const Position& getPosition(uint32_t id) const {
return positions[idBits.getIndexOfBit(id)];
}
}; };
uint32_t mIndex; uint32_t mIndex;

View File

@ -752,6 +752,7 @@ void VelocityTracker::addMovement(const MotionEvent* event) {
switch (actionMasked) { switch (actionMasked) {
case AMOTION_EVENT_ACTION_DOWN: case AMOTION_EVENT_ACTION_DOWN:
case AMOTION_EVENT_ACTION_HOVER_ENTER:
// Clear all pointers on down before adding the new movement. // Clear all pointers on down before adding the new movement.
clear(); clear();
break; break;
@ -764,12 +765,11 @@ void VelocityTracker::addMovement(const MotionEvent* event) {
clearPointers(downIdBits); clearPointers(downIdBits);
break; break;
} }
case AMOTION_EVENT_ACTION_OUTSIDE: case AMOTION_EVENT_ACTION_MOVE:
case AMOTION_EVENT_ACTION_CANCEL: case AMOTION_EVENT_ACTION_HOVER_MOVE:
case AMOTION_EVENT_ACTION_SCROLL: break;
case AMOTION_EVENT_ACTION_UP: default:
case AMOTION_EVENT_ACTION_POINTER_UP: // Ignore all other actions because they do not convey any new information about
// Ignore these actions because they do not convey any new information about
// pointer movement. We also want to preserve the last known velocity of the pointers. // pointer movement. We also want to preserve the last known velocity of the pointers.
// Note that ACTION_UP and ACTION_POINTER_UP always report the last known position // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position
// of the pointers that went up. ACTION_POINTER_UP does include the new position of // of the pointers that went up. ACTION_POINTER_UP does include the new position of
@ -814,68 +814,36 @@ void VelocityTracker::addMovement(const MotionEvent* event) {
bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const { bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const {
const Movement& newestMovement = mMovements[mIndex]; const Movement& newestMovement = mMovements[mIndex];
if (newestMovement.idBits.hasBit(id)) { if (newestMovement.idBits.hasBit(id)) {
// Find the oldest sample that contains the pointer and that is not older than MAX_AGE. const Position& newestPosition = newestMovement.getPosition(id);
nsecs_t minTime = newestMovement.eventTime - MAX_AGE;
uint32_t oldestIndex = mIndex;
uint32_t numTouches = 1;
do {
uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1;
const Movement& nextOldestMovement = mMovements[nextOldestIndex];
if (!nextOldestMovement.idBits.hasBit(id)
|| nextOldestMovement.eventTime < minTime) {
break;
}
oldestIndex = nextOldestIndex;
} while (++numTouches < HISTORY_SIZE);
// Calculate an exponentially weighted moving average of the velocity estimate
// at different points in time measured relative to the oldest sample.
// This is essentially an IIR filter. Newer samples are weighted more heavily
// than older samples. Samples at equal time points are weighted more or less
// equally.
//
// One tricky problem is that the sample data may be poorly conditioned.
// Sometimes samples arrive very close together in time which can cause us to
// overestimate the velocity at that time point. Most samples might be measured
// 16ms apart but some consecutive samples could be only 0.5sm apart because
// the hardware or driver reports them irregularly or in bursts.
float accumVx = 0; float accumVx = 0;
float accumVy = 0; float accumVy = 0;
uint32_t index = oldestIndex; float duration = 0;
uint32_t samplesUsed = 0;
const Movement& oldestMovement = mMovements[oldestIndex];
const Position& oldestPosition =
oldestMovement.positions[oldestMovement.idBits.getIndexOfBit(id)];
nsecs_t lastDuration = 0;
while (numTouches-- > 1) { // Iterate over movement samples in reverse time order and accumulate velocity.
if (++index == HISTORY_SIZE) { uint32_t index = mIndex;
index = 0; do {
} index = (index == 0 ? HISTORY_SIZE : index) - 1;
const Movement& movement = mMovements[index]; const Movement& movement = mMovements[index];
nsecs_t duration = movement.eventTime - oldestMovement.eventTime; if (!movement.idBits.hasBit(id)) {
break;
// If the duration between samples is small, we may significantly overestimate
// the velocity. Consequently, we impose a minimum duration constraint on the
// samples that we include in the calculation.
if (duration >= MIN_DURATION) {
const Position& position = movement.positions[movement.idBits.getIndexOfBit(id)];
float scale = 1000000000.0f / duration; // one over time delta in seconds
float vx = (position.x - oldestPosition.x) * scale;
float vy = (position.y - oldestPosition.y) * scale;
accumVx = (accumVx * lastDuration + vx * duration) / (duration + lastDuration);
accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration);
lastDuration = duration;
samplesUsed += 1;
} }
}
nsecs_t age = newestMovement.eventTime - movement.eventTime;
if (age > MAX_AGE) {
break;
}
const Position& position = movement.getPosition(id);
accumVx += newestPosition.x - position.x;
accumVy += newestPosition.y - position.y;
duration += age;
} while (index != mIndex);
// Make sure we used at least one sample. // Make sure we used at least one sample.
if (samplesUsed != 0) { if (duration >= MIN_DURATION) {
*outVx = accumVx; float scale = 1000000000.0f / duration; // one over time delta in seconds
*outVy = accumVy; *outVx = accumVx * scale;
*outVy = accumVy * scale;
return true; return true;
} }
} }