diff options
Diffstat (limited to 'libs')
-rw-r--r-- | libs/ui/Input.cpp | 246 | ||||
-rw-r--r-- | libs/ui/InputTransport.cpp | 12 | ||||
-rw-r--r-- | libs/utils/Looper.cpp | 22 | ||||
-rw-r--r-- | libs/utils/Timers.cpp | 18 |
4 files changed, 282 insertions, 16 deletions
diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp index 19d590a..684c332 100644 --- a/libs/ui/Input.cpp +++ b/libs/ui/Input.cpp @@ -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 --- @@ -458,6 +483,16 @@ float MotionEvent::getHistoricalAxisValue(int32_t axis, size_t pointerIndex, return value; } +ssize_t MotionEvent::findPointerIndex(int32_t pointerId) const { + size_t pointerCount = mPointerIds.size(); + for (size_t i = 0; i < pointerCount; i++) { + if (mPointerIds.itemAt(i) == pointerId) { + return i; + } + } + return -1; +} + void MotionEvent::offsetLocation(float xOffset, float yOffset) { mXOffset += xOffset; mYOffset += yOffset; @@ -633,6 +668,217 @@ bool MotionEvent::isTouchEvent(int32_t source, int32_t action) { } +// --- VelocityTracker --- + +VelocityTracker::VelocityTracker() { + clear(); +} + +void VelocityTracker::clear() { + mIndex = 0; + mMovements[0].idBits.clear(); + mActivePointerId = -1; +} + +void VelocityTracker::clearPointers(BitSet32 idBits) { + BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value); + mMovements[mIndex].idBits = remainingIdBits; + + if (mActivePointerId >= 0 && idBits.hasBit(mActivePointerId)) { + mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1; + } +} + +void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions) { + if (++mIndex == HISTORY_SIZE) { + mIndex = 0; + } + + while (idBits.count() > MAX_POINTERS) { + idBits.clearBit(idBits.lastMarkedBit()); + } + + 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 (mActivePointerId < 0 || !idBits.hasBit(mActivePointerId)) { + mActivePointerId = count != 0 ? idBits.firstMarkedBit() : -1; + } + +#if DEBUG_VELOCITY + LOGD("VelocityTracker: addMovement eventTime=%lld, idBits=0x%08x, activePointerId=%d", + eventTime, idBits.value, mActivePointerId); + 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), vx=%0.3f, vy=%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 +} + +void VelocityTracker::addMovement(const MotionEvent* event) { + int32_t actionMasked = event->getActionMasked(); + + switch (actionMasked) { + case AMOTION_EVENT_ACTION_DOWN: + // Clear all pointers on down before adding the new movement. + clear(); + break; + case AMOTION_EVENT_ACTION_POINTER_DOWN: { + // Start a new movement trace for a pointer that just went down. + // We do this on down instead of on up because the client may want to query the + // final velocity for a pointer that just went up. + BitSet32 downIdBits; + downIdBits.markBit(event->getActionIndex()); + clearPointers(downIdBits); + break; + } + case AMOTION_EVENT_ACTION_OUTSIDE: + case AMOTION_EVENT_ACTION_CANCEL: + case AMOTION_EVENT_ACTION_SCROLL: + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + // 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. + // 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 + // pointers that remained down but we will also receive an ACTION_MOVE with this + // information if any of them actually moved. Since we don't know how many pointers + // will be going up at once it makes sense to just wait for the following ACTION_MOVE + // before adding the movement. + return; + } + + size_t pointerCount = event->getPointerCount(); + if (pointerCount > MAX_POINTERS) { + pointerCount = MAX_POINTERS; + } + + BitSet32 idBits; + for (size_t i = 0; i < pointerCount; i++) { + idBits.markBit(event->getPointerId(i)); + } + + nsecs_t eventTime; + Position positions[pointerCount]; + + size_t historySize = event->getHistorySize(); + for (size_t h = 0; h < historySize; h++) { + eventTime = event->getHistoricalEventTime(h); + for (size_t i = 0; i < pointerCount; i++) { + positions[i].x = event->getHistoricalX(i, h); + positions[i].y = event->getHistoricalY(i, h); + } + addMovement(eventTime, idBits, positions); + } + + eventTime = event->getEventTime(); + for (size_t i = 0; i < pointerCount; i++) { + positions[i].x = event->getX(i); + positions[i].y = event->getY(i); + } + addMovement(eventTime, idBits, positions); +} + +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); + + // 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 accumVy = 0; + uint32_t index = oldestIndex; + 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) { + if (++index == HISTORY_SIZE) { + index = 0; + } + const Movement& movement = mMovements[index]; + nsecs_t duration = movement.eventTime - oldestMovement.eventTime; + + // 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; + } + } + + // Make sure we used at least one sample. + if (samplesUsed != 0) { + // Scale the velocity linearly if the window of samples is small. + nsecs_t totalDuration = newestMovement.eventTime - oldestMovement.eventTime; + if (totalDuration < MIN_WINDOW) { + float scale = float(totalDuration) / float(MIN_WINDOW); + accumVx *= scale; + accumVy *= scale; + } + + *outVx = accumVx; + *outVy = accumVy; + return true; + } + } + + // No data available for this pointer. + *outVx = 0; + *outVy = 0; + return false; +} + + // --- InputDeviceInfo --- InputDeviceInfo::InputDeviceInfo() { diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp index 5c57a76..93d0d1f 100644 --- a/libs/ui/InputTransport.cpp +++ b/libs/ui/InputTransport.cpp @@ -27,8 +27,14 @@ namespace android { +#define ROUND_UP(value, boundary) (((value) + (boundary) - 1) & ~((boundary) - 1)) +#define MIN_HISTORY_DEPTH 20 + // Must be at least sizeof(InputMessage) + sufficient space for pointer data -static const int DEFAULT_MESSAGE_BUFFER_SIZE = 16384; +static const int DEFAULT_MESSAGE_BUFFER_SIZE = ROUND_UP( + sizeof(InputMessage) + MIN_HISTORY_DEPTH + * (sizeof(InputMessage::SampleData) + MAX_POINTERS * sizeof(PointerCoords)), + 4096); // Signal sent by the producer to the consumer to inform it that a new message is // available to be consumed in the shared memory buffer. @@ -406,7 +412,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 +481,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; diff --git a/libs/utils/Looper.cpp b/libs/utils/Looper.cpp index 18f858b..b54fb9d 100644 --- a/libs/utils/Looper.cpp +++ b/libs/utils/Looper.cpp @@ -218,14 +218,10 @@ int Looper::pollInner(int timeoutMillis) { // Adjust the timeout based on when the next message is due. if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); - if (mNextMessageUptime <= now) { - timeoutMillis = 0; - } else { - uint64_t delay = (mNextMessageUptime - now + 999999LL) / 1000000LL; - if (delay < INT_MAX - && (timeoutMillis < 0 || int(delay) < timeoutMillis)) { - timeoutMillis = int(delay); - } + int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime); + if (messageTimeoutMillis >= 0 + && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) { + timeoutMillis = messageTimeoutMillis; } #if DEBUG_POLL_AND_WAKE LOGD("%p ~ pollOnce - next message in %lldns, adjusted timeout: timeoutMillis=%d", @@ -444,12 +440,11 @@ int Looper::pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outDat return result; } - nsecs_t timeoutNanos = endTime - systemTime(SYSTEM_TIME_MONOTONIC); - if (timeoutNanos <= 0) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + timeoutMillis = toMillisecondTimeoutDelay(now, endTime); + if (timeoutMillis == 0) { return ALOOPER_POLL_TIMEOUT; } - - timeoutMillis = int(nanoseconds_to_milliseconds(timeoutNanos + 999999LL)); } } } @@ -667,7 +662,8 @@ void Looper::wakeAndLock() { #endif void Looper::sendMessage(const sp<MessageHandler>& handler, const Message& message) { - sendMessageAtTime(LLONG_MIN, handler, message); + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + sendMessageAtTime(now, handler, message); } void Looper::sendMessageDelayed(nsecs_t uptimeDelay, const sp<MessageHandler>& handler, diff --git a/libs/utils/Timers.cpp b/libs/utils/Timers.cpp index 784f035..64a29f5 100644 --- a/libs/utils/Timers.cpp +++ b/libs/utils/Timers.cpp @@ -26,6 +26,7 @@ #include <sys/time.h> #include <time.h> #include <errno.h> +#include <limits.h> #ifdef HAVE_WIN32_THREADS #include <windows.h> @@ -53,6 +54,23 @@ nsecs_t systemTime(int clock) #endif } +int toMillisecondTimeoutDelay(nsecs_t referenceTime, nsecs_t timeoutTime) +{ + int timeoutDelayMillis; + if (timeoutTime > referenceTime) { + uint64_t timeoutDelay = uint64_t(timeoutTime - referenceTime); + if (timeoutDelay > uint64_t((INT_MAX - 1) * 1000000LL)) { + timeoutDelayMillis = -1; + } else { + timeoutDelayMillis = (timeoutDelay + 999999LL) / 1000000LL; + } + } else { + timeoutDelayMillis = 0; + } + return timeoutDelayMillis; +} + + /* * =========================================================================== * DurationTimer |