diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index f284f511ac6b9..1ccc66fdf63e7 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -590,8 +590,14 @@ public class GestureDetector { 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 6618f07e1089a..b5ca2c2a8c1ca 100644 --- a/core/java/android/view/InputEventConsistencyVerifier.java +++ b/core/java/android/view/InputEventConsistencyVerifier.java @@ -54,6 +54,7 @@ public final class InputEventConsistencyVerifier { // Copy of the most recent events. private InputEvent[] mRecentEvents; + private boolean[] mRecentEventsUnhandled; private int mMostRecentEventIndex; // Current event and its type. @@ -65,6 +66,7 @@ public final class InputEventConsistencyVerifier { // 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 @@ public final class InputEventConsistencyVerifier { // 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 @@ public final class InputEventConsistencyVerifier { 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 @@ public final class InputEventConsistencyVerifier { // 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 @@ public final class InputEventConsistencyVerifier { 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 @@ public final class InputEventConsistencyVerifier { problem("ACTION_UP but trackball is not down."); } else { mTrackballDown = false; + mTrackballUnhandled = false; } ensureHistorySizeIsZeroForThisAction(event); ensurePointerCountIsOneForThisAction(event); @@ -285,11 +302,13 @@ public final class InputEventConsistencyVerifier { 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 @@ public final class InputEventConsistencyVerifier { } } + /** + * 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 @@ public final class InputEventConsistencyVerifier { 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 @@ public final class InputEventConsistencyVerifier { 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 @@ public final class InputEventConsistencyVerifier { 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 @@ public final class InputEventConsistencyVerifier { 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 final class InputEventConsistencyVerifier { public int deviceId; public int source; public int keyCode; + public boolean unhandled; private KeyState() { } @@ -625,6 +701,7 @@ public final class InputEventConsistencyVerifier { 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 456857a48bac4..5e07e1a7c86dd 100644 --- a/core/java/android/view/ScaleGestureDetector.java +++ b/core/java/android/view/ScaleGestureDetector.java @@ -183,15 +183,15 @@ public class ScaleGestureDetector { } 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 @@ public class ScaleGestureDetector { 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 dc8e52f03ca83..fe8af1974deb7 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4614,8 +4614,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } //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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility && 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 0d4f3d093eb9b..08daa280b10a9 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -1131,9 +1131,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } 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 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } 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 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager 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; }