diff options
Diffstat (limited to 'libs/ui/InputDispatcher.cpp')
| -rw-r--r-- | libs/ui/InputDispatcher.cpp | 3188 | 
1 files changed, 3188 insertions, 0 deletions
| diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp new file mode 100644 index 000000000000..b8a26b04f0e1 --- /dev/null +++ b/libs/ui/InputDispatcher.cpp @@ -0,0 +1,3188 @@ +// +// Copyright 2010 The Android Open Source Project +// +// The input dispatcher. +// +#define LOG_TAG "InputDispatcher" + +//#define LOG_NDEBUG 0 + +// Log detailed debug messages about each inbound event notification to the dispatcher. +#define DEBUG_INBOUND_EVENT_DETAILS 0 + +// Log detailed debug messages about each outbound event processed by the dispatcher. +#define DEBUG_OUTBOUND_EVENT_DETAILS 0 + +// Log debug messages about batching. +#define DEBUG_BATCHING 0 + +// Log debug messages about the dispatch cycle. +#define DEBUG_DISPATCH_CYCLE 0 + +// Log debug messages about registrations. +#define DEBUG_REGISTRATION 0 + +// Log debug messages about performance statistics. +#define DEBUG_PERFORMANCE_STATISTICS 0 + +// Log debug messages about input event injection. +#define DEBUG_INJECTION 0 + +// Log debug messages about input event throttling. +#define DEBUG_THROTTLING 0 + +// Log debug messages about input focus tracking. +#define DEBUG_FOCUS 0 + +// Log debug messages about the app switch latency optimization. +#define DEBUG_APP_SWITCH 0 + +#include <cutils/log.h> +#include <ui/InputDispatcher.h> +#include <ui/PowerManager.h> + +#include <stddef.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> + +namespace android { + +// Delay between reporting long touch events to the power manager. +const nsecs_t EVENT_IGNORE_DURATION = 300 * 1000000LL; // 300 ms + +// Default input dispatching timeout if there is no focused application or paused window +// from which to determine an appropriate dispatching timeout. +const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec + +// Amount of time to allow for all pending events to be processed when an app switch +// key is on the way.  This is used to preempt input dispatch and drop input events +// when an application takes too long to respond and the user has pressed an app switch key. +const nsecs_t APP_SWITCH_TIMEOUT = 500 * 1000000LL; // 0.5sec + + +static inline nsecs_t now() { +    return systemTime(SYSTEM_TIME_MONOTONIC); +} + +static inline const char* toString(bool value) { +    return value ? "true" : "false"; +} + + +// --- InputWindow --- + +bool InputWindow::visibleFrameIntersects(const InputWindow* other) const { +    return visibleFrameRight > other->visibleFrameLeft +        && visibleFrameLeft < other->visibleFrameRight +        && visibleFrameBottom > other->visibleFrameTop +        && visibleFrameTop < other->visibleFrameBottom; +} + +bool InputWindow::touchableAreaContainsPoint(int32_t x, int32_t y) const { +    return x >= touchableAreaLeft && x <= touchableAreaRight +            && y >= touchableAreaTop && y <= touchableAreaBottom; +} + + +// --- InputDispatcher --- + +InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) : +    mPolicy(policy), +    mPendingEvent(NULL), mAppSwitchDueTime(LONG_LONG_MAX), +    mDispatchEnabled(true), mDispatchFrozen(false), +    mFocusedWindow(NULL), mTouchDown(false), mTouchedWindow(NULL), +    mFocusedApplication(NULL), +    mCurrentInputTargetsValid(false), +    mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) { +    mPollLoop = new PollLoop(false); + +    mInboundQueue.headSentinel.refCount = -1; +    mInboundQueue.headSentinel.type = EventEntry::TYPE_SENTINEL; +    mInboundQueue.headSentinel.eventTime = LONG_LONG_MIN; + +    mInboundQueue.tailSentinel.refCount = -1; +    mInboundQueue.tailSentinel.type = EventEntry::TYPE_SENTINEL; +    mInboundQueue.tailSentinel.eventTime = LONG_LONG_MAX; + +    mKeyRepeatState.lastKeyEntry = NULL; + +    int32_t maxEventsPerSecond = policy->getMaxEventsPerSecond(); +    mThrottleState.minTimeBetweenEvents = 1000000000LL / maxEventsPerSecond; +    mThrottleState.lastDeviceId = -1; + +#if DEBUG_THROTTLING +    mThrottleState.originalSampleCount = 0; +    LOGD("Throttling - Max events per second = %d", maxEventsPerSecond); +#endif +} + +InputDispatcher::~InputDispatcher() { +    { // acquire lock +        AutoMutex _l(mLock); + +        resetKeyRepeatLocked(); +        releasePendingEventLocked(true); +        drainInboundQueueLocked(); +    } + +    while (mConnectionsByReceiveFd.size() != 0) { +        unregisterInputChannel(mConnectionsByReceiveFd.valueAt(0)->inputChannel); +    } +} + +void InputDispatcher::dispatchOnce() { +    nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout(); +    nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay(); + +    nsecs_t nextWakeupTime = LONG_LONG_MAX; +    { // acquire lock +        AutoMutex _l(mLock); +        dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime); + +        if (runCommandsLockedInterruptible()) { +            nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately +        } +    } // release lock + +    // Wait for callback or timeout or wake.  (make sure we round up, not down) +    nsecs_t currentTime = now(); +    int32_t timeoutMillis; +    if (nextWakeupTime > currentTime) { +        uint64_t timeout = uint64_t(nextWakeupTime - currentTime); +        timeout = (timeout + 999999LL) / 1000000LL; +        timeoutMillis = timeout > INT_MAX ? -1 : int32_t(timeout); +    } else { +        timeoutMillis = 0; +    } + +    mPollLoop->pollOnce(timeoutMillis); +} + +void InputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout, +        nsecs_t keyRepeatDelay, nsecs_t* nextWakeupTime) { +    nsecs_t 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. +    if (keyRepeatTimeout < 0) { +        resetKeyRepeatLocked(); +    } + +    // If dispatching is disabled, drop all events in the queue. +    if (! mDispatchEnabled) { +        if (mPendingEvent || ! mInboundQueue.isEmpty()) { +            LOGI("Dropping pending events because input dispatch is disabled."); +            releasePendingEventLocked(true); +            drainInboundQueueLocked(); +        } +        return; +    } + +    // If dispatching is frozen, do not process timeouts or try to deliver any new events. +    if (mDispatchFrozen) { +#if DEBUG_FOCUS +        LOGD("Dispatch frozen.  Waiting some more."); +#endif +        return; +    } + +    // Optimize latency of app switches. +    // Essentially we start a short timeout when an app switch key (HOME / ENDCALL) has +    // been pressed.  When it expires, we preempt dispatch and drop all other pending events. +    bool isAppSwitchDue = mAppSwitchDueTime <= currentTime; +    if (mAppSwitchDueTime < *nextWakeupTime) { +        *nextWakeupTime = mAppSwitchDueTime; +    } + +    // Detect and process timeouts for all connections and determine if there are any +    // synchronous event dispatches pending.  This step is entirely non-interruptible. +    bool havePendingSyncTarget = false; +    size_t activeConnectionCount = mActiveConnections.size(); +    for (size_t i = 0; i < activeConnectionCount; i++) { +        Connection* connection = mActiveConnections.itemAt(i); + +        if (connection->hasPendingSyncTarget()) { +            if (isAppSwitchDue) { +                connection->preemptSyncTarget(); +            } else { +                havePendingSyncTarget = 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); +        *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately +    } +    mTimedOutConnections.clear(); + +    // If we have a pending synchronous target, skip dispatch. +    if (havePendingSyncTarget) { +        return; +    } + +    // Ready to start a new event. +    // If we don't already have a pending event, go grab one. +    if (! mPendingEvent) { +        if (mInboundQueue.isEmpty()) { +            if (isAppSwitchDue) { +                // The inbound queue is empty so the app switch key we were waiting +                // for will never arrive.  Stop waiting for it. +                resetPendingAppSwitchLocked(false); +                isAppSwitchDue = false; +            } + +            // Synthesize a key repeat if appropriate. +            if (mKeyRepeatState.lastKeyEntry) { +                if (currentTime >= mKeyRepeatState.nextRepeatTime) { +                    mPendingEvent = synthesizeKeyRepeatLocked(currentTime, keyRepeatDelay); +                } else { +                    if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) { +                        *nextWakeupTime = mKeyRepeatState.nextRepeatTime; +                    } +                } +            } +            if (! mPendingEvent) { +                return; +            } +        } else { +            // Inbound queue has at least one entry. +            EventEntry* entry = mInboundQueue.headSentinel.next; + +            // Throttle the entry if it is a move event and there are no +            // other events behind it in the queue.  Due to movement batching, additional +            // samples may be appended to this event by the time the throttling timeout +            // expires. +            // TODO Make this smarter and consider throttling per device independently. +            if (entry->type == EventEntry::TYPE_MOTION) { +                MotionEntry* motionEntry = static_cast<MotionEntry*>(entry); +                int32_t deviceId = motionEntry->deviceId; +                uint32_t source = motionEntry->source; +                if (! isAppSwitchDue +                        && motionEntry->next == & mInboundQueue.tailSentinel // exactly one event +                        && motionEntry->action == AMOTION_EVENT_ACTION_MOVE +                        && deviceId == mThrottleState.lastDeviceId +                        && source == mThrottleState.lastSource) { +                    nsecs_t nextTime = mThrottleState.lastEventTime +                            + mThrottleState.minTimeBetweenEvents; +                    if (currentTime < nextTime) { +                        // Throttle it! +#if DEBUG_THROTTLING +                        LOGD("Throttling - Delaying motion event for " +                                "device 0x%x, source 0x%08x by up to %0.3fms.", +                                deviceId, source, (nextTime - currentTime) * 0.000001); +#endif +                        if (nextTime < *nextWakeupTime) { +                            *nextWakeupTime = nextTime; +                        } +                        if (mThrottleState.originalSampleCount == 0) { +                            mThrottleState.originalSampleCount = +                                    motionEntry->countSamples(); +                        } +                        return; +                    } +                } + +#if DEBUG_THROTTLING +                if (mThrottleState.originalSampleCount != 0) { +                    uint32_t count = motionEntry->countSamples(); +                    LOGD("Throttling - Motion event sample count grew by %d from %d to %d.", +                            count - mThrottleState.originalSampleCount, +                            mThrottleState.originalSampleCount, count); +                    mThrottleState.originalSampleCount = 0; +                } +#endif + +                mThrottleState.lastEventTime = entry->eventTime < currentTime +                        ? entry->eventTime : currentTime; +                mThrottleState.lastDeviceId = deviceId; +                mThrottleState.lastSource = source; +            } + +            mInboundQueue.dequeue(entry); +            mPendingEvent = entry; +        } +    } + +    // Now we have an event to dispatch. +    assert(mPendingEvent != NULL); +    bool wasDispatched = false; +    bool wasDropped = false; +    switch (mPendingEvent->type) { +    case EventEntry::TYPE_CONFIGURATION_CHANGED: { +        ConfigurationChangedEntry* typedEntry = +                static_cast<ConfigurationChangedEntry*>(mPendingEvent); +        wasDispatched = dispatchConfigurationChangedLocked(currentTime, typedEntry); +        break; +    } + +    case EventEntry::TYPE_KEY: { +        KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent); +        if (isAppSwitchPendingLocked()) { +            if (isAppSwitchKey(typedEntry->keyCode)) { +                resetPendingAppSwitchLocked(true); +            } else if (isAppSwitchDue) { +                LOGI("Dropping key because of pending overdue app switch."); +                wasDropped = true; +                break; +            } +        } +        wasDispatched = dispatchKeyLocked(currentTime, typedEntry, keyRepeatTimeout, +                nextWakeupTime); +        break; +    } + +    case EventEntry::TYPE_MOTION: { +        MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent); +        if (isAppSwitchDue) { +            LOGI("Dropping motion because of pending overdue app switch."); +            wasDropped = true; +            break; +        } +        wasDispatched = dispatchMotionLocked(currentTime, typedEntry, nextWakeupTime); +        break; +    } + +    default: +        assert(false); +        wasDropped = true; +        break; +    } + +    if (wasDispatched || wasDropped) { +        releasePendingEventLocked(wasDropped); +        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up immediately +    } +} + +bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) { +    bool needWake = mInboundQueue.isEmpty(); +    mInboundQueue.enqueueAtTail(entry); + +    switch (entry->type) { +    case EventEntry::TYPE_KEY: +        needWake |= detectPendingAppSwitchLocked(static_cast<KeyEntry*>(entry)); +        break; +    } + +    return needWake; +} + +bool InputDispatcher::isAppSwitchKey(int32_t keyCode) { +    return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL; +} + +bool InputDispatcher::isAppSwitchPendingLocked() { +    return mAppSwitchDueTime != LONG_LONG_MAX; +} + +bool InputDispatcher::detectPendingAppSwitchLocked(KeyEntry* inboundKeyEntry) { +    if (inboundKeyEntry->action == AKEY_EVENT_ACTION_UP +            && ! (inboundKeyEntry->flags & AKEY_EVENT_FLAG_CANCELED) +            && isAppSwitchKey(inboundKeyEntry->keyCode) +            && isEventFromReliableSourceLocked(inboundKeyEntry)) { +#if DEBUG_APP_SWITCH +        LOGD("App switch is pending!"); +#endif +        mAppSwitchDueTime = inboundKeyEntry->eventTime + APP_SWITCH_TIMEOUT; +        return true; // need wake +    } +    return false; +} + +void InputDispatcher::resetPendingAppSwitchLocked(bool handled) { +    mAppSwitchDueTime = LONG_LONG_MAX; + +#if DEBUG_APP_SWITCH +    if (handled) { +        LOGD("App switch has arrived."); +    } else { +        LOGD("App switch was abandoned."); +    } +#endif +} + +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::drainInboundQueueLocked() { +    while (! mInboundQueue.isEmpty()) { +        EventEntry* entry = mInboundQueue.dequeueAtHead(); +        releaseInboundEventLocked(entry, true /*wasDropped*/); +    } +} + +void InputDispatcher::releasePendingEventLocked(bool wasDropped) { +    if (mPendingEvent) { +        releaseInboundEventLocked(mPendingEvent, wasDropped); +        mPendingEvent = NULL; +    } +} + +void InputDispatcher::releaseInboundEventLocked(EventEntry* entry, bool wasDropped) { +    if (wasDropped) { +#if DEBUG_DISPATCH_CYCLE +        LOGD("Pending event was dropped."); +#endif +        setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED); +    } +    mAllocator.releaseEventEntry(entry); +} + +bool InputDispatcher::isEventFromReliableSourceLocked(EventEntry* entry) { +    return ! entry->isInjected() +            || entry->injectorUid == 0 +            || mPolicy->checkInjectEventsPermissionNonReentrant( +                    entry->injectorPid, entry->injectorUid); +} + +void InputDispatcher::resetKeyRepeatLocked() { +    if (mKeyRepeatState.lastKeyEntry) { +        mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry); +        mKeyRepeatState.lastKeyEntry = NULL; +    } +} + +InputDispatcher::KeyEntry* InputDispatcher::synthesizeKeyRepeatLocked( +        nsecs_t currentTime, nsecs_t keyRepeatDelay) { +    KeyEntry* entry = mKeyRepeatState.lastKeyEntry; + +    // Reuse the repeated key entry if it is otherwise unreferenced. +    uint32_t policyFlags = entry->policyFlags & POLICY_FLAG_RAW_MASK; +    if (entry->refCount == 1) { +        entry->recycle(); +        entry->eventTime = currentTime; +        entry->policyFlags = policyFlags; +        entry->repeatCount += 1; +    } else { +        KeyEntry* newEntry = mAllocator.obtainKeyEntry(currentTime, +                entry->deviceId, entry->source, policyFlags, +                entry->action, entry->flags, entry->keyCode, entry->scanCode, +                entry->metaState, entry->repeatCount + 1, entry->downTime); + +        mKeyRepeatState.lastKeyEntry = newEntry; +        mAllocator.releaseKeyEntry(entry); + +        entry = newEntry; +    } +    entry->syntheticRepeat = true; + +    // Increment reference count since we keep a reference to the event in +    // mKeyRepeatState.lastKeyEntry in addition to the one we return. +    entry->refCount += 1; + +    if (entry->repeatCount == 1) { +        entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS; +    } + +    mKeyRepeatState.nextRepeatTime = currentTime + keyRepeatDelay; +    return entry; +} + +bool InputDispatcher::dispatchConfigurationChangedLocked( +        nsecs_t currentTime, ConfigurationChangedEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS +    LOGD("dispatchConfigurationChanged - eventTime=%lld", entry->eventTime); +#endif + +    // Reset key repeating in case a keyboard device was added or removed or something. +    resetKeyRepeatLocked(); + +    // Enqueue a command to run outside the lock to tell the policy that the configuration changed. +    CommandEntry* commandEntry = postCommandLocked( +            & InputDispatcher::doNotifyConfigurationChangedInterruptible); +    commandEntry->eventTime = entry->eventTime; +    return true; +} + +bool InputDispatcher::dispatchKeyLocked( +        nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout, +        nsecs_t* nextWakeupTime) { +    // Preprocessing. +    if (! entry->dispatchInProgress) { +        logOutboundKeyDetailsLocked("dispatchKey - ", entry); + +        if (entry->repeatCount == 0 +                && entry->action == AKEY_EVENT_ACTION_DOWN +                && ! entry->isInjected()) { +            if (mKeyRepeatState.lastKeyEntry +                    && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) { +                // We have seen two identical key downs in a row which indicates that the device +                // driver is automatically generating key repeats itself.  We take note of the +                // repeat here, but we disable our own next key repeat timer since it is clear that +                // we will not need to synthesize key repeats ourselves. +                entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1; +                resetKeyRepeatLocked(); +                mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves +            } else { +                // Not a repeat.  Save key down state in case we do see a repeat later. +                resetKeyRepeatLocked(); +                mKeyRepeatState.nextRepeatTime = entry->eventTime + keyRepeatTimeout; +            } +            mKeyRepeatState.lastKeyEntry = entry; +            entry->refCount += 1; +        } else if (! entry->syntheticRepeat) { +            resetKeyRepeatLocked(); +        } + +        entry->dispatchInProgress = true; +        startFindingTargetsLocked(); +    } + +    // Identify targets. +    if (! mCurrentInputTargetsValid) { +        InputWindow* window = NULL; +        int32_t injectionResult = findFocusedWindowLocked(currentTime, +                entry, nextWakeupTime, & window); +        if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { +            return false; +        } + +        setInjectionResultLocked(entry, injectionResult); +        if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { +            return true; +        } + +        addMonitoringTargetsLocked(); +        finishFindingTargetsLocked(window); +    } + +    // Give the policy a chance to intercept the key. +    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) { +        CommandEntry* commandEntry = postCommandLocked( +                & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible); +        commandEntry->inputChannel = mCurrentInputChannel; +        commandEntry->keyEntry = entry; +        entry->refCount += 1; +        return false; // wait for the command to run +    } +    if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) { +        return true; +    } + +    // Dispatch the key. +    dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); + +    // Poke user activity. +    pokeUserActivityLocked(entry->eventTime, mCurrentInputWindowType, POWER_MANAGER_BUTTON_EVENT); +    return true; +} + +void InputDispatcher::logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS +    LOGD("%seventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, " +            "action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, " +            "downTime=%lld", +            prefix, +            entry->eventTime, entry->deviceId, entry->source, entry->policyFlags, +            entry->action, entry->flags, entry->keyCode, entry->scanCode, entry->metaState, +            entry->downTime); +#endif +} + +bool InputDispatcher::dispatchMotionLocked( +        nsecs_t currentTime, MotionEntry* entry, nsecs_t* nextWakeupTime) { +    // Preprocessing. +    if (! entry->dispatchInProgress) { +        logOutboundMotionDetailsLocked("dispatchMotion - ", entry); + +        entry->dispatchInProgress = true; +        startFindingTargetsLocked(); +    } + +    bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER; + +    // Identify targets. +    if (! mCurrentInputTargetsValid) { +        InputWindow* window = NULL; +        int32_t injectionResult; +        if (isPointerEvent) { +            // Pointer event.  (eg. touchscreen) +            injectionResult = findTouchedWindowLocked(currentTime, +                    entry, nextWakeupTime, & window); +        } else { +            // Non touch event.  (eg. trackball) +            injectionResult = findFocusedWindowLocked(currentTime, +                    entry, nextWakeupTime, & window); +        } +        if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { +            return false; +        } + +        setInjectionResultLocked(entry, injectionResult); +        if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { +            return true; +        } + +        addMonitoringTargetsLocked(); +        finishFindingTargetsLocked(window); +    } + +    // Dispatch the motion. +    dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); + +    // Poke user activity. +    int32_t eventType; +    if (isPointerEvent) { +        switch (entry->action) { +        case AMOTION_EVENT_ACTION_DOWN: +            eventType = POWER_MANAGER_TOUCH_EVENT; +            break; +        case AMOTION_EVENT_ACTION_UP: +            eventType = POWER_MANAGER_TOUCH_UP_EVENT; +            break; +        default: +            if (entry->eventTime - entry->downTime >= EVENT_IGNORE_DURATION) { +                eventType = POWER_MANAGER_TOUCH_EVENT; +            } else { +                eventType = POWER_MANAGER_LONG_TOUCH_EVENT; +            } +            break; +        } +    } else { +        eventType = POWER_MANAGER_BUTTON_EVENT; +    } +    pokeUserActivityLocked(entry->eventTime, mCurrentInputWindowType, eventType); +    return true; +} + + +void InputDispatcher::logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry) { +#if DEBUG_OUTBOUND_EVENT_DETAILS +    LOGD("%seventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, " +            "action=0x%x, flags=0x%x, " +            "metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%lld", +            prefix, +            entry->eventTime, entry->deviceId, entry->source, entry->policyFlags, +            entry->action, entry->flags, +            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; +    const MotionSample* sample = & entry->firstSample; +    for (; sample->next != NULL; sample = sample->next) { +        sampleCount += 1; +    } +    for (uint32_t i = 0; i < entry->pointerCount; i++) { +        LOGD("  Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, " +                "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, " +                "orientation=%f", +                i, entry->pointerIds[i], +                sample->pointerCoords[i].x, sample->pointerCoords[i].y, +                sample->pointerCoords[i].pressure, sample->pointerCoords[i].size, +                sample->pointerCoords[i].touchMajor, sample->pointerCoords[i].touchMinor, +                sample->pointerCoords[i].toolMajor, sample->pointerCoords[i].toolMinor, +                sample->pointerCoords[i].orientation); +    } + +    // Keep in mind that due to batching, it is possible for the number of samples actually +    // dispatched to change before the application finally consumed them. +    if (entry->action == AMOTION_EVENT_ACTION_MOVE) { +        LOGD("  ... Total movement samples currently batched %d ...", sampleCount); +    } +#endif +} + +void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime, +        EventEntry* eventEntry, bool resumeWithAppendedMotionSample) { +#if DEBUG_DISPATCH_CYCLE +    LOGD("dispatchEventToCurrentInputTargets - " +            "resumeWithAppendedMotionSample=%s", +            toString(resumeWithAppendedMotionSample)); +#endif + +    assert(eventEntry->dispatchInProgress); // should already have been set to true + +    for (size_t i = 0; i < mCurrentInputTargets.size(); i++) { +        const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i); + +        ssize_t connectionIndex = getConnectionIndex(inputTarget.inputChannel); +        if (connectionIndex >= 0) { +            sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); +            prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget, +                    resumeWithAppendedMotionSample); +        } else { +            LOGW("Framework requested delivery of an input event to channel '%s' but it " +                    "is not registered with the input dispatcher.", +                    inputTarget.inputChannel->getName().string()); +        } +    } +} + +void InputDispatcher::startFindingTargetsLocked() { +    mCurrentInputTargetsValid = false; +    mCurrentInputTargets.clear(); +    mCurrentInputChannel.clear(); +    mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE; +} + +void InputDispatcher::finishFindingTargetsLocked(const InputWindow* window) { +    mCurrentInputWindowType = window->layoutParamsType; +    mCurrentInputChannel = window->inputChannel; +    mCurrentInputTargetsValid = true; +} + +int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime, +        const EventEntry* entry, const InputApplication* application, const InputWindow* window, +        nsecs_t* nextWakeupTime) { +    if (application == NULL && window == NULL) { +        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) { +#if DEBUG_FOCUS +            LOGD("Waiting for system to become ready for input."); +#endif +            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY; +            mInputTargetWaitStartTime = currentTime; +            mInputTargetWaitTimeoutTime = LONG_LONG_MAX; +            mInputTargetWaitTimeoutExpired = false; +        } +    } else { +        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) { +#if DEBUG_FOCUS +            LOGD("Waiting for application to become ready for input: name=%s, window=%s", +                    application ? application->name.string() : "<unknown>", +                    window ? window->inputChannel->getName().string() : "<unknown>"); +#endif +            nsecs_t timeout = window ? window->dispatchingTimeout : +                application ? application->dispatchingTimeout : DEFAULT_INPUT_DISPATCHING_TIMEOUT; + +            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY; +            mInputTargetWaitStartTime = currentTime; +            mInputTargetWaitTimeoutTime = currentTime + timeout; +            mInputTargetWaitTimeoutExpired = false; +        } +    } + +    if (mInputTargetWaitTimeoutExpired) { +        return INPUT_EVENT_INJECTION_TIMED_OUT; +    } + +    if (currentTime >= mInputTargetWaitTimeoutTime) { +        LOGI("Application is not ready for input: name=%s, window=%s," +                "%01.1fms since event, %01.1fms since wait started", +                application ? application->name.string() : "<unknown>", +                window ? window->inputChannel->getName().string() : "<unknown>", +                (currentTime - entry->eventTime) / 1000000.0, +                (currentTime - mInputTargetWaitStartTime) / 1000000.0); + +        CommandEntry* commandEntry = postCommandLocked( +                & InputDispatcher::doTargetsNotReadyTimeoutLockedInterruptible); +        if (application) { +            commandEntry->inputApplicationHandle = application->handle; +        } +        if (window) { +            commandEntry->inputChannel = window->inputChannel; +        } + +        // Force poll loop to wake up immediately on next iteration once we get the +        // ANR response back from the policy. +        *nextWakeupTime = LONG_LONG_MIN; +        return INPUT_EVENT_INJECTION_PENDING; +    } else { +        // Force poll loop to wake up when timeout is due. +        if (mInputTargetWaitTimeoutTime < *nextWakeupTime) { +            *nextWakeupTime = mInputTargetWaitTimeoutTime; +        } +        return INPUT_EVENT_INJECTION_PENDING; +    } +} + +void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout) { +    if (newTimeout > 0) { +        // Extend the timeout. +        mInputTargetWaitTimeoutTime = now() + newTimeout; +    } else { +        // Give up. +        mInputTargetWaitTimeoutExpired = true; +    } +} + +nsecs_t InputDispatcher::getTimeSpentWaitingForApplicationWhileFindingTargetsLocked( +        nsecs_t currentTime) { +    if (mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) { +        return currentTime - mInputTargetWaitStartTime; +    } +    return 0; +} + +void InputDispatcher::resetANRTimeoutsLocked() { +#if DEBUG_FOCUS +        LOGD("Resetting ANR timeouts."); +#endif + +    // Reset timeouts for all active connections. +    nsecs_t currentTime = now(); +    for (size_t i = 0; i < mActiveConnections.size(); i++) { +        Connection* connection = mActiveConnections[i]; +        connection->resetTimeout(currentTime); +    } + +    // Reset input target wait timeout. +    mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE; +} + +int32_t InputDispatcher::findFocusedWindowLocked(nsecs_t currentTime, const EventEntry* entry, +        nsecs_t* nextWakeupTime, InputWindow** outWindow) { +    *outWindow = NULL; +    mCurrentInputTargets.clear(); + +    int32_t injectionResult; + +    // If there is no currently focused window and no focused application +    // then drop the event. +    if (! mFocusedWindow) { +        if (mFocusedApplication) { +#if DEBUG_FOCUS +            LOGD("Waiting because there is no focused window but there is a " +                    "focused application that may eventually add a window: '%s'.", +                    mFocusedApplication->name.string()); +#endif +            injectionResult = handleTargetsNotReadyLocked(currentTime, entry, +                    mFocusedApplication, NULL, nextWakeupTime); +            goto Unresponsive; +        } + +        LOGI("Dropping event because there is no focused window or focused application."); +        injectionResult = INPUT_EVENT_INJECTION_FAILED; +        goto Failed; +    } + +    // Check permissions. +    if (! checkInjectionPermission(mFocusedWindow, entry->injectorPid, entry->injectorUid)) { +        injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; +        goto Failed; +    } + +    // If the currently focused window is paused then keep waiting. +    if (mFocusedWindow->paused) { +#if DEBUG_FOCUS +        LOGD("Waiting because focused window is paused."); +#endif +        injectionResult = handleTargetsNotReadyLocked(currentTime, entry, +                mFocusedApplication, mFocusedWindow, nextWakeupTime); +        goto Unresponsive; +    } + +    // Success!  Output targets. +    injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; +    *outWindow = mFocusedWindow; +    addWindowTargetLocked(mFocusedWindow, InputTarget::FLAG_SYNC, +            getTimeSpentWaitingForApplicationWhileFindingTargetsLocked(currentTime)); + +    // Done. +Failed: +Unresponsive: +#if DEBUG_FOCUS +    LOGD("findFocusedWindow finished: injectionResult=%d", +            injectionResult); +    logDispatchStateLocked(); +#endif +    return injectionResult; +} + +int32_t InputDispatcher::findTouchedWindowLocked(nsecs_t currentTime, const MotionEntry* entry, +        nsecs_t* nextWakeupTime, InputWindow** outWindow) { +    enum InjectionPermission { +        INJECTION_PERMISSION_UNKNOWN, +        INJECTION_PERMISSION_GRANTED, +        INJECTION_PERMISSION_DENIED +    }; + +    *outWindow = NULL; +    mCurrentInputTargets.clear(); + +    nsecs_t startTime = now(); + +    // For security reasons, we defer updating the touch state until we are sure that +    // event injection will be allowed. +    // +    // FIXME In the original code, screenWasOff could never be set to true. +    //       The reason is that the POLICY_FLAG_WOKE_HERE +    //       and POLICY_FLAG_BRIGHT_HERE flags were set only when preprocessing raw +    //       EV_KEY, EV_REL and EV_ABS events.  As it happens, the touch event was +    //       actually enqueued using the policyFlags that appeared in the final EV_SYN +    //       events upon which no preprocessing took place.  So policyFlags was always 0. +    //       In the new native input dispatcher we're a bit more careful about event +    //       preprocessing so the touches we receive can actually have non-zero policyFlags. +    //       Unfortunately we obtain undesirable behavior. +    // +    //       Here's what happens: +    // +    //       When the device dims in anticipation of going to sleep, touches +    //       in windows which have FLAG_TOUCHABLE_WHEN_WAKING cause +    //       the device to brighten and reset the user activity timer. +    //       Touches on other windows (such as the launcher window) +    //       are dropped.  Then after a moment, the device goes to sleep.  Oops. +    // +    //       Also notice how screenWasOff was being initialized using POLICY_FLAG_BRIGHT_HERE +    //       instead of POLICY_FLAG_WOKE_HERE... +    // +    bool screenWasOff = false; // original policy: policyFlags & POLICY_FLAG_BRIGHT_HERE; + +    int32_t action = entry->action; + +    // Update the touch state as needed based on the properties of the touch event. +    int32_t injectionResult; +    InjectionPermission injectionPermission; +    if (action == AMOTION_EVENT_ACTION_DOWN) { +        /* Case 1: ACTION_DOWN */ + +        InputWindow* newTouchedWindow = NULL; +        mTempTouchedOutsideTargets.clear(); + +        int32_t x = int32_t(entry->firstSample.pointerCoords[0].x); +        int32_t y = int32_t(entry->firstSample.pointerCoords[0].y); +        InputWindow* topErrorWindow = NULL; +        bool obscured = false; + +        // Traverse windows from front to back to find touched window and outside targets. +        size_t numWindows = mWindows.size(); +        for (size_t i = 0; i < numWindows; i++) { +            InputWindow* window = & mWindows.editItemAt(i); +            int32_t flags = window->layoutParamsFlags; + +            if (flags & InputWindow::FLAG_SYSTEM_ERROR) { +                if (! topErrorWindow) { +                    topErrorWindow = window; +                } +            } + +            if (window->visible) { +                if (! (flags & InputWindow::FLAG_NOT_TOUCHABLE)) { +                    bool isTouchModal = (flags & (InputWindow::FLAG_NOT_FOCUSABLE +                            | InputWindow::FLAG_NOT_TOUCH_MODAL)) == 0; +                    if (isTouchModal || window->touchableAreaContainsPoint(x, y)) { +                        if (! screenWasOff || flags & InputWindow::FLAG_TOUCHABLE_WHEN_WAKING) { +                            newTouchedWindow = window; +                            obscured = isWindowObscuredLocked(window); +                        } +                        break; // found touched window, exit window loop +                    } +                } + +                if (flags & InputWindow::FLAG_WATCH_OUTSIDE_TOUCH) { +                    OutsideTarget outsideTarget; +                    outsideTarget.window = window; +                    outsideTarget.obscured = isWindowObscuredLocked(window); +                    mTempTouchedOutsideTargets.push(outsideTarget); +                } +            } +        } + +        // If there is an error window but it is not taking focus (typically because +        // it is invisible) then wait for it.  Any other focused window may in +        // fact be in ANR state. +        if (topErrorWindow && newTouchedWindow != topErrorWindow) { +#if DEBUG_FOCUS +            LOGD("Waiting because system error window is pending."); +#endif +            injectionResult = handleTargetsNotReadyLocked(currentTime, entry, +                    NULL, NULL, nextWakeupTime); +            injectionPermission = INJECTION_PERMISSION_UNKNOWN; +            goto Unresponsive; +        } + +        // If we did not find a touched window then fail. +        if (! newTouchedWindow) { +            if (mFocusedApplication) { +#if DEBUG_FOCUS +                LOGD("Waiting because there is no touched window but there is a " +                        "focused application that may eventually add a new window: '%s'.", +                        mFocusedApplication->name.string()); +#endif +                injectionResult = handleTargetsNotReadyLocked(currentTime, entry, +                        mFocusedApplication, NULL, nextWakeupTime); +                injectionPermission = INJECTION_PERMISSION_UNKNOWN; +                goto Unresponsive; +            } + +            LOGI("Dropping event because there is no touched window or focused application."); +            injectionResult = INPUT_EVENT_INJECTION_FAILED; +            injectionPermission = INJECTION_PERMISSION_UNKNOWN; +            goto Failed; +        } + +        // Check permissions. +        if (! checkInjectionPermission(newTouchedWindow, entry->injectorPid, entry->injectorUid)) { +            injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; +            injectionPermission = INJECTION_PERMISSION_DENIED; +            goto Failed; +        } + +        // If the touched window is paused then keep waiting. +        if (newTouchedWindow->paused) { +#if DEBUG_INPUT_DISPATCHER_POLICY +            LOGD("Waiting because touched window is paused."); +#endif +            injectionResult = handleTargetsNotReadyLocked(currentTime, entry, +                    NULL, newTouchedWindow, nextWakeupTime); +            injectionPermission = INJECTION_PERMISSION_GRANTED; +            goto Unresponsive; +        } + +        // Success!  Update the touch dispatch state for real. +        releaseTouchedWindowLocked(); + +        mTouchedWindow = newTouchedWindow; +        mTouchedWindowIsObscured = obscured; + +        if (newTouchedWindow->hasWallpaper) { +            mTouchedWallpaperWindows.appendVector(mWallpaperWindows); +        } +    } else { +        /* Case 2: Everything but ACTION_DOWN */ + +        // Check permissions. +        if (! checkInjectionPermission(mTouchedWindow, entry->injectorPid, entry->injectorUid)) { +            injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED; +            injectionPermission = INJECTION_PERMISSION_DENIED; +            goto Failed; +        } + +        // If the pointer is not currently down, then ignore the event. +        if (! mTouchDown) { +            LOGI("Dropping event because the pointer is not down."); +            injectionResult = INPUT_EVENT_INJECTION_FAILED; +            injectionPermission = INJECTION_PERMISSION_GRANTED; +            goto Failed; +        } + +        // If there is no currently touched window then fail. +        if (! mTouchedWindow) { +#if DEBUG_INPUT_DISPATCHER_POLICY +            LOGD("Dropping event because there is no touched window to receive it."); +#endif +            injectionResult = INPUT_EVENT_INJECTION_FAILED; +            injectionPermission = INJECTION_PERMISSION_GRANTED; +            goto Failed; +        } + +        // If the touched window is paused then keep waiting. +        if (mTouchedWindow->paused) { +#if DEBUG_INPUT_DISPATCHER_POLICY +            LOGD("Waiting because touched window is paused."); +#endif +            injectionResult = handleTargetsNotReadyLocked(currentTime, entry, +                    NULL, mTouchedWindow, nextWakeupTime); +            injectionPermission = INJECTION_PERMISSION_GRANTED; +            goto Unresponsive; +        } +    } + +    // Success!  Output targets. +    injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; +    injectionPermission = INJECTION_PERMISSION_GRANTED; + +    { +        size_t numWallpaperWindows = mTouchedWallpaperWindows.size(); +        for (size_t i = 0; i < numWallpaperWindows; i++) { +            addWindowTargetLocked(mTouchedWallpaperWindows[i], +                    InputTarget::FLAG_WINDOW_IS_OBSCURED, 0); +        } + +        size_t numOutsideTargets = mTempTouchedOutsideTargets.size(); +        for (size_t i = 0; i < numOutsideTargets; i++) { +            const OutsideTarget& outsideTarget = mTempTouchedOutsideTargets[i]; +            int32_t outsideTargetFlags = InputTarget::FLAG_OUTSIDE; +            if (outsideTarget.obscured) { +                outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; +            } +            addWindowTargetLocked(outsideTarget.window, outsideTargetFlags, 0); +        } +        mTempTouchedOutsideTargets.clear(); + +        int32_t targetFlags = InputTarget::FLAG_SYNC; +        if (mTouchedWindowIsObscured) { +            targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED; +        } +        addWindowTargetLocked(mTouchedWindow, targetFlags, +                getTimeSpentWaitingForApplicationWhileFindingTargetsLocked(currentTime)); +        *outWindow = mTouchedWindow; +    } + +Failed: +    // Check injection permission once and for all. +    if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) { +        if (checkInjectionPermission(action == AMOTION_EVENT_ACTION_DOWN ? NULL : mTouchedWindow, +                entry->injectorPid, entry->injectorUid)) { +            injectionPermission = INJECTION_PERMISSION_GRANTED; +        } else { +            injectionPermission = INJECTION_PERMISSION_DENIED; +        } +    } + +    // Update final pieces of touch state if the injector had permission. +    if (injectionPermission == INJECTION_PERMISSION_GRANTED) { +        if (action == AMOTION_EVENT_ACTION_DOWN) { +            if (mTouchDown) { +                // This is weird.  We got a down but we thought it was already down! +                LOGW("Pointer down received while already down."); +            } else { +                mTouchDown = true; +            } + +            if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { +                // Since we failed to identify a target for this touch down, we may still +                // be holding on to an earlier target from a previous touch down.  Release it. +                releaseTouchedWindowLocked(); +            } +        } else if (action == AMOTION_EVENT_ACTION_UP) { +            mTouchDown = false; +            releaseTouchedWindowLocked(); +        } +    } else { +        LOGW("Not updating touch focus because injection was denied."); +    } + +Unresponsive: +#if DEBUG_FOCUS +    LOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d", +            injectionResult, injectionPermission); +    logDispatchStateLocked(); +#endif +    return injectionResult; +} + +void InputDispatcher::releaseTouchedWindowLocked() { +    mTouchedWindow = NULL; +    mTouchedWindowIsObscured = false; +    mTouchedWallpaperWindows.clear(); +} + +void InputDispatcher::addWindowTargetLocked(const InputWindow* window, int32_t targetFlags, +        nsecs_t timeSpentWaitingForApplication) { +    mCurrentInputTargets.push(); + +    InputTarget& target = mCurrentInputTargets.editTop(); +    target.inputChannel = window->inputChannel; +    target.flags = targetFlags; +    target.timeout = window->dispatchingTimeout; +    target.timeSpentWaitingForApplication = timeSpentWaitingForApplication; +    target.xOffset = - window->frameLeft; +    target.yOffset = - window->frameTop; +} + +void InputDispatcher::addMonitoringTargetsLocked() { +    for (size_t i = 0; i < mMonitoringChannels.size(); i++) { +        mCurrentInputTargets.push(); + +        InputTarget& target = mCurrentInputTargets.editTop(); +        target.inputChannel = mMonitoringChannels[i]; +        target.flags = 0; +        target.timeout = -1; +        target.timeSpentWaitingForApplication = 0; +        target.xOffset = 0; +        target.yOffset = 0; +    } +} + +bool InputDispatcher::checkInjectionPermission(const InputWindow* window, +        int32_t injectorPid, int32_t injectorUid) { +    if (injectorUid > 0 && (window == NULL || window->ownerUid != injectorUid)) { +        bool result = mPolicy->checkInjectEventsPermissionNonReentrant(injectorPid, injectorUid); +        if (! result) { +            if (window) { +                LOGW("Permission denied: injecting event from pid %d uid %d to window " +                        "with input channel %s owned by uid %d", +                        injectorPid, injectorUid, window->inputChannel->getName().string(), +                        window->ownerUid); +            } else { +                LOGW("Permission denied: injecting event from pid %d uid %d", +                        injectorPid, injectorUid); +            } +            return false; +        } +    } +    return true; +} + +bool InputDispatcher::isWindowObscuredLocked(const InputWindow* window) { +    size_t numWindows = mWindows.size(); +    for (size_t i = 0; i < numWindows; i++) { +        const InputWindow* other = & mWindows.itemAt(i); +        if (other == window) { +            break; +        } +        if (other->visible && window->visibleFrameIntersects(other)) { +            return true; +        } +    } +    return false; +} + +void InputDispatcher::pokeUserActivityLocked(nsecs_t eventTime, +        int32_t windowType, int32_t eventType) { +    CommandEntry* commandEntry = postCommandLocked( +            & InputDispatcher::doPokeUserActivityLockedInterruptible); +    commandEntry->eventTime = eventTime; +    commandEntry->windowType = windowType; +    commandEntry->userActivityEventType = eventType; +} + +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, +            toString(resumeWithAppendedMotionSample)); +#endif + +    // Skip this event if the connection status is not normal. +    // We don't want to enqueue additional outbound events if the connection is broken or +    // not responding. +    if (connection->status != Connection::STATUS_NORMAL) { +        LOGW("channel '%s' ~ Dropping event because the channel status is %s", +                connection->getInputChannelName(), connection->getStatusLabel()); + +        // If the connection is not responding but the user is poking the application anyways, +        // retrigger the original timeout. +        if (connection->status == Connection::STATUS_NOT_RESPONDING) { +            timeoutDispatchCycleLocked(currentTime, connection); +        } +        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; +        } +    } + +    // Bring the input state back in line with reality in case it drifted off during an ANR. +    if (connection->inputState.isOutOfSync()) { +        mTempCancelationEvents.clear(); +        connection->inputState.synthesizeCancelationEvents(& mAllocator, mTempCancelationEvents); +        connection->inputState.resetOutOfSync(); + +        if (! mTempCancelationEvents.isEmpty()) { +            LOGI("channel '%s' ~ Generated %d cancelation events to bring channel back in sync " +                    "with reality.", +                    connection->getInputChannelName(), mTempCancelationEvents.size()); + +            for (size_t i = 0; i < mTempCancelationEvents.size(); i++) { +                EventEntry* cancelationEventEntry = mTempCancelationEvents.itemAt(i); +                switch (cancelationEventEntry->type) { +                case EventEntry::TYPE_KEY: +                    logOutboundKeyDetailsLocked("  ", +                            static_cast<KeyEntry*>(cancelationEventEntry)); +                    break; +                case EventEntry::TYPE_MOTION: +                    logOutboundMotionDetailsLocked("  ", +                            static_cast<MotionEntry*>(cancelationEventEntry)); +                    break; +                } + +                DispatchEntry* cancelationDispatchEntry = +                        mAllocator.obtainDispatchEntry(cancelationEventEntry, +                        0, inputTarget->xOffset, inputTarget->yOffset, inputTarget->timeout); +                connection->outboundQueue.enqueueAtTail(cancelationDispatchEntry); + +                mAllocator.releaseEventEntry(cancelationEventEntry); +            } +        } +    } + +    // This is a new event. +    // Enqueue a new dispatch entry onto the outbound queue for this connection. +    DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry, // increments ref +            inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset, +            inputTarget->timeout); +    if (dispatchEntry->isSyncTarget()) { +        eventEntry->pendingSyncDispatches += 1; +    } + +    // Handle the case where we could not stream a new motion sample because the consumer has +    // already consumed the motion event (otherwise the corresponding dispatch entry would +    // still be in the outbound queue for this connection).  We set the head motion sample +    // to the list starting with the newly appended motion sample. +    if (resumeWithAppendedMotionSample) { +#if DEBUG_BATCHING +        LOGD("channel '%s' ~ Preparing a new dispatch cycle for additional motion samples " +                "that cannot be streamed because the motion event has already been consumed.", +                connection->getInputChannelName()); +#endif +        MotionSample* appendedMotionSample = static_cast<MotionEntry*>(eventEntry)->lastSample; +        dispatchEntry->headMotionSample = appendedMotionSample; +    } + +    // Enqueue the dispatch entry. +    connection->outboundQueue.enqueueAtTail(dispatchEntry); + +    // If the outbound queue was previously empty, start the dispatch cycle going. +    if (wasEmpty) { +        activateConnectionLocked(connection.get()); +        startDispatchCycleLocked(currentTime, connection, +                inputTarget->timeSpentWaitingForApplication); +    } +} + +void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, +        const sp<Connection>& connection, nsecs_t timeSpentWaitingForApplication) { +#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.headSentinel.next; +    assert(! dispatchEntry->inProgress); + +    // Mark the dispatch entry as in progress. +    dispatchEntry->inProgress = true; + +    // Update the connection's input state. +    InputState::Consistency consistency = connection->inputState.trackEvent( +            dispatchEntry->eventEntry); + +#if FILTER_INPUT_EVENTS +    // Filter out inconsistent sequences of input events. +    // The input system may drop or inject events in a way that could violate implicit +    // invariants on input state and potentially cause an application to crash +    // or think that a key or pointer is stuck down.  Technically we make no guarantees +    // of consistency but it would be nice to improve on this where possible. +    // XXX: This code is a proof of concept only.  Not ready for prime time. +    if (consistency == InputState::TOLERABLE) { +#if DEBUG_DISPATCH_CYCLE +        LOGD("channel '%s' ~ Sending an event that is inconsistent with the connection's " +                "current input state but that is likely to be tolerated by the application.", +                connection->getInputChannelName()); +#endif +    } else if (consistency == InputState::BROKEN) { +        LOGI("channel '%s' ~ Dropping an event that is inconsistent with the connection's " +                "current input state and that is likely to cause the application to crash.", +                connection->getInputChannelName()); +        startNextDispatchCycleLocked(currentTime, connection); +        return; +    } +#endif + +    // Publish the event. +    status_t status; +    switch (dispatchEntry->eventEntry->type) { +    case EventEntry::TYPE_KEY: { +        KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry); + +        // Apply target flags. +        int32_t action = keyEntry->action; +        int32_t flags = keyEntry->flags; +        if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) { +            flags |= AKEY_EVENT_FLAG_CANCELED; +        } + +        // Publish the key event. +        status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->source, +                action, flags, keyEntry->keyCode, keyEntry->scanCode, +                keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime, +                keyEntry->eventTime); + +        if (status) { +            LOGE("channel '%s' ~ Could not publish key event, " +                    "status=%d", connection->getInputChannelName(), status); +            abortDispatchCycleLocked(currentTime, connection, true /*broken*/); +            return; +        } +        break; +    } + +    case EventEntry::TYPE_MOTION: { +        MotionEntry* motionEntry = static_cast<MotionEntry*>(dispatchEntry->eventEntry); + +        // Apply target flags. +        int32_t action = motionEntry->action; +        int32_t flags = motionEntry->flags; +        if (dispatchEntry->targetFlags & InputTarget::FLAG_OUTSIDE) { +            action = AMOTION_EVENT_ACTION_OUTSIDE; +        } +        if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) { +            action = AMOTION_EVENT_ACTION_CANCEL; +        } +        if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) { +            flags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; +        } + +        // If headMotionSample is non-NULL, then it points to the first new sample that we +        // were unable to dispatch during the previous cycle so we resume dispatching from +        // that point in the list of motion samples. +        // Otherwise, we just start from the first sample of the motion event. +        MotionSample* firstMotionSample = dispatchEntry->headMotionSample; +        if (! firstMotionSample) { +            firstMotionSample = & motionEntry->firstSample; +        } + +        // Set the X and Y offset depending on the input source. +        float xOffset, yOffset; +        if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) { +            xOffset = dispatchEntry->xOffset; +            yOffset = dispatchEntry->yOffset; +        } else { +            xOffset = 0.0f; +            yOffset = 0.0f; +        } + +        // Publish the motion event and the first motion sample. +        status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId, +                motionEntry->source, action, flags, motionEntry->edgeFlags, motionEntry->metaState, +                xOffset, yOffset, +                motionEntry->xPrecision, motionEntry->yPrecision, +                motionEntry->downTime, firstMotionSample->eventTime, +                motionEntry->pointerCount, motionEntry->pointerIds, +                firstMotionSample->pointerCoords); + +        if (status) { +            LOGE("channel '%s' ~ Could not publish motion event, " +                    "status=%d", connection->getInputChannelName(), status); +            abortDispatchCycleLocked(currentTime, connection, true /*broken*/); +            return; +        } + +        // Append additional motion samples. +        MotionSample* nextMotionSample = firstMotionSample->next; +        for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) { +            status = connection->inputPublisher.appendMotionSample( +                    nextMotionSample->eventTime, nextMotionSample->pointerCoords); +            if (status == NO_MEMORY) { +#if DEBUG_DISPATCH_CYCLE +                    LOGD("channel '%s' ~ Shared memory buffer full.  Some motion samples will " +                            "be sent in the next dispatch cycle.", +                            connection->getInputChannelName()); +#endif +                break; +            } +            if (status != OK) { +                LOGE("channel '%s' ~ Could not append motion sample " +                        "for a reason other than out of memory, status=%d", +                        connection->getInputChannelName(), status); +                abortDispatchCycleLocked(currentTime, connection, true /*broken*/); +                return; +            } +        } + +        // Remember the next motion sample that we could not dispatch, in case we ran out +        // of space in the shared memory buffer. +        dispatchEntry->tailMotionSample = nextMotionSample; +        break; +    } + +    default: { +        assert(false); +    } +    } + +    // Send the dispatch signal. +    status = connection->inputPublisher.sendDispatchSignal(); +    if (status) { +        LOGE("channel '%s' ~ Could not send dispatch signal, status=%d", +                connection->getInputChannelName(), status); +        abortDispatchCycleLocked(currentTime, connection, true /*broken*/); +        return; +    } + +    // Record information about the newly started dispatch cycle. +    connection->lastEventTime = dispatchEntry->eventEntry->eventTime; +    connection->lastDispatchTime = currentTime; + +    nsecs_t timeout = dispatchEntry->timeout - timeSpentWaitingForApplication; +    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; +    } + +    startNextDispatchCycleLocked(currentTime, connection); +} + +void InputDispatcher::startNextDispatchCycleLocked(nsecs_t currentTime, +        const sp<Connection>& connection) { +    // Start the next dispatch cycle for this connection. +    while (! connection->outboundQueue.isEmpty()) { +        DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.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, 0); +                return; +            } +            // Finished. +            connection->outboundQueue.dequeueAtHead(); +            if (dispatchEntry->isSyncTarget()) { +                decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry); +            } +            mAllocator.releaseDispatchEntry(dispatchEntry); +        } else { +            // If the head is not in progress, then we must have already dequeued the in +            // progress event, which means we actually aborted it (due to ANR). +            // So just start the next event for this connection. +            startDispatchCycleLocked(currentTime, connection, 0); +            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) { +        // Enter the not responding state. +        connection->status = Connection::STATUS_NOT_RESPONDING; +        connection->lastANRTime = currentTime; +    } else if (connection->status != Connection::STATUS_NOT_RESPONDING) { +        // Connection is broken or dead. +        return; +    } + +    // Notify other system components. +    // This enqueues a command which will eventually call resumeAfterTimeoutDispatchCycleLocked. +    onDispatchCycleANRLocked(currentTime, connection); +} + +void InputDispatcher::resumeAfterTimeoutDispatchCycleLocked(nsecs_t currentTime, +        const sp<Connection>& connection, nsecs_t newTimeout) { +#if DEBUG_DISPATCH_CYCLE +    LOGD("channel '%s' ~ resumeAfterTimeoutDispatchCycleLocked - newTimeout=%lld", +            connection->getInputChannelName(), newTimeout); +#endif + +    if (connection->status != Connection::STATUS_NOT_RESPONDING) { +        return; +    } + +    if (newTimeout > 0) { +        // The system has decided to give the application some more time. +        // Keep waiting synchronously and resume normal dispatch. +        connection->status = Connection::STATUS_NORMAL; +        connection->setNextTimeoutTime(currentTime, newTimeout); +    } else { +        // The system is about to throw up an ANR dialog and has requested that we abort dispatch. +        // Reset the timeout. +        connection->nextTimeoutTime = LONG_LONG_MAX; + +        // Input state will no longer be realistic. +        connection->inputState.setOutOfSync(); + +        if (! connection->outboundQueue.isEmpty()) { +            // Make the current pending dispatch asynchronous (if it isn't already) so that +            // subsequent events can be delivered to the ANR dialog or to another application. +            DispatchEntry* currentDispatchEntry = connection->outboundQueue.headSentinel.next; +            currentDispatchEntry->preemptSyncTarget(); + +            // Drain all but the first entry in the outbound queue.  We keep the first entry +            // since that is the one that dispatch is stuck on.  We throw away the others +            // so that we don't spam the application with stale messages if it eventually +            // wakes up and recovers from the ANR. +            drainOutboundQueueLocked(connection.get(), currentDispatchEntry->next); +        } +    } +} + +void InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime, +        const sp<Connection>& connection, bool broken) { +#if DEBUG_DISPATCH_CYCLE +    LOGD("channel '%s' ~ abortDispatchCycle - broken=%s", +            connection->getInputChannelName(), toString(broken)); +#endif + +    // Clear the pending timeout. +    connection->nextTimeoutTime = LONG_LONG_MAX; + +    // Input state will no longer be realistic. +    connection->inputState.setOutOfSync(); + +    // Clear the outbound queue. +    drainOutboundQueueLocked(connection.get(), connection->outboundQueue.headSentinel.next); + +    // 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); +        } +    } +} + +void InputDispatcher::drainOutboundQueueLocked(Connection* connection, +        DispatchEntry* firstDispatchEntryToDrain) { +    for (DispatchEntry* dispatchEntry = firstDispatchEntryToDrain; +            dispatchEntry != & connection->outboundQueue.tailSentinel;) { +        DispatchEntry* next = dispatchEntry->next; +        connection->outboundQueue.dequeue(dispatchEntry); + +        if (dispatchEntry->isSyncTarget()) { +            decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry); +        } +        mAllocator.releaseDispatchEntry(dispatchEntry); + +        dispatchEntry = next; +    } + +    if (connection->outboundQueue.isEmpty()) { +        deactivateConnectionLocked(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 needWake; +    { // acquire lock +        AutoMutex _l(mLock); + +        ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry(eventTime); +        needWake = enqueueInboundEventLocked(newEntry); +    } // release lock + +    if (needWake) { +        mPollLoop->wake(); +    } +} + +void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source, +        uint32_t policyFlags, int32_t action, int32_t flags, +        int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) { +#if DEBUG_INBOUND_EVENT_DETAILS +    LOGD("notifyKey - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, action=0x%x, " +            "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld", +            eventTime, deviceId, source, policyFlags, action, flags, +            keyCode, scanCode, metaState, downTime); +#endif + +    bool needWake; +    { // acquire lock +        AutoMutex _l(mLock); + +        int32_t repeatCount = 0; +        KeyEntry* newEntry = mAllocator.obtainKeyEntry(eventTime, +                deviceId, source, policyFlags, action, flags, keyCode, scanCode, +                metaState, repeatCount, downTime); + +        needWake = enqueueInboundEventLocked(newEntry); +    } // release lock + +    if (needWake) { +        mPollLoop->wake(); +    } +} + +void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source, +        uint32_t policyFlags, int32_t action, int32_t flags, int32_t metaState, int32_t edgeFlags, +        uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords, +        float xPrecision, float yPrecision, nsecs_t downTime) { +#if DEBUG_INBOUND_EVENT_DETAILS +    LOGD("notifyMotion - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, " +            "action=0x%x, flags=0x%x, metaState=0x%x, edgeFlags=0x%x, " +            "xPrecision=%f, yPrecision=%f, downTime=%lld", +            eventTime, deviceId, source, policyFlags, action, flags, metaState, edgeFlags, +            xPrecision, yPrecision, downTime); +    for (uint32_t i = 0; i < pointerCount; i++) { +        LOGD("  Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, " +                "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, " +                "orientation=%f", +                i, pointerIds[i], pointerCoords[i].x, pointerCoords[i].y, +                pointerCoords[i].pressure, pointerCoords[i].size, +                pointerCoords[i].touchMajor, pointerCoords[i].touchMinor, +                pointerCoords[i].toolMajor, pointerCoords[i].toolMinor, +                pointerCoords[i].orientation); +    } +#endif + +    bool needWake; +    { // acquire lock +        AutoMutex _l(mLock); + +        // Attempt batching and streaming of move events. +        if (action == AMOTION_EVENT_ACTION_MOVE) { +            // BATCHING CASE +            // +            // Try to append a move sample to the tail of the inbound queue for this device. +            // Give up if we encounter a non-move motion event for this device since that +            // means we cannot append any new samples until a new motion event has started. +            for (EventEntry* entry = mInboundQueue.tailSentinel.prev; +                    entry != & mInboundQueue.headSentinel; entry = entry->prev) { +                if (entry->type != EventEntry::TYPE_MOTION) { +                    // Keep looking for motion events. +                    continue; +                } + +                MotionEntry* motionEntry = static_cast<MotionEntry*>(entry); +                if (motionEntry->deviceId != deviceId) { +                    // Keep looking for this device. +                    continue; +                } + +                if (motionEntry->action != AMOTION_EVENT_ACTION_MOVE +                        || motionEntry->pointerCount != pointerCount +                        || motionEntry->isInjected()) { +                    // Last motion event in the queue for this device is not compatible for +                    // appending new samples.  Stop here. +                    goto NoBatchingOrStreaming; +                } + +                // The last motion event is a move and is compatible for appending. +                // Do the batching magic. +                mAllocator.appendMotionSample(motionEntry, eventTime, pointerCoords); +#if DEBUG_BATCHING +                LOGD("Appended motion sample onto batch for most recent " +                        "motion event for this device in the inbound queue."); +#endif +                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.tailSentinel.prev; +                        if (dispatchEntry->isSyncTarget()) { +                            if (dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION) { +                                goto NoBatchingOrStreaming; +                            } + +                            MotionEntry* syncedMotionEntry = static_cast<MotionEntry*>( +                                    dispatchEntry->eventEntry); +                            if (syncedMotionEntry->action != AMOTION_EVENT_ACTION_MOVE +                                    || syncedMotionEntry->deviceId != deviceId +                                    || syncedMotionEntry->pointerCount != pointerCount +                                    || syncedMotionEntry->isInjected()) { +                                goto NoBatchingOrStreaming; +                            } + +                            // Found synced move entry.  Append sample and resume dispatch. +                            mAllocator.appendMotionSample(syncedMotionEntry, eventTime, +                                    pointerCoords); +    #if DEBUG_BATCHING +                            LOGD("Appended motion sample onto batch for most recent synchronously " +                                    "dispatched motion event for this device in the outbound queues."); +    #endif +                            nsecs_t currentTime = now(); +                            dispatchEventToCurrentInputTargetsLocked(currentTime, syncedMotionEntry, +                                    true /*resumeWithAppendedMotionSample*/); + +                            runCommandsLockedInterruptible(); +                            return; // done! +                        } +                    } +                } +            } + +NoBatchingOrStreaming:; +        } + +        // Just enqueue a new motion event. +        MotionEntry* newEntry = mAllocator.obtainMotionEntry(eventTime, +                deviceId, source, policyFlags, action, flags, metaState, edgeFlags, +                xPrecision, yPrecision, downTime, +                pointerCount, pointerIds, pointerCoords); + +        needWake = enqueueInboundEventLocked(newEntry); +    } // release lock + +    if (needWake) { +        mPollLoop->wake(); +    } +} + +int32_t InputDispatcher::injectInputEvent(const InputEvent* event, +        int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) { +#if DEBUG_INBOUND_EVENT_DETAILS +    LOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, " +            "syncMode=%d, timeoutMillis=%d", +            event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis); +#endif + +    nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis); + +    EventEntry* injectedEntry; +    bool needWake; +    { // acquire lock +        AutoMutex _l(mLock); + +        injectedEntry = createEntryFromInjectedInputEventLocked(event); +        if (! injectedEntry) { +            return INPUT_EVENT_INJECTION_FAILED; +        } + +        injectedEntry->refCount += 1; +        injectedEntry->injectorPid = injectorPid; +        injectedEntry->injectorUid = injectorUid; + +        if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) { +            injectedEntry->injectionIsAsync = true; +        } + +        needWake = enqueueInboundEventLocked(injectedEntry); +    } // release lock + +    if (needWake) { +        mPollLoop->wake(); +    } + +    int32_t injectionResult; +    { // acquire lock +        AutoMutex _l(mLock); + +        if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) { +            injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; +        } else { +            for (;;) { +                injectionResult = injectedEntry->injectionResult; +                if (injectionResult != INPUT_EVENT_INJECTION_PENDING) { +                    break; +                } + +                nsecs_t remainingTimeout = endTime - now(); +                if (remainingTimeout <= 0) { +#if DEBUG_INJECTION +                    LOGD("injectInputEvent - Timed out waiting for injection result " +                            "to become available."); +#endif +                    injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT; +                    break; +                } + +                mInjectionResultAvailableCondition.waitRelative(mLock, remainingTimeout); +            } + +            if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED +                    && syncMode == INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED) { +                while (injectedEntry->pendingSyncDispatches != 0) { +#if DEBUG_INJECTION +                    LOGD("injectInputEvent - Waiting for %d pending synchronous dispatches.", +                            injectedEntry->pendingSyncDispatches); +#endif +                    nsecs_t remainingTimeout = endTime - now(); +                    if (remainingTimeout <= 0) { +#if DEBUG_INJECTION +                    LOGD("injectInputEvent - Timed out waiting for pending synchronous " +                            "dispatches to finish."); +#endif +                        injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT; +                        break; +                    } + +                    mInjectionSyncFinishedCondition.waitRelative(mLock, remainingTimeout); +                } +            } +        } + +        mAllocator.releaseEventEntry(injectedEntry); +    } // release lock + +#if DEBUG_INJECTION +    LOGD("injectInputEvent - Finished with result %d.  " +            "injectorPid=%d, injectorUid=%d", +            injectionResult, injectorPid, injectorUid); +#endif + +    return injectionResult; +} + +void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t injectionResult) { +    if (entry->isInjected()) { +#if DEBUG_INJECTION +        LOGD("Setting input event injection result to %d.  " +                "injectorPid=%d, injectorUid=%d", +                 injectionResult, entry->injectorPid, entry->injectorUid); +#endif + +        if (entry->injectionIsAsync) { +            // Log the outcome since the injector did not wait for the injection result. +            switch (injectionResult) { +            case INPUT_EVENT_INJECTION_SUCCEEDED: +                LOGV("Asynchronous input event injection succeeded."); +                break; +            case INPUT_EVENT_INJECTION_FAILED: +                LOGW("Asynchronous input event injection failed."); +                break; +            case INPUT_EVENT_INJECTION_PERMISSION_DENIED: +                LOGW("Asynchronous input event injection permission denied."); +                break; +            case INPUT_EVENT_INJECTION_TIMED_OUT: +                LOGW("Asynchronous input event injection timed out."); +                break; +            } +        } + +        entry->injectionResult = injectionResult; +        mInjectionResultAvailableCondition.broadcast(); +    } +} + +void InputDispatcher::decrementPendingSyncDispatchesLocked(EventEntry* entry) { +    entry->pendingSyncDispatches -= 1; + +    if (entry->isInjected() && entry->pendingSyncDispatches == 0) { +        mInjectionSyncFinishedCondition.broadcast(); +    } +} + +static bool isValidKeyAction(int32_t action) { +    switch (action) { +    case AKEY_EVENT_ACTION_DOWN: +    case AKEY_EVENT_ACTION_UP: +        return true; +    default: +        return false; +    } +} + +static bool isValidMotionAction(int32_t action) { +    switch (action & AMOTION_EVENT_ACTION_MASK) { +    case AMOTION_EVENT_ACTION_DOWN: +    case AMOTION_EVENT_ACTION_UP: +    case AMOTION_EVENT_ACTION_CANCEL: +    case AMOTION_EVENT_ACTION_MOVE: +    case AMOTION_EVENT_ACTION_POINTER_DOWN: +    case AMOTION_EVENT_ACTION_POINTER_UP: +    case AMOTION_EVENT_ACTION_OUTSIDE: +        return true; +    default: +        return false; +    } +} + +InputDispatcher::EventEntry* InputDispatcher::createEntryFromInjectedInputEventLocked( +        const InputEvent* event) { +    switch (event->getType()) { +    case AINPUT_EVENT_TYPE_KEY: { +        const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event); +        if (! isValidKeyAction(keyEvent->getAction())) { +            LOGE("Dropping injected key event since it has invalid action code 0x%x", +                    keyEvent->getAction()); +            return NULL; +        } + +        uint32_t policyFlags = POLICY_FLAG_INJECTED; + +        KeyEntry* keyEntry = mAllocator.obtainKeyEntry(keyEvent->getEventTime(), +                keyEvent->getDeviceId(), keyEvent->getSource(), policyFlags, +                keyEvent->getAction(), keyEvent->getFlags(), +                keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(), +                keyEvent->getRepeatCount(), keyEvent->getDownTime()); +        return keyEntry; +    } + +    case AINPUT_EVENT_TYPE_MOTION: { +        const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event); +        if (! isValidMotionAction(motionEvent->getAction())) { +            LOGE("Dropping injected motion event since it has invalid action code 0x%x.", +                    motionEvent->getAction()); +            return NULL; +        } +        if (motionEvent->getPointerCount() == 0 +                || motionEvent->getPointerCount() > MAX_POINTERS) { +            LOGE("Dropping injected motion event since it has an invalid pointer count %d.", +                    motionEvent->getPointerCount()); +        } + +        uint32_t policyFlags = POLICY_FLAG_INJECTED; + +        const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes(); +        const PointerCoords* samplePointerCoords = motionEvent->getSamplePointerCoords(); +        size_t pointerCount = motionEvent->getPointerCount(); + +        MotionEntry* motionEntry = mAllocator.obtainMotionEntry(*sampleEventTimes, +                motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags, +                motionEvent->getAction(), motionEvent->getFlags(), +                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::setInputWindows(const Vector<InputWindow>& inputWindows) { +#if DEBUG_FOCUS +    LOGD("setInputWindows"); +#endif +    { // acquire lock +        AutoMutex _l(mLock); + +        sp<InputChannel> touchedWindowChannel; +        if (mTouchedWindow) { +            touchedWindowChannel = mTouchedWindow->inputChannel; +            mTouchedWindow = NULL; +        } +        size_t numTouchedWallpapers = mTouchedWallpaperWindows.size(); +        if (numTouchedWallpapers != 0) { +            for (size_t i = 0; i < numTouchedWallpapers; i++) { +                mTempTouchedWallpaperChannels.push(mTouchedWallpaperWindows[i]->inputChannel); +            } +            mTouchedWallpaperWindows.clear(); +        } + +        bool hadFocusedWindow = mFocusedWindow != NULL; + +        mFocusedWindow = NULL; +        mWallpaperWindows.clear(); + +        mWindows.clear(); +        mWindows.appendVector(inputWindows); + +        size_t numWindows = mWindows.size(); +        for (size_t i = 0; i < numWindows; i++) { +            InputWindow* window = & mWindows.editItemAt(i); +            if (window->hasFocus) { +                mFocusedWindow = window; +            } + +            if (window->layoutParamsType == InputWindow::TYPE_WALLPAPER) { +                mWallpaperWindows.push(window); + +                for (size_t j = 0; j < numTouchedWallpapers; j++) { +                    if (window->inputChannel == mTempTouchedWallpaperChannels[i]) { +                        mTouchedWallpaperWindows.push(window); +                    } +                } +            } + +            if (window->inputChannel == touchedWindowChannel) { +                mTouchedWindow = window; +            } +        } + +        mTempTouchedWallpaperChannels.clear(); + +        if ((hadFocusedWindow && ! mFocusedWindow) +                || (mFocusedWindow && ! mFocusedWindow->visible)) { +            preemptInputDispatchInnerLocked(); +        } + +#if DEBUG_FOCUS +        logDispatchStateLocked(); +#endif +    } // release lock + +    // Wake up poll loop since it may need to make new input dispatching choices. +    mPollLoop->wake(); +} + +void InputDispatcher::setFocusedApplication(const InputApplication* inputApplication) { +#if DEBUG_FOCUS +    LOGD("setFocusedApplication"); +#endif +    { // acquire lock +        AutoMutex _l(mLock); + +        releaseFocusedApplicationLocked(); + +        if (inputApplication) { +            mFocusedApplicationStorage = *inputApplication; +            mFocusedApplication = & mFocusedApplicationStorage; +        } + +#if DEBUG_FOCUS +        logDispatchStateLocked(); +#endif +    } // release lock + +    // Wake up poll loop since it may need to make new input dispatching choices. +    mPollLoop->wake(); +} + +void InputDispatcher::releaseFocusedApplicationLocked() { +    if (mFocusedApplication) { +        mFocusedApplication = NULL; +        mFocusedApplicationStorage.handle.clear(); +    } +} + +void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) { +#if DEBUG_FOCUS +    LOGD("setInputDispatchMode: enabled=%d, frozen=%d", enabled, frozen); +#endif + +    bool changed; +    { // acquire lock +        AutoMutex _l(mLock); + +        if (mDispatchEnabled != enabled || mDispatchFrozen != frozen) { +            if (mDispatchFrozen && ! frozen) { +                resetANRTimeoutsLocked(); +            } + +            mDispatchEnabled = enabled; +            mDispatchFrozen = frozen; +            changed = true; +        } else { +            changed = false; +        } + +#if DEBUG_FOCUS +        logDispatchStateLocked(); +#endif +    } // release lock + +    if (changed) { +        // Wake up poll loop since it may need to make new input dispatching choices. +        mPollLoop->wake(); +    } +} + +void InputDispatcher::preemptInputDispatch() { +#if DEBUG_FOCUS +    LOGD("preemptInputDispatch"); +#endif + +    bool preemptedOne; +    { // acquire lock +        AutoMutex _l(mLock); +        preemptedOne = preemptInputDispatchInnerLocked(); +    } // release lock + +    if (preemptedOne) { +        // Wake up the poll loop so it can get a head start dispatching the next event. +        mPollLoop->wake(); +    } +} + +bool InputDispatcher::preemptInputDispatchInnerLocked() { +    bool preemptedOne = false; +    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->preemptSyncTarget(); +            preemptedOne = true; +        } +    } +    return preemptedOne; +} + +void InputDispatcher::logDispatchStateLocked() { +    String8 dump; +    dumpDispatchStateLocked(dump); +    LOGD("%s", dump.string()); +} + +void InputDispatcher::dumpDispatchStateLocked(String8& dump) { +    dump.appendFormat("  dispatchEnabled: %d\n", mDispatchEnabled); +    dump.appendFormat("  dispatchFrozen: %d\n", mDispatchFrozen); + +    if (mFocusedApplication) { +        dump.appendFormat("  focusedApplication: name='%s', dispatchingTimeout=%0.3fms\n", +                mFocusedApplication->name.string(), +                mFocusedApplication->dispatchingTimeout / 1000000.0); +    } else { +        dump.append("  focusedApplication: <null>\n"); +    } +    dump.appendFormat("  focusedWindow: '%s'\n", +            mFocusedWindow != NULL ? mFocusedWindow->inputChannel->getName().string() : "<null>"); +    dump.appendFormat("  touchedWindow: '%s', touchDown=%d\n", +            mTouchedWindow != NULL ? mTouchedWindow->inputChannel->getName().string() : "<null>", +            mTouchDown); +    for (size_t i = 0; i < mTouchedWallpaperWindows.size(); i++) { +        dump.appendFormat("  touchedWallpaperWindows[%d]: '%s'\n", +                i, mTouchedWallpaperWindows[i]->inputChannel->getName().string()); +    } +    for (size_t i = 0; i < mWindows.size(); i++) { +        dump.appendFormat("  windows[%d]: '%s', paused=%s, hasFocus=%s, hasWallpaper=%s, " +                "visible=%s, flags=0x%08x, type=0x%08x, " +                "frame=[%d,%d][%d,%d], " +                "visibleFrame=[%d,%d][%d,%d], " +                "touchableArea=[%d,%d][%d,%d], " +                "ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n", +                i, mWindows[i].inputChannel->getName().string(), +                toString(mWindows[i].paused), +                toString(mWindows[i].hasFocus), +                toString(mWindows[i].hasWallpaper), +                toString(mWindows[i].visible), +                mWindows[i].layoutParamsFlags, mWindows[i].layoutParamsType, +                mWindows[i].frameLeft, mWindows[i].frameTop, +                mWindows[i].frameRight, mWindows[i].frameBottom, +                mWindows[i].visibleFrameLeft, mWindows[i].visibleFrameTop, +                mWindows[i].visibleFrameRight, mWindows[i].visibleFrameBottom, +                mWindows[i].touchableAreaLeft, mWindows[i].touchableAreaTop, +                mWindows[i].touchableAreaRight, mWindows[i].touchableAreaBottom, +                mWindows[i].ownerPid, mWindows[i].ownerUid, +                mWindows[i].dispatchingTimeout / 1000000.0); +    } + +    for (size_t i = 0; i < mMonitoringChannels.size(); i++) { +        const sp<InputChannel>& channel = mMonitoringChannels[i]; +        dump.appendFormat("  monitoringChannel[%d]: '%s'\n", +                i, channel->getName().string()); +    } + +    for (size_t i = 0; i < mActiveConnections.size(); i++) { +        const Connection* connection = mActiveConnections[i]; +        dump.appendFormat("  activeConnection[%d]: '%s', status=%s, hasPendingSyncTarget=%s, " +                "inputState.isNeutral=%s, inputState.isOutOfSync=%s\n", +                i, connection->getInputChannelName(), connection->getStatusLabel(), +                toString(connection->hasPendingSyncTarget()), +                toString(connection->inputState.isNeutral()), +                toString(connection->inputState.isOutOfSync())); +    } + +    if (isAppSwitchPendingLocked()) { +        dump.appendFormat("  appSwitch: pending, due in %01.1fms\n", +                (mAppSwitchDueTime - now()) / 1000000.0); +    } else { +        dump.append("  appSwitch: not pending\n"); +    } +} + +status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor) { +#if DEBUG_REGISTRATION +    LOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().string(), +            toString(monitor)); +#endif + +    { // acquire lock +        AutoMutex _l(mLock); + +        if (getConnectionIndex(inputChannel) >= 0) { +            LOGW("Attempted to register already registered input channel '%s'", +                    inputChannel->getName().string()); +            return BAD_VALUE; +        } + +        sp<Connection> connection = new Connection(inputChannel); +        status_t status = connection->initialize(); +        if (status) { +            LOGE("Failed to initialize input publisher for input channel '%s', status=%d", +                    inputChannel->getName().string(), status); +            return status; +        } + +        int32_t receiveFd = inputChannel->getReceivePipeFd(); +        mConnectionsByReceiveFd.add(receiveFd, connection); + +        if (monitor) { +            mMonitoringChannels.push(inputChannel); +        } + +        mPollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this); + +        runCommandsLockedInterruptible(); +    } // release lock +    return OK; +} + +status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) { +#if DEBUG_REGISTRATION +    LOGD("channel '%s' ~ unregisterInputChannel", inputChannel->getName().string()); +#endif + +    { // acquire lock +        AutoMutex _l(mLock); + +        ssize_t connectionIndex = getConnectionIndex(inputChannel); +        if (connectionIndex < 0) { +            LOGW("Attempted to unregister already unregistered input channel '%s'", +                    inputChannel->getName().string()); +            return BAD_VALUE; +        } + +        sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); +        mConnectionsByReceiveFd.removeItemsAt(connectionIndex); + +        connection->status = Connection::STATUS_ZOMBIE; + +        for (size_t i = 0; i < mMonitoringChannels.size(); i++) { +            if (mMonitoringChannels[i] == inputChannel) { +                mMonitoringChannels.removeAt(i); +                break; +            } +        } + +        mPollLoop->removeCallback(inputChannel->getReceivePipeFd()); + +        nsecs_t currentTime = now(); +        abortDispatchCycleLocked(currentTime, connection, true /*broken*/); + +        runCommandsLockedInterruptible(); +    } // release lock + +    // Wake the poll loop because removing the connection may have changed the current +    // synchronization state. +    mPollLoop->wake(); +    return OK; +} + +ssize_t InputDispatcher::getConnectionIndex(const sp<InputChannel>& inputChannel) { +    ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(inputChannel->getReceivePipeFd()); +    if (connectionIndex >= 0) { +        sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); +        if (connection->inputChannel.get() == inputChannel.get()) { +            return connectionIndex; +        } +    } + +    return -1; +} + +void InputDispatcher::activateConnectionLocked(Connection* connection) { +    for (size_t i = 0; i < mActiveConnections.size(); i++) { +        if (mActiveConnections.itemAt(i) == connection) { +            return; +        } +    } +    mActiveConnections.add(connection); +} + +void InputDispatcher::deactivateConnectionLocked(Connection* connection) { +    for (size_t i = 0; i < mActiveConnections.size(); i++) { +        if (mActiveConnections.itemAt(i) == connection) { +            mActiveConnections.removeAt(i); +            return; +        } +    } +} + +void InputDispatcher::onDispatchCycleStartedLocked( +        nsecs_t currentTime, const sp<Connection>& connection) { +} + +void InputDispatcher::onDispatchCycleFinishedLocked( +        nsecs_t currentTime, const sp<Connection>& connection, bool recoveredFromANR) { +    if (recoveredFromANR) { +        LOGI("channel '%s' ~ Recovered from ANR.  %01.1fms since event, " +                "%01.1fms since dispatch, %01.1fms since ANR", +                connection->getInputChannelName(), +                connection->getEventLatencyMillis(currentTime), +                connection->getDispatchLatencyMillis(currentTime), +                connection->getANRLatencyMillis(currentTime)); + +        CommandEntry* commandEntry = postCommandLocked( +                & InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible); +        commandEntry->connection = connection; +    } +} + +void InputDispatcher::onDispatchCycleANRLocked( +        nsecs_t currentTime, const sp<Connection>& connection) { +    LOGI("channel '%s' ~ Not responding!  %01.1fms since event, %01.1fms since dispatch", +            connection->getInputChannelName(), +            connection->getEventLatencyMillis(currentTime), +            connection->getDispatchLatencyMillis(currentTime)); + +    CommandEntry* commandEntry = postCommandLocked( +            & InputDispatcher::doNotifyInputChannelANRLockedInterruptible); +    commandEntry->connection = connection; +} + +void InputDispatcher::onDispatchCycleBrokenLocked( +        nsecs_t currentTime, const sp<Connection>& connection) { +    LOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!", +            connection->getInputChannelName()); + +    CommandEntry* commandEntry = postCommandLocked( +            & InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible); +    commandEntry->connection = connection; +} + +void InputDispatcher::doNotifyConfigurationChangedInterruptible( +        CommandEntry* commandEntry) { +    mLock.unlock(); + +    mPolicy->notifyConfigurationChanged(commandEntry->eventTime); + +    mLock.lock(); +} + +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 = mPolicy->notifyInputChannelANR(connection->inputChannel); + +        mLock.lock(); + +        nsecs_t currentTime = now(); +        resumeAfterTimeoutDispatchCycleLocked(currentTime, connection, newTimeout); +    } +} + +void InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible( +        CommandEntry* commandEntry) { +    sp<Connection> connection = commandEntry->connection; + +    if (connection->status != Connection::STATUS_ZOMBIE) { +        mLock.unlock(); + +        mPolicy->notifyInputChannelRecoveredFromANR(connection->inputChannel); + +        mLock.lock(); +    } +} + +void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible( +        CommandEntry* commandEntry) { +    KeyEntry* entry = commandEntry->keyEntry; +    mReusableKeyEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags, +            entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount, +            entry->downTime, entry->eventTime); + +    mLock.unlock(); + +    bool consumed = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputChannel, +            & mReusableKeyEvent, entry->policyFlags); + +    mLock.lock(); + +    entry->interceptKeyResult = consumed +            ? KeyEntry::INTERCEPT_KEY_RESULT_SKIP +            : KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE; +    mAllocator.releaseKeyEntry(entry); +} + +void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) { +    mLock.unlock(); + +    mPolicy->pokeUserActivity(commandEntry->eventTime, commandEntry->windowType, +            commandEntry->userActivityEventType); + +    mLock.lock(); +} + +void InputDispatcher::doTargetsNotReadyTimeoutLockedInterruptible( +        CommandEntry* commandEntry) { +    mLock.unlock(); + +    nsecs_t newTimeout; +    if (commandEntry->inputChannel.get()) { +        newTimeout = mPolicy->notifyInputChannelANR(commandEntry->inputChannel); +    } else if (commandEntry->inputApplicationHandle.get()) { +        newTimeout = mPolicy->notifyANR(commandEntry->inputApplicationHandle); +    } else { +        newTimeout = 0; +    } + +    mLock.lock(); + +    resumeAfterTargetsNotReadyTimeoutLocked(newTimeout); +} + +void InputDispatcher::dump(String8& dump) { +    dumpDispatchStateLocked(dump); +} + + +// --- InputDispatcher::Allocator --- + +InputDispatcher::Allocator::Allocator() { +} + +void InputDispatcher::Allocator::initializeEventEntry(EventEntry* entry, int32_t type, +        nsecs_t eventTime) { +    entry->type = type; +    entry->refCount = 1; +    entry->dispatchInProgress = false; +    entry->eventTime = eventTime; +    entry->injectionResult = INPUT_EVENT_INJECTION_PENDING; +    entry->injectionIsAsync = false; +    entry->injectorPid = -1; +    entry->injectorUid = -1; +    entry->pendingSyncDispatches = 0; +} + +InputDispatcher::ConfigurationChangedEntry* +InputDispatcher::Allocator::obtainConfigurationChangedEntry(nsecs_t eventTime) { +    ConfigurationChangedEntry* entry = mConfigurationChangeEntryPool.alloc(); +    initializeEventEntry(entry, EventEntry::TYPE_CONFIGURATION_CHANGED, eventTime); +    return entry; +} + +InputDispatcher::KeyEntry* InputDispatcher::Allocator::obtainKeyEntry(nsecs_t eventTime, +        int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action, +        int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, +        int32_t repeatCount, nsecs_t downTime) { +    KeyEntry* entry = mKeyEntryPool.alloc(); +    initializeEventEntry(entry, EventEntry::TYPE_KEY, eventTime); + +    entry->deviceId = deviceId; +    entry->source = source; +    entry->policyFlags = policyFlags; +    entry->action = action; +    entry->flags = flags; +    entry->keyCode = keyCode; +    entry->scanCode = scanCode; +    entry->metaState = metaState; +    entry->repeatCount = repeatCount; +    entry->downTime = downTime; +    entry->syntheticRepeat = false; +    entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN; +    return entry; +} + +InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry(nsecs_t eventTime, +        int32_t deviceId, int32_t source, uint32_t policyFlags, int32_t action, int32_t flags, +        int32_t metaState, int32_t edgeFlags, float xPrecision, float yPrecision, +        nsecs_t downTime, uint32_t pointerCount, +        const int32_t* pointerIds, const PointerCoords* pointerCoords) { +    MotionEntry* entry = mMotionEntryPool.alloc(); +    initializeEventEntry(entry, EventEntry::TYPE_MOTION, eventTime); + +    entry->eventTime = eventTime; +    entry->deviceId = deviceId; +    entry->source = source; +    entry->policyFlags = policyFlags; +    entry->action = action; +    entry->flags = flags; +    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, +        int32_t targetFlags, float xOffset, float yOffset, nsecs_t timeout) { +    DispatchEntry* entry = mDispatchEntryPool.alloc(); +    entry->eventEntry = eventEntry; +    eventEntry->refCount += 1; +    entry->targetFlags = targetFlags; +    entry->xOffset = xOffset; +    entry->yOffset = yOffset; +    entry->timeout = timeout; +    entry->inProgress = false; +    entry->headMotionSample = NULL; +    entry->tailMotionSample = NULL; +    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::EventEntry --- + +void InputDispatcher::EventEntry::recycle() { +    injectionResult = INPUT_EVENT_INJECTION_PENDING; +    dispatchInProgress = false; +    pendingSyncDispatches = 0; +} + + +// --- InputDispatcher::KeyEntry --- + +void InputDispatcher::KeyEntry::recycle() { +    EventEntry::recycle(); +    syntheticRepeat = false; +    interceptKeyResult = INTERCEPT_KEY_RESULT_UNKNOWN; +} + + +// --- InputDispatcher::MotionEntry --- + +uint32_t InputDispatcher::MotionEntry::countSamples() const { +    uint32_t count = 1; +    for (MotionSample* sample = firstSample.next; sample != NULL; sample = sample->next) { +        count += 1; +    } +    return count; +} + + +// --- InputDispatcher::InputState --- + +InputDispatcher::InputState::InputState() : +        mIsOutOfSync(false) { +} + +InputDispatcher::InputState::~InputState() { +} + +bool InputDispatcher::InputState::isNeutral() const { +    return mKeyMementos.isEmpty() && mMotionMementos.isEmpty(); +} + +bool InputDispatcher::InputState::isOutOfSync() const { +    return mIsOutOfSync; +} + +void InputDispatcher::InputState::setOutOfSync() { +    if (! isNeutral()) { +        mIsOutOfSync = true; +    } +} + +void InputDispatcher::InputState::resetOutOfSync() { +    mIsOutOfSync = false; +} + +InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackEvent( +        const EventEntry* entry) { +    switch (entry->type) { +    case EventEntry::TYPE_KEY: +        return trackKey(static_cast<const KeyEntry*>(entry)); + +    case EventEntry::TYPE_MOTION: +        return trackMotion(static_cast<const MotionEntry*>(entry)); + +    default: +        return CONSISTENT; +    } +} + +InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackKey( +        const KeyEntry* entry) { +    int32_t action = entry->action; +    for (size_t i = 0; i < mKeyMementos.size(); i++) { +        KeyMemento& memento = mKeyMementos.editItemAt(i); +        if (memento.deviceId == entry->deviceId +                && memento.source == entry->source +                && memento.keyCode == entry->keyCode +                && memento.scanCode == entry->scanCode) { +            switch (action) { +            case AKEY_EVENT_ACTION_UP: +                mKeyMementos.removeAt(i); +                if (isNeutral()) { +                    mIsOutOfSync = false; +                } +                return CONSISTENT; + +            case AKEY_EVENT_ACTION_DOWN: +                return TOLERABLE; + +            default: +                return BROKEN; +            } +        } +    } + +    switch (action) { +    case AKEY_EVENT_ACTION_DOWN: { +        mKeyMementos.push(); +        KeyMemento& memento = mKeyMementos.editTop(); +        memento.deviceId = entry->deviceId; +        memento.source = entry->source; +        memento.keyCode = entry->keyCode; +        memento.scanCode = entry->scanCode; +        memento.downTime = entry->downTime; +        return CONSISTENT; +    } + +    default: +        return BROKEN; +    } +} + +InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackMotion( +        const MotionEntry* entry) { +    int32_t action = entry->action & AMOTION_EVENT_ACTION_MASK; +    for (size_t i = 0; i < mMotionMementos.size(); i++) { +        MotionMemento& memento = mMotionMementos.editItemAt(i); +        if (memento.deviceId == entry->deviceId +                && memento.source == entry->source) { +            switch (action) { +            case AMOTION_EVENT_ACTION_UP: +            case AMOTION_EVENT_ACTION_CANCEL: +                mMotionMementos.removeAt(i); +                if (isNeutral()) { +                    mIsOutOfSync = false; +                } +                return CONSISTENT; + +            case AMOTION_EVENT_ACTION_DOWN: +                return TOLERABLE; + +            case AMOTION_EVENT_ACTION_POINTER_DOWN: +                if (entry->pointerCount == memento.pointerCount + 1) { +                    memento.setPointers(entry); +                    return CONSISTENT; +                } +                return BROKEN; + +            case AMOTION_EVENT_ACTION_POINTER_UP: +                if (entry->pointerCount == memento.pointerCount - 1) { +                    memento.setPointers(entry); +                    return CONSISTENT; +                } +                return BROKEN; + +            case AMOTION_EVENT_ACTION_MOVE: +                if (entry->pointerCount == memento.pointerCount) { +                    return CONSISTENT; +                } +                return BROKEN; + +            default: +                return BROKEN; +            } +        } +    } + +    switch (action) { +    case AMOTION_EVENT_ACTION_DOWN: { +        mMotionMementos.push(); +        MotionMemento& memento = mMotionMementos.editTop(); +        memento.deviceId = entry->deviceId; +        memento.source = entry->source; +        memento.xPrecision = entry->xPrecision; +        memento.yPrecision = entry->yPrecision; +        memento.downTime = entry->downTime; +        memento.setPointers(entry); +        return CONSISTENT; +    } + +    default: +        return BROKEN; +    } +} + +void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) { +    pointerCount = entry->pointerCount; +    for (uint32_t i = 0; i < entry->pointerCount; i++) { +        pointerIds[i] = entry->pointerIds[i]; +        pointerCoords[i] = entry->lastSample->pointerCoords[i]; +    } +} + +void InputDispatcher::InputState::synthesizeCancelationEvents( +        Allocator* allocator, Vector<EventEntry*>& outEvents) const { +    for (size_t i = 0; i < mKeyMementos.size(); i++) { +        const KeyMemento& memento = mKeyMementos.itemAt(i); +        outEvents.push(allocator->obtainKeyEntry(now(), +                memento.deviceId, memento.source, 0, +                AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_CANCELED, +                memento.keyCode, memento.scanCode, 0, 0, memento.downTime)); +    } + +    for (size_t i = 0; i < mMotionMementos.size(); i++) { +        const MotionMemento& memento = mMotionMementos.itemAt(i); +        outEvents.push(allocator->obtainMotionEntry(now(), +                memento.deviceId, memento.source, 0, +                AMOTION_EVENT_ACTION_CANCEL, 0, 0, 0, +                memento.xPrecision, memento.yPrecision, memento.downTime, +                memento.pointerCount, memento.pointerIds, memento.pointerCoords)); +    } +} + +void InputDispatcher::InputState::clear() { +    mKeyMementos.clear(); +    mMotionMementos.clear(); +    mIsOutOfSync = false; +} + + +// --- 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; +} + +void InputDispatcher::Connection::resetTimeout(nsecs_t currentTime) { +    if (outboundQueue.isEmpty()) { +        nextTimeoutTime = LONG_LONG_MAX; +    } else { +        setNextTimeoutTime(currentTime, outboundQueue.headSentinel.next->timeout); +    } +} + +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.tailSentinel.prev; +            dispatchEntry != & outboundQueue.headSentinel; dispatchEntry = dispatchEntry->prev) { +        if (dispatchEntry->eventEntry == eventEntry) { +            return dispatchEntry; +        } +    } +    return NULL; +} + + +// --- InputDispatcher::CommandEntry --- + +InputDispatcher::CommandEntry::CommandEntry() : +    keyEntry(NULL) { +} + +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 | 
