1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view;
18
19import android.os.Build;
20import android.util.Log;
21
22/**
23 * Checks whether a sequence of input events is self-consistent.
24 * Logs a description of each problem detected.
25 * <p>
26 * When a problem is detected, the event is tainted.  This mechanism prevents the same
27 * error from being reported multiple times.
28 * </p>
29 *
30 * @hide
31 */
32public final class InputEventConsistencyVerifier {
33    private static final boolean IS_ENG_BUILD = "eng".equals(Build.TYPE);
34
35    private static final String EVENT_TYPE_KEY = "KeyEvent";
36    private static final String EVENT_TYPE_TRACKBALL = "TrackballEvent";
37    private static final String EVENT_TYPE_TOUCH = "TouchEvent";
38    private static final String EVENT_TYPE_GENERIC_MOTION = "GenericMotionEvent";
39
40    // The number of recent events to log when a problem is detected.
41    // Can be set to 0 to disable logging recent events but the runtime overhead of
42    // this feature is negligible on current hardware.
43    private static final int RECENT_EVENTS_TO_LOG = 5;
44
45    // The object to which the verifier is attached.
46    private final Object mCaller;
47
48    // Consistency verifier flags.
49    private final int mFlags;
50
51    // Tag for logging which a client can set to help distinguish the output
52    // from different verifiers since several can be active at the same time.
53    // If not provided defaults to the simple class name.
54    private final String mLogTag;
55
56    // The most recently checked event and the nesting level at which it was checked.
57    // This is only set when the verifier is called from a nesting level greater than 0
58    // so that the verifier can detect when it has been asked to verify the same event twice.
59    // It does not make sense to examine the contents of the last event since it may have
60    // been recycled.
61    private int mLastEventSeq;
62    private String mLastEventType;
63    private int mLastNestingLevel;
64
65    // Copy of the most recent events.
66    private InputEvent[] mRecentEvents;
67    private boolean[] mRecentEventsUnhandled;
68    private int mMostRecentEventIndex;
69
70    // Current event and its type.
71    private InputEvent mCurrentEvent;
72    private String mCurrentEventType;
73
74    // Linked list of key state objects.
75    private KeyState mKeyStateList;
76
77    // Current state of the trackball.
78    private boolean mTrackballDown;
79    private boolean mTrackballUnhandled;
80
81    // Bitfield of pointer ids that are currently down.
82    // Assumes that the largest possible pointer id is 31, which is potentially subject to change.
83    // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
84    private int mTouchEventStreamPointers;
85
86    // The device id and source of the current stream of touch events.
87    private int mTouchEventStreamDeviceId = -1;
88    private int mTouchEventStreamSource;
89
90    // Set to true when we discover that the touch event stream is inconsistent.
91    // Reset on down or cancel.
92    private boolean mTouchEventStreamIsTainted;
93
94    // Set to true if the touch event stream is partially unhandled.
95    private boolean mTouchEventStreamUnhandled;
96
97    // Set to true if we received hover enter.
98    private boolean mHoverEntered;
99
100    // The bitset of buttons which we've received ACTION_BUTTON_PRESS for.
101    private int mButtonsPressed;
102
103    // The current violation message.
104    private StringBuilder mViolationMessage;
105
106    /**
107     * Indicates that the verifier is intended to act on raw device input event streams.
108     * Disables certain checks for invariants that are established by the input dispatcher
109     * itself as it delivers input events, such as key repeating behavior.
110     */
111    public static final int FLAG_RAW_DEVICE_INPUT = 1 << 0;
112
113    /**
114     * Creates an input consistency verifier.
115     * @param caller The object to which the verifier is attached.
116     * @param flags Flags to the verifier, or 0 if none.
117     */
118    public InputEventConsistencyVerifier(Object caller, int flags) {
119        this(caller, flags, null);
120    }
121
122    /**
123     * Creates an input consistency verifier.
124     * @param caller The object to which the verifier is attached.
125     * @param flags Flags to the verifier, or 0 if none.
126     * @param logTag Tag for logging. If null defaults to the short class name.
127     */
128    public InputEventConsistencyVerifier(Object caller, int flags, String logTag) {
129        this.mCaller = caller;
130        this.mFlags = flags;
131        this.mLogTag = (logTag != null) ? logTag : "InputEventConsistencyVerifier";
132    }
133
134    /**
135     * Determines whether the instrumentation should be enabled.
136     * @return True if it should be enabled.
137     */
138    public static boolean isInstrumentationEnabled() {
139        return IS_ENG_BUILD;
140    }
141
142    /**
143     * Resets the state of the input event consistency verifier.
144     */
145    public void reset() {
146        mLastEventSeq = -1;
147        mLastNestingLevel = 0;
148        mTrackballDown = false;
149        mTrackballUnhandled = false;
150        mTouchEventStreamPointers = 0;
151        mTouchEventStreamIsTainted = false;
152        mTouchEventStreamUnhandled = false;
153        mHoverEntered = false;
154        mButtonsPressed = 0;
155
156        while (mKeyStateList != null) {
157            final KeyState state = mKeyStateList;
158            mKeyStateList = state.next;
159            state.recycle();
160        }
161    }
162
163    /**
164     * Checks an arbitrary input event.
165     * @param event The event.
166     * @param nestingLevel The nesting level: 0 if called from the base class,
167     * or 1 from a subclass.  If the event was already checked by this consistency verifier
168     * at a higher nesting level, it will not be checked again.  Used to handle the situation
169     * where a subclass dispatching method delegates to its superclass's dispatching method
170     * and both dispatching methods call into the consistency verifier.
171     */
172    public void onInputEvent(InputEvent event, int nestingLevel) {
173        if (event instanceof KeyEvent) {
174            final KeyEvent keyEvent = (KeyEvent)event;
175            onKeyEvent(keyEvent, nestingLevel);
176        } else {
177            final MotionEvent motionEvent = (MotionEvent)event;
178            if (motionEvent.isTouchEvent()) {
179                onTouchEvent(motionEvent, nestingLevel);
180            } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
181                onTrackballEvent(motionEvent, nestingLevel);
182            } else {
183                onGenericMotionEvent(motionEvent, nestingLevel);
184            }
185        }
186    }
187
188    /**
189     * Checks a key event.
190     * @param event The event.
191     * @param nestingLevel The nesting level: 0 if called from the base class,
192     * or 1 from a subclass.  If the event was already checked by this consistency verifier
193     * at a higher nesting level, it will not be checked again.  Used to handle the situation
194     * where a subclass dispatching method delegates to its superclass's dispatching method
195     * and both dispatching methods call into the consistency verifier.
196     */
197    public void onKeyEvent(KeyEvent event, int nestingLevel) {
198        if (!startEvent(event, nestingLevel, EVENT_TYPE_KEY)) {
199            return;
200        }
201
202        try {
203            ensureMetaStateIsNormalized(event.getMetaState());
204
205            final int action = event.getAction();
206            final int deviceId = event.getDeviceId();
207            final int source = event.getSource();
208            final int keyCode = event.getKeyCode();
209            switch (action) {
210                case KeyEvent.ACTION_DOWN: {
211                    KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
212                    if (state != null) {
213                        // If the key is already down, ensure it is a repeat.
214                        // We don't perform this check when processing raw device input
215                        // because the input dispatcher itself is responsible for setting
216                        // the key repeat count before it delivers input events.
217                        if (state.unhandled) {
218                            state.unhandled = false;
219                        } else if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0
220                                && event.getRepeatCount() == 0) {
221                            problem("ACTION_DOWN but key is already down and this event "
222                                    + "is not a key repeat.");
223                        }
224                    } else {
225                        addKeyState(deviceId, source, keyCode);
226                    }
227                    break;
228                }
229                case KeyEvent.ACTION_UP: {
230                    KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ true);
231                    if (state == null) {
232                        problem("ACTION_UP but key was not down.");
233                    } else {
234                        state.recycle();
235                    }
236                    break;
237                }
238                case KeyEvent.ACTION_MULTIPLE:
239                    break;
240                default:
241                    problem("Invalid action " + KeyEvent.actionToString(action)
242                            + " for key event.");
243                    break;
244            }
245        } finally {
246            finishEvent();
247        }
248    }
249
250    /**
251     * Checks a trackball event.
252     * @param event The event.
253     * @param nestingLevel The nesting level: 0 if called from the base class,
254     * or 1 from a subclass.  If the event was already checked by this consistency verifier
255     * at a higher nesting level, it will not be checked again.  Used to handle the situation
256     * where a subclass dispatching method delegates to its superclass's dispatching method
257     * and both dispatching methods call into the consistency verifier.
258     */
259    public void onTrackballEvent(MotionEvent event, int nestingLevel) {
260        if (!startEvent(event, nestingLevel, EVENT_TYPE_TRACKBALL)) {
261            return;
262        }
263
264        try {
265            ensureMetaStateIsNormalized(event.getMetaState());
266
267            final int action = event.getAction();
268            final int source = event.getSource();
269            if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
270                switch (action) {
271                    case MotionEvent.ACTION_DOWN:
272                        if (mTrackballDown && !mTrackballUnhandled) {
273                            problem("ACTION_DOWN but trackball is already down.");
274                        } else {
275                            mTrackballDown = true;
276                            mTrackballUnhandled = false;
277                        }
278                        ensureHistorySizeIsZeroForThisAction(event);
279                        ensurePointerCountIsOneForThisAction(event);
280                        break;
281                    case MotionEvent.ACTION_UP:
282                        if (!mTrackballDown) {
283                            problem("ACTION_UP but trackball is not down.");
284                        } else {
285                            mTrackballDown = false;
286                            mTrackballUnhandled = false;
287                        }
288                        ensureHistorySizeIsZeroForThisAction(event);
289                        ensurePointerCountIsOneForThisAction(event);
290                        break;
291                    case MotionEvent.ACTION_MOVE:
292                        ensurePointerCountIsOneForThisAction(event);
293                        break;
294                    default:
295                        problem("Invalid action " + MotionEvent.actionToString(action)
296                                + " for trackball event.");
297                        break;
298                }
299
300                if (mTrackballDown && event.getPressure() <= 0) {
301                    problem("Trackball is down but pressure is not greater than 0.");
302                } else if (!mTrackballDown && event.getPressure() != 0) {
303                    problem("Trackball is up but pressure is not equal to 0.");
304                }
305            } else {
306                problem("Source was not SOURCE_CLASS_TRACKBALL.");
307            }
308        } finally {
309            finishEvent();
310        }
311    }
312
313    /**
314     * Checks a touch event.
315     * @param event The event.
316     * @param nestingLevel The nesting level: 0 if called from the base class,
317     * or 1 from a subclass.  If the event was already checked by this consistency verifier
318     * at a higher nesting level, it will not be checked again.  Used to handle the situation
319     * where a subclass dispatching method delegates to its superclass's dispatching method
320     * and both dispatching methods call into the consistency verifier.
321     */
322    public void onTouchEvent(MotionEvent event, int nestingLevel) {
323        if (!startEvent(event, nestingLevel, EVENT_TYPE_TOUCH)) {
324            return;
325        }
326
327        final int action = event.getAction();
328        final boolean newStream = action == MotionEvent.ACTION_DOWN
329                || action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_OUTSIDE;
330        if (newStream && (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled)) {
331            mTouchEventStreamIsTainted = false;
332            mTouchEventStreamUnhandled = false;
333            mTouchEventStreamPointers = 0;
334        }
335        if (mTouchEventStreamIsTainted) {
336            event.setTainted(true);
337        }
338
339        try {
340            ensureMetaStateIsNormalized(event.getMetaState());
341
342            final int deviceId = event.getDeviceId();
343            final int source = event.getSource();
344
345            if (!newStream && mTouchEventStreamDeviceId != -1
346                    && (mTouchEventStreamDeviceId != deviceId
347                            || mTouchEventStreamSource != source)) {
348                problem("Touch event stream contains events from multiple sources: "
349                        + "previous device id " + mTouchEventStreamDeviceId
350                        + ", previous source " + Integer.toHexString(mTouchEventStreamSource)
351                        + ", new device id " + deviceId
352                        + ", new source " + Integer.toHexString(source));
353            }
354            mTouchEventStreamDeviceId = deviceId;
355            mTouchEventStreamSource = source;
356
357            final int pointerCount = event.getPointerCount();
358            if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
359                switch (action) {
360                    case MotionEvent.ACTION_DOWN:
361                        if (mTouchEventStreamPointers != 0) {
362                            problem("ACTION_DOWN but pointers are already down.  "
363                                    + "Probably missing ACTION_UP from previous gesture.");
364                        }
365                        ensureHistorySizeIsZeroForThisAction(event);
366                        ensurePointerCountIsOneForThisAction(event);
367                        mTouchEventStreamPointers = 1 << event.getPointerId(0);
368                        break;
369                    case MotionEvent.ACTION_UP:
370                        ensureHistorySizeIsZeroForThisAction(event);
371                        ensurePointerCountIsOneForThisAction(event);
372                        mTouchEventStreamPointers = 0;
373                        mTouchEventStreamIsTainted = false;
374                        break;
375                    case MotionEvent.ACTION_MOVE: {
376                        final int expectedPointerCount =
377                                Integer.bitCount(mTouchEventStreamPointers);
378                        if (pointerCount != expectedPointerCount) {
379                            problem("ACTION_MOVE contained " + pointerCount
380                                    + " pointers but there are currently "
381                                    + expectedPointerCount + " pointers down.");
382                            mTouchEventStreamIsTainted = true;
383                        }
384                        break;
385                    }
386                    case MotionEvent.ACTION_CANCEL:
387                        mTouchEventStreamPointers = 0;
388                        mTouchEventStreamIsTainted = false;
389                        break;
390                    case MotionEvent.ACTION_OUTSIDE:
391                        if (mTouchEventStreamPointers != 0) {
392                            problem("ACTION_OUTSIDE but pointers are still down.");
393                        }
394                        ensureHistorySizeIsZeroForThisAction(event);
395                        ensurePointerCountIsOneForThisAction(event);
396                        mTouchEventStreamIsTainted = false;
397                        break;
398                    default: {
399                        final int actionMasked = event.getActionMasked();
400                        final int actionIndex = event.getActionIndex();
401                        if (actionMasked == MotionEvent.ACTION_POINTER_DOWN) {
402                            if (mTouchEventStreamPointers == 0) {
403                                problem("ACTION_POINTER_DOWN but no other pointers were down.");
404                                mTouchEventStreamIsTainted = true;
405                            }
406                            if (actionIndex < 0 || actionIndex >= pointerCount) {
407                                problem("ACTION_POINTER_DOWN index is " + actionIndex
408                                        + " but the pointer count is " + pointerCount + ".");
409                                mTouchEventStreamIsTainted = true;
410                            } else {
411                                final int id = event.getPointerId(actionIndex);
412                                final int idBit = 1 << id;
413                                if ((mTouchEventStreamPointers & idBit) != 0) {
414                                    problem("ACTION_POINTER_DOWN specified pointer id " + id
415                                            + " which is already down.");
416                                    mTouchEventStreamIsTainted = true;
417                                } else {
418                                    mTouchEventStreamPointers |= idBit;
419                                }
420                            }
421                            ensureHistorySizeIsZeroForThisAction(event);
422                        } else if (actionMasked == MotionEvent.ACTION_POINTER_UP) {
423                            if (actionIndex < 0 || actionIndex >= pointerCount) {
424                                problem("ACTION_POINTER_UP index is " + actionIndex
425                                        + " but the pointer count is " + pointerCount + ".");
426                                mTouchEventStreamIsTainted = true;
427                            } else {
428                                final int id = event.getPointerId(actionIndex);
429                                final int idBit = 1 << id;
430                                if ((mTouchEventStreamPointers & idBit) == 0) {
431                                    problem("ACTION_POINTER_UP specified pointer id " + id
432                                            + " which is not currently down.");
433                                    mTouchEventStreamIsTainted = true;
434                                } else {
435                                    mTouchEventStreamPointers &= ~idBit;
436                                }
437                            }
438                            ensureHistorySizeIsZeroForThisAction(event);
439                        } else {
440                            problem("Invalid action " + MotionEvent.actionToString(action)
441                                    + " for touch event.");
442                        }
443                        break;
444                    }
445                }
446            } else {
447                problem("Source was not SOURCE_CLASS_POINTER.");
448            }
449        } finally {
450            finishEvent();
451        }
452    }
453
454    /**
455     * Checks a generic motion event.
456     * @param event The event.
457     * @param nestingLevel The nesting level: 0 if called from the base class,
458     * or 1 from a subclass.  If the event was already checked by this consistency verifier
459     * at a higher nesting level, it will not be checked again.  Used to handle the situation
460     * where a subclass dispatching method delegates to its superclass's dispatching method
461     * and both dispatching methods call into the consistency verifier.
462     */
463    public void onGenericMotionEvent(MotionEvent event, int nestingLevel) {
464        if (!startEvent(event, nestingLevel, EVENT_TYPE_GENERIC_MOTION)) {
465            return;
466        }
467
468        try {
469            ensureMetaStateIsNormalized(event.getMetaState());
470
471            final int action = event.getAction();
472            final int source = event.getSource();
473            final int buttonState = event.getButtonState();
474            final int actionButton = event.getActionButton();
475            if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
476                switch (action) {
477                    case MotionEvent.ACTION_HOVER_ENTER:
478                        ensurePointerCountIsOneForThisAction(event);
479                        mHoverEntered = true;
480                        break;
481                    case MotionEvent.ACTION_HOVER_MOVE:
482                        ensurePointerCountIsOneForThisAction(event);
483                        break;
484                    case MotionEvent.ACTION_HOVER_EXIT:
485                        ensurePointerCountIsOneForThisAction(event);
486                        if (!mHoverEntered) {
487                            problem("ACTION_HOVER_EXIT without prior ACTION_HOVER_ENTER");
488                        }
489                        mHoverEntered = false;
490                        break;
491                    case MotionEvent.ACTION_SCROLL:
492                        ensureHistorySizeIsZeroForThisAction(event);
493                        ensurePointerCountIsOneForThisAction(event);
494                        break;
495                    case MotionEvent.ACTION_BUTTON_PRESS:
496                        ensureActionButtonIsNonZeroForThisAction(event);
497                        if ((mButtonsPressed & actionButton) != 0) {
498                            problem("Action button for ACTION_BUTTON_PRESS event is " +
499                                    actionButton + ", but it has already been pressed and " +
500                                    "has yet to be released.");
501                        }
502
503                        mButtonsPressed |= actionButton;
504                        // The system will automatically mirror the stylus buttons onto the button
505                        // state as the old set of generic buttons for apps targeting pre-M. If
506                        // it looks this has happened, go ahead and set the generic buttons as
507                        // pressed to prevent spurious errors.
508                        if (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY &&
509                                (buttonState & MotionEvent.BUTTON_SECONDARY) != 0) {
510                            mButtonsPressed |= MotionEvent.BUTTON_SECONDARY;
511                        } else if (actionButton == MotionEvent.BUTTON_STYLUS_SECONDARY &&
512                                (buttonState & MotionEvent.BUTTON_TERTIARY) != 0) {
513                            mButtonsPressed |= MotionEvent.BUTTON_TERTIARY;
514                        }
515
516                        if (mButtonsPressed != buttonState) {
517                            problem(String.format("Reported button state differs from " +
518                                    "expected button state based on press and release events. " +
519                                    "Is 0x%08x but expected 0x%08x.",
520                                    buttonState, mButtonsPressed));
521                        }
522                        break;
523                    case MotionEvent.ACTION_BUTTON_RELEASE:
524                        ensureActionButtonIsNonZeroForThisAction(event);
525                        if ((mButtonsPressed & actionButton) != actionButton) {
526                            problem("Action button for ACTION_BUTTON_RELEASE event is " +
527                                    actionButton + ", but it was either never pressed or has " +
528                                    "already been released.");
529                        }
530
531                        mButtonsPressed &= ~actionButton;
532                        // The system will automatically mirror the stylus buttons onto the button
533                        // state as the old set of generic buttons for apps targeting pre-M. If
534                        // it looks this has happened, go ahead and set the generic buttons as
535                        // released to prevent spurious errors.
536                        if (actionButton == MotionEvent.BUTTON_STYLUS_PRIMARY &&
537                                (buttonState & MotionEvent.BUTTON_SECONDARY) == 0) {
538                            mButtonsPressed &= ~MotionEvent.BUTTON_SECONDARY;
539                        } else if (actionButton == MotionEvent.BUTTON_STYLUS_SECONDARY &&
540                                (buttonState & MotionEvent.BUTTON_TERTIARY) == 0) {
541                            mButtonsPressed &= ~MotionEvent.BUTTON_TERTIARY;
542                        }
543
544                        if (mButtonsPressed != buttonState) {
545                            problem(String.format("Reported button state differs from " +
546                                    "expected button state based on press and release events. " +
547                                    "Is 0x%08x but expected 0x%08x.",
548                                    buttonState, mButtonsPressed));
549                        }
550                        break;
551                    default:
552                        problem("Invalid action for generic pointer event.");
553                        break;
554                }
555            } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
556                switch (action) {
557                    case MotionEvent.ACTION_MOVE:
558                        ensurePointerCountIsOneForThisAction(event);
559                        break;
560                    default:
561                        problem("Invalid action for generic joystick event.");
562                        break;
563                }
564            }
565        } finally {
566            finishEvent();
567        }
568    }
569
570    /**
571     * Notifies the verifier that a given event was unhandled and the rest of the
572     * trace for the event should be ignored.
573     * This method should only be called if the event was previously checked by
574     * the consistency verifier using {@link #onInputEvent} and other methods.
575     * @param event The event.
576     * @param nestingLevel The nesting level: 0 if called from the base class,
577     * or 1 from a subclass.  If the event was already checked by this consistency verifier
578     * at a higher nesting level, it will not be checked again.  Used to handle the situation
579     * where a subclass dispatching method delegates to its superclass's dispatching method
580     * and both dispatching methods call into the consistency verifier.
581     */
582    public void onUnhandledEvent(InputEvent event, int nestingLevel) {
583        if (nestingLevel != mLastNestingLevel) {
584            return;
585        }
586
587        if (mRecentEventsUnhandled != null) {
588            mRecentEventsUnhandled[mMostRecentEventIndex] = true;
589        }
590
591        if (event instanceof KeyEvent) {
592            final KeyEvent keyEvent = (KeyEvent)event;
593            final int deviceId = keyEvent.getDeviceId();
594            final int source = keyEvent.getSource();
595            final int keyCode = keyEvent.getKeyCode();
596            final KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
597            if (state != null) {
598                state.unhandled = true;
599            }
600        } else {
601            final MotionEvent motionEvent = (MotionEvent)event;
602            if (motionEvent.isTouchEvent()) {
603                mTouchEventStreamUnhandled = true;
604            } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
605                if (mTrackballDown) {
606                    mTrackballUnhandled = true;
607                }
608            }
609        }
610    }
611
612    private void ensureMetaStateIsNormalized(int metaState) {
613        final int normalizedMetaState = KeyEvent.normalizeMetaState(metaState);
614        if (normalizedMetaState != metaState) {
615            problem(String.format("Metastate not normalized.  Was 0x%08x but expected 0x%08x.",
616                    metaState, normalizedMetaState));
617        }
618    }
619
620    private void ensurePointerCountIsOneForThisAction(MotionEvent event) {
621        final int pointerCount = event.getPointerCount();
622        if (pointerCount != 1) {
623            problem("Pointer count is " + pointerCount + " but it should always be 1 for "
624                    + MotionEvent.actionToString(event.getAction()));
625        }
626    }
627
628    private void ensureActionButtonIsNonZeroForThisAction(MotionEvent event) {
629        final int actionButton = event.getActionButton();
630        if (actionButton == 0) {
631            problem("No action button set. Action button should always be non-zero for " +
632                    MotionEvent.actionToString(event.getAction()));
633
634        }
635    }
636
637    private void ensureHistorySizeIsZeroForThisAction(MotionEvent event) {
638        final int historySize = event.getHistorySize();
639        if (historySize != 0) {
640            problem("History size is " + historySize + " but it should always be 0 for "
641                    + MotionEvent.actionToString(event.getAction()));
642        }
643    }
644
645    private boolean startEvent(InputEvent event, int nestingLevel, String eventType) {
646        // Ignore the event if we already checked it at a higher nesting level.
647        final int seq = event.getSequenceNumber();
648        if (seq == mLastEventSeq && nestingLevel < mLastNestingLevel
649                && eventType == mLastEventType) {
650            return false;
651        }
652
653        if (nestingLevel > 0) {
654            mLastEventSeq = seq;
655            mLastEventType = eventType;
656            mLastNestingLevel = nestingLevel;
657        } else {
658            mLastEventSeq = -1;
659            mLastEventType = null;
660            mLastNestingLevel = 0;
661        }
662
663        mCurrentEvent = event;
664        mCurrentEventType = eventType;
665        return true;
666    }
667
668    private void finishEvent() {
669        if (mViolationMessage != null && mViolationMessage.length() != 0) {
670            if (!mCurrentEvent.isTainted()) {
671                // Write a log message only if the event was not already tainted.
672                mViolationMessage.append("\n  in ").append(mCaller);
673                mViolationMessage.append("\n  ");
674                appendEvent(mViolationMessage, 0, mCurrentEvent, false);
675
676                if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents != null) {
677                    mViolationMessage.append("\n  -- recent events --");
678                    for (int i = 0; i < RECENT_EVENTS_TO_LOG; i++) {
679                        final int index = (mMostRecentEventIndex + RECENT_EVENTS_TO_LOG - i)
680                                % RECENT_EVENTS_TO_LOG;
681                        final InputEvent event = mRecentEvents[index];
682                        if (event == null) {
683                            break;
684                        }
685                        mViolationMessage.append("\n  ");
686                        appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]);
687                    }
688                }
689
690                Log.d(mLogTag, mViolationMessage.toString());
691
692                // Taint the event so that we do not generate additional violations from it
693                // further downstream.
694                mCurrentEvent.setTainted(true);
695            }
696            mViolationMessage.setLength(0);
697        }
698
699        if (RECENT_EVENTS_TO_LOG != 0) {
700            if (mRecentEvents == null) {
701                mRecentEvents = new InputEvent[RECENT_EVENTS_TO_LOG];
702                mRecentEventsUnhandled = new boolean[RECENT_EVENTS_TO_LOG];
703            }
704            final int index = (mMostRecentEventIndex + 1) % RECENT_EVENTS_TO_LOG;
705            mMostRecentEventIndex = index;
706            if (mRecentEvents[index] != null) {
707                mRecentEvents[index].recycle();
708            }
709            mRecentEvents[index] = mCurrentEvent.copy();
710            mRecentEventsUnhandled[index] = false;
711        }
712
713        mCurrentEvent = null;
714        mCurrentEventType = null;
715    }
716
717    private static void appendEvent(StringBuilder message, int index,
718            InputEvent event, boolean unhandled) {
719        message.append(index).append(": sent at ").append(event.getEventTimeNano());
720        message.append(", ");
721        if (unhandled) {
722            message.append("(unhandled) ");
723        }
724        message.append(event);
725    }
726
727    private void problem(String message) {
728        if (mViolationMessage == null) {
729            mViolationMessage = new StringBuilder();
730        }
731        if (mViolationMessage.length() == 0) {
732            mViolationMessage.append(mCurrentEventType).append(": ");
733        } else {
734            mViolationMessage.append("\n  ");
735        }
736        mViolationMessage.append(message);
737    }
738
739    private KeyState findKeyState(int deviceId, int source, int keyCode, boolean remove) {
740        KeyState last = null;
741        KeyState state = mKeyStateList;
742        while (state != null) {
743            if (state.deviceId == deviceId && state.source == source
744                    && state.keyCode == keyCode) {
745                if (remove) {
746                    if (last != null) {
747                        last.next = state.next;
748                    } else {
749                        mKeyStateList = state.next;
750                    }
751                    state.next = null;
752                }
753                return state;
754            }
755            last = state;
756            state = state.next;
757        }
758        return null;
759    }
760
761    private void addKeyState(int deviceId, int source, int keyCode) {
762        KeyState state = KeyState.obtain(deviceId, source, keyCode);
763        state.next = mKeyStateList;
764        mKeyStateList = state;
765    }
766
767    private static final class KeyState {
768        private static Object mRecycledListLock = new Object();
769        private static KeyState mRecycledList;
770
771        public KeyState next;
772        public int deviceId;
773        public int source;
774        public int keyCode;
775        public boolean unhandled;
776
777        private KeyState() {
778        }
779
780        public static KeyState obtain(int deviceId, int source, int keyCode) {
781            KeyState state;
782            synchronized (mRecycledListLock) {
783                state = mRecycledList;
784                if (state != null) {
785                    mRecycledList = state.next;
786                } else {
787                    state = new KeyState();
788                }
789            }
790            state.deviceId = deviceId;
791            state.source = source;
792            state.keyCode = keyCode;
793            state.unhandled = false;
794            return state;
795        }
796
797        public void recycle() {
798            synchronized (mRecycledListLock) {
799                next = mRecycledList;
800                mRecycledList = next;
801            }
802        }
803    }
804}
805