38a7fabd96
Finished the input device capability API. Added a mechanism for calibrating touch devices to obtain more accurate information about the touch contact area. Improved pointer location to show new coordinates and capabilities. Optimized pointer location display and formatting to avoid allocating large numbers of temporary objects. The GC churn was causing the application to stutter very badly when more than a couple of fingers were down). Added more diagnostics. Change-Id: Ie25380278ed6f16c5b04cd9df848015850383498
1866 lines
72 KiB
C++
1866 lines
72 KiB
C++
//
|
|
// Copyright 2010 The Android Open Source Project
|
|
//
|
|
// The input dispatcher.
|
|
//
|
|
#define LOG_TAG "InputDispatcher"
|
|
|
|
//#define LOG_NDEBUG 0
|
|
|
|
// Log detailed debug messages about each inbound event notification to the dispatcher.
|
|
#define DEBUG_INBOUND_EVENT_DETAILS 0
|
|
|
|
// Log detailed debug messages about each outbound event processed by the dispatcher.
|
|
#define DEBUG_OUTBOUND_EVENT_DETAILS 0
|
|
|
|
// Log debug messages about batching.
|
|
#define DEBUG_BATCHING 0
|
|
|
|
// Log debug messages about the dispatch cycle.
|
|
#define DEBUG_DISPATCH_CYCLE 0
|
|
|
|
// Log debug messages about registrations.
|
|
#define DEBUG_REGISTRATION 0
|
|
|
|
// Log debug messages about performance statistics.
|
|
#define DEBUG_PERFORMANCE_STATISTICS 0
|
|
|
|
// Log debug messages about input event injection.
|
|
#define DEBUG_INJECTION 0
|
|
|
|
// Log debug messages about input event throttling.
|
|
#define DEBUG_THROTTLING 0
|
|
|
|
#include <cutils/log.h>
|
|
#include <ui/InputDispatcher.h>
|
|
|
|
#include <stddef.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
|
|
namespace android {
|
|
|
|
// TODO, this needs to be somewhere else, perhaps in the policy
|
|
static inline bool isMovementKey(int32_t keyCode) {
|
|
return keyCode == AKEYCODE_DPAD_UP
|
|
|| keyCode == AKEYCODE_DPAD_DOWN
|
|
|| keyCode == AKEYCODE_DPAD_LEFT
|
|
|| keyCode == AKEYCODE_DPAD_RIGHT;
|
|
}
|
|
|
|
static inline nsecs_t now() {
|
|
return systemTime(SYSTEM_TIME_MONOTONIC);
|
|
}
|
|
|
|
// --- InputDispatcher ---
|
|
|
|
InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
|
|
mPolicy(policy) {
|
|
mPollLoop = new PollLoop(false);
|
|
|
|
mInboundQueue.head.refCount = -1;
|
|
mInboundQueue.head.type = EventEntry::TYPE_SENTINEL;
|
|
mInboundQueue.head.eventTime = LONG_LONG_MIN;
|
|
|
|
mInboundQueue.tail.refCount = -1;
|
|
mInboundQueue.tail.type = EventEntry::TYPE_SENTINEL;
|
|
mInboundQueue.tail.eventTime = LONG_LONG_MAX;
|
|
|
|
mKeyRepeatState.lastKeyEntry = NULL;
|
|
|
|
int32_t maxEventsPerSecond = policy->getMaxEventsPerSecond();
|
|
mThrottleState.minTimeBetweenEvents = 1000000000LL / maxEventsPerSecond;
|
|
mThrottleState.lastDeviceId = -1;
|
|
|
|
#if DEBUG_THROTTLING
|
|
mThrottleState.originalSampleCount = 0;
|
|
LOGD("Throttling - Max events per second = %d", maxEventsPerSecond);
|
|
#endif
|
|
|
|
mCurrentInputTargetsValid = false;
|
|
}
|
|
|
|
InputDispatcher::~InputDispatcher() {
|
|
resetKeyRepeatLocked();
|
|
|
|
while (mConnectionsByReceiveFd.size() != 0) {
|
|
unregisterInputChannel(mConnectionsByReceiveFd.valueAt(0)->inputChannel);
|
|
}
|
|
|
|
for (EventEntry* entry = mInboundQueue.head.next; entry != & mInboundQueue.tail; ) {
|
|
EventEntry* next = entry->next;
|
|
mAllocator.releaseEventEntry(next);
|
|
entry = next;
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::dispatchOnce() {
|
|
nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout();
|
|
|
|
bool skipPoll = false;
|
|
nsecs_t currentTime;
|
|
nsecs_t nextWakeupTime = LONG_LONG_MAX;
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
currentTime = now();
|
|
|
|
// Reset the key repeat timer whenever we disallow key events, even if the next event
|
|
// is not a key. This is to ensure that we abort a key repeat if the device is just coming
|
|
// out of sleep.
|
|
// XXX we should handle resetting input state coming out of sleep more generally elsewhere
|
|
if (keyRepeatTimeout < 0) {
|
|
resetKeyRepeatLocked();
|
|
}
|
|
|
|
// Detect and process timeouts for all connections and determine if there are any
|
|
// synchronous event dispatches pending. This step is entirely non-interruptible.
|
|
bool hasPendingSyncTarget = false;
|
|
size_t activeConnectionCount = mActiveConnections.size();
|
|
for (size_t i = 0; i < activeConnectionCount; i++) {
|
|
Connection* connection = mActiveConnections.itemAt(i);
|
|
|
|
if (connection->hasPendingSyncTarget()) {
|
|
hasPendingSyncTarget = true;
|
|
}
|
|
|
|
nsecs_t connectionTimeoutTime = connection->nextTimeoutTime;
|
|
if (connectionTimeoutTime <= currentTime) {
|
|
mTimedOutConnections.add(connection);
|
|
} else if (connectionTimeoutTime < nextWakeupTime) {
|
|
nextWakeupTime = connectionTimeoutTime;
|
|
}
|
|
}
|
|
|
|
size_t timedOutConnectionCount = mTimedOutConnections.size();
|
|
for (size_t i = 0; i < timedOutConnectionCount; i++) {
|
|
Connection* connection = mTimedOutConnections.itemAt(i);
|
|
timeoutDispatchCycleLocked(currentTime, connection);
|
|
skipPoll = true;
|
|
}
|
|
mTimedOutConnections.clear();
|
|
|
|
// If we don't have a pending sync target, then we can begin delivering a new event.
|
|
// (Otherwise we wait for dispatch to complete for that target.)
|
|
if (! hasPendingSyncTarget) {
|
|
if (mInboundQueue.isEmpty()) {
|
|
if (mKeyRepeatState.lastKeyEntry) {
|
|
if (currentTime >= mKeyRepeatState.nextRepeatTime) {
|
|
processKeyRepeatLockedInterruptible(currentTime, keyRepeatTimeout);
|
|
skipPoll = true;
|
|
} else {
|
|
if (mKeyRepeatState.nextRepeatTime < nextWakeupTime) {
|
|
nextWakeupTime = mKeyRepeatState.nextRepeatTime;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Inbound queue has at least one entry.
|
|
EventEntry* entry = mInboundQueue.head.next;
|
|
|
|
// Consider throttling the entry if it is a move event and there are no
|
|
// other events behind it in the queue. Due to movement batching, additional
|
|
// samples may be appended to this event by the time the throttling timeout
|
|
// expires.
|
|
// TODO Make this smarter and consider throttling per device independently.
|
|
if (entry->type == EventEntry::TYPE_MOTION) {
|
|
MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
|
|
int32_t deviceId = motionEntry->deviceId;
|
|
uint32_t source = motionEntry->source;
|
|
if (motionEntry->next == & mInboundQueue.tail
|
|
&& motionEntry->action == AMOTION_EVENT_ACTION_MOVE
|
|
&& deviceId == mThrottleState.lastDeviceId
|
|
&& source == mThrottleState.lastSource) {
|
|
nsecs_t nextTime = mThrottleState.lastEventTime
|
|
+ mThrottleState.minTimeBetweenEvents;
|
|
if (currentTime < nextTime) {
|
|
// Throttle it!
|
|
#if DEBUG_THROTTLING
|
|
LOGD("Throttling - Delaying motion event for "
|
|
"device 0x%x, source 0x%08x by up to %0.3fms.",
|
|
deviceId, source, (nextTime - currentTime) * 0.000001);
|
|
#endif
|
|
if (nextTime < nextWakeupTime) {
|
|
nextWakeupTime = nextTime;
|
|
}
|
|
if (mThrottleState.originalSampleCount == 0) {
|
|
mThrottleState.originalSampleCount =
|
|
motionEntry->countSamples();
|
|
}
|
|
goto Throttle;
|
|
}
|
|
}
|
|
|
|
#if DEBUG_THROTTLING
|
|
if (mThrottleState.originalSampleCount != 0) {
|
|
uint32_t count = motionEntry->countSamples();
|
|
LOGD("Throttling - Motion event sample count grew by %d from %d to %d.",
|
|
count - mThrottleState.originalSampleCount,
|
|
mThrottleState.originalSampleCount, count);
|
|
mThrottleState.originalSampleCount = 0;
|
|
}
|
|
#endif
|
|
|
|
mThrottleState.lastEventTime = entry->eventTime < currentTime
|
|
? entry->eventTime : currentTime;
|
|
mThrottleState.lastDeviceId = deviceId;
|
|
mThrottleState.lastSource = source;
|
|
}
|
|
|
|
// Start processing the entry but leave it on the queue until later so that the
|
|
// input reader can keep appending samples onto a motion event between the
|
|
// time we started processing it and the time we finally enqueue dispatch
|
|
// entries for it.
|
|
switch (entry->type) {
|
|
case EventEntry::TYPE_CONFIGURATION_CHANGED: {
|
|
ConfigurationChangedEntry* typedEntry =
|
|
static_cast<ConfigurationChangedEntry*>(entry);
|
|
processConfigurationChangedLockedInterruptible(currentTime, typedEntry);
|
|
break;
|
|
}
|
|
|
|
case EventEntry::TYPE_KEY: {
|
|
KeyEntry* typedEntry = static_cast<KeyEntry*>(entry);
|
|
processKeyLockedInterruptible(currentTime, typedEntry, keyRepeatTimeout);
|
|
break;
|
|
}
|
|
|
|
case EventEntry::TYPE_MOTION: {
|
|
MotionEntry* typedEntry = static_cast<MotionEntry*>(entry);
|
|
processMotionLockedInterruptible(currentTime, typedEntry);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
// Dequeue and release the event entry that we just processed.
|
|
mInboundQueue.dequeue(entry);
|
|
mAllocator.releaseEventEntry(entry);
|
|
skipPoll = true;
|
|
|
|
Throttle: ;
|
|
}
|
|
}
|
|
|
|
// Run any deferred commands.
|
|
skipPoll |= runCommandsLockedInterruptible();
|
|
} // release lock
|
|
|
|
// If we dispatched anything, don't poll just now. Wait for the next iteration.
|
|
// Contents may have shifted during flight.
|
|
if (skipPoll) {
|
|
return;
|
|
}
|
|
|
|
// Wait for callback or timeout or wake. (make sure we round up, not down)
|
|
nsecs_t timeout = (nextWakeupTime - currentTime + 999999LL) / 1000000LL;
|
|
int32_t timeoutMillis = timeout > INT_MAX ? -1 : timeout > 0 ? int32_t(timeout) : 0;
|
|
mPollLoop->pollOnce(timeoutMillis);
|
|
}
|
|
|
|
bool InputDispatcher::runCommandsLockedInterruptible() {
|
|
if (mCommandQueue.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
do {
|
|
CommandEntry* commandEntry = mCommandQueue.dequeueAtHead();
|
|
|
|
Command command = commandEntry->command;
|
|
(this->*command)(commandEntry); // commands are implicitly 'LockedInterruptible'
|
|
|
|
commandEntry->connection.clear();
|
|
mAllocator.releaseCommandEntry(commandEntry);
|
|
} while (! mCommandQueue.isEmpty());
|
|
return true;
|
|
}
|
|
|
|
InputDispatcher::CommandEntry* InputDispatcher::postCommandLocked(Command command) {
|
|
CommandEntry* commandEntry = mAllocator.obtainCommandEntry(command);
|
|
mCommandQueue.enqueueAtTail(commandEntry);
|
|
return commandEntry;
|
|
}
|
|
|
|
void InputDispatcher::processConfigurationChangedLockedInterruptible(
|
|
nsecs_t currentTime, ConfigurationChangedEntry* entry) {
|
|
#if DEBUG_OUTBOUND_EVENT_DETAILS
|
|
LOGD("processConfigurationChanged - eventTime=%lld", entry->eventTime);
|
|
#endif
|
|
|
|
// Reset key repeating in case a keyboard device was added or removed or something.
|
|
resetKeyRepeatLocked();
|
|
|
|
mLock.unlock();
|
|
|
|
mPolicy->notifyConfigurationChanged(entry->eventTime);
|
|
|
|
mLock.lock();
|
|
}
|
|
|
|
void InputDispatcher::processKeyLockedInterruptible(
|
|
nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout) {
|
|
#if DEBUG_OUTBOUND_EVENT_DETAILS
|
|
LOGD("processKey - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, action=0x%x, "
|
|
"flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld",
|
|
entry->eventTime, entry->deviceId, entry->source, entry->policyFlags, entry->action,
|
|
entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
|
|
entry->downTime);
|
|
#endif
|
|
|
|
if (entry->action == AKEY_EVENT_ACTION_DOWN && ! entry->isInjected()) {
|
|
if (mKeyRepeatState.lastKeyEntry
|
|
&& mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) {
|
|
// We have seen two identical key downs in a row which indicates that the device
|
|
// driver is automatically generating key repeats itself. We take note of the
|
|
// repeat here, but we disable our own next key repeat timer since it is clear that
|
|
// we will not need to synthesize key repeats ourselves.
|
|
entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1;
|
|
resetKeyRepeatLocked();
|
|
mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves
|
|
} else {
|
|
// Not a repeat. Save key down state in case we do see a repeat later.
|
|
resetKeyRepeatLocked();
|
|
mKeyRepeatState.nextRepeatTime = entry->eventTime + keyRepeatTimeout;
|
|
}
|
|
mKeyRepeatState.lastKeyEntry = entry;
|
|
entry->refCount += 1;
|
|
} else {
|
|
resetKeyRepeatLocked();
|
|
}
|
|
|
|
identifyInputTargetsAndDispatchKeyLockedInterruptible(currentTime, entry);
|
|
}
|
|
|
|
void InputDispatcher::processKeyRepeatLockedInterruptible(
|
|
nsecs_t currentTime, nsecs_t keyRepeatTimeout) {
|
|
KeyEntry* entry = mKeyRepeatState.lastKeyEntry;
|
|
|
|
// Search the inbound queue for a key up corresponding to this device.
|
|
// It doesn't make sense to generate a key repeat event if the key is already up.
|
|
for (EventEntry* queuedEntry = mInboundQueue.head.next;
|
|
queuedEntry != & mInboundQueue.tail; queuedEntry = entry->next) {
|
|
if (queuedEntry->type == EventEntry::TYPE_KEY) {
|
|
KeyEntry* queuedKeyEntry = static_cast<KeyEntry*>(queuedEntry);
|
|
if (queuedKeyEntry->deviceId == entry->deviceId
|
|
&& entry->action == AKEY_EVENT_ACTION_UP) {
|
|
resetKeyRepeatLocked();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Synthesize a key repeat after the repeat timeout expired.
|
|
// Reuse the repeated key entry if it is otherwise unreferenced.
|
|
uint32_t policyFlags = entry->policyFlags & POLICY_FLAG_RAW_MASK;
|
|
if (entry->refCount == 1) {
|
|
entry->eventTime = currentTime;
|
|
entry->policyFlags = policyFlags;
|
|
entry->repeatCount += 1;
|
|
} else {
|
|
KeyEntry* newEntry = mAllocator.obtainKeyEntry(currentTime,
|
|
entry->deviceId, entry->source, policyFlags,
|
|
entry->action, entry->flags, entry->keyCode, entry->scanCode,
|
|
entry->metaState, entry->repeatCount + 1, entry->downTime);
|
|
|
|
mKeyRepeatState.lastKeyEntry = newEntry;
|
|
mAllocator.releaseKeyEntry(entry);
|
|
|
|
entry = newEntry;
|
|
}
|
|
|
|
if (entry->repeatCount == 1) {
|
|
entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS;
|
|
}
|
|
|
|
mKeyRepeatState.nextRepeatTime = currentTime + keyRepeatTimeout;
|
|
|
|
#if DEBUG_OUTBOUND_EVENT_DETAILS
|
|
LOGD("processKeyRepeat - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
|
|
"action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, "
|
|
"repeatCount=%d, downTime=%lld",
|
|
entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
|
|
entry->action, entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
|
|
entry->repeatCount, entry->downTime);
|
|
#endif
|
|
|
|
identifyInputTargetsAndDispatchKeyLockedInterruptible(currentTime, entry);
|
|
}
|
|
|
|
void InputDispatcher::processMotionLockedInterruptible(
|
|
nsecs_t currentTime, MotionEntry* entry) {
|
|
#if DEBUG_OUTBOUND_EVENT_DETAILS
|
|
LOGD("processMotion - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, action=0x%x, "
|
|
"metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%lld",
|
|
entry->eventTime, entry->deviceId, entry->source, entry->policyFlags, entry->action,
|
|
entry->metaState, entry->edgeFlags, entry->xPrecision, entry->yPrecision,
|
|
entry->downTime);
|
|
|
|
// Print the most recent sample that we have available, this may change due to batching.
|
|
size_t sampleCount = 1;
|
|
MotionSample* sample = & entry->firstSample;
|
|
for (; sample->next != NULL; sample = sample->next) {
|
|
sampleCount += 1;
|
|
}
|
|
for (uint32_t i = 0; i < entry->pointerCount; i++) {
|
|
LOGD(" Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, "
|
|
"touchMajor=%f, touchMinor=%d, toolMajor=%f, toolMinor=%f, "
|
|
"orientation=%f",
|
|
i, entry->pointerIds[i],
|
|
sample->pointerCoords[i].x, sample->pointerCoords[i].y,
|
|
sample->pointerCoords[i].pressure, sample->pointerCoords[i].size,
|
|
sample->pointerCoords[i].touchMajor, sample->pointerCoords[i].touchMinor,
|
|
sample->pointerCoords[i].toolMajor, sample->pointerCoords[i].toolMinor,
|
|
sample->pointerCoords[i].orientation);
|
|
}
|
|
|
|
// Keep in mind that due to batching, it is possible for the number of samples actually
|
|
// dispatched to change before the application finally consumed them.
|
|
if (entry->action == AMOTION_EVENT_ACTION_MOVE) {
|
|
LOGD(" ... Total movement samples currently batched %d ...", sampleCount);
|
|
}
|
|
#endif
|
|
|
|
identifyInputTargetsAndDispatchMotionLockedInterruptible(currentTime, entry);
|
|
}
|
|
|
|
void InputDispatcher::identifyInputTargetsAndDispatchKeyLockedInterruptible(
|
|
nsecs_t currentTime, KeyEntry* entry) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("identifyInputTargetsAndDispatchKey");
|
|
#endif
|
|
|
|
entry->dispatchInProgress = true;
|
|
mCurrentInputTargetsValid = false;
|
|
mLock.unlock();
|
|
|
|
mReusableKeyEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags,
|
|
entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount,
|
|
entry->downTime, entry->eventTime);
|
|
|
|
mCurrentInputTargets.clear();
|
|
int32_t injectionResult = mPolicy->waitForKeyEventTargets(& mReusableKeyEvent,
|
|
entry->policyFlags, entry->injectorPid, entry->injectorUid,
|
|
mCurrentInputTargets);
|
|
|
|
mLock.lock();
|
|
mCurrentInputTargetsValid = true;
|
|
|
|
setInjectionResultLocked(entry, injectionResult);
|
|
|
|
if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
|
|
dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::identifyInputTargetsAndDispatchMotionLockedInterruptible(
|
|
nsecs_t currentTime, MotionEntry* entry) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("identifyInputTargetsAndDispatchMotion");
|
|
#endif
|
|
|
|
entry->dispatchInProgress = true;
|
|
mCurrentInputTargetsValid = false;
|
|
mLock.unlock();
|
|
|
|
mReusableMotionEvent.initialize(entry->deviceId, entry->source, entry->action,
|
|
entry->edgeFlags, entry->metaState,
|
|
0, 0, entry->xPrecision, entry->yPrecision,
|
|
entry->downTime, entry->eventTime, entry->pointerCount, entry->pointerIds,
|
|
entry->firstSample.pointerCoords);
|
|
|
|
mCurrentInputTargets.clear();
|
|
int32_t injectionResult = mPolicy->waitForMotionEventTargets(& mReusableMotionEvent,
|
|
entry->policyFlags, entry->injectorPid, entry->injectorUid,
|
|
mCurrentInputTargets);
|
|
|
|
mLock.lock();
|
|
mCurrentInputTargetsValid = true;
|
|
|
|
setInjectionResultLocked(entry, injectionResult);
|
|
|
|
if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
|
|
dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime,
|
|
EventEntry* eventEntry, bool resumeWithAppendedMotionSample) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("dispatchEventToCurrentInputTargets - "
|
|
"resumeWithAppendedMotionSample=%s",
|
|
resumeWithAppendedMotionSample ? "true" : "false");
|
|
#endif
|
|
|
|
assert(eventEntry->dispatchInProgress); // should already have been set to true
|
|
|
|
for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {
|
|
const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i);
|
|
|
|
ssize_t connectionIndex = getConnectionIndex(inputTarget.inputChannel);
|
|
if (connectionIndex >= 0) {
|
|
sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
|
|
prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget,
|
|
resumeWithAppendedMotionSample);
|
|
} else {
|
|
LOGW("Framework requested delivery of an input event to channel '%s' but it "
|
|
"is not registered with the input dispatcher.",
|
|
inputTarget.inputChannel->getName().string());
|
|
}
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
|
|
const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
|
|
bool resumeWithAppendedMotionSample) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("channel '%s' ~ prepareDispatchCycle - flags=%d, timeout=%lldns, "
|
|
"xOffset=%f, yOffset=%f, resumeWithAppendedMotionSample=%s",
|
|
connection->getInputChannelName(), inputTarget->flags, inputTarget->timeout,
|
|
inputTarget->xOffset, inputTarget->yOffset,
|
|
resumeWithAppendedMotionSample ? "true" : "false");
|
|
#endif
|
|
|
|
// Skip this event if the connection status is not normal.
|
|
// We don't want to queue outbound events at all if the connection is broken or
|
|
// not responding.
|
|
if (connection->status != Connection::STATUS_NORMAL) {
|
|
LOGV("channel '%s' ~ Dropping event because the channel status is %s",
|
|
connection->getStatusLabel());
|
|
return;
|
|
}
|
|
|
|
// Resume the dispatch cycle with a freshly appended motion sample.
|
|
// First we check that the last dispatch entry in the outbound queue is for the same
|
|
// motion event to which we appended the motion sample. If we find such a dispatch
|
|
// entry, and if it is currently in progress then we try to stream the new sample.
|
|
bool wasEmpty = connection->outboundQueue.isEmpty();
|
|
|
|
if (! wasEmpty && resumeWithAppendedMotionSample) {
|
|
DispatchEntry* motionEventDispatchEntry =
|
|
connection->findQueuedDispatchEntryForEvent(eventEntry);
|
|
if (motionEventDispatchEntry) {
|
|
// If the dispatch entry is not in progress, then we must be busy dispatching an
|
|
// earlier event. Not a problem, the motion event is on the outbound queue and will
|
|
// be dispatched later.
|
|
if (! motionEventDispatchEntry->inProgress) {
|
|
#if DEBUG_BATCHING
|
|
LOGD("channel '%s' ~ Not streaming because the motion event has "
|
|
"not yet been dispatched. "
|
|
"(Waiting for earlier events to be consumed.)",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// If the dispatch entry is in progress but it already has a tail of pending
|
|
// motion samples, then it must mean that the shared memory buffer filled up.
|
|
// Not a problem, when this dispatch cycle is finished, we will eventually start
|
|
// a new dispatch cycle to process the tail and that tail includes the newly
|
|
// appended motion sample.
|
|
if (motionEventDispatchEntry->tailMotionSample) {
|
|
#if DEBUG_BATCHING
|
|
LOGD("channel '%s' ~ Not streaming because no new samples can "
|
|
"be appended to the motion event in this dispatch cycle. "
|
|
"(Waiting for next dispatch cycle to start.)",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// The dispatch entry is in progress and is still potentially open for streaming.
|
|
// Try to stream the new motion sample. This might fail if the consumer has already
|
|
// consumed the motion event (or if the channel is broken).
|
|
MotionSample* appendedMotionSample = static_cast<MotionEntry*>(eventEntry)->lastSample;
|
|
status_t status = connection->inputPublisher.appendMotionSample(
|
|
appendedMotionSample->eventTime, appendedMotionSample->pointerCoords);
|
|
if (status == OK) {
|
|
#if DEBUG_BATCHING
|
|
LOGD("channel '%s' ~ Successfully streamed new motion sample.",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#if DEBUG_BATCHING
|
|
if (status == NO_MEMORY) {
|
|
LOGD("channel '%s' ~ Could not append motion sample to currently "
|
|
"dispatched move event because the shared memory buffer is full. "
|
|
"(Waiting for next dispatch cycle to start.)",
|
|
connection->getInputChannelName());
|
|
} else if (status == status_t(FAILED_TRANSACTION)) {
|
|
LOGD("channel '%s' ~ Could not append motion sample to currently "
|
|
"dispatched move event because the event has already been consumed. "
|
|
"(Waiting for next dispatch cycle to start.)",
|
|
connection->getInputChannelName());
|
|
} else {
|
|
LOGD("channel '%s' ~ Could not append motion sample to currently "
|
|
"dispatched move event due to an error, status=%d. "
|
|
"(Waiting for next dispatch cycle to start.)",
|
|
connection->getInputChannelName(), status);
|
|
}
|
|
#endif
|
|
// Failed to stream. Start a new tail of pending motion samples to dispatch
|
|
// in the next cycle.
|
|
motionEventDispatchEntry->tailMotionSample = appendedMotionSample;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// This is a new event.
|
|
// Enqueue a new dispatch entry onto the outbound queue for this connection.
|
|
DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry); // increments ref
|
|
dispatchEntry->targetFlags = inputTarget->flags;
|
|
dispatchEntry->xOffset = inputTarget->xOffset;
|
|
dispatchEntry->yOffset = inputTarget->yOffset;
|
|
dispatchEntry->timeout = inputTarget->timeout;
|
|
dispatchEntry->inProgress = false;
|
|
dispatchEntry->headMotionSample = NULL;
|
|
dispatchEntry->tailMotionSample = NULL;
|
|
|
|
if (dispatchEntry->isSyncTarget()) {
|
|
eventEntry->pendingSyncDispatches += 1;
|
|
}
|
|
|
|
// Handle the case where we could not stream a new motion sample because the consumer has
|
|
// already consumed the motion event (otherwise the corresponding dispatch entry would
|
|
// still be in the outbound queue for this connection). We set the head motion sample
|
|
// to the list starting with the newly appended motion sample.
|
|
if (resumeWithAppendedMotionSample) {
|
|
#if DEBUG_BATCHING
|
|
LOGD("channel '%s' ~ Preparing a new dispatch cycle for additional motion samples "
|
|
"that cannot be streamed because the motion event has already been consumed.",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
MotionSample* appendedMotionSample = static_cast<MotionEntry*>(eventEntry)->lastSample;
|
|
dispatchEntry->headMotionSample = appendedMotionSample;
|
|
}
|
|
|
|
// Enqueue the dispatch entry.
|
|
connection->outboundQueue.enqueueAtTail(dispatchEntry);
|
|
|
|
// If the outbound queue was previously empty, start the dispatch cycle going.
|
|
if (wasEmpty) {
|
|
activateConnectionLocked(connection.get());
|
|
startDispatchCycleLocked(currentTime, connection);
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
|
|
const sp<Connection>& connection) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("channel '%s' ~ startDispatchCycle",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
|
|
assert(connection->status == Connection::STATUS_NORMAL);
|
|
assert(! connection->outboundQueue.isEmpty());
|
|
|
|
DispatchEntry* dispatchEntry = connection->outboundQueue.head.next;
|
|
assert(! dispatchEntry->inProgress);
|
|
|
|
// TODO throttle successive ACTION_MOVE motion events for the same device
|
|
// possible implementation could set a brief poll timeout here and resume starting the
|
|
// dispatch cycle when elapsed
|
|
|
|
// Publish the event.
|
|
status_t status;
|
|
switch (dispatchEntry->eventEntry->type) {
|
|
case EventEntry::TYPE_KEY: {
|
|
KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry);
|
|
|
|
// Apply target flags.
|
|
int32_t action = keyEntry->action;
|
|
int32_t flags = keyEntry->flags;
|
|
if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) {
|
|
flags |= AKEY_EVENT_FLAG_CANCELED;
|
|
}
|
|
|
|
// Publish the key event.
|
|
status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->source,
|
|
action, flags, keyEntry->keyCode, keyEntry->scanCode,
|
|
keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
|
|
keyEntry->eventTime);
|
|
|
|
if (status) {
|
|
LOGE("channel '%s' ~ Could not publish key event, "
|
|
"status=%d", connection->getInputChannelName(), status);
|
|
abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case EventEntry::TYPE_MOTION: {
|
|
MotionEntry* motionEntry = static_cast<MotionEntry*>(dispatchEntry->eventEntry);
|
|
|
|
// Apply target flags.
|
|
int32_t action = motionEntry->action;
|
|
if (dispatchEntry->targetFlags & InputTarget::FLAG_OUTSIDE) {
|
|
action = AMOTION_EVENT_ACTION_OUTSIDE;
|
|
}
|
|
if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) {
|
|
action = AMOTION_EVENT_ACTION_CANCEL;
|
|
}
|
|
|
|
// If headMotionSample is non-NULL, then it points to the first new sample that we
|
|
// were unable to dispatch during the previous cycle so we resume dispatching from
|
|
// that point in the list of motion samples.
|
|
// Otherwise, we just start from the first sample of the motion event.
|
|
MotionSample* firstMotionSample = dispatchEntry->headMotionSample;
|
|
if (! firstMotionSample) {
|
|
firstMotionSample = & motionEntry->firstSample;
|
|
}
|
|
|
|
// Set the X and Y offset depending on the input source.
|
|
float xOffset, yOffset;
|
|
if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) {
|
|
xOffset = dispatchEntry->xOffset;
|
|
yOffset = dispatchEntry->yOffset;
|
|
} else {
|
|
xOffset = 0.0f;
|
|
yOffset = 0.0f;
|
|
}
|
|
|
|
// Publish the motion event and the first motion sample.
|
|
status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId,
|
|
motionEntry->source, action, motionEntry->edgeFlags, motionEntry->metaState,
|
|
xOffset, yOffset,
|
|
motionEntry->xPrecision, motionEntry->yPrecision,
|
|
motionEntry->downTime, firstMotionSample->eventTime,
|
|
motionEntry->pointerCount, motionEntry->pointerIds,
|
|
firstMotionSample->pointerCoords);
|
|
|
|
if (status) {
|
|
LOGE("channel '%s' ~ Could not publish motion event, "
|
|
"status=%d", connection->getInputChannelName(), status);
|
|
abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
|
|
return;
|
|
}
|
|
|
|
// Append additional motion samples.
|
|
MotionSample* nextMotionSample = firstMotionSample->next;
|
|
for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) {
|
|
status = connection->inputPublisher.appendMotionSample(
|
|
nextMotionSample->eventTime, nextMotionSample->pointerCoords);
|
|
if (status == NO_MEMORY) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("channel '%s' ~ Shared memory buffer full. Some motion samples will "
|
|
"be sent in the next dispatch cycle.",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
break;
|
|
}
|
|
if (status != OK) {
|
|
LOGE("channel '%s' ~ Could not append motion sample "
|
|
"for a reason other than out of memory, status=%d",
|
|
connection->getInputChannelName(), status);
|
|
abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Remember the next motion sample that we could not dispatch, in case we ran out
|
|
// of space in the shared memory buffer.
|
|
dispatchEntry->tailMotionSample = nextMotionSample;
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// Send the dispatch signal.
|
|
status = connection->inputPublisher.sendDispatchSignal();
|
|
if (status) {
|
|
LOGE("channel '%s' ~ Could not send dispatch signal, status=%d",
|
|
connection->getInputChannelName(), status);
|
|
abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
|
|
return;
|
|
}
|
|
|
|
// Record information about the newly started dispatch cycle.
|
|
dispatchEntry->inProgress = true;
|
|
|
|
connection->lastEventTime = dispatchEntry->eventEntry->eventTime;
|
|
connection->lastDispatchTime = currentTime;
|
|
|
|
nsecs_t timeout = dispatchEntry->timeout;
|
|
connection->setNextTimeoutTime(currentTime, timeout);
|
|
|
|
// Notify other system components.
|
|
onDispatchCycleStartedLocked(currentTime, connection);
|
|
}
|
|
|
|
void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
|
|
const sp<Connection>& connection) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("channel '%s' ~ finishDispatchCycle - %01.1fms since event, "
|
|
"%01.1fms since dispatch",
|
|
connection->getInputChannelName(),
|
|
connection->getEventLatencyMillis(currentTime),
|
|
connection->getDispatchLatencyMillis(currentTime));
|
|
#endif
|
|
|
|
if (connection->status == Connection::STATUS_BROKEN
|
|
|| connection->status == Connection::STATUS_ZOMBIE) {
|
|
return;
|
|
}
|
|
|
|
// Clear the pending timeout.
|
|
connection->nextTimeoutTime = LONG_LONG_MAX;
|
|
|
|
if (connection->status == Connection::STATUS_NOT_RESPONDING) {
|
|
// Recovering from an ANR.
|
|
connection->status = Connection::STATUS_NORMAL;
|
|
|
|
// Notify other system components.
|
|
onDispatchCycleFinishedLocked(currentTime, connection, true /*recoveredFromANR*/);
|
|
} else {
|
|
// Normal finish. Not much to do here.
|
|
|
|
// Notify other system components.
|
|
onDispatchCycleFinishedLocked(currentTime, connection, false /*recoveredFromANR*/);
|
|
}
|
|
|
|
// Reset the publisher since the event has been consumed.
|
|
// We do this now so that the publisher can release some of its internal resources
|
|
// while waiting for the next dispatch cycle to begin.
|
|
status_t status = connection->inputPublisher.reset();
|
|
if (status) {
|
|
LOGE("channel '%s' ~ Could not reset publisher, status=%d",
|
|
connection->getInputChannelName(), status);
|
|
abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
|
|
return;
|
|
}
|
|
|
|
// Start the next dispatch cycle for this connection.
|
|
while (! connection->outboundQueue.isEmpty()) {
|
|
DispatchEntry* dispatchEntry = connection->outboundQueue.head.next;
|
|
if (dispatchEntry->inProgress) {
|
|
// Finish or resume current event in progress.
|
|
if (dispatchEntry->tailMotionSample) {
|
|
// We have a tail of undispatched motion samples.
|
|
// Reuse the same DispatchEntry and start a new cycle.
|
|
dispatchEntry->inProgress = false;
|
|
dispatchEntry->headMotionSample = dispatchEntry->tailMotionSample;
|
|
dispatchEntry->tailMotionSample = NULL;
|
|
startDispatchCycleLocked(currentTime, connection);
|
|
return;
|
|
}
|
|
// Finished.
|
|
connection->outboundQueue.dequeueAtHead();
|
|
if (dispatchEntry->isSyncTarget()) {
|
|
decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry);
|
|
}
|
|
mAllocator.releaseDispatchEntry(dispatchEntry);
|
|
} else {
|
|
// If the head is not in progress, then we must have already dequeued the in
|
|
// progress event, which means we actually aborted it (due to ANR).
|
|
// So just start the next event for this connection.
|
|
startDispatchCycleLocked(currentTime, connection);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Outbound queue is empty, deactivate the connection.
|
|
deactivateConnectionLocked(connection.get());
|
|
}
|
|
|
|
void InputDispatcher::timeoutDispatchCycleLocked(nsecs_t currentTime,
|
|
const sp<Connection>& connection) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("channel '%s' ~ timeoutDispatchCycle",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
|
|
if (connection->status != Connection::STATUS_NORMAL) {
|
|
return;
|
|
}
|
|
|
|
// Enter the not responding state.
|
|
connection->status = Connection::STATUS_NOT_RESPONDING;
|
|
connection->lastANRTime = currentTime;
|
|
|
|
// Notify other system components.
|
|
// This enqueues a command which will eventually either call
|
|
// resumeAfterTimeoutDispatchCycleLocked or abortDispatchCycleLocked.
|
|
onDispatchCycleANRLocked(currentTime, connection);
|
|
}
|
|
|
|
void InputDispatcher::resumeAfterTimeoutDispatchCycleLocked(nsecs_t currentTime,
|
|
const sp<Connection>& connection, nsecs_t newTimeout) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("channel '%s' ~ resumeAfterTimeoutDispatchCycleLocked",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
|
|
if (connection->status != Connection::STATUS_NOT_RESPONDING) {
|
|
return;
|
|
}
|
|
|
|
// Resume normal dispatch.
|
|
connection->status = Connection::STATUS_NORMAL;
|
|
connection->setNextTimeoutTime(currentTime, newTimeout);
|
|
}
|
|
|
|
void InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime,
|
|
const sp<Connection>& connection, bool broken) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("channel '%s' ~ abortDispatchCycle - broken=%s",
|
|
connection->getInputChannelName(), broken ? "true" : "false");
|
|
#endif
|
|
|
|
// Clear the pending timeout.
|
|
connection->nextTimeoutTime = LONG_LONG_MAX;
|
|
|
|
// Clear the outbound queue.
|
|
if (! connection->outboundQueue.isEmpty()) {
|
|
do {
|
|
DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead();
|
|
if (dispatchEntry->isSyncTarget()) {
|
|
decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry);
|
|
}
|
|
mAllocator.releaseDispatchEntry(dispatchEntry);
|
|
} while (! connection->outboundQueue.isEmpty());
|
|
|
|
deactivateConnectionLocked(connection.get());
|
|
}
|
|
|
|
// Handle the case where the connection appears to be unrecoverably broken.
|
|
// Ignore already broken or zombie connections.
|
|
if (broken) {
|
|
if (connection->status == Connection::STATUS_NORMAL
|
|
|| connection->status == Connection::STATUS_NOT_RESPONDING) {
|
|
connection->status = Connection::STATUS_BROKEN;
|
|
|
|
// Notify other system components.
|
|
onDispatchCycleBrokenLocked(currentTime, connection);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) {
|
|
InputDispatcher* d = static_cast<InputDispatcher*>(data);
|
|
|
|
{ // acquire lock
|
|
AutoMutex _l(d->mLock);
|
|
|
|
ssize_t connectionIndex = d->mConnectionsByReceiveFd.indexOfKey(receiveFd);
|
|
if (connectionIndex < 0) {
|
|
LOGE("Received spurious receive callback for unknown input channel. "
|
|
"fd=%d, events=0x%x", receiveFd, events);
|
|
return false; // remove the callback
|
|
}
|
|
|
|
nsecs_t currentTime = now();
|
|
|
|
sp<Connection> connection = d->mConnectionsByReceiveFd.valueAt(connectionIndex);
|
|
if (events & (POLLERR | POLLHUP | POLLNVAL)) {
|
|
LOGE("channel '%s' ~ Consumer closed input channel or an error occurred. "
|
|
"events=0x%x", connection->getInputChannelName(), events);
|
|
d->abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
|
|
d->runCommandsLockedInterruptible();
|
|
return false; // remove the callback
|
|
}
|
|
|
|
if (! (events & POLLIN)) {
|
|
LOGW("channel '%s' ~ Received spurious callback for unhandled poll event. "
|
|
"events=0x%x", connection->getInputChannelName(), events);
|
|
return true;
|
|
}
|
|
|
|
status_t status = connection->inputPublisher.receiveFinishedSignal();
|
|
if (status) {
|
|
LOGE("channel '%s' ~ Failed to receive finished signal. status=%d",
|
|
connection->getInputChannelName(), status);
|
|
d->abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
|
|
d->runCommandsLockedInterruptible();
|
|
return false; // remove the callback
|
|
}
|
|
|
|
d->finishDispatchCycleLocked(currentTime, connection);
|
|
d->runCommandsLockedInterruptible();
|
|
return true;
|
|
} // release lock
|
|
}
|
|
|
|
void InputDispatcher::notifyConfigurationChanged(nsecs_t eventTime) {
|
|
#if DEBUG_INBOUND_EVENT_DETAILS
|
|
LOGD("notifyConfigurationChanged - eventTime=%lld", eventTime);
|
|
#endif
|
|
|
|
bool wasEmpty;
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
|
|
ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry(eventTime);
|
|
|
|
wasEmpty = mInboundQueue.isEmpty();
|
|
mInboundQueue.enqueueAtTail(newEntry);
|
|
} // release lock
|
|
|
|
if (wasEmpty) {
|
|
mPollLoop->wake();
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::notifyAppSwitchComing(nsecs_t eventTime) {
|
|
#if DEBUG_INBOUND_EVENT_DETAILS
|
|
LOGD("notifyAppSwitchComing - eventTime=%lld", eventTime);
|
|
#endif
|
|
|
|
// Remove movement keys from the queue from most recent to least recent, stopping at the
|
|
// first non-movement key.
|
|
// TODO: Include a detailed description of why we do this...
|
|
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
|
|
for (EventEntry* entry = mInboundQueue.tail.prev; entry != & mInboundQueue.head; ) {
|
|
EventEntry* prev = entry->prev;
|
|
|
|
if (entry->type == EventEntry::TYPE_KEY) {
|
|
KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
|
|
if (isMovementKey(keyEntry->keyCode)) {
|
|
LOGV("Dropping movement key during app switch: keyCode=%d, action=%d",
|
|
keyEntry->keyCode, keyEntry->action);
|
|
mInboundQueue.dequeue(keyEntry);
|
|
|
|
setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
|
|
|
|
mAllocator.releaseKeyEntry(keyEntry);
|
|
} else {
|
|
// stop at last non-movement key
|
|
break;
|
|
}
|
|
}
|
|
|
|
entry = prev;
|
|
}
|
|
} // release lock
|
|
}
|
|
|
|
void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,
|
|
uint32_t policyFlags, int32_t action, int32_t flags,
|
|
int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) {
|
|
#if DEBUG_INBOUND_EVENT_DETAILS
|
|
LOGD("notifyKey - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, action=0x%x, "
|
|
"flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld",
|
|
eventTime, deviceId, source, policyFlags, action, flags,
|
|
keyCode, scanCode, metaState, downTime);
|
|
#endif
|
|
|
|
bool wasEmpty;
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
|
|
int32_t repeatCount = 0;
|
|
KeyEntry* newEntry = mAllocator.obtainKeyEntry(eventTime,
|
|
deviceId, source, policyFlags, action, flags, keyCode, scanCode,
|
|
metaState, repeatCount, downTime);
|
|
|
|
wasEmpty = mInboundQueue.isEmpty();
|
|
mInboundQueue.enqueueAtTail(newEntry);
|
|
} // release lock
|
|
|
|
if (wasEmpty) {
|
|
mPollLoop->wake();
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source,
|
|
uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags,
|
|
uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
|
|
float xPrecision, float yPrecision, nsecs_t downTime) {
|
|
#if DEBUG_INBOUND_EVENT_DETAILS
|
|
LOGD("notifyMotion - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
|
|
"action=0x%x, metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, "
|
|
"downTime=%lld",
|
|
eventTime, deviceId, source, policyFlags, action, metaState, edgeFlags,
|
|
xPrecision, yPrecision, downTime);
|
|
for (uint32_t i = 0; i < pointerCount; i++) {
|
|
LOGD(" Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, "
|
|
"touchMajor=%f, touchMinor=%d, toolMajor=%f, toolMinor=%f, "
|
|
"orientation=%f",
|
|
i, pointerIds[i], pointerCoords[i].x, pointerCoords[i].y,
|
|
pointerCoords[i].pressure, pointerCoords[i].size,
|
|
pointerCoords[i].touchMajor, pointerCoords[i].touchMinor,
|
|
pointerCoords[i].toolMajor, pointerCoords[i].toolMinor,
|
|
pointerCoords[i].orientation);
|
|
}
|
|
#endif
|
|
|
|
bool wasEmpty;
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
|
|
// Attempt batching and streaming of move events.
|
|
if (action == AMOTION_EVENT_ACTION_MOVE) {
|
|
// BATCHING CASE
|
|
//
|
|
// Try to append a move sample to the tail of the inbound queue for this device.
|
|
// Give up if we encounter a non-move motion event for this device since that
|
|
// means we cannot append any new samples until a new motion event has started.
|
|
for (EventEntry* entry = mInboundQueue.tail.prev;
|
|
entry != & mInboundQueue.head; entry = entry->prev) {
|
|
if (entry->type != EventEntry::TYPE_MOTION) {
|
|
// Keep looking for motion events.
|
|
continue;
|
|
}
|
|
|
|
MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
|
|
if (motionEntry->deviceId != deviceId) {
|
|
// Keep looking for this device.
|
|
continue;
|
|
}
|
|
|
|
if (motionEntry->action != AMOTION_EVENT_ACTION_MOVE
|
|
|| motionEntry->pointerCount != pointerCount
|
|
|| motionEntry->isInjected()) {
|
|
// Last motion event in the queue for this device is not compatible for
|
|
// appending new samples. Stop here.
|
|
goto NoBatchingOrStreaming;
|
|
}
|
|
|
|
// The last motion event is a move and is compatible for appending.
|
|
// Do the batching magic.
|
|
mAllocator.appendMotionSample(motionEntry, eventTime, pointerCoords);
|
|
#if DEBUG_BATCHING
|
|
LOGD("Appended motion sample onto batch for most recent "
|
|
"motion event for this device in the inbound queue.");
|
|
#endif
|
|
|
|
// Sanity check for special case because dispatch is interruptible.
|
|
// The dispatch logic is partially interruptible and releases its lock while
|
|
// identifying targets. However, as soon as the targets have been identified,
|
|
// the dispatcher proceeds to write a dispatch entry into all relevant outbound
|
|
// queues and then promptly removes the motion entry from the queue.
|
|
//
|
|
// Consequently, we should never observe the case where the inbound queue contains
|
|
// an in-progress motion entry unless the current input targets are invalid
|
|
// (currently being computed). Check for this!
|
|
assert(! (motionEntry->dispatchInProgress && mCurrentInputTargetsValid));
|
|
|
|
return; // done!
|
|
}
|
|
|
|
// STREAMING CASE
|
|
//
|
|
// There is no pending motion event (of any kind) for this device in the inbound queue.
|
|
// Search the outbound queues for a synchronously dispatched motion event for this
|
|
// device. If found, then we append the new sample to that event and then try to
|
|
// push it out to all current targets. It is possible that some targets will already
|
|
// have consumed the motion event. This case is automatically handled by the
|
|
// logic in prepareDispatchCycleLocked by tracking where resumption takes place.
|
|
//
|
|
// The reason we look for a synchronously dispatched motion event is because we
|
|
// want to be sure that no other motion events have been dispatched since the move.
|
|
// It's also convenient because it means that the input targets are still valid.
|
|
// This code could be improved to support streaming of asynchronously dispatched
|
|
// motion events (which might be significantly more efficient) but it may become
|
|
// a little more complicated as a result.
|
|
//
|
|
// Note: This code crucially depends on the invariant that an outbound queue always
|
|
// contains at most one synchronous event and it is always last (but it might
|
|
// not be first!).
|
|
if (mCurrentInputTargetsValid) {
|
|
for (size_t i = 0; i < mActiveConnections.size(); i++) {
|
|
Connection* connection = mActiveConnections.itemAt(i);
|
|
if (! connection->outboundQueue.isEmpty()) {
|
|
DispatchEntry* dispatchEntry = connection->outboundQueue.tail.prev;
|
|
if (dispatchEntry->isSyncTarget()) {
|
|
if (dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION) {
|
|
goto NoBatchingOrStreaming;
|
|
}
|
|
|
|
MotionEntry* syncedMotionEntry = static_cast<MotionEntry*>(
|
|
dispatchEntry->eventEntry);
|
|
if (syncedMotionEntry->action != AMOTION_EVENT_ACTION_MOVE
|
|
|| syncedMotionEntry->deviceId != deviceId
|
|
|| syncedMotionEntry->pointerCount != pointerCount
|
|
|| syncedMotionEntry->isInjected()) {
|
|
goto NoBatchingOrStreaming;
|
|
}
|
|
|
|
// Found synced move entry. Append sample and resume dispatch.
|
|
mAllocator.appendMotionSample(syncedMotionEntry, eventTime,
|
|
pointerCoords);
|
|
#if DEBUG_BATCHING
|
|
LOGD("Appended motion sample onto batch for most recent synchronously "
|
|
"dispatched motion event for this device in the outbound queues.");
|
|
#endif
|
|
nsecs_t currentTime = now();
|
|
dispatchEventToCurrentInputTargetsLocked(currentTime, syncedMotionEntry,
|
|
true /*resumeWithAppendedMotionSample*/);
|
|
|
|
runCommandsLockedInterruptible();
|
|
return; // done!
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NoBatchingOrStreaming:;
|
|
}
|
|
|
|
// Just enqueue a new motion event.
|
|
MotionEntry* newEntry = mAllocator.obtainMotionEntry(eventTime,
|
|
deviceId, source, policyFlags, action, metaState, edgeFlags,
|
|
xPrecision, yPrecision, downTime,
|
|
pointerCount, pointerIds, pointerCoords);
|
|
|
|
wasEmpty = mInboundQueue.isEmpty();
|
|
mInboundQueue.enqueueAtTail(newEntry);
|
|
} // release lock
|
|
|
|
if (wasEmpty) {
|
|
mPollLoop->wake();
|
|
}
|
|
}
|
|
|
|
int32_t InputDispatcher::injectInputEvent(const InputEvent* event,
|
|
int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) {
|
|
#if DEBUG_INBOUND_EVENT_DETAILS
|
|
LOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, "
|
|
"syncMode=%d, timeoutMillis=%d",
|
|
event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis);
|
|
#endif
|
|
|
|
nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis);
|
|
|
|
EventEntry* injectedEntry;
|
|
bool wasEmpty;
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
|
|
injectedEntry = createEntryFromInputEventLocked(event);
|
|
injectedEntry->refCount += 1;
|
|
injectedEntry->injectorPid = injectorPid;
|
|
injectedEntry->injectorUid = injectorUid;
|
|
|
|
if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
|
|
injectedEntry->injectionIsAsync = true;
|
|
}
|
|
|
|
wasEmpty = mInboundQueue.isEmpty();
|
|
mInboundQueue.enqueueAtTail(injectedEntry);
|
|
|
|
} // release lock
|
|
|
|
if (wasEmpty) {
|
|
mPollLoop->wake();
|
|
}
|
|
|
|
int32_t injectionResult;
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
|
|
if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
|
|
injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
|
|
} else {
|
|
for (;;) {
|
|
injectionResult = injectedEntry->injectionResult;
|
|
if (injectionResult != INPUT_EVENT_INJECTION_PENDING) {
|
|
break;
|
|
}
|
|
|
|
nsecs_t remainingTimeout = endTime - now();
|
|
if (remainingTimeout <= 0) {
|
|
#if DEBUG_INJECTION
|
|
LOGD("injectInputEvent - Timed out waiting for injection result "
|
|
"to become available.");
|
|
#endif
|
|
injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
|
|
break;
|
|
}
|
|
|
|
mInjectionResultAvailableCondition.waitRelative(mLock, remainingTimeout);
|
|
}
|
|
|
|
if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED
|
|
&& syncMode == INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED) {
|
|
while (injectedEntry->pendingSyncDispatches != 0) {
|
|
#if DEBUG_INJECTION
|
|
LOGD("injectInputEvent - Waiting for %d pending synchronous dispatches.",
|
|
injectedEntry->pendingSyncDispatches);
|
|
#endif
|
|
nsecs_t remainingTimeout = endTime - now();
|
|
if (remainingTimeout <= 0) {
|
|
#if DEBUG_INJECTION
|
|
LOGD("injectInputEvent - Timed out waiting for pending synchronous "
|
|
"dispatches to finish.");
|
|
#endif
|
|
injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
|
|
break;
|
|
}
|
|
|
|
mInjectionSyncFinishedCondition.waitRelative(mLock, remainingTimeout);
|
|
}
|
|
}
|
|
}
|
|
|
|
mAllocator.releaseEventEntry(injectedEntry);
|
|
} // release lock
|
|
|
|
#if DEBUG_INJECTION
|
|
LOGD("injectInputEvent - Finished with result %d. "
|
|
"injectorPid=%d, injectorUid=%d",
|
|
injectionResult, injectorPid, injectorUid);
|
|
#endif
|
|
|
|
return injectionResult;
|
|
}
|
|
|
|
void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t injectionResult) {
|
|
if (entry->isInjected()) {
|
|
#if DEBUG_INJECTION
|
|
LOGD("Setting input event injection result to %d. "
|
|
"injectorPid=%d, injectorUid=%d",
|
|
injectionResult, entry->injectorPid, entry->injectorUid);
|
|
#endif
|
|
|
|
if (entry->injectionIsAsync) {
|
|
// Log the outcome since the injector did not wait for the injection result.
|
|
switch (injectionResult) {
|
|
case INPUT_EVENT_INJECTION_SUCCEEDED:
|
|
LOGV("Asynchronous input event injection succeeded.");
|
|
break;
|
|
case INPUT_EVENT_INJECTION_FAILED:
|
|
LOGW("Asynchronous input event injection failed.");
|
|
break;
|
|
case INPUT_EVENT_INJECTION_PERMISSION_DENIED:
|
|
LOGW("Asynchronous input event injection permission denied.");
|
|
break;
|
|
case INPUT_EVENT_INJECTION_TIMED_OUT:
|
|
LOGW("Asynchronous input event injection timed out.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
entry->injectionResult = injectionResult;
|
|
mInjectionResultAvailableCondition.broadcast();
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::decrementPendingSyncDispatchesLocked(EventEntry* entry) {
|
|
entry->pendingSyncDispatches -= 1;
|
|
|
|
if (entry->isInjected() && entry->pendingSyncDispatches == 0) {
|
|
mInjectionSyncFinishedCondition.broadcast();
|
|
}
|
|
}
|
|
|
|
InputDispatcher::EventEntry* InputDispatcher::createEntryFromInputEventLocked(
|
|
const InputEvent* event) {
|
|
switch (event->getType()) {
|
|
case AINPUT_EVENT_TYPE_KEY: {
|
|
const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event);
|
|
uint32_t policyFlags = 0; // XXX consider adding a policy flag to track injected events
|
|
|
|
KeyEntry* keyEntry = mAllocator.obtainKeyEntry(keyEvent->getEventTime(),
|
|
keyEvent->getDeviceId(), keyEvent->getSource(), policyFlags,
|
|
keyEvent->getAction(), keyEvent->getFlags(),
|
|
keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(),
|
|
keyEvent->getRepeatCount(), keyEvent->getDownTime());
|
|
return keyEntry;
|
|
}
|
|
|
|
case AINPUT_EVENT_TYPE_MOTION: {
|
|
const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event);
|
|
uint32_t policyFlags = 0; // XXX consider adding a policy flag to track injected events
|
|
|
|
const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes();
|
|
const PointerCoords* samplePointerCoords = motionEvent->getSamplePointerCoords();
|
|
size_t pointerCount = motionEvent->getPointerCount();
|
|
|
|
MotionEntry* motionEntry = mAllocator.obtainMotionEntry(*sampleEventTimes,
|
|
motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags,
|
|
motionEvent->getAction(), motionEvent->getMetaState(), motionEvent->getEdgeFlags(),
|
|
motionEvent->getXPrecision(), motionEvent->getYPrecision(),
|
|
motionEvent->getDownTime(), uint32_t(pointerCount),
|
|
motionEvent->getPointerIds(), samplePointerCoords);
|
|
for (size_t i = motionEvent->getHistorySize(); i > 0; i--) {
|
|
sampleEventTimes += 1;
|
|
samplePointerCoords += pointerCount;
|
|
mAllocator.appendMotionSample(motionEntry, *sampleEventTimes, samplePointerCoords);
|
|
}
|
|
return motionEntry;
|
|
}
|
|
|
|
default:
|
|
assert(false);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::resetKeyRepeatLocked() {
|
|
if (mKeyRepeatState.lastKeyEntry) {
|
|
mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry);
|
|
mKeyRepeatState.lastKeyEntry = NULL;
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::preemptInputDispatch() {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("preemptInputDispatch");
|
|
#endif
|
|
|
|
bool preemptedOne = false;
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
|
|
for (size_t i = 0; i < mActiveConnections.size(); i++) {
|
|
Connection* connection = mActiveConnections[i];
|
|
if (connection->hasPendingSyncTarget()) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("channel '%s' ~ Preempted pending synchronous dispatch",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
connection->outboundQueue.tail.prev->targetFlags &= ~ InputTarget::FLAG_SYNC;
|
|
preemptedOne = true;
|
|
}
|
|
}
|
|
} // release lock
|
|
|
|
if (preemptedOne) {
|
|
// Wake up the poll loop so it can get a head start dispatching the next event.
|
|
mPollLoop->wake();
|
|
}
|
|
}
|
|
|
|
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel) {
|
|
#if DEBUG_REGISTRATION
|
|
LOGD("channel '%s' ~ registerInputChannel", inputChannel->getName().string());
|
|
#endif
|
|
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
|
|
if (getConnectionIndex(inputChannel) >= 0) {
|
|
LOGW("Attempted to register already registered input channel '%s'",
|
|
inputChannel->getName().string());
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
sp<Connection> connection = new Connection(inputChannel);
|
|
status_t status = connection->initialize();
|
|
if (status) {
|
|
LOGE("Failed to initialize input publisher for input channel '%s', status=%d",
|
|
inputChannel->getName().string(), status);
|
|
return status;
|
|
}
|
|
|
|
int32_t receiveFd = inputChannel->getReceivePipeFd();
|
|
mConnectionsByReceiveFd.add(receiveFd, connection);
|
|
|
|
mPollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this);
|
|
|
|
runCommandsLockedInterruptible();
|
|
} // release lock
|
|
return OK;
|
|
}
|
|
|
|
status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) {
|
|
#if DEBUG_REGISTRATION
|
|
LOGD("channel '%s' ~ unregisterInputChannel", inputChannel->getName().string());
|
|
#endif
|
|
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
|
|
ssize_t connectionIndex = getConnectionIndex(inputChannel);
|
|
if (connectionIndex < 0) {
|
|
LOGW("Attempted to unregister already unregistered input channel '%s'",
|
|
inputChannel->getName().string());
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
|
|
mConnectionsByReceiveFd.removeItemsAt(connectionIndex);
|
|
|
|
connection->status = Connection::STATUS_ZOMBIE;
|
|
|
|
mPollLoop->removeCallback(inputChannel->getReceivePipeFd());
|
|
|
|
nsecs_t currentTime = now();
|
|
abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
|
|
|
|
runCommandsLockedInterruptible();
|
|
} // release lock
|
|
|
|
// Wake the poll loop because removing the connection may have changed the current
|
|
// synchronization state.
|
|
mPollLoop->wake();
|
|
return OK;
|
|
}
|
|
|
|
ssize_t InputDispatcher::getConnectionIndex(const sp<InputChannel>& inputChannel) {
|
|
ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(inputChannel->getReceivePipeFd());
|
|
if (connectionIndex >= 0) {
|
|
sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
|
|
if (connection->inputChannel.get() == inputChannel.get()) {
|
|
return connectionIndex;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void InputDispatcher::activateConnectionLocked(Connection* connection) {
|
|
for (size_t i = 0; i < mActiveConnections.size(); i++) {
|
|
if (mActiveConnections.itemAt(i) == connection) {
|
|
return;
|
|
}
|
|
}
|
|
mActiveConnections.add(connection);
|
|
}
|
|
|
|
void InputDispatcher::deactivateConnectionLocked(Connection* connection) {
|
|
for (size_t i = 0; i < mActiveConnections.size(); i++) {
|
|
if (mActiveConnections.itemAt(i) == connection) {
|
|
mActiveConnections.removeAt(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::onDispatchCycleStartedLocked(
|
|
nsecs_t currentTime, const sp<Connection>& connection) {
|
|
}
|
|
|
|
void InputDispatcher::onDispatchCycleFinishedLocked(
|
|
nsecs_t currentTime, const sp<Connection>& connection, bool recoveredFromANR) {
|
|
if (recoveredFromANR) {
|
|
LOGI("channel '%s' ~ Recovered from ANR. %01.1fms since event, "
|
|
"%01.1fms since dispatch, %01.1fms since ANR",
|
|
connection->getInputChannelName(),
|
|
connection->getEventLatencyMillis(currentTime),
|
|
connection->getDispatchLatencyMillis(currentTime),
|
|
connection->getANRLatencyMillis(currentTime));
|
|
|
|
CommandEntry* commandEntry = postCommandLocked(
|
|
& InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible);
|
|
commandEntry->connection = connection;
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::onDispatchCycleANRLocked(
|
|
nsecs_t currentTime, const sp<Connection>& connection) {
|
|
LOGI("channel '%s' ~ Not responding! %01.1fms since event, %01.1fms since dispatch",
|
|
connection->getInputChannelName(),
|
|
connection->getEventLatencyMillis(currentTime),
|
|
connection->getDispatchLatencyMillis(currentTime));
|
|
|
|
CommandEntry* commandEntry = postCommandLocked(
|
|
& InputDispatcher::doNotifyInputChannelANRLockedInterruptible);
|
|
commandEntry->connection = connection;
|
|
}
|
|
|
|
void InputDispatcher::onDispatchCycleBrokenLocked(
|
|
nsecs_t currentTime, const sp<Connection>& connection) {
|
|
LOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!",
|
|
connection->getInputChannelName());
|
|
|
|
CommandEntry* commandEntry = postCommandLocked(
|
|
& InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible);
|
|
commandEntry->connection = connection;
|
|
}
|
|
|
|
void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible(
|
|
CommandEntry* commandEntry) {
|
|
sp<Connection> connection = commandEntry->connection;
|
|
|
|
if (connection->status != Connection::STATUS_ZOMBIE) {
|
|
mLock.unlock();
|
|
|
|
mPolicy->notifyInputChannelBroken(connection->inputChannel);
|
|
|
|
mLock.lock();
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::doNotifyInputChannelANRLockedInterruptible(
|
|
CommandEntry* commandEntry) {
|
|
sp<Connection> connection = commandEntry->connection;
|
|
|
|
if (connection->status != Connection::STATUS_ZOMBIE) {
|
|
mLock.unlock();
|
|
|
|
nsecs_t newTimeout;
|
|
bool resume = mPolicy->notifyInputChannelANR(connection->inputChannel, newTimeout);
|
|
|
|
mLock.lock();
|
|
|
|
nsecs_t currentTime = now();
|
|
if (resume) {
|
|
resumeAfterTimeoutDispatchCycleLocked(currentTime, connection, newTimeout);
|
|
} else {
|
|
abortDispatchCycleLocked(currentTime, connection, false /*(not) broken*/);
|
|
}
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible(
|
|
CommandEntry* commandEntry) {
|
|
sp<Connection> connection = commandEntry->connection;
|
|
|
|
if (connection->status != Connection::STATUS_ZOMBIE) {
|
|
mLock.unlock();
|
|
|
|
mPolicy->notifyInputChannelRecoveredFromANR(connection->inputChannel);
|
|
|
|
mLock.lock();
|
|
}
|
|
}
|
|
|
|
|
|
// --- InputDispatcher::Allocator ---
|
|
|
|
InputDispatcher::Allocator::Allocator() {
|
|
}
|
|
|
|
void InputDispatcher::Allocator::initializeEventEntry(EventEntry* entry, int32_t type,
|
|
nsecs_t eventTime) {
|
|
entry->type = type;
|
|
entry->refCount = 1;
|
|
entry->dispatchInProgress = false;
|
|
entry->eventTime = eventTime;
|
|
entry->injectionResult = INPUT_EVENT_INJECTION_PENDING;
|
|
entry->injectionIsAsync = false;
|
|
entry->injectorPid = -1;
|
|
entry->injectorUid = -1;
|
|
entry->pendingSyncDispatches = 0;
|
|
}
|
|
|
|
InputDispatcher::ConfigurationChangedEntry*
|
|
InputDispatcher::Allocator::obtainConfigurationChangedEntry(nsecs_t eventTime) {
|
|
ConfigurationChangedEntry* entry = mConfigurationChangeEntryPool.alloc();
|
|
initializeEventEntry(entry, EventEntry::TYPE_CONFIGURATION_CHANGED, eventTime);
|
|
return entry;
|
|
}
|
|
|
|
InputDispatcher::KeyEntry* InputDispatcher::Allocator::obtainKeyEntry(nsecs_t eventTime,
|
|
int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action,
|
|
int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
|
|
int32_t repeatCount, nsecs_t downTime) {
|
|
KeyEntry* entry = mKeyEntryPool.alloc();
|
|
initializeEventEntry(entry, EventEntry::TYPE_KEY, eventTime);
|
|
|
|
entry->deviceId = deviceId;
|
|
entry->source = source;
|
|
entry->policyFlags = policyFlags;
|
|
entry->action = action;
|
|
entry->flags = flags;
|
|
entry->keyCode = keyCode;
|
|
entry->scanCode = scanCode;
|
|
entry->metaState = metaState;
|
|
entry->repeatCount = repeatCount;
|
|
entry->downTime = downTime;
|
|
return entry;
|
|
}
|
|
|
|
InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry(nsecs_t eventTime,
|
|
int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action,
|
|
int32_t metaState, int32_t edgeFlags, float xPrecision, float yPrecision,
|
|
nsecs_t downTime, uint32_t pointerCount,
|
|
const int32_t* pointerIds, const PointerCoords* pointerCoords) {
|
|
MotionEntry* entry = mMotionEntryPool.alloc();
|
|
initializeEventEntry(entry, EventEntry::TYPE_MOTION, eventTime);
|
|
|
|
entry->eventTime = eventTime;
|
|
entry->deviceId = deviceId;
|
|
entry->source = source;
|
|
entry->policyFlags = policyFlags;
|
|
entry->action = action;
|
|
entry->metaState = metaState;
|
|
entry->edgeFlags = edgeFlags;
|
|
entry->xPrecision = xPrecision;
|
|
entry->yPrecision = yPrecision;
|
|
entry->downTime = downTime;
|
|
entry->pointerCount = pointerCount;
|
|
entry->firstSample.eventTime = eventTime;
|
|
entry->firstSample.next = NULL;
|
|
entry->lastSample = & entry->firstSample;
|
|
for (uint32_t i = 0; i < pointerCount; i++) {
|
|
entry->pointerIds[i] = pointerIds[i];
|
|
entry->firstSample.pointerCoords[i] = pointerCoords[i];
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
InputDispatcher::DispatchEntry* InputDispatcher::Allocator::obtainDispatchEntry(
|
|
EventEntry* eventEntry) {
|
|
DispatchEntry* entry = mDispatchEntryPool.alloc();
|
|
entry->eventEntry = eventEntry;
|
|
eventEntry->refCount += 1;
|
|
return entry;
|
|
}
|
|
|
|
InputDispatcher::CommandEntry* InputDispatcher::Allocator::obtainCommandEntry(Command command) {
|
|
CommandEntry* entry = mCommandEntryPool.alloc();
|
|
entry->command = command;
|
|
return entry;
|
|
}
|
|
|
|
void InputDispatcher::Allocator::releaseEventEntry(EventEntry* entry) {
|
|
switch (entry->type) {
|
|
case EventEntry::TYPE_CONFIGURATION_CHANGED:
|
|
releaseConfigurationChangedEntry(static_cast<ConfigurationChangedEntry*>(entry));
|
|
break;
|
|
case EventEntry::TYPE_KEY:
|
|
releaseKeyEntry(static_cast<KeyEntry*>(entry));
|
|
break;
|
|
case EventEntry::TYPE_MOTION:
|
|
releaseMotionEntry(static_cast<MotionEntry*>(entry));
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::Allocator::releaseConfigurationChangedEntry(
|
|
ConfigurationChangedEntry* entry) {
|
|
entry->refCount -= 1;
|
|
if (entry->refCount == 0) {
|
|
mConfigurationChangeEntryPool.free(entry);
|
|
} else {
|
|
assert(entry->refCount > 0);
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::Allocator::releaseKeyEntry(KeyEntry* entry) {
|
|
entry->refCount -= 1;
|
|
if (entry->refCount == 0) {
|
|
mKeyEntryPool.free(entry);
|
|
} else {
|
|
assert(entry->refCount > 0);
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::Allocator::releaseMotionEntry(MotionEntry* entry) {
|
|
entry->refCount -= 1;
|
|
if (entry->refCount == 0) {
|
|
for (MotionSample* sample = entry->firstSample.next; sample != NULL; ) {
|
|
MotionSample* next = sample->next;
|
|
mMotionSamplePool.free(sample);
|
|
sample = next;
|
|
}
|
|
mMotionEntryPool.free(entry);
|
|
} else {
|
|
assert(entry->refCount > 0);
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::Allocator::releaseDispatchEntry(DispatchEntry* entry) {
|
|
releaseEventEntry(entry->eventEntry);
|
|
mDispatchEntryPool.free(entry);
|
|
}
|
|
|
|
void InputDispatcher::Allocator::releaseCommandEntry(CommandEntry* entry) {
|
|
mCommandEntryPool.free(entry);
|
|
}
|
|
|
|
void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry,
|
|
nsecs_t eventTime, const PointerCoords* pointerCoords) {
|
|
MotionSample* sample = mMotionSamplePool.alloc();
|
|
sample->eventTime = eventTime;
|
|
uint32_t pointerCount = motionEntry->pointerCount;
|
|
for (uint32_t i = 0; i < pointerCount; i++) {
|
|
sample->pointerCoords[i] = pointerCoords[i];
|
|
}
|
|
|
|
sample->next = NULL;
|
|
motionEntry->lastSample->next = sample;
|
|
motionEntry->lastSample = sample;
|
|
}
|
|
|
|
// --- InputDispatcher::MotionEntry ---
|
|
|
|
uint32_t InputDispatcher::MotionEntry::countSamples() const {
|
|
uint32_t count = 1;
|
|
for (MotionSample* sample = firstSample.next; sample != NULL; sample = sample->next) {
|
|
count += 1;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
// --- InputDispatcher::Connection ---
|
|
|
|
InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel) :
|
|
status(STATUS_NORMAL), inputChannel(inputChannel), inputPublisher(inputChannel),
|
|
nextTimeoutTime(LONG_LONG_MAX),
|
|
lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX),
|
|
lastANRTime(LONG_LONG_MAX) {
|
|
}
|
|
|
|
InputDispatcher::Connection::~Connection() {
|
|
}
|
|
|
|
status_t InputDispatcher::Connection::initialize() {
|
|
return inputPublisher.initialize();
|
|
}
|
|
|
|
void InputDispatcher::Connection::setNextTimeoutTime(nsecs_t currentTime, nsecs_t timeout) {
|
|
nextTimeoutTime = (timeout >= 0) ? currentTime + timeout : LONG_LONG_MAX;
|
|
}
|
|
|
|
const char* InputDispatcher::Connection::getStatusLabel() const {
|
|
switch (status) {
|
|
case STATUS_NORMAL:
|
|
return "NORMAL";
|
|
|
|
case STATUS_BROKEN:
|
|
return "BROKEN";
|
|
|
|
case STATUS_NOT_RESPONDING:
|
|
return "NOT_RESPONDING";
|
|
|
|
case STATUS_ZOMBIE:
|
|
return "ZOMBIE";
|
|
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
InputDispatcher::DispatchEntry* InputDispatcher::Connection::findQueuedDispatchEntryForEvent(
|
|
const EventEntry* eventEntry) const {
|
|
for (DispatchEntry* dispatchEntry = outboundQueue.tail.prev;
|
|
dispatchEntry != & outboundQueue.head; dispatchEntry = dispatchEntry->prev) {
|
|
if (dispatchEntry->eventEntry == eventEntry) {
|
|
return dispatchEntry;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// --- InputDispatcher::CommandEntry ---
|
|
|
|
InputDispatcher::CommandEntry::CommandEntry() {
|
|
}
|
|
|
|
InputDispatcher::CommandEntry::~CommandEntry() {
|
|
}
|
|
|
|
|
|
// --- InputDispatcherThread ---
|
|
|
|
InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) :
|
|
Thread(/*canCallJava*/ true), mDispatcher(dispatcher) {
|
|
}
|
|
|
|
InputDispatcherThread::~InputDispatcherThread() {
|
|
}
|
|
|
|
bool InputDispatcherThread::threadLoop() {
|
|
mDispatcher->dispatchOnce();
|
|
return true;
|
|
}
|
|
|
|
} // namespace android
|