blob: 14dcada3b51897cfcb6d893fc1be41471d49734a [file] [log] [blame]
//
// 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 1
// Log detailed debug messages about each outbound event processed by the dispatcher.
#define DEBUG_OUTBOUND_EVENT_DETAILS 1
// Log debug messages about batching.
#define DEBUG_BATCHING 1
// Log debug messages about the dispatch cycle.
#define DEBUG_DISPATCH_CYCLE 1
// Log debug messages about registrations.
#define DEBUG_REGISTRATION 1
// Log debug messages about performance statistics.
#define DEBUG_PERFORMANCE_STATISTICS 1
#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 == KEYCODE_DPAD_UP
|| keyCode == KEYCODE_DPAD_DOWN
|| keyCode == KEYCODE_DPAD_LEFT
|| keyCode == KEYCODE_DPAD_RIGHT;
}
// --- InputDispatcher ---
InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
mPolicy(policy) {
mPollLoop = new PollLoop();
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;
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 = systemTime(SYSTEM_TIME_MONOTONIC);
// 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();
}
// Process timeouts for all connections and determine if there are any synchronous
// event dispatches pending.
bool hasPendingSyncTarget = false;
for (size_t i = 0; i < mActiveConnections.size(); ) {
Connection* connection = mActiveConnections.itemAt(i);
nsecs_t connectionTimeoutTime = connection->nextTimeoutTime;
if (connectionTimeoutTime <= currentTime) {
bool deactivated = timeoutDispatchCycleLocked(currentTime, connection);
if (deactivated) {
// Don't increment i because the connection has been removed
// from mActiveConnections (hence, deactivated).
continue;
}
}
if (connectionTimeoutTime < nextWakeupTime) {
nextWakeupTime = connectionTimeoutTime;
}
if (connection->hasPendingSyncTarget()) {
hasPendingSyncTarget = true;
}
i += 1;
}
// 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.
// Start processing it 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.
EventEntry* entry = mInboundQueue.head.next;
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;
}
}
// 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.
nsecs_t timeout = nanoseconds_to_milliseconds(nextWakeupTime - currentTime);
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'
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
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, nature=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->nature, entry->policyFlags, entry->action,
entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
entry->downTime);
#endif
// TODO: Poke user activity.
if (entry->action == KEY_EVENT_ACTION_DOWN) {
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) {
// TODO Old WindowManagerServer code sniffs the input queue for following key up
// events and drops the repeat if one is found. We should do something similar.
// One good place to do it is in notifyKey as soon as the key up enters the
// inbound event queue.
// Synthesize a key repeat after the repeat timeout expired.
// We reuse the previous key entry if otherwise unreferenced.
KeyEntry* entry = mKeyRepeatState.lastKeyEntry;
if (entry->refCount == 1) {
entry->repeatCount += 1;
} else {
KeyEntry* newEntry = mAllocator.obtainKeyEntry();
newEntry->deviceId = entry->deviceId;
newEntry->nature = entry->nature;
newEntry->policyFlags = entry->policyFlags;
newEntry->action = entry->action;
newEntry->flags = entry->flags;
newEntry->keyCode = entry->keyCode;
newEntry->scanCode = entry->scanCode;
newEntry->metaState = entry->metaState;
newEntry->repeatCount = entry->repeatCount + 1;
mKeyRepeatState.lastKeyEntry = newEntry;
mAllocator.releaseKeyEntry(entry);
entry = newEntry;
}
entry->eventTime = currentTime;
entry->downTime = currentTime;
entry->policyFlags = 0;
mKeyRepeatState.nextRepeatTime = currentTime + keyRepeatTimeout;
#if DEBUG_OUTBOUND_EVENT_DETAILS
LOGD("processKeyRepeat - eventTime=%lld, deviceId=0x%x, nature=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->nature, 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, nature=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->nature, 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",
i, entry->pointerIds[i],
sample->pointerCoords[i].x,
sample->pointerCoords[i].y,
sample->pointerCoords[i].pressure,
sample->pointerCoords[i].size);
}
// 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 == MOTION_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->nature, entry->action, entry->flags,
entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount,
entry->downTime, entry->eventTime);
mCurrentInputTargets.clear();
mPolicy->getKeyEventTargets(& mReusableKeyEvent, entry->policyFlags,
mCurrentInputTargets);
mLock.lock();
mCurrentInputTargetsValid = true;
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->nature, 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();
mPolicy->getMotionEventTargets(& mReusableMotionEvent, entry->policyFlags,
mCurrentInputTargets);
mLock.lock();
mCurrentInputTargetsValid = true;
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 = mConnectionsByReceiveFd.indexOfKey(
inputTarget.inputChannel->getReceivePipeFd());
if (connectionIndex >= 0) {
sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
prepareDispatchCycleLocked(currentTime, connection.get(), 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, 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 "
"dispatchedmove 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;
// 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);
startDispatchCycleLocked(currentTime, connection);
}
}
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, 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 |= KEY_EVENT_FLAG_CANCELED;
}
// Publish the key event.
status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->nature,
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 = MOTION_EVENT_ACTION_OUTSIDE;
}
if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) {
action = MOTION_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;
}
// Publish the motion event and the first motion sample.
status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId,
motionEntry->nature, action, motionEntry->edgeFlags, motionEntry->metaState,
dispatchEntry->xOffset, dispatchEntry->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->nextTimeoutTime = (timeout >= 0) ? currentTime + timeout : LONG_LONG_MAX;
// Notify other system components.
onDispatchCycleStartedLocked(currentTime, connection);
}
void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, 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();
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);
}
bool InputDispatcher::timeoutDispatchCycleLocked(nsecs_t currentTime, Connection* connection) {
#if DEBUG_DISPATCH_CYCLE
LOGD("channel '%s' ~ timeoutDispatchCycle",
connection->getInputChannelName());
#endif
if (connection->status != Connection::STATUS_NORMAL) {
return false;
}
// Enter the not responding state.
connection->status = Connection::STATUS_NOT_RESPONDING;
connection->lastANRTime = currentTime;
bool deactivated = abortDispatchCycleLocked(currentTime, connection, false /*(not) broken*/);
// Notify other system components.
onDispatchCycleANRLocked(currentTime, connection);
return deactivated;
}
bool InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime, 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.
bool deactivated = ! connection->outboundQueue.isEmpty();
if (deactivated) {
do {
DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead();
mAllocator.releaseDispatchEntry(dispatchEntry);
} while (! connection->outboundQueue.isEmpty());
deactivateConnectionLocked(connection);
}
// 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);
}
}
return deactivated;
}
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 = systemTime(SYSTEM_TIME_MONOTONIC);
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.get(), 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.get(), true /*broken*/);
d->runCommandsLockedInterruptible();
return false; // remove the callback
}
d->finishDispatchCycleLocked(currentTime, connection.get());
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();
newEntry->eventTime = 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);
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 nature,
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, nature=0x%x, policyFlags=0x%x, action=0x%x, "
"flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld",
eventTime, deviceId, nature, policyFlags, action, flags,
keyCode, scanCode, metaState, downTime);
#endif
bool wasEmpty;
{ // acquire lock
AutoMutex _l(mLock);
KeyEntry* newEntry = mAllocator.obtainKeyEntry();
newEntry->eventTime = eventTime;
newEntry->deviceId = deviceId;
newEntry->nature = nature;
newEntry->policyFlags = policyFlags;
newEntry->action = action;
newEntry->flags = flags;
newEntry->keyCode = keyCode;
newEntry->scanCode = scanCode;
newEntry->metaState = metaState;
newEntry->repeatCount = 0;
newEntry->downTime = downTime;
wasEmpty = mInboundQueue.isEmpty();
mInboundQueue.enqueueAtTail(newEntry);
} // release lock
if (wasEmpty) {
mPollLoop->wake();
}
}
void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t nature,
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, nature=0x%x, policyFlags=0x%x, "
"action=0x%x, metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, "
"downTime=%lld",
eventTime, deviceId, nature, 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",
i, pointerIds[i], pointerCoords[i].x, pointerCoords[i].y,
pointerCoords[i].pressure, pointerCoords[i].size);
}
#endif
bool wasEmpty;
{ // acquire lock
AutoMutex _l(mLock);
// Attempt batching and streaming of move events.
if (action == MOTION_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 != MOTION_EVENT_ACTION_MOVE
|| motionEntry->pointerCount != pointerCount) {
// 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, pointerCount, 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->targetFlags & InputTarget::FLAG_SYNC) {
if (dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION) {
goto NoBatchingOrStreaming;
}
MotionEntry* syncedMotionEntry = static_cast<MotionEntry*>(
dispatchEntry->eventEntry);
if (syncedMotionEntry->action != MOTION_EVENT_ACTION_MOVE
|| syncedMotionEntry->deviceId != deviceId
|| syncedMotionEntry->pointerCount != pointerCount) {
goto NoBatchingOrStreaming;
}
// Found synced move entry. Append sample and resume dispatch.
mAllocator.appendMotionSample(syncedMotionEntry, eventTime,
pointerCount, 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 = systemTime(SYSTEM_TIME_MONOTONIC);
dispatchEventToCurrentInputTargetsLocked(currentTime, syncedMotionEntry,
true /*resumeWithAppendedMotionSample*/);
runCommandsLockedInterruptible();
return; // done!
}
}
}
}
NoBatchingOrStreaming:;
}
// Just enqueue a new motion event.
MotionEntry* newEntry = mAllocator.obtainMotionEntry();
newEntry->eventTime = eventTime;
newEntry->deviceId = deviceId;
newEntry->nature = nature;
newEntry->policyFlags = policyFlags;
newEntry->action = action;
newEntry->metaState = metaState;
newEntry->edgeFlags = edgeFlags;
newEntry->xPrecision = xPrecision;
newEntry->yPrecision = yPrecision;
newEntry->downTime = downTime;
newEntry->pointerCount = pointerCount;
newEntry->firstSample.eventTime = eventTime;
newEntry->lastSample = & newEntry->firstSample;
for (uint32_t i = 0; i < pointerCount; i++) {
newEntry->pointerIds[i] = pointerIds[i];
newEntry->firstSample.pointerCoords[i] = pointerCoords[i];
}
wasEmpty = mInboundQueue.isEmpty();
mInboundQueue.enqueueAtTail(newEntry);
} // release lock
if (wasEmpty) {
mPollLoop->wake();
}
}
void InputDispatcher::resetKeyRepeatLocked() {
if (mKeyRepeatState.lastKeyEntry) {
mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry);
mKeyRepeatState.lastKeyEntry = NULL;
}
}
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel) {
#if DEBUG_REGISTRATION
LOGD("channel '%s' - Registered", inputChannel->getName().string());
#endif
int receiveFd;
{ // acquire lock
AutoMutex _l(mLock);
receiveFd = inputChannel->getReceivePipeFd();
if (mConnectionsByReceiveFd.indexOfKey(receiveFd) >= 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;
}
mConnectionsByReceiveFd.add(receiveFd, connection);
runCommandsLockedInterruptible();
} // release lock
mPollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this);
return OK;
}
status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) {
#if DEBUG_REGISTRATION
LOGD("channel '%s' - Unregistered", inputChannel->getName().string());
#endif
int32_t receiveFd;
{ // acquire lock
AutoMutex _l(mLock);
receiveFd = inputChannel->getReceivePipeFd();
ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd);
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;
nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
abortDispatchCycleLocked(currentTime, connection.get(), true /*broken*/);
runCommandsLockedInterruptible();
} // release lock
mPollLoop->removeCallback(receiveFd);
// Wake the poll loop because removing the connection may have changed the current
// synchronization state.
mPollLoop->wake();
return OK;
}
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, Connection* connection) {
}
void InputDispatcher::onDispatchCycleFinishedLocked(
nsecs_t currentTime, 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->inputChannel = connection->inputChannel;
}
}
void InputDispatcher::onDispatchCycleANRLocked(
nsecs_t currentTime, 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->inputChannel = connection->inputChannel;
}
void InputDispatcher::onDispatchCycleBrokenLocked(
nsecs_t currentTime, Connection* connection) {
LOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!",
connection->getInputChannelName());
CommandEntry* commandEntry = postCommandLocked(
& InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible);
commandEntry->inputChannel = connection->inputChannel;
}
void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible(
CommandEntry* commandEntry) {
mLock.unlock();
mPolicy->notifyInputChannelBroken(commandEntry->inputChannel);
commandEntry->inputChannel.clear();
mLock.lock();
}
void InputDispatcher::doNotifyInputChannelANRLockedInterruptible(
CommandEntry* commandEntry) {
mLock.unlock();
mPolicy->notifyInputChannelANR(commandEntry->inputChannel);
commandEntry->inputChannel.clear();
mLock.lock();
}
void InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible(
CommandEntry* commandEntry) {
mLock.unlock();
mPolicy->notifyInputChannelRecoveredFromANR(commandEntry->inputChannel);
commandEntry->inputChannel.clear();
mLock.lock();
}
// --- InputDispatcher::Allocator ---
InputDispatcher::Allocator::Allocator() {
}
InputDispatcher::ConfigurationChangedEntry*
InputDispatcher::Allocator::obtainConfigurationChangedEntry() {
ConfigurationChangedEntry* entry = mConfigurationChangeEntryPool.alloc();
entry->refCount = 1;
entry->type = EventEntry::TYPE_CONFIGURATION_CHANGED;
entry->dispatchInProgress = false;
return entry;
}
InputDispatcher::KeyEntry* InputDispatcher::Allocator::obtainKeyEntry() {
KeyEntry* entry = mKeyEntryPool.alloc();
entry->refCount = 1;
entry->type = EventEntry::TYPE_KEY;
entry->dispatchInProgress = false;
return entry;
}
InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry() {
MotionEntry* entry = mMotionEntryPool.alloc();
entry->refCount = 1;
entry->type = EventEntry::TYPE_MOTION;
entry->firstSample.next = NULL;
entry->dispatchInProgress = false;
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, int32_t pointerCount, const PointerCoords* pointerCoords) {
MotionSample* sample = mMotionSamplePool.alloc();
sample->eventTime = eventTime;
for (int32_t i = 0; i < pointerCount; i++) {
sample->pointerCoords[i] = pointerCoords[i];
}
sample->next = NULL;
motionEntry->lastSample->next = sample;
motionEntry->lastSample = sample;
}
// --- 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();
}
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