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

* commit '82e4373ed3775395a23d161e58c003e82511921a':
  Use touch pad gestures to manipulate the pointer. (DO NOT MERGE)
This commit is contained in:
Jeff Brown
2011-05-25 14:34:48 -07:00
committed by Android Git Automerger
8 changed files with 1620 additions and 283 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;

View File

@ -2178,8 +2178,8 @@ InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet
if (pointerIds.hasBit(pointerId)) {
splitPointerIndexMap[splitPointerCount] = originalPointerIndex;
splitPointerIds[splitPointerCount] = pointerId;
splitPointerCoords[splitPointerCount] =
originalMotionEntry->firstSample.pointerCoords[originalPointerIndex];
splitPointerCoords[splitPointerCount].copyFrom(
originalMotionEntry->firstSample.pointerCoords[originalPointerIndex]);
splitPointerCount += 1;
}
}
@ -2242,8 +2242,8 @@ InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet
for (uint32_t splitPointerIndex = 0; splitPointerIndex < splitPointerCount;
splitPointerIndex++) {
uint32_t originalPointerIndex = splitPointerIndexMap[splitPointerIndex];
splitPointerCoords[splitPointerIndex] =
originalMotionSample->pointerCoords[originalPointerIndex];
splitPointerCoords[splitPointerIndex].copyFrom(
originalMotionSample->pointerCoords[originalPointerIndex]);
}
mAllocator.appendMotionSample(splitMotionEntry, originalMotionSample->eventTime,
@ -3497,7 +3497,7 @@ InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry(nsec
entry->lastSample = & entry->firstSample;
for (uint32_t i = 0; i < pointerCount; i++) {
entry->pointerIds[i] = pointerIds[i];
entry->firstSample.pointerCoords[i] = pointerCoords[i];
entry->firstSample.pointerCoords[i].copyFrom(pointerCoords[i]);
}
return entry;
}
@ -3601,7 +3601,7 @@ void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry,
sample->eventTime = eventTime;
uint32_t pointerCount = motionEntry->pointerCount;
for (uint32_t i = 0; i < pointerCount; i++) {
sample->pointerCoords[i] = pointerCoords[i];
sample->pointerCoords[i].copyFrom(pointerCoords[i]);
}
sample->next = NULL;
@ -3738,7 +3738,7 @@ void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry*
pointerCount = entry->pointerCount;
for (uint32_t i = 0; i < entry->pointerCount; i++) {
pointerIds[i] = entry->pointerIds[i];
pointerCoords[i] = entry->lastSample->pointerCoords[i];
pointerCoords[i].copyFrom(entry->lastSample->pointerCoords[i]);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -558,6 +558,8 @@ public:
virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
const int32_t* keyCodes, uint8_t* outFlags);
virtual void fadePointer();
protected:
Mutex mLock;
@ -611,10 +613,12 @@ protected:
PointerData pointers[MAX_POINTERS];
BitSet32 idBits;
uint32_t idToIndex[MAX_POINTER_ID + 1];
uint32_t buttonState;
void copyFrom(const TouchData& other) {
pointerCount = other.pointerCount;
idBits = other.idBits;
buttonState = other.buttonState;
for (uint32_t i = 0; i < pointerCount; i++) {
pointers[i] = other.pointers[i];
@ -627,17 +631,20 @@ protected:
inline void clear() {
pointerCount = 0;
idBits.clear();
buttonState = 0;
}
};
// Input sources supported by the device.
uint32_t mTouchSource; // sources when reporting touch data
uint32_t mPointerSource; // sources when reporting pointer gestures
// Immutable configuration parameters.
struct Parameters {
enum DeviceType {
DEVICE_TYPE_TOUCH_SCREEN,
DEVICE_TYPE_TOUCH_PAD,
DEVICE_TYPE_POINTER,
};
DeviceType deviceType;
@ -735,11 +742,17 @@ protected:
// Current and previous touch sample data.
TouchData mCurrentTouch;
PointerCoords mCurrentTouchCoords[MAX_POINTERS];
TouchData mLastTouch;
PointerCoords mLastTouchCoords[MAX_POINTERS];
// The time the primary pointer last went down.
nsecs_t mDownTime;
// The pointer controller, or null if the device is not a pointer.
sp<PointerControllerInterface> mPointerController;
struct LockedState {
Vector<VirtualKey> virtualKeys;
@ -804,6 +817,17 @@ protected:
int32_t keyCode;
int32_t scanCode;
} currentVirtualKey;
// Scale factor for gesture based pointer movements.
float pointerGestureXMovementScale;
float pointerGestureYMovementScale;
// Scale factor for gesture based zooming and other freeform motions.
float pointerGestureXZoomScale;
float pointerGestureYZoomScale;
// The maximum swipe width squared.
int32_t pointerGestureMaxSwipeWidthSquared;
} mLocked;
virtual void configureParameters();
@ -869,13 +893,148 @@ private:
uint64_t distance : 48; // squared distance
};
struct PointerGesture {
enum Mode {
// No fingers, button is not pressed.
// Nothing happening.
NEUTRAL,
// No fingers, button is not pressed.
// Tap detected.
// Emits DOWN and UP events at the pointer location.
TAP,
// Button is pressed.
// Pointer follows the active finger if there is one. Other fingers are ignored.
// Emits DOWN, MOVE and UP events at the pointer location.
CLICK_OR_DRAG,
// Exactly one finger, button is not pressed.
// Pointer follows the active finger.
// Emits HOVER_MOVE events at the pointer location.
HOVER,
// More than two fingers involved but they haven't moved enough for us
// to figure out what is intended.
INDETERMINATE_MULTITOUCH,
// Exactly two fingers moving in the same direction, button is not pressed.
// Pointer does not move.
// Emits DOWN, MOVE and UP events with a single pointer coordinate that
// follows the midpoint between both fingers.
// The centroid is fixed when entering this state.
SWIPE,
// Two or more fingers moving in arbitrary directions, button is not pressed.
// Pointer does not move.
// Emits DOWN, POINTER_DOWN, MOVE, POINTER_UP and UP events that follow
// each finger individually relative to the initial centroid of the finger.
// The centroid is fixed when entering this state.
FREEFORM,
// Waiting for quiet time to end before starting the next gesture.
QUIET,
};
// The active pointer id from the raw touch data.
int32_t activeTouchId; // -1 if none
// The active pointer id from the gesture last delivered to the application.
int32_t activeGestureId; // -1 if none
// Pointer coords and ids for the current and previous pointer gesture.
Mode currentGestureMode;
uint32_t currentGesturePointerCount;
BitSet32 currentGestureIdBits;
uint32_t currentGestureIdToIndex[MAX_POINTER_ID + 1];
PointerCoords currentGestureCoords[MAX_POINTERS];
Mode lastGestureMode;
uint32_t lastGesturePointerCount;
BitSet32 lastGestureIdBits;
uint32_t lastGestureIdToIndex[MAX_POINTER_ID + 1];
PointerCoords lastGestureCoords[MAX_POINTERS];
// Tracks for all pointers originally went down.
TouchData touchOrigin;
// Describes how touch ids are mapped to gesture ids for freeform gestures.
uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1];
// Initial centroid of the movement.
// Used to calculate how far the touch pointers have moved since the gesture started.
int32_t initialCentroidX;
int32_t initialCentroidY;
// Initial pointer location.
// Used to track where the pointer was when the gesture started.
float initialPointerX;
float initialPointerY;
// Time the pointer gesture last went down.
nsecs_t downTime;
// Time we started waiting for a tap gesture.
nsecs_t tapTime;
// Time we started waiting for quiescence.
nsecs_t quietTime;
// A velocity tracker for determining whether to switch active pointers during drags.
VelocityTracker velocityTracker;
void reset() {
activeTouchId = -1;
activeGestureId = -1;
currentGestureMode = NEUTRAL;
currentGesturePointerCount = 0;
currentGestureIdBits.clear();
lastGestureMode = NEUTRAL;
lastGesturePointerCount = 0;
lastGestureIdBits.clear();
touchOrigin.clear();
initialCentroidX = 0;
initialCentroidY = 0;
initialPointerX = 0;
initialPointerY = 0;
downTime = 0;
velocityTracker.clear();
resetTapTime();
resetQuietTime();
}
void resetTapTime() {
tapTime = LLONG_MIN;
}
void resetQuietTime() {
quietTime = LLONG_MIN;
}
} mPointerGesture;
void initializeLocked();
TouchResult consumeOffScreenTouches(nsecs_t when, uint32_t policyFlags);
void dispatchTouches(nsecs_t when, uint32_t policyFlags);
void dispatchTouch(nsecs_t when, uint32_t policyFlags, TouchData* touch,
BitSet32 idBits, uint32_t changedId, uint32_t pointerCount,
int32_t motionEventAction);
void prepareTouches(int32_t* outEdgeFlags, float* outXPrecision, float* outYPrecision);
void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags);
void preparePointerGestures(nsecs_t when,
bool* outCancelPreviousGesture, bool* outFinishPreviousGesture);
// Dispatches a motion event.
// If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the
// method will take care of setting the index and transmuting the action to DOWN or UP
// it is the first / last pointer to go down / up.
void dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
int32_t action, int32_t flags, uint32_t metaState, int32_t edgeFlags,
const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits,
int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime);
// Updates pointer coords for pointers with specified ids that have moved.
// Returns true if any of them changed.
bool updateMovedPointerCoords(const PointerCoords* inCoords, const uint32_t* inIdToIndex,
PointerCoords* outCoords, const uint32_t* outIdToIndex, BitSet32 idBits) const;
void suppressSwipeOntoVirtualKeys(nsecs_t when);
bool isPointInsideSurfaceLocked(int32_t x, int32_t y);
@ -907,6 +1066,7 @@ private:
FIELD_ABS_Y = 4,
FIELD_ABS_PRESSURE = 8,
FIELD_ABS_TOOL_WIDTH = 16,
FIELD_BUTTONS = 32,
};
uint32_t fields;
@ -917,8 +1077,13 @@ private:
int32_t absPressure;
int32_t absToolWidth;
uint32_t buttonDown;
uint32_t buttonUp;
inline void clear() {
fields = 0;
buttonDown = 0;
buttonUp = 0;
}
} mAccumulator;
@ -927,6 +1092,7 @@ private:
int32_t mY;
int32_t mPressure;
int32_t mToolWidth;
uint32_t mButtonState;
void initialize();
@ -978,12 +1144,20 @@ private:
}
} pointers[MAX_POINTERS + 1]; // + 1 to remove the need for extra range checks
// Bitfield of buttons that went down or up.
uint32_t buttonDown;
uint32_t buttonUp;
inline void clear() {
pointerCount = 0;
pointers[0].clear();
buttonDown = 0;
buttonUp = 0;
}
} mAccumulator;
uint32_t mButtonState;
void initialize();
void sync(nsecs_t when);

View File

@ -2460,11 +2460,21 @@ void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper* mapper) {
}
TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecified_ReturnsTouchPad) {
TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNotACursor_ReturnsPointer) {
SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
prepareAxes(POSITION);
addMapperAndConfigure(mapper);
ASSERT_EQ(AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD, mapper->getSources());
}
TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndIsACursor_ReturnsTouchPad) {
SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
mFakeEventHub->addRelativeAxis(DEVICE_ID, REL_X);
mFakeEventHub->addRelativeAxis(DEVICE_ID, REL_Y);
prepareAxes(POSITION);
addMapperAndConfigure(mapper);
ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper->getSources());
}