Merge "Track unhandled input events in consistency verifiers."
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index f284f51..1ccc66f 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -590,8 +590,14 @@
             mHandler.removeMessages(SHOW_PRESS);
             mHandler.removeMessages(LONG_PRESS);
             break;
+
         case MotionEvent.ACTION_CANCEL:
             cancel();
+            break;
+        }
+
+        if (!handled && mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
         }
         return handled;
     }
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index 6618f07..b5ca2c2 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -54,6 +54,7 @@
 
     // Copy of the most recent events.
     private InputEvent[] mRecentEvents;
+    private boolean[] mRecentEventsUnhandled;
     private int mMostRecentEventIndex;
 
     // Current event and its type.
@@ -65,6 +66,7 @@
 
     // Current state of the trackball.
     private boolean mTrackballDown;
+    private boolean mTrackballUnhandled;
 
     // Bitfield of pointer ids that are currently down.
     // Assumes that the largest possible pointer id is 31, which is potentially subject to change.
@@ -79,6 +81,9 @@
     // Reset on down or cancel.
     private boolean mTouchEventStreamIsTainted;
 
+    // Set to true if the touch event stream is partially unhandled.
+    private boolean mTouchEventStreamUnhandled;
+
     // Set to true if we received hover enter.
     private boolean mHoverEntered;
 
@@ -117,9 +122,17 @@
         mLastEvent = null;
         mLastNestingLevel = 0;
         mTrackballDown = false;
+        mTrackballUnhandled = false;
         mTouchEventStreamPointers = 0;
         mTouchEventStreamIsTainted = false;
+        mTouchEventStreamUnhandled = false;
         mHoverEntered = false;
+
+        while (mKeyStateList != null) {
+            final KeyState state = mKeyStateList;
+            mKeyStateList = state.next;
+            state.recycle();
+        }
     }
 
     /**
@@ -176,7 +189,9 @@
                         // We don't perform this check when processing raw device input
                         // because the input dispatcher itself is responsible for setting
                         // the key repeat count before it delivers input events.
-                        if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0
+                        if (state.unhandled) {
+                            state.unhandled = false;
+                        } else if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0
                                 && event.getRepeatCount() == 0) {
                             problem("ACTION_DOWN but key is already down and this event "
                                     + "is not a key repeat.");
@@ -229,10 +244,11 @@
             if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                 switch (action) {
                     case MotionEvent.ACTION_DOWN:
-                        if (mTrackballDown) {
+                        if (mTrackballDown && !mTrackballUnhandled) {
                             problem("ACTION_DOWN but trackball is already down.");
                         } else {
                             mTrackballDown = true;
+                            mTrackballUnhandled = false;
                         }
                         ensureHistorySizeIsZeroForThisAction(event);
                         ensurePointerCountIsOneForThisAction(event);
@@ -242,6 +258,7 @@
                             problem("ACTION_UP but trackball is not down.");
                         } else {
                             mTrackballDown = false;
+                            mTrackballUnhandled = false;
                         }
                         ensureHistorySizeIsZeroForThisAction(event);
                         ensurePointerCountIsOneForThisAction(event);
@@ -285,11 +302,13 @@
         final int action = event.getAction();
         final boolean newStream = action == MotionEvent.ACTION_DOWN
                 || action == MotionEvent.ACTION_CANCEL;
-        if (mTouchEventStreamIsTainted) {
+        if (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled) {
             if (newStream) {
                 mTouchEventStreamIsTainted = false;
+                mTouchEventStreamUnhandled = false;
+                mTouchEventStreamPointers = 0;
             } else {
-                finishEvent(true);
+                finishEvent(mTouchEventStreamIsTainted);
                 return;
             }
         }
@@ -467,6 +486,48 @@
         }
     }
 
+    /**
+     * Notifies the verifier that a given event was unhandled and the rest of the
+     * trace for the event should be ignored.
+     * This method should only be called if the event was previously checked by
+     * the consistency verifier using {@link #onInputEvent} and other methods.
+     * @param event The event.
+     * @param nestingLevel The nesting level: 0 if called from the base class,
+     * or 1 from a subclass.  If the event was already checked by this consistency verifier
+     * at a higher nesting level, it will not be checked again.  Used to handle the situation
+     * where a subclass dispatching method delegates to its superclass's dispatching method
+     * and both dispatching methods call into the consistency verifier.
+     */
+    public void onUnhandledEvent(InputEvent event, int nestingLevel) {
+        if (nestingLevel != mLastNestingLevel) {
+            return;
+        }
+
+        if (mRecentEventsUnhandled != null) {
+            mRecentEventsUnhandled[mMostRecentEventIndex] = true;
+        }
+
+        if (event instanceof KeyEvent) {
+            final KeyEvent keyEvent = (KeyEvent)event;
+            final int deviceId = keyEvent.getDeviceId();
+            final int source = keyEvent.getSource();
+            final int keyCode = keyEvent.getKeyCode();
+            final KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
+            if (state != null) {
+                state.unhandled = true;
+            }
+        } else {
+            final MotionEvent motionEvent = (MotionEvent)event;
+            if (motionEvent.isTouchEvent()) {
+                mTouchEventStreamUnhandled = true;
+            } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+                if (mTrackballDown) {
+                    mTrackballUnhandled = true;
+                }
+            }
+        }
+    }
+
     private void ensureMetaStateIsNormalized(int metaState) {
         final int normalizedMetaState = KeyEvent.normalizeMetaState(metaState);
         if (normalizedMetaState != metaState) {
@@ -518,7 +579,8 @@
     private void finishEvent(boolean tainted) {
         if (mViolationMessage != null && mViolationMessage.length() != 0) {
             mViolationMessage.append("\n  in ").append(mCaller);
-            mViolationMessage.append("\n  ").append(mCurrentEvent);
+            mViolationMessage.append("\n  ");
+            appendEvent(mViolationMessage, 0, mCurrentEvent, false);
 
             if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents != null) {
                 mViolationMessage.append("\n  -- recent events --");
@@ -529,7 +591,8 @@
                     if (event == null) {
                         break;
                     }
-                    mViolationMessage.append("\n  ").append(i + 1).append(": ").append(event);
+                    mViolationMessage.append("\n  ");
+                    appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]);
                 }
             }
 
@@ -547,6 +610,7 @@
         if (RECENT_EVENTS_TO_LOG != 0) {
             if (mRecentEvents == null) {
                 mRecentEvents = new InputEvent[RECENT_EVENTS_TO_LOG];
+                mRecentEventsUnhandled = new boolean[RECENT_EVENTS_TO_LOG];
             }
             final int index = (mMostRecentEventIndex + 1) % RECENT_EVENTS_TO_LOG;
             mMostRecentEventIndex = index;
@@ -554,12 +618,23 @@
                 mRecentEvents[index].recycle();
             }
             mRecentEvents[index] = mCurrentEvent.copy();
+            mRecentEventsUnhandled[index] = false;
         }
 
         mCurrentEvent = null;
         mCurrentEventType = null;
     }
 
+    private static void appendEvent(StringBuilder message, int index,
+            InputEvent event, boolean unhandled) {
+        message.append(index).append(": sent at ").append(event.getEventTimeNano());
+        message.append(", ");
+        if (unhandled) {
+            message.append("(unhandled) ");
+        }
+        message.append(event);
+    }
+
     private void problem(String message) {
         if (mViolationMessage == null) {
             mViolationMessage = new StringBuilder();
@@ -608,6 +683,7 @@
         public int deviceId;
         public int source;
         public int keyCode;
+        public boolean unhandled;
 
         private KeyState() {
         }
@@ -625,6 +701,7 @@
             state.deviceId = deviceId;
             state.source = source;
             state.keyCode = keyCode;
+            state.unhandled = false;
             return state;
         }
 
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 456857a..5e07e1a 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -183,15 +183,15 @@
         }
 
         final int action = event.getActionMasked();
-        boolean handled = true;
 
         if (action == MotionEvent.ACTION_DOWN) {
             reset(); // Start fresh
         }
 
-        if (mInvalidGesture) return false;
-
-        if (!mGestureInProgress) {
+        boolean handled = true;
+        if (mInvalidGesture) {
+            handled = false;
+        } else if (!mGestureInProgress) {
             switch (action) {
             case MotionEvent.ACTION_DOWN: {
                 mActiveId0 = event.getPointerId(0);
@@ -467,6 +467,10 @@
                 break;
             }
         }
+
+        if (!handled && mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+        }
         return handled;
     }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index dc8e52f..fe8af19 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4614,8 +4614,15 @@
             return true;
         }
 
-        return event.dispatch(this, mAttachInfo != null
-                ? mAttachInfo.mKeyDispatchState : null, this);
+        if (event.dispatch(this, mAttachInfo != null
+                ? mAttachInfo.mKeyDispatchState : null, this)) {
+            return true;
+        }
+
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+        }
+        return false;
     }
 
     /**
@@ -4640,16 +4647,22 @@
             mInputEventConsistencyVerifier.onTouchEvent(event, 0);
         }
 
-        if (!onFilterTouchEventForSecurity(event)) {
-            return false;
+        if (onFilterTouchEventForSecurity(event)) {
+            //noinspection SimplifiableIfStatement
+            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
+                    mOnTouchListener.onTouch(this, event)) {
+                return true;
+            }
+
+            if (onTouchEvent(event)) {
+                return true;
+            }
         }
 
-        //noinspection SimplifiableIfStatement
-        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
-                mOnTouchListener.onTouch(this, event)) {
-            return true;
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
         }
-        return onTouchEvent(event);
+        return false;
     }
 
     /**
@@ -4682,7 +4695,14 @@
         }
 
         //Log.i("view", "view=" + this + ", " + event.toString());
-        return onTrackballEvent(event);
+        if (onTrackballEvent(event)) {
+            return true;
+        }
+
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+        }
+        return false;
     }
 
     /**
@@ -4723,7 +4743,15 @@
                 && mOnGenericMotionListener.onGenericMotion(this, event)) {
             return true;
         }
-        return onGenericMotionEvent(event);
+
+        if (onGenericMotionEvent(event)) {
+            return true;
+        }
+
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+        }
+        return false;
     }
 
     /**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 0d4f3d0..08daa28 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1131,9 +1131,17 @@
         }
 
         if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
-            return super.dispatchKeyEvent(event);
+            if (super.dispatchKeyEvent(event)) {
+                return true;
+            }
         } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
-            return mFocused.dispatchKeyEvent(event);
+            if (mFocused.dispatchKeyEvent(event)) {
+                return true;
+            }
+        }
+
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
         }
         return false;
     }
@@ -1161,9 +1169,17 @@
         }
 
         if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
-            return super.dispatchTrackballEvent(event);
+            if (super.dispatchTrackballEvent(event)) {
+                return true;
+            }
         } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
-            return mFocused.dispatchTrackballEvent(event);
+            if (mFocused.dispatchTrackballEvent(event)) {
+                return true;
+            }
+        }
+
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
         }
         return false;
     }
@@ -1344,155 +1360,158 @@
             mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
         }
 
-        if (!onFilterTouchEventForSecurity(ev)) {
-            return false;
-        }
+        boolean handled = false;
+        if (onFilterTouchEventForSecurity(ev)) {
+            final int action = ev.getAction();
+            final int actionMasked = action & MotionEvent.ACTION_MASK;
 
-        final int action = ev.getAction();
-        final int actionMasked = action & MotionEvent.ACTION_MASK;
-
-        // Handle an initial down.
-        if (actionMasked == MotionEvent.ACTION_DOWN) {
-            // Throw away all previous state when starting a new touch gesture.
-            // The framework may have dropped the up or cancel event for the previous gesture
-            // due to an app switch, ANR, or some other state change.
-            cancelAndClearTouchTargets(ev);
-            resetTouchState();
-        }
-
-        // Check for interception.
-        final boolean intercepted;
-        if (actionMasked == MotionEvent.ACTION_DOWN
-                || mFirstTouchTarget != null) {
-            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
-            if (!disallowIntercept) {
-                intercepted = onInterceptTouchEvent(ev);
-                ev.setAction(action); // restore action in case onInterceptTouchEvent() changed it
-            } else {
-                intercepted = false;
+            // Handle an initial down.
+            if (actionMasked == MotionEvent.ACTION_DOWN) {
+                // Throw away all previous state when starting a new touch gesture.
+                // The framework may have dropped the up or cancel event for the previous gesture
+                // due to an app switch, ANR, or some other state change.
+                cancelAndClearTouchTargets(ev);
+                resetTouchState();
             }
-        } else {
-            // There are no touch targets and this action is not an initial down
-            // so this view group continues to intercept touches.
-            intercepted = true;
-        }
 
-        // Check for cancelation.
-        final boolean canceled = resetCancelNextUpFlag(this)
-                || actionMasked == MotionEvent.ACTION_CANCEL;
-
-        // Update list of touch targets for pointer down, if needed.
-        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
-        TouchTarget newTouchTarget = null;
-        boolean alreadyDispatchedToNewTouchTarget = false;
-        if (!canceled && !intercepted) {
+            // Check for interception.
+            final boolean intercepted;
             if (actionMasked == MotionEvent.ACTION_DOWN
-                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
-                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
-                final int actionIndex = ev.getActionIndex(); // always 0 for down
-                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
-                        : TouchTarget.ALL_POINTER_IDS;
+                    || mFirstTouchTarget != null) {
+                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
+                if (!disallowIntercept) {
+                    intercepted = onInterceptTouchEvent(ev);
+                    ev.setAction(action); // restore action in case it was changed
+                } else {
+                    intercepted = false;
+                }
+            } else {
+                // There are no touch targets and this action is not an initial down
+                // so this view group continues to intercept touches.
+                intercepted = true;
+            }
 
-                // Clean up earlier touch targets for this pointer id in case they
-                // have become out of sync.
-                removePointersFromTouchTargets(idBitsToAssign);
+            // Check for cancelation.
+            final boolean canceled = resetCancelNextUpFlag(this)
+                    || actionMasked == MotionEvent.ACTION_CANCEL;
 
-                final int childrenCount = mChildrenCount;
-                if (childrenCount != 0) {
-                    // Find a child that can receive the event.  Scan children from front to back.
-                    final View[] children = mChildren;
-                    final float x = ev.getX(actionIndex);
-                    final float y = ev.getY(actionIndex);
+            // Update list of touch targets for pointer down, if needed.
+            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
+            TouchTarget newTouchTarget = null;
+            boolean alreadyDispatchedToNewTouchTarget = false;
+            if (!canceled && !intercepted) {
+                if (actionMasked == MotionEvent.ACTION_DOWN
+                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
+                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
+                    final int actionIndex = ev.getActionIndex(); // always 0 for down
+                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
+                            : TouchTarget.ALL_POINTER_IDS;
 
-                    for (int i = childrenCount - 1; i >= 0; i--) {
-                        final View child = children[i];
-                        if (!canViewReceivePointerEvents(child)
-                                || !isTransformedTouchPointInView(x, y, child, null)) {
+                    // Clean up earlier touch targets for this pointer id in case they
+                    // have become out of sync.
+                    removePointersFromTouchTargets(idBitsToAssign);
+
+                    final int childrenCount = mChildrenCount;
+                    if (childrenCount != 0) {
+                        // Find a child that can receive the event.
+                        // Scan children from front to back.
+                        final View[] children = mChildren;
+                        final float x = ev.getX(actionIndex);
+                        final float y = ev.getY(actionIndex);
+
+                        for (int i = childrenCount - 1; i >= 0; i--) {
+                            final View child = children[i];
+                            if (!canViewReceivePointerEvents(child)
+                                    || !isTransformedTouchPointInView(x, y, child, null)) {
+                                continue;
+                            }
+
+                            newTouchTarget = getTouchTarget(child);
+                            if (newTouchTarget != null) {
+                                // Child is already receiving touch within its bounds.
+                                // Give it the new pointer in addition to the ones it is handling.
+                                newTouchTarget.pointerIdBits |= idBitsToAssign;
+                                break;
+                            }
+
+                            resetCancelNextUpFlag(child);
+                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
+                                // Child wants to receive touch within its bounds.
+                                mLastTouchDownTime = ev.getDownTime();
+                                mLastTouchDownIndex = i;
+                                mLastTouchDownX = ev.getX();
+                                mLastTouchDownY = ev.getY();
+                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
+                                alreadyDispatchedToNewTouchTarget = true;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (newTouchTarget == null && mFirstTouchTarget != null) {
+                        // Did not find a child to receive the event.
+                        // Assign the pointer to the least recently added target.
+                        newTouchTarget = mFirstTouchTarget;
+                        while (newTouchTarget.next != null) {
+                            newTouchTarget = newTouchTarget.next;
+                        }
+                        newTouchTarget.pointerIdBits |= idBitsToAssign;
+                    }
+                }
+            }
+
+            // Dispatch to touch targets.
+            if (mFirstTouchTarget == null) {
+                // No touch targets so treat this as an ordinary view.
+                handled = dispatchTransformedTouchEvent(ev, canceled, null,
+                        TouchTarget.ALL_POINTER_IDS);
+            } else {
+                // Dispatch to touch targets, excluding the new touch target if we already
+                // dispatched to it.  Cancel touch targets if necessary.
+                TouchTarget predecessor = null;
+                TouchTarget target = mFirstTouchTarget;
+                while (target != null) {
+                    final TouchTarget next = target.next;
+                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
+                        handled = true;
+                    } else {
+                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
+                        || intercepted;
+                        if (dispatchTransformedTouchEvent(ev, cancelChild,
+                                target.child, target.pointerIdBits)) {
+                            handled = true;
+                        }
+                        if (cancelChild) {
+                            if (predecessor == null) {
+                                mFirstTouchTarget = next;
+                            } else {
+                                predecessor.next = next;
+                            }
+                            target.recycle();
+                            target = next;
                             continue;
                         }
-
-                        newTouchTarget = getTouchTarget(child);
-                        if (newTouchTarget != null) {
-                            // Child is already receiving touch within its bounds.
-                            // Give it the new pointer in addition to the ones it is handling.
-                            newTouchTarget.pointerIdBits |= idBitsToAssign;
-                            break;
-                        }
-
-                        resetCancelNextUpFlag(child);
-                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
-                            // Child wants to receive touch within its bounds.
-                            mLastTouchDownTime = ev.getDownTime();
-                            mLastTouchDownIndex = i;
-                            mLastTouchDownX = ev.getX();
-                            mLastTouchDownY = ev.getY();
-                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
-                            alreadyDispatchedToNewTouchTarget = true;
-                            break;
-                        }
                     }
+                    predecessor = target;
+                    target = next;
                 }
+            }
 
-                if (newTouchTarget == null && mFirstTouchTarget != null) {
-                    // Did not find a child to receive the event.
-                    // Assign the pointer to the least recently added target.
-                    newTouchTarget = mFirstTouchTarget;
-                    while (newTouchTarget.next != null) {
-                        newTouchTarget = newTouchTarget.next;
-                    }
-                    newTouchTarget.pointerIdBits |= idBitsToAssign;
-                }
+            // Update list of touch targets for pointer up or cancel, if needed.
+            if (canceled
+                    || actionMasked == MotionEvent.ACTION_UP
+                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
+                resetTouchState();
+            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
+                final int actionIndex = ev.getActionIndex();
+                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
+                removePointersFromTouchTargets(idBitsToRemove);
             }
         }
 
-        // Dispatch to touch targets.
-        boolean handled = false;
-        if (mFirstTouchTarget == null) {
-            // No touch targets so treat this as an ordinary view.
-            handled = dispatchTransformedTouchEvent(ev, canceled, null,
-                    TouchTarget.ALL_POINTER_IDS);
-        } else {
-            // Dispatch to touch targets, excluding the new touch target if we already
-            // dispatched to it.  Cancel touch targets if necessary.
-            TouchTarget predecessor = null;
-            TouchTarget target = mFirstTouchTarget;
-            while (target != null) {
-                final TouchTarget next = target.next;
-                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
-                    handled = true;
-                } else {
-                    final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
-                    if (dispatchTransformedTouchEvent(ev, cancelChild,
-                            target.child, target.pointerIdBits)) {
-                        handled = true;
-                    }
-                    if (cancelChild) {
-                        if (predecessor == null) {
-                            mFirstTouchTarget = next;
-                        } else {
-                            predecessor.next = next;
-                        }
-                        target.recycle();
-                        target = next;
-                        continue;
-                    }
-                }
-                predecessor = target;
-                target = next;
-            }
+        if (!handled && mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
         }
-
-        // Update list of touch targets for pointer up or cancel, if needed.
-        if (canceled
-                || actionMasked == MotionEvent.ACTION_UP
-                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
-            resetTouchState();
-        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
-            final int actionIndex = ev.getActionIndex();
-            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
-            removePointersFromTouchTargets(idBitsToRemove);
-        }
-
         return handled;
     }