Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bbdc50b1 authored by Jeff Brown's avatar Jeff Brown
Browse files

Track unhandled input events in consistency verifiers.

This fixes spurious verification errors that would be generated
when a view declined an initial event such as ACTION_DOWN.  Since
the view would not receive the rest of the event stream, it would
not see the corresponding ACTION_UP and the next ACTION_DOWN would
trigger a spurious verification error.

Change-Id: I2386acf378cd1765d5446faed5ad9c6525f8b400
parent 79ac969d
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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;
    }
+83 −6
Original line number Diff line number Diff line
@@ -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;
        }

+8 −4
Original line number Diff line number Diff line
@@ -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;
    }

+39 −11
Original line number Diff line number Diff line
@@ -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;
            }
        return onTouchEvent(event);

            if (onTouchEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        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;
    }

    /**
+151 −132
Original line number Diff line number Diff line
@@ -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,10 +1360,8 @@ 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;

@@ -1367,7 +1381,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case onInterceptTouchEvent() changed it
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
@@ -1399,7 +1413,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager

                    final int childrenCount = mChildrenCount;
                    if (childrenCount != 0) {
                    // Find a child that can receive the event.  Scan children from front to back.
                        // 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);
@@ -1446,7 +1461,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
            }

            // 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,
@@ -1461,7 +1475,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                        || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
@@ -1492,7 +1507,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }