Merge "Use touch pad gestures to manipulate the pointer. (DO NOT MERGE)" into honeycomb-mr2

This commit is contained in:
Jeff Brown 2011-05-25 14:31:38 -07:00 committed by Android (Google) Code Review
commit bf7493c52b
4 changed files with 217 additions and 2 deletions

View File

@ -27,6 +27,7 @@
#include <utils/Timers.h>
#include <utils/RefBase.h>
#include <utils/String8.h>
#include <utils/BitSet.h>
#ifdef HAVE_ANDROID_OS
class SkMatrix;
@ -210,6 +211,13 @@ struct PointerCoords {
status_t writeToParcel(Parcel* parcel) const;
#endif
bool operator==(const PointerCoords& other) const;
inline bool operator!=(const PointerCoords& other) const {
return !(*this == other);
}
void copyFrom(const PointerCoords& other);
private:
void tooManyAxes(int axis);
};
@ -544,6 +552,53 @@ private:
MotionEvent mMotionEvent;
};
/*
* Calculates the velocity of pointer motions over time.
* Uses essentially the same algorithm as android.view.VelocityTracker.
*/
class VelocityTracker {
public:
struct Position {
float x, y;
};
VelocityTracker();
// Resets the velocity tracker state.
void clear();
// Adds movement information for a set of pointers.
// The idBits bitfield specifies the pointer ids of the pointers whose positions
// are included in the movement.
// The positions array contains position information for each pointer in order by
// increasing id. Its size should be equal to the number of one bits in idBits.
void addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions);
// Gets the velocity of the specified pointer id in position units per second.
// Returns false and sets the velocity components to zero if there is no movement
// information for the pointer.
bool getVelocity(uint32_t id, float* outVx, float* outVy) const;
private:
// Number of samples to keep.
static const uint32_t HISTORY_SIZE = 10;
// Oldest sample to consider when calculating the velocity.
static const nsecs_t MAX_AGE = 200 * 1000000; // 200 ms
// The minimum duration between samples when estimating velocity.
static const nsecs_t MIN_DURATION = 5 * 1000000; // 5 ms
struct Movement {
nsecs_t eventTime;
BitSet32 idBits;
Position positions[MAX_POINTERS];
};
uint32_t mIndex;
Movement mMovements[HISTORY_SIZE];
};
/*
* Describes the characteristics and capabilities of an input device.
*/

View File

@ -61,6 +61,12 @@ struct BitSet32 {
// Result is undefined if all bits are marked.
inline uint32_t firstUnmarkedBit() const { return __builtin_clz(~ value); }
// Gets the index of the specified bit in the set, which is the number of
// marked bits that appear before the specified bit.
inline uint32_t getIndexOfBit(uint32_t n) const {
return __builtin_popcount(value & ~(0xffffffffUL >> n));
}
inline bool operator== (const BitSet32& other) const { return value == other.value; }
inline bool operator!= (const BitSet32& other) const { return value != other.value; }
};

View File

@ -7,8 +7,12 @@
//#define LOG_NDEBUG 0
// Log debug messages about keymap probing.
#define DEBUG_PROBE 0
// Log debug messages about velocity tracking.
#define DEBUG_VELOCITY 0
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
@ -347,6 +351,27 @@ void PointerCoords::tooManyAxes(int axis) {
"cannot contain more than %d axis values.", axis, int(MAX_AXES));
}
bool PointerCoords::operator==(const PointerCoords& other) const {
if (bits != other.bits) {
return false;
}
uint32_t count = __builtin_popcountll(bits);
for (uint32_t i = 0; i < count; i++) {
if (values[i] != other.values[i]) {
return false;
}
}
return true;
}
void PointerCoords::copyFrom(const PointerCoords& other) {
bits = other.bits;
uint32_t count = __builtin_popcountll(bits);
for (uint32_t i = 0; i < count; i++) {
values[i] = other.values[i];
}
}
// --- MotionEvent ---
@ -633,6 +658,135 @@ bool MotionEvent::isTouchEvent(int32_t source, int32_t action) {
}
// --- VelocityTracker ---
VelocityTracker::VelocityTracker() {
clear();
}
void VelocityTracker::clear() {
mIndex = 0;
mMovements[0].idBits.clear();
}
void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions) {
if (++mIndex == HISTORY_SIZE) {
mIndex = 0;
}
Movement& movement = mMovements[mIndex];
movement.eventTime = eventTime;
movement.idBits = idBits;
uint32_t count = idBits.count();
for (uint32_t i = 0; i < count; i++) {
movement.positions[i] = positions[i];
}
#if DEBUG_VELOCITY
LOGD("VelocityTracker: addMovement eventTime=%lld, idBits=0x%08x", eventTime, idBits.value);
for (BitSet32 iterBits(idBits); !iterBits.isEmpty(); ) {
uint32_t id = iterBits.firstMarkedBit();
uint32_t index = idBits.getIndexOfBit(id);
iterBits.clearBit(id);
float vx, vy;
bool available = getVelocity(id, &vx, &vy);
if (available) {
LOGD(" %d: position (%0.3f, %0.3f), velocity (%0.3f, %0.3f), speed %0.3f",
id, positions[index].x, positions[index].y, vx, vy, sqrtf(vx * vx + vy * vy));
} else {
assert(vx == 0 && vy == 0);
LOGD(" %d: position (%0.3f, %0.3f), velocity not available",
id, positions[index].x, positions[index].y);
}
}
#endif
}
bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const {
const Movement& newestMovement = mMovements[mIndex];
if (newestMovement.idBits.hasBit(id)) {
// Find the oldest sample that contains the pointer and that is not older than MAX_AGE.
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);
// If we have a lot of samples, skip the last received sample since it is
// probably pretty noisy compared to the sum of all of the traces already acquired.
//
// NOTE: This condition exists in the android.view.VelocityTracker and imposes a
// bias against the most recent data.
if (numTouches > 3) {
numTouches -= 1;
}
// Calculate an exponentially weighted moving average of the velocity at different
// points in time measured relative to the oldest samples. This is essentially
// an IIR filter.
//
// One problem with this algorithm 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 due to
// the way they are reported by the hardware or driver (sometimes in bursts or with
// significant jitter). The instantaneous velocity for those samples 0.5ms apart will
// be calculated to be 32 times what it should have been.
// To work around this effect, we impose a minimum duration on the samples.
//
// FIXME: Samples close together in time can have an disproportionately large
// impact on the result because all samples are equally weighted. The average should
// instead take the time factor into account.
//
// FIXME: The minimum duration condition does not exist in
// android.view.VelocityTracker yet. It is less important there because sample times
// are truncated to the millisecond so back to back samples will often appear to be
// zero milliseconds apart and will be ignored if they are the oldest ones.
float accumVx = 0;
float accumVy = 0;
uint32_t index = oldestIndex;
uint32_t samplesUsed = 0;
const Movement& oldestMovement = mMovements[oldestIndex];
const Position& oldestPosition =
oldestMovement.positions[oldestMovement.idBits.getIndexOfBit(id)];
while (numTouches-- > 1) {
if (++index == HISTORY_SIZE) {
index = 0;
}
const Movement& movement = mMovements[index];
nsecs_t duration = movement.eventTime - oldestMovement.eventTime;
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 == 0 ? vx : (accumVx + vx) * 0.5f;
accumVy = accumVy == 0 ? vy : (accumVy + vy) * 0.5f;
samplesUsed += 1;
}
}
// Make sure we used at least one sample.
if (samplesUsed != 0) {
*outVx = accumVx;
*outVy = accumVy;
return true;
}
}
// No data available for this pointer.
*outVx = 0;
*outVy = 0;
return false;
}
// --- InputDeviceInfo ---
InputDeviceInfo::InputDeviceInfo() {

View File

@ -406,7 +406,7 @@ status_t InputPublisher::publishMotionEvent(
for (size_t i = 0; i < pointerCount; i++) {
mSharedMessage->motion.pointerIds[i] = pointerIds[i];
mSharedMessage->motion.sampleData[0].coords[i] = pointerCoords[i];
mSharedMessage->motion.sampleData[0].coords[i].copyFrom(pointerCoords[i]);
}
// Cache essential information about the motion event to ensure that a malicious consumer
@ -475,7 +475,7 @@ status_t InputPublisher::appendMotionSample(
mMotionEventSampleDataTail->eventTime = eventTime;
for (size_t i = 0; i < mMotionEventPointerCount; i++) {
mMotionEventSampleDataTail->coords[i] = pointerCoords[i];
mMotionEventSampleDataTail->coords[i].copyFrom(pointerCoords[i]);
}
mMotionEventSampleDataTail = newTail;