replicant-frameworks_native/libs/ui/InputDispatcher.cpp
Jeff Brown e839a589bf Native input dispatch rewrite work in progress.
The old dispatch mechanism has been left in place and continues to
be used by default for now.  To enable native input dispatch,
edit the ENABLE_NATIVE_DISPATCH constant in WindowManagerPolicy.

Includes part of the new input event NDK API.  Some details TBD.

To wire up input dispatch, as the ViewRoot adds a window to the
window session it receives an InputChannel object as an output
argument.  The InputChannel encapsulates the file descriptors for a
shared memory region and two pipe end-points.  The ViewRoot then
provides the InputChannel to the InputQueue.  Behind the
scenes, InputQueue simply attaches handlers to the native PollLoop object
that underlies the MessageQueue.  This way MessageQueue doesn't need
to know anything about input dispatch per-se, it just exposes (in native
code) a PollLoop that other components can use to monitor file descriptor
state changes.

There can be zero or more targets for any given input event.  Each
input target is specified by its input channel and some parameters
including flags, an X/Y coordinate offset, and the dispatch timeout.
An input target can request either synchronous dispatch (for foreground apps)
or asynchronous dispatch (fire-and-forget for wallpapers and "outside"
targets).  Currently, finding the appropriate input targets for an event
requires a call back into the WindowManagerServer from native code.
In the future this will be refactored to avoid most of these callbacks
except as required to handle pending focus transitions.

End-to-end event dispatch mostly works!

To do: event injection, rate limiting, ANRs, testing, optimization, etc.

Change-Id: I8c36b2b9e0a2d27392040ecda0f51b636456de25
2010-06-13 17:42:16 -07:00

1316 lines
52 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 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 performance statistics.
#define DEBUG_PERFORMANCE_STATISTICS 1
#include <cutils/log.h>
#include <ui/InputDispatcher.h>
#include <stddef.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <poll.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<InputDispatchPolicyInterface>& 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;
}
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() {
bool allowKeyRepeat = mPolicy->allowKeyRepeat();
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 (! allowKeyRepeat) {
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) {
processKeyRepeatLocked(currentTime);
return; // dispatched once
} else {
if (mKeyRepeatState.nextRepeatTime < nextWakeupTime) {
nextWakeupTime = mKeyRepeatState.nextRepeatTime;
}
}
}
} else {
// Inbound queue has at least one entry. Dequeue it and begin dispatching.
// Note that we do not hold the lock for this process because dispatching may
// involve making many callbacks.
EventEntry* entry = mInboundQueue.dequeueAtHead();
switch (entry->type) {
case EventEntry::TYPE_CONFIGURATION_CHANGED: {
ConfigurationChangedEntry* typedEntry =
static_cast<ConfigurationChangedEntry*>(entry);
processConfigurationChangedLocked(currentTime, typedEntry);
mAllocator.releaseConfigurationChangedEntry(typedEntry);
break;
}
case EventEntry::TYPE_KEY: {
KeyEntry* typedEntry = static_cast<KeyEntry*>(entry);
processKeyLocked(currentTime, typedEntry);
mAllocator.releaseKeyEntry(typedEntry);
break;
}
case EventEntry::TYPE_MOTION: {
MotionEntry* typedEntry = static_cast<MotionEntry*>(entry);
processMotionLocked(currentTime, typedEntry);
mAllocator.releaseMotionEntry(typedEntry);
break;
}
default:
assert(false);
break;
}
return; // dispatched once
}
}
} // release lock
// 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);
}
void InputDispatcher::processConfigurationChangedLocked(nsecs_t currentTime,
ConfigurationChangedEntry* entry) {
#if DEBUG_OUTBOUND_EVENT_DETAILS
LOGD("processConfigurationChanged - eventTime=%lld, touchScreenConfig=%d, "
"keyboardConfig=%d, navigationConfig=%d", entry->eventTime,
entry->touchScreenConfig, entry->keyboardConfig, entry->navigationConfig);
#endif
mPolicy->notifyConfigurationChanged(entry->eventTime, entry->touchScreenConfig,
entry->keyboardConfig, entry->navigationConfig);
}
void InputDispatcher::processKeyLocked(nsecs_t currentTime, KeyEntry* entry) {
#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 + mPolicy->getKeyRepeatTimeout();
}
mKeyRepeatState.lastKeyEntry = entry;
entry->refCount += 1;
} else {
resetKeyRepeatLocked();
}
identifyInputTargetsAndDispatchKeyLocked(currentTime, entry);
}
void InputDispatcher::processKeyRepeatLocked(nsecs_t currentTime) {
// 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 + mPolicy->getKeyRepeatTimeout();
#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
identifyInputTargetsAndDispatchKeyLocked(currentTime, entry);
}
void InputDispatcher::processMotionLocked(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
identifyInputTargetsAndDispatchMotionLocked(currentTime, entry);
}
void InputDispatcher::identifyInputTargetsAndDispatchKeyLocked(
nsecs_t currentTime, KeyEntry* entry) {
#if DEBUG_DISPATCH_CYCLE
LOGD("identifyInputTargetsAndDispatchKey");
#endif
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);
dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
}
void InputDispatcher::identifyInputTargetsAndDispatchMotionLocked(
nsecs_t currentTime, MotionEntry* entry) {
#if DEBUG_DISPATCH_CYCLE
LOGD("identifyInputTargetsAndDispatchMotion");
#endif
mReusableMotionEvent.initialize(entry->deviceId, entry->nature, entry->action,
entry->edgeFlags, entry->metaState,
entry->firstSample.pointerCoords[0].x, entry->firstSample.pointerCoords[0].y,
entry->xPrecision, entry->yPrecision,
entry->downTime, entry->eventTime, entry->pointerCount, entry->pointerIds,
entry->firstSample.pointerCoords);
mCurrentInputTargets.clear();
mPolicy->getMotionEventTargets(& mReusableMotionEvent, entry->policyFlags,
mCurrentInputTargets);
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
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->status == Connection::STATUS_BROKEN ? "BROKEN" : "NOT RESPONDING");
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) {
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
if (connection->status == Connection::STATUS_BROKEN) {
return false;
}
// Clear the pending timeout.
connection->nextTimeoutTime = LONG_LONG_MAX;
// Clear the outbound queue.
while (! connection->outboundQueue.isEmpty()) {
DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead();
mAllocator.releaseDispatchEntry(dispatchEntry);
}
// Outbound queue is empty, deactivate the connection.
deactivateConnectionLocked(connection);
// Handle the case where the connection appears to be unrecoverably broken.
if (broken) {
connection->status = Connection::STATUS_BROKEN;
// Notify other system components.
onDispatchCycleBrokenLocked(currentTime, connection);
}
return true; /*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*/);
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*/);
return false; // remove the callback
}
d->finishDispatchCycleLocked(currentTime, connection.get());
return true;
} // release lock
}
void InputDispatcher::notifyConfigurationChanged(nsecs_t eventTime, int32_t touchScreenConfig,
int32_t keyboardConfig, int32_t navigationConfig) {
#if DEBUG_INBOUND_EVENT_DETAILS
LOGD("notifyConfigurationChanged - eventTime=%lld, touchScreenConfig=%d, "
"keyboardConfig=%d, navigationConfig=%d", eventTime,
touchScreenConfig, keyboardConfig, navigationConfig);
#endif
bool wasEmpty;
{ // acquire lock
AutoMutex _l(mLock);
ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry();
newEntry->eventTime = eventTime;
newEntry->touchScreenConfig = touchScreenConfig;
newEntry->keyboardConfig = keyboardConfig;
newEntry->navigationConfig = navigationConfig;
wasEmpty = mInboundQueue.isEmpty();
mInboundQueue.enqueueAtTail(newEntry);
} // release lock
if (wasEmpty) {
mPollLoop->wake();
}
}
void InputDispatcher::notifyLidSwitchChanged(nsecs_t eventTime, bool lidOpen) {
#if DEBUG_INBOUND_EVENT_DETAILS
LOGD("notifyLidSwitchChanged - eventTime=%lld, open=%s", eventTime,
lidOpen ? "true" : "false");
#endif
// Send lid switch notification immediately and synchronously.
mPolicy->notifyLidSwitchChanged(eventTime, lidOpen);
}
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 and exit.
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
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!).
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*/);
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) {
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);
} // release lock
mPollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this);
return OK;
}
status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) {
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*/);
} // 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));
// TODO tell framework
}
}
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));
// TODO tell framework
}
void InputDispatcher::onDispatchCycleBrokenLocked(nsecs_t currentTime, Connection* connection) {
LOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!",
connection->getInputChannelName());
// TODO tell framework
}
// --- InputDispatcher::Allocator ---
InputDispatcher::Allocator::Allocator() {
}
InputDispatcher::ConfigurationChangedEntry*
InputDispatcher::Allocator::obtainConfigurationChangedEntry() {
ConfigurationChangedEntry* entry = mConfigurationChangeEntryPool.alloc();
entry->refCount = 1;
entry->type = EventEntry::TYPE_CONFIGURATION_CHANGED;
return entry;
}
InputDispatcher::KeyEntry* InputDispatcher::Allocator::obtainKeyEntry() {
KeyEntry* entry = mKeyEntryPool.alloc();
entry->refCount = 1;
entry->type = EventEntry::TYPE_KEY;
return entry;
}
InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry() {
MotionEntry* entry = mMotionEntryPool.alloc();
entry->refCount = 1;
entry->type = EventEntry::TYPE_MOTION;
entry->firstSample.next = NULL;
return entry;
}
InputDispatcher::DispatchEntry* InputDispatcher::Allocator::obtainDispatchEntry(
EventEntry* eventEntry) {
DispatchEntry* entry = mDispatchEntryPool.alloc();
entry->eventEntry = eventEntry;
eventEntry->refCount += 1;
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) {
freeMotionSampleList(entry->firstSample.next);
mMotionEntryPool.free(entry);
} else {
assert(entry->refCount > 0);
}
}
void InputDispatcher::Allocator::releaseDispatchEntry(DispatchEntry* entry) {
releaseEventEntry(entry->eventEntry);
mDispatchEntryPool.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;
}
void InputDispatcher::Allocator::freeMotionSample(MotionSample* sample) {
mMotionSamplePool.free(sample);
}
void InputDispatcher::Allocator::freeMotionSampleList(MotionSample* head) {
while (head) {
MotionSample* next = head->next;
mMotionSamplePool.free(head);
head = next;
}
}
// --- 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();
}
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;
}
// --- InputDispatcherThread ---
InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) :
Thread(/*canCallJava*/ true), mDispatcher(dispatcher) {
}
InputDispatcherThread::~InputDispatcherThread() {
}
bool InputDispatcherThread::threadLoop() {
mDispatcher->dispatchOnce();
return true;
}
} // namespace android