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