Loading include/ui/Input.h +55 −0 Original line number Original line Diff line number Diff line Loading @@ -27,6 +27,7 @@ #include <utils/Timers.h> #include <utils/Timers.h> #include <utils/RefBase.h> #include <utils/RefBase.h> #include <utils/String8.h> #include <utils/String8.h> #include <utils/BitSet.h> #ifdef HAVE_ANDROID_OS #ifdef HAVE_ANDROID_OS class SkMatrix; class SkMatrix; Loading Loading @@ -208,6 +209,13 @@ struct PointerCoords { status_t writeToParcel(Parcel* parcel) const; status_t writeToParcel(Parcel* parcel) const; #endif #endif bool operator==(const PointerCoords& other) const; inline bool operator!=(const PointerCoords& other) const { return !(*this == other); } void copyFrom(const PointerCoords& other); private: private: void tooManyAxes(int axis); void tooManyAxes(int axis); }; }; Loading Loading @@ -542,6 +550,53 @@ private: MotionEvent mMotionEvent; 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. * Describes the characteristics and capabilities of an input device. */ */ Loading include/utils/BitSet.h +6 −0 Original line number Original line Diff line number Diff line Loading @@ -61,6 +61,12 @@ struct BitSet32 { // Result is undefined if all bits are marked. // Result is undefined if all bits are marked. inline uint32_t firstUnmarkedBit() const { return __builtin_clz(~ value); } 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; } inline bool operator!= (const BitSet32& other) const { return value != other.value; } inline bool operator!= (const BitSet32& other) const { return value != other.value; } }; }; Loading libs/ui/Input.cpp +154 −0 Original line number Original line Diff line number Diff line Loading @@ -7,8 +7,12 @@ //#define LOG_NDEBUG 0 //#define LOG_NDEBUG 0 // Log debug messages about keymap probing. #define DEBUG_PROBE 0 #define DEBUG_PROBE 0 // Log debug messages about velocity tracking. #define DEBUG_VELOCITY 0 #include <stdlib.h> #include <stdlib.h> #include <unistd.h> #include <unistd.h> #include <ctype.h> #include <ctype.h> Loading Loading @@ -329,6 +333,27 @@ void PointerCoords::tooManyAxes(int axis) { "cannot contain more than %d axis values.", axis, int(MAX_AXES)); "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 --- // --- MotionEvent --- Loading Loading @@ -634,6 +659,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::InputDeviceInfo() { InputDeviceInfo::InputDeviceInfo() { Loading libs/ui/InputTransport.cpp +2 −2 Original line number Original line Diff line number Diff line Loading @@ -406,7 +406,7 @@ status_t InputPublisher::publishMotionEvent( for (size_t i = 0; i < pointerCount; i++) { for (size_t i = 0; i < pointerCount; i++) { mSharedMessage->motion.pointerIds[i] = pointerIds[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 // Cache essential information about the motion event to ensure that a malicious consumer Loading Loading @@ -475,7 +475,7 @@ status_t InputPublisher::appendMotionSample( mMotionEventSampleDataTail->eventTime = eventTime; mMotionEventSampleDataTail->eventTime = eventTime; for (size_t i = 0; i < mMotionEventPointerCount; i++) { for (size_t i = 0; i < mMotionEventPointerCount; i++) { mMotionEventSampleDataTail->coords[i] = pointerCoords[i]; mMotionEventSampleDataTail->coords[i].copyFrom(pointerCoords[i]); } } mMotionEventSampleDataTail = newTail; mMotionEventSampleDataTail = newTail; Loading Loading
include/ui/Input.h +55 −0 Original line number Original line Diff line number Diff line Loading @@ -27,6 +27,7 @@ #include <utils/Timers.h> #include <utils/Timers.h> #include <utils/RefBase.h> #include <utils/RefBase.h> #include <utils/String8.h> #include <utils/String8.h> #include <utils/BitSet.h> #ifdef HAVE_ANDROID_OS #ifdef HAVE_ANDROID_OS class SkMatrix; class SkMatrix; Loading Loading @@ -208,6 +209,13 @@ struct PointerCoords { status_t writeToParcel(Parcel* parcel) const; status_t writeToParcel(Parcel* parcel) const; #endif #endif bool operator==(const PointerCoords& other) const; inline bool operator!=(const PointerCoords& other) const { return !(*this == other); } void copyFrom(const PointerCoords& other); private: private: void tooManyAxes(int axis); void tooManyAxes(int axis); }; }; Loading Loading @@ -542,6 +550,53 @@ private: MotionEvent mMotionEvent; 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. * Describes the characteristics and capabilities of an input device. */ */ Loading
include/utils/BitSet.h +6 −0 Original line number Original line Diff line number Diff line Loading @@ -61,6 +61,12 @@ struct BitSet32 { // Result is undefined if all bits are marked. // Result is undefined if all bits are marked. inline uint32_t firstUnmarkedBit() const { return __builtin_clz(~ value); } 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; } inline bool operator!= (const BitSet32& other) const { return value != other.value; } inline bool operator!= (const BitSet32& other) const { return value != other.value; } }; }; Loading
libs/ui/Input.cpp +154 −0 Original line number Original line Diff line number Diff line Loading @@ -7,8 +7,12 @@ //#define LOG_NDEBUG 0 //#define LOG_NDEBUG 0 // Log debug messages about keymap probing. #define DEBUG_PROBE 0 #define DEBUG_PROBE 0 // Log debug messages about velocity tracking. #define DEBUG_VELOCITY 0 #include <stdlib.h> #include <stdlib.h> #include <unistd.h> #include <unistd.h> #include <ctype.h> #include <ctype.h> Loading Loading @@ -329,6 +333,27 @@ void PointerCoords::tooManyAxes(int axis) { "cannot contain more than %d axis values.", axis, int(MAX_AXES)); "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 --- // --- MotionEvent --- Loading Loading @@ -634,6 +659,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::InputDeviceInfo() { InputDeviceInfo::InputDeviceInfo() { Loading
libs/ui/InputTransport.cpp +2 −2 Original line number Original line Diff line number Diff line Loading @@ -406,7 +406,7 @@ status_t InputPublisher::publishMotionEvent( for (size_t i = 0; i < pointerCount; i++) { for (size_t i = 0; i < pointerCount; i++) { mSharedMessage->motion.pointerIds[i] = pointerIds[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 // Cache essential information about the motion event to ensure that a malicious consumer Loading Loading @@ -475,7 +475,7 @@ status_t InputPublisher::appendMotionSample( mMotionEventSampleDataTail->eventTime = eventTime; mMotionEventSampleDataTail->eventTime = eventTime; for (size_t i = 0; i < mMotionEventPointerCount; i++) { for (size_t i = 0; i < mMotionEventPointerCount; i++) { mMotionEventSampleDataTail->coords[i] = pointerCoords[i]; mMotionEventSampleDataTail->coords[i].copyFrom(pointerCoords[i]); } } mMotionEventSampleDataTail = newTail; mMotionEventSampleDataTail = newTail; Loading