OSDN Git Service

Add new MotionEvent actions for button press and release.
[android-x86/frameworks-base.git] / core / java / android / view / InputEventConsistencyVerifier.java
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.os.Build;
20 import 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  */
32 public 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 }