// // 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 #include #include #include #include #include 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& 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(entry); processConfigurationChangedLockedInterruptible(currentTime, typedEntry); break; } case EventEntry::TYPE_KEY: { KeyEntry* typedEntry = static_cast(entry); processKeyLockedInterruptible(currentTime, typedEntry, keyRepeatTimeout); break; } case EventEntry::TYPE_MOTION: { MotionEntry* typedEntry = static_cast(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, 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); 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 = 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(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(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(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(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(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 = 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(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(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( 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) { #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 = 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) { #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 = 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(entry)); break; case EventEntry::TYPE_KEY: releaseKeyEntry(static_cast(entry)); break; case EventEntry::TYPE_MOTION: releaseMotionEntry(static_cast(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) : 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& dispatcher) : Thread(/*canCallJava*/ true), mDispatcher(dispatcher) { } InputDispatcherThread::~InputDispatcherThread() { } bool InputDispatcherThread::threadLoop() { mDispatcher->dispatchOnce(); return true; } } // namespace android