Improve input event consistency invariants.

Fixed some issues where inconsistent streams of events could
be generated by the dispatcher, particularly when switching from
hovering with one device to hovering with another.

Fixed a bug where the touch pad would fail to generate a new
HOVER_MOVE following a tap event.  As a result, the hover event
stream would not resume until the user touched the touch pad
again.

Change-Id: I444dce84641fb12e56a0af84c931520771d6c493
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index 49f3bbe..9b081b2 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -239,7 +239,7 @@
                     break;
             }
         } finally {
-            finishEvent(false);
+            finishEvent();
         }
     }
 
@@ -302,7 +302,7 @@
                 problem("Source was not SOURCE_CLASS_TRACKBALL.");
             }
         } finally {
-            finishEvent(false);
+            finishEvent();
         }
     }
 
@@ -328,7 +328,9 @@
             mTouchEventStreamUnhandled = false;
             mTouchEventStreamPointers = 0;
         }
-        final boolean wasTainted = mTouchEventStreamIsTainted;
+        if (mTouchEventStreamIsTainted) {
+            event.setTainted(true);
+        }
 
         try {
             ensureMetaStateIsNormalized(event.getMetaState());
@@ -441,7 +443,7 @@
                 problem("Source was not SOURCE_CLASS_POINTER.");
             }
         } finally {
-            finishEvent(wasTainted);
+            finishEvent();
         }
     }
 
@@ -499,7 +501,7 @@
                 }
             }
         } finally {
-            finishEvent(false);
+            finishEvent();
         }
     }
 
@@ -591,9 +593,9 @@
         return true;
     }
 
-    private void finishEvent(boolean tainted) {
+    private void finishEvent() {
         if (mViolationMessage != null && mViolationMessage.length() != 0) {
-            if (!tainted) {
+            if (!mCurrentEvent.isTainted()) {
                 // Write a log message only if the event was not already tainted.
                 mViolationMessage.append("\n  in ").append(mCaller);
                 mViolationMessage.append("\n  ");
@@ -614,17 +616,14 @@
                 }
 
                 Log.d(mLogTag, mViolationMessage.toString());
-                tainted = true;
+
+                // Taint the event so that we do not generate additional violations from it
+                // further downstream.
+                mCurrentEvent.setTainted(true);
             }
             mViolationMessage.setLength(0);
         }
 
-        if (tainted) {
-            // Taint the event so that we do not generate additional violations from it
-            // further downstream.
-            mCurrentEvent.setTainted(true);
-        }
-
         if (RECENT_EVENTS_TO_LOG != 0) {
             if (mRecentEvents == null) {
                 mRecentEvents = new InputEvent[RECENT_EVENTS_TO_LOG];
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index f45e78b..88f59d4 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -2885,7 +2885,7 @@
                     toolTypeToString(getToolType(i)));
         }
 
-        msg.append(", buttonState=").append(KeyEvent.metaStateToString(getButtonState()));
+        msg.append(", buttonState=").append(MotionEvent.buttonStateToString(getButtonState()));
         msg.append(", metaState=").append(KeyEvent.metaStateToString(getMetaState()));
         msg.append(", flags=0x").append(Integer.toHexString(getFlags()));
         msg.append(", edgeFlags=0x").append(Integer.toHexString(getEdgeFlags()));
diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp
index ffdfe66..c46d6f4 100644
--- a/libs/ui/InputTransport.cpp
+++ b/libs/ui/InputTransport.cpp
@@ -443,7 +443,8 @@
 
     if (! mPinned || ! mMotionEventSampleDataTail) {
         LOGE("channel '%s' publisher ~ Cannot append motion sample because there is no current "
-                "AMOTION_EVENT_ACTION_MOVE event.", mChannel->getName().string());
+                "AMOTION_EVENT_ACTION_MOVE or AMOTION_EVENT_ACTION_HOVER_MOVE event.",
+                mChannel->getName().string());
         return INVALID_OPERATION;
     }
 
diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp
index 4a50d8a..b9029a7 100644
--- a/services/input/InputDispatcher.cpp
+++ b/services/input/InputDispatcher.cpp
@@ -569,9 +569,9 @@
         break;
     case DROP_REASON_BLOCKED:
         LOGI("Dropped event because the current application is not responding and the user "
-                "has started interating with a different application.");
+                "has started interacting with a different application.");
         reason = "inbound event was dropped because the current application is not responding "
-                "and the user has started interating with a different application";
+                "and the user has started interacting with a different application";
         break;
     case DROP_REASON_STALE:
         LOGI("Dropped event because it is stale.");
@@ -1239,37 +1239,35 @@
     const InputWindow* newHoverWindow = NULL;
 
     bool isSplit = mTouchState.split;
-    bool wrongDevice = mTouchState.down
-            && (mTouchState.deviceId != entry->deviceId
-                    || mTouchState.source != entry->source);
+    bool switchedDevice = mTouchState.deviceId != entry->deviceId
+            || mTouchState.source != entry->source;
     bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE
             || maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER
             || maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT);
     bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN
             || maskedAction == AMOTION_EVENT_ACTION_SCROLL
             || isHoverAction);
+    bool wrongDevice = false;
     if (newGesture) {
         bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN;
-        if (wrongDevice && !down) {
+        if (switchedDevice && mTouchState.down && !down) {
+#if DEBUG_FOCUS
+            LOGD("Dropping event because a pointer for a different device is already down.");
+#endif
             mTempTouchState.copyFrom(mTouchState);
-        } else {
-            mTempTouchState.reset();
-            mTempTouchState.down = down;
-            mTempTouchState.deviceId = entry->deviceId;
-            mTempTouchState.source = entry->source;
-            isSplit = false;
-            wrongDevice = false;
+            injectionResult = INPUT_EVENT_INJECTION_FAILED;
+            switchedDevice = false;
+            wrongDevice = true;
+            goto Failed;
         }
+        mTempTouchState.reset();
+        mTempTouchState.down = down;
+        mTempTouchState.deviceId = entry->deviceId;
+        mTempTouchState.source = entry->source;
+        isSplit = false;
     } else {
         mTempTouchState.copyFrom(mTouchState);
     }
-    if (wrongDevice) {
-#if DEBUG_FOCUS
-        LOGD("Dropping event because a pointer for a different device is already down.");
-#endif
-        injectionResult = INPUT_EVENT_INJECTION_FAILED;
-        goto Failed;
-    }
 
     if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
         /* Case 1: New splittable pointer going down, or need target for hover or scroll. */
@@ -1598,18 +1596,38 @@
     // Update final pieces of touch state if the injector had permission.
     if (injectionPermission == INJECTION_PERMISSION_GRANTED) {
         if (!wrongDevice) {
-            if (maskedAction == AMOTION_EVENT_ACTION_UP
-                    || maskedAction == AMOTION_EVENT_ACTION_CANCEL
-                    || isHoverAction) {
+            if (switchedDevice) {
+#if DEBUG_FOCUS
+                LOGD("Conflicting pointer actions: Switched to a different device.");
+#endif
+                *outConflictingPointerActions = true;
+            }
+
+            if (isHoverAction) {
+                // Started hovering, therefore no longer down.
+                if (mTouchState.down) {
+#if DEBUG_FOCUS
+                    LOGD("Conflicting pointer actions: Hover received while pointer was down.");
+#endif
+                    *outConflictingPointerActions = true;
+                }
+                mTouchState.reset();
+                if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER
+                        || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+                    mTouchState.deviceId = entry->deviceId;
+                    mTouchState.source = entry->source;
+                }
+            } else if (maskedAction == AMOTION_EVENT_ACTION_UP
+                    || maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
                 // All pointers up or canceled.
                 mTouchState.reset();
             } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
                 // First pointer went down.
                 if (mTouchState.down) {
-                    *outConflictingPointerActions = true;
 #if DEBUG_FOCUS
-                    LOGD("Pointer down received while already down.");
+                    LOGD("Conflicting pointer actions: Down received while already down.");
 #endif
+                    *outConflictingPointerActions = true;
                 }
                 mTouchState.copyFrom(mTempTouchState);
             } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
@@ -1868,6 +1886,18 @@
                 return;
             }
 
+            // If the motion event was modified in flight, then we cannot stream the sample.
+            if ((motionEventDispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_MASK)
+                    != InputTarget::FLAG_DISPATCH_AS_IS) {
+#if DEBUG_BATCHING
+                LOGD("channel '%s' ~ Not streaming because the motion event was not "
+                        "being dispatched as-is.  "
+                        "(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).
@@ -1972,6 +2002,66 @@
         dispatchEntry->headMotionSample = appendedMotionSample;
     }
 
+    // Apply target flags and update the connection's input state.
+    switch (eventEntry->type) {
+    case EventEntry::TYPE_KEY: {
+        KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
+        dispatchEntry->resolvedAction = keyEntry->action;
+        dispatchEntry->resolvedFlags = keyEntry->flags;
+
+        if (!connection->inputState.trackKey(keyEntry,
+                dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) {
+#if DEBUG_DISPATCH_CYCLE
+            LOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event",
+                    connection->getInputChannelName());
+#endif
+            return; // skip the inconsistent event
+        }
+        break;
+    }
+
+    case EventEntry::TYPE_MOTION: {
+        MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
+        if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
+            dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_OUTSIDE;
+        } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT) {
+            dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_EXIT;
+        } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER) {
+            dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
+        } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
+            dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_CANCEL;
+        } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER) {
+            dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_DOWN;
+        } else {
+            dispatchEntry->resolvedAction = motionEntry->action;
+        }
+        if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE
+                && !connection->inputState.isHovering(
+                        motionEntry->deviceId, motionEntry->source)) {
+#if DEBUG_DISPATCH_CYCLE
+        LOGD("channel '%s' ~ enqueueDispatchEntryLocked: filling in missing hover enter event",
+                connection->getInputChannelName());
+#endif
+            dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
+        }
+
+        dispatchEntry->resolvedFlags = motionEntry->flags;
+        if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) {
+            dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+        }
+
+        if (!connection->inputState.trackMotion(motionEntry,
+                dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) {
+#if DEBUG_DISPATCH_CYCLE
+            LOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent motion event",
+                    connection->getInputChannelName());
+#endif
+            return; // skip the inconsistent event
+        }
+        break;
+    }
+    }
+
     // Enqueue the dispatch entry.
     connection->outboundQueue.enqueueAtTail(dispatchEntry);
 }
@@ -1999,16 +2089,11 @@
     case EventEntry::TYPE_KEY: {
         KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
 
-        // Apply target flags.
-        int32_t action = keyEntry->action;
-        int32_t flags = keyEntry->flags;
-
-        // Update the connection's input state.
-        connection->inputState.trackKey(keyEntry, action);
-
         // Publish the key event.
-        status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->source,
-                action, flags, keyEntry->keyCode, keyEntry->scanCode,
+        status = connection->inputPublisher.publishKeyEvent(
+                keyEntry->deviceId, keyEntry->source,
+                dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
+                keyEntry->keyCode, keyEntry->scanCode,
                 keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
                 keyEntry->eventTime);
 
@@ -2024,24 +2109,6 @@
     case EventEntry::TYPE_MOTION: {
         MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
 
-        // Apply target flags.
-        int32_t action = motionEntry->action;
-        int32_t flags = motionEntry->flags;
-        if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
-            action = AMOTION_EVENT_ACTION_OUTSIDE;
-        } else if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT) {
-            action = AMOTION_EVENT_ACTION_HOVER_EXIT;
-        } else if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER) {
-            action = AMOTION_EVENT_ACTION_HOVER_ENTER;
-        } else if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
-            action = AMOTION_EVENT_ACTION_CANCEL;
-        } else if (dispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER) {
-            action = AMOTION_EVENT_ACTION_DOWN;
-        }
-        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.
@@ -2082,13 +2149,11 @@
             }
         }
 
-        // Update the connection's input state.
-        connection->inputState.trackMotion(motionEntry, action);
-
         // Publish the motion event and the first motion sample.
-        status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId,
-                motionEntry->source, action, flags, motionEntry->edgeFlags,
-                motionEntry->metaState, motionEntry->buttonState,
+        status = connection->inputPublisher.publishMotionEvent(
+                motionEntry->deviceId, motionEntry->source,
+                dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
+                motionEntry->edgeFlags, motionEntry->metaState, motionEntry->buttonState,
                 xOffset, yOffset,
                 motionEntry->xPrecision, motionEntry->yPrecision,
                 motionEntry->downTime, firstMotionSample->eventTime,
@@ -2102,8 +2167,8 @@
             return;
         }
 
-        if (action == AMOTION_EVENT_ACTION_MOVE
-                || action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+        if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_MOVE
+                || dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
             // Append additional motion samples.
             MotionSample* nextMotionSample = firstMotionSample->next;
             for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) {
@@ -2355,23 +2420,22 @@
                 break;
             }
 
-            int32_t xOffset, yOffset;
-            float scaleFactor;
+            InputTarget target;
             const InputWindow* window = getWindowLocked(connection->inputChannel);
             if (window) {
-                xOffset = -window->frameLeft;
-                yOffset = -window->frameTop;
-                scaleFactor = window->scaleFactor;
+                target.xOffset = -window->frameLeft;
+                target.yOffset = -window->frameTop;
+                target.scaleFactor = window->scaleFactor;
             } else {
-                xOffset = 0;
-                yOffset = 0;
-                scaleFactor = 1.0f;
+                target.xOffset = 0;
+                target.yOffset = 0;
+                target.scaleFactor = 1.0f;
             }
+            target.inputChannel = connection->inputChannel;
+            target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
 
-            DispatchEntry* cancelationDispatchEntry =
-                    mAllocator.obtainDispatchEntry(cancelationEventEntry, // increments ref
-                    0, xOffset, yOffset, scaleFactor);
-            connection->outboundQueue.enqueueAtTail(cancelationDispatchEntry);
+            enqueueDispatchEntryLocked(connection, cancelationEventEntry, // increments ref
+                    &target, false, InputTarget::FLAG_DISPATCH_AS_IS);
 
             mAllocator.releaseEventEntry(cancelationEventEntry);
         }
@@ -3327,6 +3391,7 @@
     resetTargetsLocked();
 
     mTouchState.reset();
+    mLastHoverWindow = NULL;
 }
 
 void InputDispatcher::logDispatchStateLocked() {
@@ -4125,111 +4190,180 @@
     return mKeyMementos.isEmpty() && mMotionMementos.isEmpty();
 }
 
-void InputDispatcher::InputState::trackEvent(const EventEntry* entry, int32_t action) {
-    switch (entry->type) {
-    case EventEntry::TYPE_KEY:
-        trackKey(static_cast<const KeyEntry*>(entry), action);
-        break;
+bool InputDispatcher::InputState::isHovering(int32_t deviceId, uint32_t source) const {
+    for (size_t i = 0; i < mMotionMementos.size(); i++) {
+        const MotionMemento& memento = mMotionMementos.itemAt(i);
+        if (memento.deviceId == deviceId
+                && memento.source == source
+                && memento.hovering) {
+            return true;
+        }
+    }
+    return false;
+}
 
-    case EventEntry::TYPE_MOTION:
-        trackMotion(static_cast<const MotionEntry*>(entry), action);
-        break;
+bool InputDispatcher::InputState::trackKey(const KeyEntry* entry,
+        int32_t action, int32_t flags) {
+    switch (action) {
+    case AKEY_EVENT_ACTION_UP: {
+        if (entry->flags & AKEY_EVENT_FLAG_FALLBACK) {
+            for (size_t i = 0; i < mFallbackKeys.size(); ) {
+                if (mFallbackKeys.valueAt(i) == entry->keyCode) {
+                    mFallbackKeys.removeItemsAt(i);
+                } else {
+                    i += 1;
+                }
+            }
+        }
+        ssize_t index = findKeyMemento(entry);
+        if (index >= 0) {
+            mKeyMementos.removeAt(index);
+            return true;
+        }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+        LOGD("Dropping inconsistent key up event: deviceId=%d, source=%08x, "
+                "keyCode=%d, scanCode=%d",
+                entry->deviceId, entry->source, entry->keyCode, entry->scanCode);
+#endif
+        return false;
+    }
+
+    case AKEY_EVENT_ACTION_DOWN: {
+        ssize_t index = findKeyMemento(entry);
+        if (index >= 0) {
+            mKeyMementos.removeAt(index);
+        }
+        addKeyMemento(entry, flags);
+        return true;
+    }
+
+    default:
+        return true;
     }
 }
 
-void InputDispatcher::InputState::trackKey(const KeyEntry* entry, int32_t action) {
-    if (action == AKEY_EVENT_ACTION_UP
-            && (entry->flags & AKEY_EVENT_FLAG_FALLBACK)) {
-        for (size_t i = 0; i < mFallbackKeys.size(); ) {
-            if (mFallbackKeys.valueAt(i) == entry->keyCode) {
-                mFallbackKeys.removeItemsAt(i);
-            } else {
-                i += 1;
-            }
+bool InputDispatcher::InputState::trackMotion(const MotionEntry* entry,
+        int32_t action, int32_t flags) {
+    int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
+    switch (actionMasked) {
+    case AMOTION_EVENT_ACTION_UP:
+    case AMOTION_EVENT_ACTION_CANCEL: {
+        ssize_t index = findMotionMemento(entry, false /*hovering*/);
+        if (index >= 0) {
+            mMotionMementos.removeAt(index);
+            return true;
         }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+        LOGD("Dropping inconsistent motion up or cancel event: deviceId=%d, source=%08x, "
+                "actionMasked=%d",
+                entry->deviceId, entry->source, actionMasked);
+#endif
+        return false;
     }
 
+    case AMOTION_EVENT_ACTION_DOWN: {
+        ssize_t index = findMotionMemento(entry, false /*hovering*/);
+        if (index >= 0) {
+            mMotionMementos.removeAt(index);
+        }
+        addMotionMemento(entry, flags, false /*hovering*/);
+        return true;
+    }
+
+    case AMOTION_EVENT_ACTION_POINTER_UP:
+    case AMOTION_EVENT_ACTION_POINTER_DOWN:
+    case AMOTION_EVENT_ACTION_MOVE: {
+        ssize_t index = findMotionMemento(entry, false /*hovering*/);
+        if (index >= 0) {
+            MotionMemento& memento = mMotionMementos.editItemAt(index);
+            memento.setPointers(entry);
+            return true;
+        }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+        LOGD("Dropping inconsistent motion pointer up/down or move event: "
+                "deviceId=%d, source=%08x, actionMasked=%d",
+                entry->deviceId, entry->source, actionMasked);
+#endif
+        return false;
+    }
+
+    case AMOTION_EVENT_ACTION_HOVER_EXIT: {
+        ssize_t index = findMotionMemento(entry, true /*hovering*/);
+        if (index >= 0) {
+            mMotionMementos.removeAt(index);
+            return true;
+        }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+        LOGD("Dropping inconsistent motion hover exit event: deviceId=%d, source=%08x",
+                entry->deviceId, entry->source);
+#endif
+        return false;
+    }
+
+    case AMOTION_EVENT_ACTION_HOVER_ENTER:
+    case AMOTION_EVENT_ACTION_HOVER_MOVE: {
+        ssize_t index = findMotionMemento(entry, true /*hovering*/);
+        if (index >= 0) {
+            mMotionMementos.removeAt(index);
+        }
+        addMotionMemento(entry, flags, true /*hovering*/);
+        return true;
+    }
+
+    default:
+        return true;
+    }
+}
+
+ssize_t InputDispatcher::InputState::findKeyMemento(const KeyEntry* entry) const {
     for (size_t i = 0; i < mKeyMementos.size(); i++) {
-        KeyMemento& memento = mKeyMementos.editItemAt(i);
+        const KeyMemento& memento = mKeyMementos.itemAt(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);
-                return;
-
-            case AKEY_EVENT_ACTION_DOWN:
-                mKeyMementos.removeAt(i);
-                goto Found;
-
-            default:
-                return;
-            }
+            return i;
         }
     }
-
-Found:
-    if (action == 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.flags = entry->flags;
-        memento.downTime = entry->downTime;
-    }
+    return -1;
 }
 
-void InputDispatcher::InputState::trackMotion(const MotionEntry* entry, int32_t action) {
-    int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
+ssize_t InputDispatcher::InputState::findMotionMemento(const MotionEntry* entry,
+        bool hovering) const {
     for (size_t i = 0; i < mMotionMementos.size(); i++) {
-        MotionMemento& memento = mMotionMementos.editItemAt(i);
+        const MotionMemento& memento = mMotionMementos.itemAt(i);
         if (memento.deviceId == entry->deviceId
-                && memento.source == entry->source) {
-            switch (actionMasked) {
-            case AMOTION_EVENT_ACTION_UP:
-            case AMOTION_EVENT_ACTION_CANCEL:
-            case AMOTION_EVENT_ACTION_HOVER_ENTER:
-            case AMOTION_EVENT_ACTION_HOVER_MOVE:
-            case AMOTION_EVENT_ACTION_HOVER_EXIT:
-                mMotionMementos.removeAt(i);
-                return;
-
-            case AMOTION_EVENT_ACTION_DOWN:
-                mMotionMementos.removeAt(i);
-                goto Found;
-
-            case AMOTION_EVENT_ACTION_POINTER_UP:
-            case AMOTION_EVENT_ACTION_POINTER_DOWN:
-            case AMOTION_EVENT_ACTION_MOVE:
-                memento.setPointers(entry);
-                return;
-
-            default:
-                return;
-            }
+                && memento.source == entry->source
+                && memento.hovering == hovering) {
+            return i;
         }
     }
+    return -1;
+}
 
-Found:
-    switch (actionMasked) {
-    case AMOTION_EVENT_ACTION_DOWN:
-    case AMOTION_EVENT_ACTION_HOVER_ENTER:
-    case AMOTION_EVENT_ACTION_HOVER_MOVE:
-    case AMOTION_EVENT_ACTION_HOVER_EXIT:
-        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);
-        memento.hovering = actionMasked != AMOTION_EVENT_ACTION_DOWN;
-    }
+void InputDispatcher::InputState::addKeyMemento(const KeyEntry* entry, int32_t flags) {
+    mKeyMementos.push();
+    KeyMemento& memento = mKeyMementos.editTop();
+    memento.deviceId = entry->deviceId;
+    memento.source = entry->source;
+    memento.keyCode = entry->keyCode;
+    memento.scanCode = entry->scanCode;
+    memento.flags = flags;
+    memento.downTime = entry->downTime;
+}
+
+void InputDispatcher::InputState::addMotionMemento(const MotionEntry* entry,
+        int32_t flags, bool hovering) {
+    mMotionMementos.push();
+    MotionMemento& memento = mMotionMementos.editTop();
+    memento.deviceId = entry->deviceId;
+    memento.source = entry->source;
+    memento.flags = flags;
+    memento.xPrecision = entry->xPrecision;
+    memento.yPrecision = entry->yPrecision;
+    memento.downTime = entry->downTime;
+    memento.setPointers(entry);
+    memento.hovering = hovering;
 }
 
 void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) {
@@ -4243,20 +4377,17 @@
 void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTime,
         Allocator* allocator, Vector<EventEntry*>& outEvents,
         const CancelationOptions& options) {
-    for (size_t i = 0; i < mKeyMementos.size(); ) {
+    for (size_t i = 0; i < mKeyMementos.size(); i++) {
         const KeyMemento& memento = mKeyMementos.itemAt(i);
         if (shouldCancelKey(memento, options)) {
             outEvents.push(allocator->obtainKeyEntry(currentTime,
                     memento.deviceId, memento.source, 0,
                     AKEY_EVENT_ACTION_UP, memento.flags | AKEY_EVENT_FLAG_CANCELED,
                     memento.keyCode, memento.scanCode, 0, 0, memento.downTime));
-            mKeyMementos.removeAt(i);
-        } else {
-            i += 1;
         }
     }
 
-    for (size_t i = 0; i < mMotionMementos.size(); ) {
+    for (size_t i = 0; i < mMotionMementos.size(); i++) {
         const MotionMemento& memento = mMotionMementos.itemAt(i);
         if (shouldCancelMotion(memento, options)) {
             outEvents.push(allocator->obtainMotionEntry(currentTime,
@@ -4264,12 +4395,9 @@
                     memento.hovering
                             ? AMOTION_EVENT_ACTION_HOVER_EXIT
                             : AMOTION_EVENT_ACTION_CANCEL,
-                    0, 0, 0, 0,
+                    memento.flags, 0, 0, 0,
                     memento.xPrecision, memento.yPrecision, memento.downTime,
                     memento.pointerCount, memento.pointerProperties, memento.pointerCoords));
-            mMotionMementos.removeAt(i);
-        } else {
-            i += 1;
         }
     }
 }
diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h
index 676d162..bdd1922 100644
--- a/services/input/InputDispatcher.h
+++ b/services/input/InputDispatcher.h
@@ -522,6 +522,10 @@
         // True if dispatch has started.
         bool inProgress;
 
+        // Set to the resolved action and flags when the event is enqueued.
+        int32_t resolvedAction;
+        int32_t resolvedFlags;
+
         // For motion events:
         //   Pointer to the first motion sample to dispatch in this cycle.
         //   Usually NULL to indicate that the list of motion samples begins at
@@ -709,14 +713,19 @@
         // Returns true if there is no state to be canceled.
         bool isNeutral() const;
 
-        // Records tracking information for an event that has just been published.
-        void trackEvent(const EventEntry* entry, int32_t action);
+        // Returns true if the specified source is known to have received a hover enter
+        // motion event.
+        bool isHovering(int32_t deviceId, uint32_t source) const;
 
         // Records tracking information for a key event that has just been published.
-        void trackKey(const KeyEntry* entry, int32_t action);
+        // Returns true if the event should be delivered, false if it is inconsistent
+        // and should be skipped.
+        bool trackKey(const KeyEntry* entry, int32_t action, int32_t flags);
 
         // Records tracking information for a motion event that has just been published.
-        void trackMotion(const MotionEntry* entry, int32_t action);
+        // Returns true if the event should be delivered, false if it is inconsistent
+        // and should be skipped.
+        bool trackMotion(const MotionEntry* entry, int32_t action, int32_t flags);
 
         // Synthesizes cancelation events for the current state and resets the tracked state.
         void synthesizeCancelationEvents(nsecs_t currentTime, Allocator* allocator,
@@ -756,6 +765,7 @@
         struct MotionMemento {
             int32_t deviceId;
             uint32_t source;
+            int32_t flags;
             float xPrecision;
             float yPrecision;
             nsecs_t downTime;
@@ -771,6 +781,12 @@
         Vector<MotionMemento> mMotionMementos;
         KeyedVector<int32_t, int32_t> mFallbackKeys;
 
+        ssize_t findKeyMemento(const KeyEntry* entry) const;
+        ssize_t findMotionMemento(const MotionEntry* entry, bool hovering) const;
+
+        void addKeyMemento(const KeyEntry* entry, int32_t flags);
+        void addMotionMemento(const MotionEntry* entry, int32_t flags, bool hovering);
+
         static bool shouldCancelKey(const KeyMemento& memento,
                 const CancelationOptions& options);
         static bool shouldCancelMotion(const MotionMemento& memento,
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 5a25f8c..a16e7d7 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -3704,6 +3704,29 @@
                 mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
                 mPointerGesture.currentGestureIdBits, -1,
                 0, 0, mPointerGesture.downTime);
+    } else if (dispatchedGestureIdBits.isEmpty()
+            && !mPointerGesture.lastGestureIdBits.isEmpty()) {
+        // Synthesize a hover move event after all pointers go up to indicate that
+        // the pointer is hovering again even if the user is not currently touching
+        // the touch pad.  This ensures that a view will receive a fresh hover enter
+        // event after a tap.
+        float x, y;
+        mPointerController->getPosition(&x, &y);
+
+        PointerProperties pointerProperties;
+        pointerProperties.clear();
+        pointerProperties.id = 0;
+        pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_INDIRECT_FINGER;
+
+        PointerCoords pointerCoords;
+        pointerCoords.clear();
+        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+
+        getDispatcher()->notifyMotion(when, getDeviceId(), mPointerSource, policyFlags,
+                AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
+                metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+                1, &pointerProperties, &pointerCoords, 0, 0, mPointerGesture.downTime);
     }
 
     // Update state.