3c5d125ed7
This factors out the boiler-plate code from the sample app to a common glue code that can be used for everyone writing this style of app: a dedicated app thread that takes care of waiting for events and processing them. As part of doing this, ALooper has a new facility to allow registration of fds that cause ALooper_pollOnce() to return the fd that has data, allowing the app to drive the loop without callbacks. Hopefully this makes some people feel better. :) Also do some other cleanup of the ALooper API, plus some actual documentation. Change-Id: Ic53bd56bdf627e3ba28a3c093faa06a92be522b8
1712 lines
66 KiB
C++
1712 lines
66 KiB
C++
//
|
|
// Copyright 2010 The Android Open Source Project
|
|
//
|
|
// The input dispatcher.
|
|
//
|
|
#define LOG_TAG "InputDispatcher"
|
|
|
|
//#define LOG_NDEBUG 0
|
|
|
|
// Log detailed debug messages about each inbound event notification to the dispatcher.
|
|
#define DEBUG_INBOUND_EVENT_DETAILS 0
|
|
|
|
// Log detailed debug messages about each outbound event processed by the dispatcher.
|
|
#define DEBUG_OUTBOUND_EVENT_DETAILS 0
|
|
|
|
// Log debug messages about batching.
|
|
#define DEBUG_BATCHING 0
|
|
|
|
// Log debug messages about the dispatch cycle.
|
|
#define DEBUG_DISPATCH_CYCLE 0
|
|
|
|
// Log debug messages about registrations.
|
|
#define DEBUG_REGISTRATION 0
|
|
|
|
// Log debug messages about performance statistics.
|
|
#define DEBUG_PERFORMANCE_STATISTICS 0
|
|
|
|
// Log debug messages about input event injection.
|
|
#define DEBUG_INJECTION 0
|
|
|
|
#include <cutils/log.h>
|
|
#include <ui/InputDispatcher.h>
|
|
|
|
#include <stddef.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
|
|
namespace android {
|
|
|
|
// TODO, this needs to be somewhere else, perhaps in the policy
|
|
static inline bool isMovementKey(int32_t keyCode) {
|
|
return keyCode == KEYCODE_DPAD_UP
|
|
|| keyCode == KEYCODE_DPAD_DOWN
|
|
|| keyCode == KEYCODE_DPAD_LEFT
|
|
|| keyCode == KEYCODE_DPAD_RIGHT;
|
|
}
|
|
|
|
static inline nsecs_t now() {
|
|
return systemTime(SYSTEM_TIME_MONOTONIC);
|
|
}
|
|
|
|
// --- InputDispatcher ---
|
|
|
|
InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
|
|
mPolicy(policy) {
|
|
mPollLoop = new PollLoop(false);
|
|
|
|
mInboundQueue.head.refCount = -1;
|
|
mInboundQueue.head.type = EventEntry::TYPE_SENTINEL;
|
|
mInboundQueue.head.eventTime = LONG_LONG_MIN;
|
|
|
|
mInboundQueue.tail.refCount = -1;
|
|
mInboundQueue.tail.type = EventEntry::TYPE_SENTINEL;
|
|
mInboundQueue.tail.eventTime = LONG_LONG_MAX;
|
|
|
|
mKeyRepeatState.lastKeyEntry = NULL;
|
|
|
|
mCurrentInputTargetsValid = false;
|
|
}
|
|
|
|
InputDispatcher::~InputDispatcher() {
|
|
resetKeyRepeatLocked();
|
|
|
|
while (mConnectionsByReceiveFd.size() != 0) {
|
|
unregisterInputChannel(mConnectionsByReceiveFd.valueAt(0)->inputChannel);
|
|
}
|
|
|
|
for (EventEntry* entry = mInboundQueue.head.next; entry != & mInboundQueue.tail; ) {
|
|
EventEntry* next = entry->next;
|
|
mAllocator.releaseEventEntry(next);
|
|
entry = next;
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::dispatchOnce() {
|
|
nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout();
|
|
|
|
bool skipPoll = false;
|
|
nsecs_t currentTime;
|
|
nsecs_t nextWakeupTime = LONG_LONG_MAX;
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
currentTime = now();
|
|
|
|
// Reset the key repeat timer whenever we disallow key events, even if the next event
|
|
// is not a key. This is to ensure that we abort a key repeat if the device is just coming
|
|
// out of sleep.
|
|
// XXX we should handle resetting input state coming out of sleep more generally elsewhere
|
|
if (keyRepeatTimeout < 0) {
|
|
resetKeyRepeatLocked();
|
|
}
|
|
|
|
// Detect and process timeouts for all connections and determine if there are any
|
|
// synchronous event dispatches pending. This step is entirely non-interruptible.
|
|
bool hasPendingSyncTarget = false;
|
|
size_t activeConnectionCount = mActiveConnections.size();
|
|
for (size_t i = 0; i < activeConnectionCount; i++) {
|
|
Connection* connection = mActiveConnections.itemAt(i);
|
|
|
|
if (connection->hasPendingSyncTarget()) {
|
|
hasPendingSyncTarget = true;
|
|
}
|
|
|
|
nsecs_t connectionTimeoutTime = connection->nextTimeoutTime;
|
|
if (connectionTimeoutTime <= currentTime) {
|
|
mTimedOutConnections.add(connection);
|
|
} else if (connectionTimeoutTime < nextWakeupTime) {
|
|
nextWakeupTime = connectionTimeoutTime;
|
|
}
|
|
}
|
|
|
|
size_t timedOutConnectionCount = mTimedOutConnections.size();
|
|
for (size_t i = 0; i < timedOutConnectionCount; i++) {
|
|
Connection* connection = mTimedOutConnections.itemAt(i);
|
|
timeoutDispatchCycleLocked(currentTime, connection);
|
|
skipPoll = true;
|
|
}
|
|
mTimedOutConnections.clear();
|
|
|
|
// If we don't have a pending sync target, then we can begin delivering a new event.
|
|
// (Otherwise we wait for dispatch to complete for that target.)
|
|
if (! hasPendingSyncTarget) {
|
|
if (mInboundQueue.isEmpty()) {
|
|
if (mKeyRepeatState.lastKeyEntry) {
|
|
if (currentTime >= mKeyRepeatState.nextRepeatTime) {
|
|
processKeyRepeatLockedInterruptible(currentTime, keyRepeatTimeout);
|
|
skipPoll = true;
|
|
} else {
|
|
if (mKeyRepeatState.nextRepeatTime < nextWakeupTime) {
|
|
nextWakeupTime = mKeyRepeatState.nextRepeatTime;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Inbound queue has at least one entry.
|
|
// Start processing it but leave it on the queue until later so that the
|
|
// input reader can keep appending samples onto a motion event between the
|
|
// time we started processing it and the time we finally enqueue dispatch
|
|
// entries for it.
|
|
EventEntry* entry = mInboundQueue.head.next;
|
|
|
|
switch (entry->type) {
|
|
case EventEntry::TYPE_CONFIGURATION_CHANGED: {
|
|
ConfigurationChangedEntry* typedEntry =
|
|
static_cast<ConfigurationChangedEntry*>(entry);
|
|
processConfigurationChangedLockedInterruptible(currentTime, typedEntry);
|
|
break;
|
|
}
|
|
|
|
case EventEntry::TYPE_KEY: {
|
|
KeyEntry* typedEntry = static_cast<KeyEntry*>(entry);
|
|
processKeyLockedInterruptible(currentTime, typedEntry, keyRepeatTimeout);
|
|
break;
|
|
}
|
|
|
|
case EventEntry::TYPE_MOTION: {
|
|
MotionEntry* typedEntry = static_cast<MotionEntry*>(entry);
|
|
processMotionLockedInterruptible(currentTime, typedEntry);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
// Dequeue and release the event entry that we just processed.
|
|
mInboundQueue.dequeue(entry);
|
|
mAllocator.releaseEventEntry(entry);
|
|
skipPoll = true;
|
|
}
|
|
}
|
|
|
|
// Run any deferred commands.
|
|
skipPoll |= runCommandsLockedInterruptible();
|
|
|
|
// Wake up synchronization waiters, if needed.
|
|
if (isFullySynchronizedLocked()) {
|
|
mFullySynchronizedCondition.broadcast();
|
|
}
|
|
} // 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'
|
|
|
|
commandEntry->connection.clear();
|
|
mAllocator.releaseCommandEntry(commandEntry);
|
|
} while (! mCommandQueue.isEmpty());
|
|
return true;
|
|
}
|
|
|
|
InputDispatcher::CommandEntry* InputDispatcher::postCommandLocked(Command command) {
|
|
CommandEntry* commandEntry = mAllocator.obtainCommandEntry(command);
|
|
mCommandQueue.enqueueAtTail(commandEntry);
|
|
return commandEntry;
|
|
}
|
|
|
|
void InputDispatcher::processConfigurationChangedLockedInterruptible(
|
|
nsecs_t currentTime, ConfigurationChangedEntry* entry) {
|
|
#if DEBUG_OUTBOUND_EVENT_DETAILS
|
|
LOGD("processConfigurationChanged - eventTime=%lld", entry->eventTime);
|
|
#endif
|
|
|
|
// Reset key repeating in case a keyboard device was added or removed or something.
|
|
resetKeyRepeatLocked();
|
|
|
|
mLock.unlock();
|
|
|
|
mPolicy->notifyConfigurationChanged(entry->eventTime);
|
|
|
|
mLock.lock();
|
|
}
|
|
|
|
void InputDispatcher::processKeyLockedInterruptible(
|
|
nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout) {
|
|
#if DEBUG_OUTBOUND_EVENT_DETAILS
|
|
LOGD("processKey - eventTime=%lld, deviceId=0x%x, 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
|
|
|
|
if (entry->action == KEY_EVENT_ACTION_DOWN && ! entry->isInjected()) {
|
|
if (mKeyRepeatState.lastKeyEntry
|
|
&& mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) {
|
|
// We have seen two identical key downs in a row which indicates that the device
|
|
// driver is automatically generating key repeats itself. We take note of the
|
|
// repeat here, but we disable our own next key repeat timer since it is clear that
|
|
// we will not need to synthesize key repeats ourselves.
|
|
entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1;
|
|
resetKeyRepeatLocked();
|
|
mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves
|
|
} else {
|
|
// Not a repeat. Save key down state in case we do see a repeat later.
|
|
resetKeyRepeatLocked();
|
|
mKeyRepeatState.nextRepeatTime = entry->eventTime + keyRepeatTimeout;
|
|
}
|
|
mKeyRepeatState.lastKeyEntry = entry;
|
|
entry->refCount += 1;
|
|
} else {
|
|
resetKeyRepeatLocked();
|
|
}
|
|
|
|
identifyInputTargetsAndDispatchKeyLockedInterruptible(currentTime, entry);
|
|
}
|
|
|
|
void InputDispatcher::processKeyRepeatLockedInterruptible(
|
|
nsecs_t currentTime, nsecs_t keyRepeatTimeout) {
|
|
KeyEntry* entry = mKeyRepeatState.lastKeyEntry;
|
|
|
|
// Search the inbound queue for a key up corresponding to this device.
|
|
// It doesn't make sense to generate a key repeat event if the key is already up.
|
|
for (EventEntry* queuedEntry = mInboundQueue.head.next;
|
|
queuedEntry != & mInboundQueue.tail; queuedEntry = entry->next) {
|
|
if (queuedEntry->type == EventEntry::TYPE_KEY) {
|
|
KeyEntry* queuedKeyEntry = static_cast<KeyEntry*>(queuedEntry);
|
|
if (queuedKeyEntry->deviceId == entry->deviceId
|
|
&& entry->action == KEY_EVENT_ACTION_UP) {
|
|
resetKeyRepeatLocked();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Synthesize a key repeat after the repeat timeout expired.
|
|
// Reuse the repeated key entry if it is otherwise unreferenced.
|
|
uint32_t policyFlags = entry->policyFlags & POLICY_FLAG_RAW_MASK;
|
|
if (entry->refCount == 1) {
|
|
entry->eventTime = currentTime;
|
|
entry->policyFlags = policyFlags;
|
|
entry->repeatCount += 1;
|
|
} else {
|
|
KeyEntry* newEntry = mAllocator.obtainKeyEntry(currentTime,
|
|
entry->deviceId, entry->nature, policyFlags,
|
|
entry->action, entry->flags, entry->keyCode, entry->scanCode,
|
|
entry->metaState, entry->repeatCount + 1, entry->downTime);
|
|
|
|
mKeyRepeatState.lastKeyEntry = newEntry;
|
|
mAllocator.releaseKeyEntry(entry);
|
|
|
|
entry = newEntry;
|
|
}
|
|
|
|
if (entry->repeatCount == 1) {
|
|
entry->flags |= KEY_EVENT_FLAG_LONG_PRESS;
|
|
}
|
|
|
|
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();
|
|
int32_t injectionResult = mPolicy->waitForKeyEventTargets(& mReusableKeyEvent,
|
|
entry->policyFlags, entry->injectorPid, entry->injectorUid,
|
|
mCurrentInputTargets);
|
|
|
|
mLock.lock();
|
|
mCurrentInputTargetsValid = true;
|
|
|
|
setInjectionResultLocked(entry, injectionResult);
|
|
|
|
if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
|
|
dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::identifyInputTargetsAndDispatchMotionLockedInterruptible(
|
|
nsecs_t currentTime, MotionEntry* entry) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("identifyInputTargetsAndDispatchMotion");
|
|
#endif
|
|
|
|
entry->dispatchInProgress = true;
|
|
mCurrentInputTargetsValid = false;
|
|
mLock.unlock();
|
|
|
|
mReusableMotionEvent.initialize(entry->deviceId, entry->nature, entry->action,
|
|
entry->edgeFlags, entry->metaState,
|
|
0, 0, entry->xPrecision, entry->yPrecision,
|
|
entry->downTime, entry->eventTime, entry->pointerCount, entry->pointerIds,
|
|
entry->firstSample.pointerCoords);
|
|
|
|
mCurrentInputTargets.clear();
|
|
int32_t injectionResult = mPolicy->waitForMotionEventTargets(& mReusableMotionEvent,
|
|
entry->policyFlags, entry->injectorPid, entry->injectorUid,
|
|
mCurrentInputTargets);
|
|
|
|
mLock.lock();
|
|
mCurrentInputTargetsValid = true;
|
|
|
|
setInjectionResultLocked(entry, injectionResult);
|
|
|
|
if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
|
|
dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime,
|
|
EventEntry* eventEntry, bool resumeWithAppendedMotionSample) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("dispatchEventToCurrentInputTargets - "
|
|
"resumeWithAppendedMotionSample=%s",
|
|
resumeWithAppendedMotionSample ? "true" : "false");
|
|
#endif
|
|
|
|
assert(eventEntry->dispatchInProgress); // should already have been set to true
|
|
|
|
for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {
|
|
const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i);
|
|
|
|
ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(
|
|
inputTarget.inputChannel->getReceivePipeFd());
|
|
if (connectionIndex >= 0) {
|
|
sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
|
|
prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget,
|
|
resumeWithAppendedMotionSample);
|
|
} else {
|
|
LOGW("Framework requested delivery of an input event to channel '%s' but it "
|
|
"is not registered with the input dispatcher.",
|
|
inputTarget.inputChannel->getName().string());
|
|
}
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
|
|
const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
|
|
bool resumeWithAppendedMotionSample) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("channel '%s' ~ prepareDispatchCycle - flags=%d, timeout=%lldns, "
|
|
"xOffset=%f, yOffset=%f, resumeWithAppendedMotionSample=%s",
|
|
connection->getInputChannelName(), inputTarget->flags, inputTarget->timeout,
|
|
inputTarget->xOffset, inputTarget->yOffset,
|
|
resumeWithAppendedMotionSample ? "true" : "false");
|
|
#endif
|
|
|
|
// Skip this event if the connection status is not normal.
|
|
// We don't want to queue outbound events at all if the connection is broken or
|
|
// not responding.
|
|
if (connection->status != Connection::STATUS_NORMAL) {
|
|
LOGV("channel '%s' ~ Dropping event because the channel status is %s",
|
|
connection->getStatusLabel());
|
|
return;
|
|
}
|
|
|
|
// Resume the dispatch cycle with a freshly appended motion sample.
|
|
// First we check that the last dispatch entry in the outbound queue is for the same
|
|
// motion event to which we appended the motion sample. If we find such a dispatch
|
|
// entry, and if it is currently in progress then we try to stream the new sample.
|
|
bool wasEmpty = connection->outboundQueue.isEmpty();
|
|
|
|
if (! wasEmpty && resumeWithAppendedMotionSample) {
|
|
DispatchEntry* motionEventDispatchEntry =
|
|
connection->findQueuedDispatchEntryForEvent(eventEntry);
|
|
if (motionEventDispatchEntry) {
|
|
// If the dispatch entry is not in progress, then we must be busy dispatching an
|
|
// earlier event. Not a problem, the motion event is on the outbound queue and will
|
|
// be dispatched later.
|
|
if (! motionEventDispatchEntry->inProgress) {
|
|
#if DEBUG_BATCHING
|
|
LOGD("channel '%s' ~ Not streaming because the motion event has "
|
|
"not yet been dispatched. "
|
|
"(Waiting for earlier events to be consumed.)",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// If the dispatch entry is in progress but it already has a tail of pending
|
|
// motion samples, then it must mean that the shared memory buffer filled up.
|
|
// Not a problem, when this dispatch cycle is finished, we will eventually start
|
|
// a new dispatch cycle to process the tail and that tail includes the newly
|
|
// appended motion sample.
|
|
if (motionEventDispatchEntry->tailMotionSample) {
|
|
#if DEBUG_BATCHING
|
|
LOGD("channel '%s' ~ Not streaming because no new samples can "
|
|
"be appended to the motion event in this dispatch cycle. "
|
|
"(Waiting for next dispatch cycle to start.)",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// The dispatch entry is in progress and is still potentially open for streaming.
|
|
// Try to stream the new motion sample. This might fail if the consumer has already
|
|
// consumed the motion event (or if the channel is broken).
|
|
MotionSample* appendedMotionSample = static_cast<MotionEntry*>(eventEntry)->lastSample;
|
|
status_t status = connection->inputPublisher.appendMotionSample(
|
|
appendedMotionSample->eventTime, appendedMotionSample->pointerCoords);
|
|
if (status == OK) {
|
|
#if DEBUG_BATCHING
|
|
LOGD("channel '%s' ~ Successfully streamed new motion sample.",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
#if DEBUG_BATCHING
|
|
if (status == NO_MEMORY) {
|
|
LOGD("channel '%s' ~ Could not append motion sample to currently "
|
|
"dispatched move event because the shared memory buffer is full. "
|
|
"(Waiting for next dispatch cycle to start.)",
|
|
connection->getInputChannelName());
|
|
} else if (status == status_t(FAILED_TRANSACTION)) {
|
|
LOGD("channel '%s' ~ Could not append motion sample to currently "
|
|
"dispatched move event because the event has already been consumed. "
|
|
"(Waiting for next dispatch cycle to start.)",
|
|
connection->getInputChannelName());
|
|
} else {
|
|
LOGD("channel '%s' ~ Could not append motion sample to currently "
|
|
"dispatched move event due to an error, status=%d. "
|
|
"(Waiting for next dispatch cycle to start.)",
|
|
connection->getInputChannelName(), status);
|
|
}
|
|
#endif
|
|
// Failed to stream. Start a new tail of pending motion samples to dispatch
|
|
// in the next cycle.
|
|
motionEventDispatchEntry->tailMotionSample = appendedMotionSample;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// This is a new event.
|
|
// Enqueue a new dispatch entry onto the outbound queue for this connection.
|
|
DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry); // increments ref
|
|
dispatchEntry->targetFlags = inputTarget->flags;
|
|
dispatchEntry->xOffset = inputTarget->xOffset;
|
|
dispatchEntry->yOffset = inputTarget->yOffset;
|
|
dispatchEntry->timeout = inputTarget->timeout;
|
|
dispatchEntry->inProgress = false;
|
|
dispatchEntry->headMotionSample = NULL;
|
|
dispatchEntry->tailMotionSample = NULL;
|
|
|
|
// Handle the case where we could not stream a new motion sample because the consumer has
|
|
// already consumed the motion event (otherwise the corresponding dispatch entry would
|
|
// still be in the outbound queue for this connection). We set the head motion sample
|
|
// to the list starting with the newly appended motion sample.
|
|
if (resumeWithAppendedMotionSample) {
|
|
#if DEBUG_BATCHING
|
|
LOGD("channel '%s' ~ Preparing a new dispatch cycle for additional motion samples "
|
|
"that cannot be streamed because the motion event has already been consumed.",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
MotionSample* appendedMotionSample = static_cast<MotionEntry*>(eventEntry)->lastSample;
|
|
dispatchEntry->headMotionSample = appendedMotionSample;
|
|
}
|
|
|
|
// Enqueue the dispatch entry.
|
|
connection->outboundQueue.enqueueAtTail(dispatchEntry);
|
|
|
|
// If the outbound queue was previously empty, start the dispatch cycle going.
|
|
if (wasEmpty) {
|
|
activateConnectionLocked(connection.get());
|
|
startDispatchCycleLocked(currentTime, connection);
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
|
|
const sp<Connection>& connection) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("channel '%s' ~ startDispatchCycle",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
|
|
assert(connection->status == Connection::STATUS_NORMAL);
|
|
assert(! connection->outboundQueue.isEmpty());
|
|
|
|
DispatchEntry* dispatchEntry = connection->outboundQueue.head.next;
|
|
assert(! dispatchEntry->inProgress);
|
|
|
|
// TODO throttle successive ACTION_MOVE motion events for the same device
|
|
// possible implementation could set a brief poll timeout here and resume starting the
|
|
// dispatch cycle when elapsed
|
|
|
|
// Publish the event.
|
|
status_t status;
|
|
switch (dispatchEntry->eventEntry->type) {
|
|
case EventEntry::TYPE_KEY: {
|
|
KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry);
|
|
|
|
// Apply target flags.
|
|
int32_t action = keyEntry->action;
|
|
int32_t flags = keyEntry->flags;
|
|
if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) {
|
|
flags |= 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->setNextTimeoutTime(currentTime, timeout);
|
|
|
|
// Notify other system components.
|
|
onDispatchCycleStartedLocked(currentTime, connection);
|
|
}
|
|
|
|
void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
|
|
const sp<Connection>& connection) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("channel '%s' ~ finishDispatchCycle - %01.1fms since event, "
|
|
"%01.1fms since dispatch",
|
|
connection->getInputChannelName(),
|
|
connection->getEventLatencyMillis(currentTime),
|
|
connection->getDispatchLatencyMillis(currentTime));
|
|
#endif
|
|
|
|
if (connection->status == Connection::STATUS_BROKEN
|
|
|| connection->status == Connection::STATUS_ZOMBIE) {
|
|
return;
|
|
}
|
|
|
|
// Clear the pending timeout.
|
|
connection->nextTimeoutTime = LONG_LONG_MAX;
|
|
|
|
if (connection->status == Connection::STATUS_NOT_RESPONDING) {
|
|
// Recovering from an ANR.
|
|
connection->status = Connection::STATUS_NORMAL;
|
|
|
|
// Notify other system components.
|
|
onDispatchCycleFinishedLocked(currentTime, connection, true /*recoveredFromANR*/);
|
|
} else {
|
|
// Normal finish. Not much to do here.
|
|
|
|
// Notify other system components.
|
|
onDispatchCycleFinishedLocked(currentTime, connection, false /*recoveredFromANR*/);
|
|
}
|
|
|
|
// Reset the publisher since the event has been consumed.
|
|
// We do this now so that the publisher can release some of its internal resources
|
|
// while waiting for the next dispatch cycle to begin.
|
|
status_t status = connection->inputPublisher.reset();
|
|
if (status) {
|
|
LOGE("channel '%s' ~ Could not reset publisher, status=%d",
|
|
connection->getInputChannelName(), status);
|
|
abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
|
|
return;
|
|
}
|
|
|
|
// Start the next dispatch cycle for this connection.
|
|
while (! connection->outboundQueue.isEmpty()) {
|
|
DispatchEntry* dispatchEntry = connection->outboundQueue.head.next;
|
|
if (dispatchEntry->inProgress) {
|
|
// Finish or resume current event in progress.
|
|
if (dispatchEntry->tailMotionSample) {
|
|
// We have a tail of undispatched motion samples.
|
|
// Reuse the same DispatchEntry and start a new cycle.
|
|
dispatchEntry->inProgress = false;
|
|
dispatchEntry->headMotionSample = dispatchEntry->tailMotionSample;
|
|
dispatchEntry->tailMotionSample = NULL;
|
|
startDispatchCycleLocked(currentTime, connection);
|
|
return;
|
|
}
|
|
// Finished.
|
|
connection->outboundQueue.dequeueAtHead();
|
|
mAllocator.releaseDispatchEntry(dispatchEntry);
|
|
} else {
|
|
// If the head is not in progress, then we must have already dequeued the in
|
|
// progress event, which means we actually aborted it (due to ANR).
|
|
// So just start the next event for this connection.
|
|
startDispatchCycleLocked(currentTime, connection);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Outbound queue is empty, deactivate the connection.
|
|
deactivateConnectionLocked(connection.get());
|
|
}
|
|
|
|
void InputDispatcher::timeoutDispatchCycleLocked(nsecs_t currentTime,
|
|
const sp<Connection>& connection) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("channel '%s' ~ timeoutDispatchCycle",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
|
|
if (connection->status != Connection::STATUS_NORMAL) {
|
|
return;
|
|
}
|
|
|
|
// Enter the not responding state.
|
|
connection->status = Connection::STATUS_NOT_RESPONDING;
|
|
connection->lastANRTime = currentTime;
|
|
|
|
// Notify other system components.
|
|
// This enqueues a command which will eventually either call
|
|
// resumeAfterTimeoutDispatchCycleLocked or abortDispatchCycleLocked.
|
|
onDispatchCycleANRLocked(currentTime, connection);
|
|
}
|
|
|
|
void InputDispatcher::resumeAfterTimeoutDispatchCycleLocked(nsecs_t currentTime,
|
|
const sp<Connection>& connection, nsecs_t newTimeout) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("channel '%s' ~ resumeAfterTimeoutDispatchCycleLocked",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
|
|
if (connection->status != Connection::STATUS_NOT_RESPONDING) {
|
|
return;
|
|
}
|
|
|
|
// Resume normal dispatch.
|
|
connection->status = Connection::STATUS_NORMAL;
|
|
connection->setNextTimeoutTime(currentTime, newTimeout);
|
|
}
|
|
|
|
void InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime,
|
|
const sp<Connection>& connection, bool broken) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("channel '%s' ~ abortDispatchCycle - broken=%s",
|
|
connection->getInputChannelName(), broken ? "true" : "false");
|
|
#endif
|
|
|
|
// Clear the pending timeout.
|
|
connection->nextTimeoutTime = LONG_LONG_MAX;
|
|
|
|
// Clear the outbound queue.
|
|
if (! connection->outboundQueue.isEmpty()) {
|
|
do {
|
|
DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead();
|
|
mAllocator.releaseDispatchEntry(dispatchEntry);
|
|
} while (! connection->outboundQueue.isEmpty());
|
|
|
|
deactivateConnectionLocked(connection.get());
|
|
}
|
|
|
|
// Handle the case where the connection appears to be unrecoverably broken.
|
|
// Ignore already broken or zombie connections.
|
|
if (broken) {
|
|
if (connection->status == Connection::STATUS_NORMAL
|
|
|| connection->status == Connection::STATUS_NOT_RESPONDING) {
|
|
connection->status = Connection::STATUS_BROKEN;
|
|
|
|
// Notify other system components.
|
|
onDispatchCycleBrokenLocked(currentTime, connection);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) {
|
|
InputDispatcher* d = static_cast<InputDispatcher*>(data);
|
|
|
|
{ // acquire lock
|
|
AutoMutex _l(d->mLock);
|
|
|
|
ssize_t connectionIndex = d->mConnectionsByReceiveFd.indexOfKey(receiveFd);
|
|
if (connectionIndex < 0) {
|
|
LOGE("Received spurious receive callback for unknown input channel. "
|
|
"fd=%d, events=0x%x", receiveFd, events);
|
|
return false; // remove the callback
|
|
}
|
|
|
|
nsecs_t currentTime = now();
|
|
|
|
sp<Connection> connection = d->mConnectionsByReceiveFd.valueAt(connectionIndex);
|
|
if (events & (POLLERR | POLLHUP | POLLNVAL)) {
|
|
LOGE("channel '%s' ~ Consumer closed input channel or an error occurred. "
|
|
"events=0x%x", connection->getInputChannelName(), events);
|
|
d->abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
|
|
d->runCommandsLockedInterruptible();
|
|
return false; // remove the callback
|
|
}
|
|
|
|
if (! (events & POLLIN)) {
|
|
LOGW("channel '%s' ~ Received spurious callback for unhandled poll event. "
|
|
"events=0x%x", connection->getInputChannelName(), events);
|
|
return true;
|
|
}
|
|
|
|
status_t status = connection->inputPublisher.receiveFinishedSignal();
|
|
if (status) {
|
|
LOGE("channel '%s' ~ Failed to receive finished signal. status=%d",
|
|
connection->getInputChannelName(), status);
|
|
d->abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
|
|
d->runCommandsLockedInterruptible();
|
|
return false; // remove the callback
|
|
}
|
|
|
|
d->finishDispatchCycleLocked(currentTime, connection);
|
|
d->runCommandsLockedInterruptible();
|
|
return true;
|
|
} // release lock
|
|
}
|
|
|
|
void InputDispatcher::notifyConfigurationChanged(nsecs_t eventTime) {
|
|
#if DEBUG_INBOUND_EVENT_DETAILS
|
|
LOGD("notifyConfigurationChanged - eventTime=%lld", eventTime);
|
|
#endif
|
|
|
|
bool wasEmpty;
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
|
|
ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry(eventTime);
|
|
|
|
wasEmpty = mInboundQueue.isEmpty();
|
|
mInboundQueue.enqueueAtTail(newEntry);
|
|
} // release lock
|
|
|
|
if (wasEmpty) {
|
|
mPollLoop->wake();
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::notifyAppSwitchComing(nsecs_t eventTime) {
|
|
#if DEBUG_INBOUND_EVENT_DETAILS
|
|
LOGD("notifyAppSwitchComing - eventTime=%lld", eventTime);
|
|
#endif
|
|
|
|
// Remove movement keys from the queue from most recent to least recent, stopping at the
|
|
// first non-movement key.
|
|
// TODO: Include a detailed description of why we do this...
|
|
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
|
|
for (EventEntry* entry = mInboundQueue.tail.prev; entry != & mInboundQueue.head; ) {
|
|
EventEntry* prev = entry->prev;
|
|
|
|
if (entry->type == EventEntry::TYPE_KEY) {
|
|
KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
|
|
if (isMovementKey(keyEntry->keyCode)) {
|
|
LOGV("Dropping movement key during app switch: keyCode=%d, action=%d",
|
|
keyEntry->keyCode, keyEntry->action);
|
|
mInboundQueue.dequeue(keyEntry);
|
|
|
|
setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
|
|
|
|
mAllocator.releaseKeyEntry(keyEntry);
|
|
} else {
|
|
// stop at last non-movement key
|
|
break;
|
|
}
|
|
}
|
|
|
|
entry = prev;
|
|
}
|
|
} // release lock
|
|
}
|
|
|
|
void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t 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);
|
|
|
|
int32_t repeatCount = 0;
|
|
KeyEntry* newEntry = mAllocator.obtainKeyEntry(eventTime,
|
|
deviceId, nature, policyFlags, action, flags, keyCode, scanCode,
|
|
metaState, repeatCount, downTime);
|
|
|
|
wasEmpty = mInboundQueue.isEmpty();
|
|
mInboundQueue.enqueueAtTail(newEntry);
|
|
} // release lock
|
|
|
|
if (wasEmpty) {
|
|
mPollLoop->wake();
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t 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
|
|
|| motionEntry->isInjected()) {
|
|
// Last motion event in the queue for this device is not compatible for
|
|
// appending new samples. Stop here.
|
|
goto NoBatchingOrStreaming;
|
|
}
|
|
|
|
// The last motion event is a move and is compatible for appending.
|
|
// Do the batching magic.
|
|
mAllocator.appendMotionSample(motionEntry, eventTime, pointerCoords);
|
|
#if DEBUG_BATCHING
|
|
LOGD("Appended motion sample onto batch for most recent "
|
|
"motion event for this device in the inbound queue.");
|
|
#endif
|
|
|
|
// Sanity check for special case because dispatch is interruptible.
|
|
// The dispatch logic is partially interruptible and releases its lock while
|
|
// identifying targets. However, as soon as the targets have been identified,
|
|
// the dispatcher proceeds to write a dispatch entry into all relevant outbound
|
|
// queues and then promptly removes the motion entry from the queue.
|
|
//
|
|
// Consequently, we should never observe the case where the inbound queue contains
|
|
// an in-progress motion entry unless the current input targets are invalid
|
|
// (currently being computed). Check for this!
|
|
assert(! (motionEntry->dispatchInProgress && mCurrentInputTargetsValid));
|
|
|
|
return; // done!
|
|
}
|
|
|
|
// STREAMING CASE
|
|
//
|
|
// There is no pending motion event (of any kind) for this device in the inbound queue.
|
|
// Search the outbound queues for a synchronously dispatched motion event for this
|
|
// device. If found, then we append the new sample to that event and then try to
|
|
// push it out to all current targets. It is possible that some targets will already
|
|
// have consumed the motion event. This case is automatically handled by the
|
|
// logic in prepareDispatchCycleLocked by tracking where resumption takes place.
|
|
//
|
|
// The reason we look for a synchronously dispatched motion event is because we
|
|
// want to be sure that no other motion events have been dispatched since the move.
|
|
// It's also convenient because it means that the input targets are still valid.
|
|
// This code could be improved to support streaming of asynchronously dispatched
|
|
// motion events (which might be significantly more efficient) but it may become
|
|
// a little more complicated as a result.
|
|
//
|
|
// Note: This code crucially depends on the invariant that an outbound queue always
|
|
// contains at most one synchronous event and it is always last (but it might
|
|
// not be first!).
|
|
if (mCurrentInputTargetsValid) {
|
|
for (size_t i = 0; i < mActiveConnections.size(); i++) {
|
|
Connection* connection = mActiveConnections.itemAt(i);
|
|
if (! connection->outboundQueue.isEmpty()) {
|
|
DispatchEntry* dispatchEntry = connection->outboundQueue.tail.prev;
|
|
if (dispatchEntry->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
|
|
|| syncedMotionEntry->isInjected()) {
|
|
goto NoBatchingOrStreaming;
|
|
}
|
|
|
|
// Found synced move entry. Append sample and resume dispatch.
|
|
mAllocator.appendMotionSample(syncedMotionEntry, eventTime,
|
|
pointerCoords);
|
|
#if DEBUG_BATCHING
|
|
LOGD("Appended motion sample onto batch for most recent synchronously "
|
|
"dispatched motion event for this device in the outbound queues.");
|
|
#endif
|
|
nsecs_t currentTime = now();
|
|
dispatchEventToCurrentInputTargetsLocked(currentTime, syncedMotionEntry,
|
|
true /*resumeWithAppendedMotionSample*/);
|
|
|
|
runCommandsLockedInterruptible();
|
|
return; // done!
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NoBatchingOrStreaming:;
|
|
}
|
|
|
|
// Just enqueue a new motion event.
|
|
MotionEntry* newEntry = mAllocator.obtainMotionEntry(eventTime,
|
|
deviceId, nature, policyFlags, action, metaState, edgeFlags,
|
|
xPrecision, yPrecision, downTime,
|
|
pointerCount, pointerIds, pointerCoords);
|
|
|
|
wasEmpty = mInboundQueue.isEmpty();
|
|
mInboundQueue.enqueueAtTail(newEntry);
|
|
} // release lock
|
|
|
|
if (wasEmpty) {
|
|
mPollLoop->wake();
|
|
}
|
|
}
|
|
|
|
int32_t InputDispatcher::injectInputEvent(const InputEvent* event,
|
|
int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis) {
|
|
#if DEBUG_INBOUND_EVENT_DETAILS
|
|
LOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, "
|
|
"sync=%d, timeoutMillis=%d",
|
|
event->getType(), injectorPid, injectorUid, sync, timeoutMillis);
|
|
#endif
|
|
|
|
nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis);
|
|
|
|
EventEntry* injectedEntry;
|
|
bool wasEmpty;
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
|
|
injectedEntry = createEntryFromInputEventLocked(event);
|
|
injectedEntry->refCount += 1;
|
|
injectedEntry->injectorPid = injectorPid;
|
|
injectedEntry->injectorUid = injectorUid;
|
|
|
|
wasEmpty = mInboundQueue.isEmpty();
|
|
mInboundQueue.enqueueAtTail(injectedEntry);
|
|
|
|
} // release lock
|
|
|
|
if (wasEmpty) {
|
|
mPollLoop->wake();
|
|
}
|
|
|
|
int32_t injectionResult;
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
|
|
for (;;) {
|
|
injectionResult = injectedEntry->injectionResult;
|
|
if (injectionResult != INPUT_EVENT_INJECTION_PENDING) {
|
|
break;
|
|
}
|
|
|
|
nsecs_t remainingTimeout = endTime - now();
|
|
if (remainingTimeout <= 0) {
|
|
injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
|
|
sync = false;
|
|
break;
|
|
}
|
|
|
|
mInjectionResultAvailableCondition.waitRelative(mLock, remainingTimeout);
|
|
}
|
|
|
|
if (sync) {
|
|
while (! isFullySynchronizedLocked()) {
|
|
nsecs_t remainingTimeout = endTime - now();
|
|
if (remainingTimeout <= 0) {
|
|
injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
|
|
break;
|
|
}
|
|
|
|
mFullySynchronizedCondition.waitRelative(mLock, remainingTimeout);
|
|
}
|
|
}
|
|
|
|
mAllocator.releaseEventEntry(injectedEntry);
|
|
} // release lock
|
|
|
|
return injectionResult;
|
|
}
|
|
|
|
void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t injectionResult) {
|
|
if (entry->isInjected()) {
|
|
#if DEBUG_INJECTION
|
|
LOGD("Setting input event injection result to %d. "
|
|
"injectorPid=%d, injectorUid=%d",
|
|
injectionResult, entry->injectorPid, entry->injectorUid);
|
|
#endif
|
|
|
|
entry->injectionResult = injectionResult;
|
|
mInjectionResultAvailableCondition.broadcast();
|
|
}
|
|
}
|
|
|
|
bool InputDispatcher::isFullySynchronizedLocked() {
|
|
return mInboundQueue.isEmpty() && mActiveConnections.isEmpty();
|
|
}
|
|
|
|
InputDispatcher::EventEntry* InputDispatcher::createEntryFromInputEventLocked(
|
|
const InputEvent* event) {
|
|
switch (event->getType()) {
|
|
case INPUT_EVENT_TYPE_KEY: {
|
|
const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event);
|
|
uint32_t policyFlags = 0; // XXX consider adding a policy flag to track injected events
|
|
|
|
KeyEntry* keyEntry = mAllocator.obtainKeyEntry(keyEvent->getEventTime(),
|
|
keyEvent->getDeviceId(), keyEvent->getNature(), policyFlags,
|
|
keyEvent->getAction(), keyEvent->getFlags(),
|
|
keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(),
|
|
keyEvent->getRepeatCount(), keyEvent->getDownTime());
|
|
return keyEntry;
|
|
}
|
|
|
|
case INPUT_EVENT_TYPE_MOTION: {
|
|
const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event);
|
|
uint32_t policyFlags = 0; // XXX consider adding a policy flag to track injected events
|
|
|
|
const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes();
|
|
const PointerCoords* samplePointerCoords = motionEvent->getSamplePointerCoords();
|
|
size_t pointerCount = motionEvent->getPointerCount();
|
|
|
|
MotionEntry* motionEntry = mAllocator.obtainMotionEntry(*sampleEventTimes,
|
|
motionEvent->getDeviceId(), motionEvent->getNature(), policyFlags,
|
|
motionEvent->getAction(), motionEvent->getMetaState(), motionEvent->getEdgeFlags(),
|
|
motionEvent->getXPrecision(), motionEvent->getYPrecision(),
|
|
motionEvent->getDownTime(), uint32_t(pointerCount),
|
|
motionEvent->getPointerIds(), samplePointerCoords);
|
|
for (size_t i = motionEvent->getHistorySize(); i > 0; i--) {
|
|
sampleEventTimes += 1;
|
|
samplePointerCoords += pointerCount;
|
|
mAllocator.appendMotionSample(motionEntry, *sampleEventTimes, samplePointerCoords);
|
|
}
|
|
return motionEntry;
|
|
}
|
|
|
|
default:
|
|
assert(false);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::resetKeyRepeatLocked() {
|
|
if (mKeyRepeatState.lastKeyEntry) {
|
|
mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry);
|
|
mKeyRepeatState.lastKeyEntry = NULL;
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::preemptInputDispatch() {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("preemptInputDispatch");
|
|
#endif
|
|
|
|
bool preemptedOne = false;
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
|
|
for (size_t i = 0; i < mActiveConnections.size(); i++) {
|
|
Connection* connection = mActiveConnections[i];
|
|
if (connection->hasPendingSyncTarget()) {
|
|
#if DEBUG_DISPATCH_CYCLE
|
|
LOGD("channel '%s' ~ Preempted pending synchronous dispatch",
|
|
connection->getInputChannelName());
|
|
#endif
|
|
connection->outboundQueue.tail.prev->targetFlags &= ~ InputTarget::FLAG_SYNC;
|
|
preemptedOne = true;
|
|
}
|
|
}
|
|
} // release lock
|
|
|
|
if (preemptedOne) {
|
|
// Wake up the poll loop so it can get a head start dispatching the next event.
|
|
mPollLoop->wake();
|
|
}
|
|
}
|
|
|
|
status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel) {
|
|
#if DEBUG_REGISTRATION
|
|
LOGD("channel '%s' ~ registerInputChannel", inputChannel->getName().string());
|
|
#endif
|
|
|
|
int receiveFd;
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
|
|
receiveFd = inputChannel->getReceivePipeFd();
|
|
if (mConnectionsByReceiveFd.indexOfKey(receiveFd) >= 0) {
|
|
LOGW("Attempted to register already registered input channel '%s'",
|
|
inputChannel->getName().string());
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
sp<Connection> connection = new Connection(inputChannel);
|
|
status_t status = connection->initialize();
|
|
if (status) {
|
|
LOGE("Failed to initialize input publisher for input channel '%s', status=%d",
|
|
inputChannel->getName().string(), status);
|
|
return status;
|
|
}
|
|
|
|
mConnectionsByReceiveFd.add(receiveFd, connection);
|
|
|
|
runCommandsLockedInterruptible();
|
|
} // release lock
|
|
|
|
mPollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this);
|
|
return OK;
|
|
}
|
|
|
|
status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) {
|
|
#if DEBUG_REGISTRATION
|
|
LOGD("channel '%s' ~ unregisterInputChannel", inputChannel->getName().string());
|
|
#endif
|
|
|
|
int32_t receiveFd;
|
|
{ // acquire lock
|
|
AutoMutex _l(mLock);
|
|
|
|
receiveFd = inputChannel->getReceivePipeFd();
|
|
ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd);
|
|
if (connectionIndex < 0) {
|
|
LOGW("Attempted to unregister already unregistered input channel '%s'",
|
|
inputChannel->getName().string());
|
|
return BAD_VALUE;
|
|
}
|
|
|
|
sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
|
|
mConnectionsByReceiveFd.removeItemsAt(connectionIndex);
|
|
|
|
connection->status = Connection::STATUS_ZOMBIE;
|
|
|
|
nsecs_t currentTime = now();
|
|
abortDispatchCycleLocked(currentTime, connection, 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, const sp<Connection>& connection) {
|
|
}
|
|
|
|
void InputDispatcher::onDispatchCycleFinishedLocked(
|
|
nsecs_t currentTime, const sp<Connection>& connection, bool recoveredFromANR) {
|
|
if (recoveredFromANR) {
|
|
LOGI("channel '%s' ~ Recovered from ANR. %01.1fms since event, "
|
|
"%01.1fms since dispatch, %01.1fms since ANR",
|
|
connection->getInputChannelName(),
|
|
connection->getEventLatencyMillis(currentTime),
|
|
connection->getDispatchLatencyMillis(currentTime),
|
|
connection->getANRLatencyMillis(currentTime));
|
|
|
|
CommandEntry* commandEntry = postCommandLocked(
|
|
& InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible);
|
|
commandEntry->connection = connection;
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::onDispatchCycleANRLocked(
|
|
nsecs_t currentTime, const sp<Connection>& connection) {
|
|
LOGI("channel '%s' ~ Not responding! %01.1fms since event, %01.1fms since dispatch",
|
|
connection->getInputChannelName(),
|
|
connection->getEventLatencyMillis(currentTime),
|
|
connection->getDispatchLatencyMillis(currentTime));
|
|
|
|
CommandEntry* commandEntry = postCommandLocked(
|
|
& InputDispatcher::doNotifyInputChannelANRLockedInterruptible);
|
|
commandEntry->connection = connection;
|
|
}
|
|
|
|
void InputDispatcher::onDispatchCycleBrokenLocked(
|
|
nsecs_t currentTime, const sp<Connection>& connection) {
|
|
LOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!",
|
|
connection->getInputChannelName());
|
|
|
|
CommandEntry* commandEntry = postCommandLocked(
|
|
& InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible);
|
|
commandEntry->connection = connection;
|
|
}
|
|
|
|
void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible(
|
|
CommandEntry* commandEntry) {
|
|
sp<Connection> connection = commandEntry->connection;
|
|
|
|
if (connection->status != Connection::STATUS_ZOMBIE) {
|
|
mLock.unlock();
|
|
|
|
mPolicy->notifyInputChannelBroken(connection->inputChannel);
|
|
|
|
mLock.lock();
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::doNotifyInputChannelANRLockedInterruptible(
|
|
CommandEntry* commandEntry) {
|
|
sp<Connection> connection = commandEntry->connection;
|
|
|
|
if (connection->status != Connection::STATUS_ZOMBIE) {
|
|
mLock.unlock();
|
|
|
|
nsecs_t newTimeout;
|
|
bool resume = mPolicy->notifyInputChannelANR(connection->inputChannel, newTimeout);
|
|
|
|
mLock.lock();
|
|
|
|
nsecs_t currentTime = now();
|
|
if (resume) {
|
|
resumeAfterTimeoutDispatchCycleLocked(currentTime, connection, newTimeout);
|
|
} else {
|
|
abortDispatchCycleLocked(currentTime, connection, false /*(not) broken*/);
|
|
}
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible(
|
|
CommandEntry* commandEntry) {
|
|
sp<Connection> connection = commandEntry->connection;
|
|
|
|
if (connection->status != Connection::STATUS_ZOMBIE) {
|
|
mLock.unlock();
|
|
|
|
mPolicy->notifyInputChannelRecoveredFromANR(connection->inputChannel);
|
|
|
|
mLock.lock();
|
|
}
|
|
}
|
|
|
|
|
|
// --- InputDispatcher::Allocator ---
|
|
|
|
InputDispatcher::Allocator::Allocator() {
|
|
}
|
|
|
|
void InputDispatcher::Allocator::initializeEventEntry(EventEntry* entry, int32_t type,
|
|
nsecs_t eventTime) {
|
|
entry->type = type;
|
|
entry->refCount = 1;
|
|
entry->dispatchInProgress = false;
|
|
entry->eventTime = eventTime;
|
|
entry->injectionResult = INPUT_EVENT_INJECTION_PENDING;
|
|
entry->injectorPid = -1;
|
|
entry->injectorUid = -1;
|
|
}
|
|
|
|
InputDispatcher::ConfigurationChangedEntry*
|
|
InputDispatcher::Allocator::obtainConfigurationChangedEntry(nsecs_t eventTime) {
|
|
ConfigurationChangedEntry* entry = mConfigurationChangeEntryPool.alloc();
|
|
initializeEventEntry(entry, EventEntry::TYPE_CONFIGURATION_CHANGED, eventTime);
|
|
return entry;
|
|
}
|
|
|
|
InputDispatcher::KeyEntry* InputDispatcher::Allocator::obtainKeyEntry(nsecs_t eventTime,
|
|
int32_t deviceId, int32_t nature, uint32_t policyFlags, int32_t action,
|
|
int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
|
|
int32_t repeatCount, nsecs_t downTime) {
|
|
KeyEntry* entry = mKeyEntryPool.alloc();
|
|
initializeEventEntry(entry, EventEntry::TYPE_KEY, eventTime);
|
|
|
|
entry->deviceId = deviceId;
|
|
entry->nature = nature;
|
|
entry->policyFlags = policyFlags;
|
|
entry->action = action;
|
|
entry->flags = flags;
|
|
entry->keyCode = keyCode;
|
|
entry->scanCode = scanCode;
|
|
entry->metaState = metaState;
|
|
entry->repeatCount = repeatCount;
|
|
entry->downTime = downTime;
|
|
return entry;
|
|
}
|
|
|
|
InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry(nsecs_t eventTime,
|
|
int32_t deviceId, int32_t nature, uint32_t policyFlags, int32_t action,
|
|
int32_t metaState, int32_t edgeFlags, float xPrecision, float yPrecision,
|
|
nsecs_t downTime, uint32_t pointerCount,
|
|
const int32_t* pointerIds, const PointerCoords* pointerCoords) {
|
|
MotionEntry* entry = mMotionEntryPool.alloc();
|
|
initializeEventEntry(entry, EventEntry::TYPE_MOTION, eventTime);
|
|
|
|
entry->eventTime = eventTime;
|
|
entry->deviceId = deviceId;
|
|
entry->nature = nature;
|
|
entry->policyFlags = policyFlags;
|
|
entry->action = action;
|
|
entry->metaState = metaState;
|
|
entry->edgeFlags = edgeFlags;
|
|
entry->xPrecision = xPrecision;
|
|
entry->yPrecision = yPrecision;
|
|
entry->downTime = downTime;
|
|
entry->pointerCount = pointerCount;
|
|
entry->firstSample.eventTime = eventTime;
|
|
entry->firstSample.next = NULL;
|
|
entry->lastSample = & entry->firstSample;
|
|
for (uint32_t i = 0; i < pointerCount; i++) {
|
|
entry->pointerIds[i] = pointerIds[i];
|
|
entry->firstSample.pointerCoords[i] = pointerCoords[i];
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
InputDispatcher::DispatchEntry* InputDispatcher::Allocator::obtainDispatchEntry(
|
|
EventEntry* eventEntry) {
|
|
DispatchEntry* entry = mDispatchEntryPool.alloc();
|
|
entry->eventEntry = eventEntry;
|
|
eventEntry->refCount += 1;
|
|
return entry;
|
|
}
|
|
|
|
InputDispatcher::CommandEntry* InputDispatcher::Allocator::obtainCommandEntry(Command command) {
|
|
CommandEntry* entry = mCommandEntryPool.alloc();
|
|
entry->command = command;
|
|
return entry;
|
|
}
|
|
|
|
void InputDispatcher::Allocator::releaseEventEntry(EventEntry* entry) {
|
|
switch (entry->type) {
|
|
case EventEntry::TYPE_CONFIGURATION_CHANGED:
|
|
releaseConfigurationChangedEntry(static_cast<ConfigurationChangedEntry*>(entry));
|
|
break;
|
|
case EventEntry::TYPE_KEY:
|
|
releaseKeyEntry(static_cast<KeyEntry*>(entry));
|
|
break;
|
|
case EventEntry::TYPE_MOTION:
|
|
releaseMotionEntry(static_cast<MotionEntry*>(entry));
|
|
break;
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::Allocator::releaseConfigurationChangedEntry(
|
|
ConfigurationChangedEntry* entry) {
|
|
entry->refCount -= 1;
|
|
if (entry->refCount == 0) {
|
|
mConfigurationChangeEntryPool.free(entry);
|
|
} else {
|
|
assert(entry->refCount > 0);
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::Allocator::releaseKeyEntry(KeyEntry* entry) {
|
|
entry->refCount -= 1;
|
|
if (entry->refCount == 0) {
|
|
mKeyEntryPool.free(entry);
|
|
} else {
|
|
assert(entry->refCount > 0);
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::Allocator::releaseMotionEntry(MotionEntry* entry) {
|
|
entry->refCount -= 1;
|
|
if (entry->refCount == 0) {
|
|
for (MotionSample* sample = entry->firstSample.next; sample != NULL; ) {
|
|
MotionSample* next = sample->next;
|
|
mMotionSamplePool.free(sample);
|
|
sample = next;
|
|
}
|
|
mMotionEntryPool.free(entry);
|
|
} else {
|
|
assert(entry->refCount > 0);
|
|
}
|
|
}
|
|
|
|
void InputDispatcher::Allocator::releaseDispatchEntry(DispatchEntry* entry) {
|
|
releaseEventEntry(entry->eventEntry);
|
|
mDispatchEntryPool.free(entry);
|
|
}
|
|
|
|
void InputDispatcher::Allocator::releaseCommandEntry(CommandEntry* entry) {
|
|
mCommandEntryPool.free(entry);
|
|
}
|
|
|
|
void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry,
|
|
nsecs_t eventTime, const PointerCoords* pointerCoords) {
|
|
MotionSample* sample = mMotionSamplePool.alloc();
|
|
sample->eventTime = eventTime;
|
|
uint32_t pointerCount = motionEntry->pointerCount;
|
|
for (uint32_t i = 0; i < pointerCount; i++) {
|
|
sample->pointerCoords[i] = pointerCoords[i];
|
|
}
|
|
|
|
sample->next = NULL;
|
|
motionEntry->lastSample->next = sample;
|
|
motionEntry->lastSample = sample;
|
|
}
|
|
|
|
// --- InputDispatcher::Connection ---
|
|
|
|
InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel) :
|
|
status(STATUS_NORMAL), inputChannel(inputChannel), inputPublisher(inputChannel),
|
|
nextTimeoutTime(LONG_LONG_MAX),
|
|
lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX),
|
|
lastANRTime(LONG_LONG_MAX) {
|
|
}
|
|
|
|
InputDispatcher::Connection::~Connection() {
|
|
}
|
|
|
|
status_t InputDispatcher::Connection::initialize() {
|
|
return inputPublisher.initialize();
|
|
}
|
|
|
|
void InputDispatcher::Connection::setNextTimeoutTime(nsecs_t currentTime, nsecs_t timeout) {
|
|
nextTimeoutTime = (timeout >= 0) ? currentTime + timeout : LONG_LONG_MAX;
|
|
}
|
|
|
|
const char* InputDispatcher::Connection::getStatusLabel() const {
|
|
switch (status) {
|
|
case STATUS_NORMAL:
|
|
return "NORMAL";
|
|
|
|
case STATUS_BROKEN:
|
|
return "BROKEN";
|
|
|
|
case STATUS_NOT_RESPONDING:
|
|
return "NOT_RESPONDING";
|
|
|
|
case STATUS_ZOMBIE:
|
|
return "ZOMBIE";
|
|
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
InputDispatcher::DispatchEntry* InputDispatcher::Connection::findQueuedDispatchEntryForEvent(
|
|
const EventEntry* eventEntry) const {
|
|
for (DispatchEntry* dispatchEntry = outboundQueue.tail.prev;
|
|
dispatchEntry != & outboundQueue.head; dispatchEntry = dispatchEntry->prev) {
|
|
if (dispatchEntry->eventEntry == eventEntry) {
|
|
return dispatchEntry;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// --- InputDispatcher::CommandEntry ---
|
|
|
|
InputDispatcher::CommandEntry::CommandEntry() {
|
|
}
|
|
|
|
InputDispatcher::CommandEntry::~CommandEntry() {
|
|
}
|
|
|
|
|
|
// --- InputDispatcherThread ---
|
|
|
|
InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) :
|
|
Thread(/*canCallJava*/ true), mDispatcher(dispatcher) {
|
|
}
|
|
|
|
InputDispatcherThread::~InputDispatcherThread() {
|
|
}
|
|
|
|
bool InputDispatcherThread::threadLoop() {
|
|
mDispatcher->dispatchOnce();
|
|
return true;
|
|
}
|
|
|
|
} // namespace android
|