• 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.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 InputEvent mLastEvent;
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 current violation message.
101     private StringBuilder mViolationMessage;
102 
103     /**
104      * Indicates that the verifier is intended to act on raw device input event streams.
105      * Disables certain checks for invariants that are established by the input dispatcher
106      * itself as it delivers input events, such as key repeating behavior.
107      */
108     public static final int FLAG_RAW_DEVICE_INPUT = 1 << 0;
109 
110     /**
111      * Creates an input consistency verifier.
112      * @param caller The object to which the verifier is attached.
113      * @param flags Flags to the verifier, or 0 if none.
114      */
InputEventConsistencyVerifier(Object caller, int flags)115     public InputEventConsistencyVerifier(Object caller, int flags) {
116         this(caller, flags, InputEventConsistencyVerifier.class.getSimpleName());
117     }
118 
119     /**
120      * Creates an input consistency verifier.
121      * @param caller The object to which the verifier is attached.
122      * @param flags Flags to the verifier, or 0 if none.
123      * @param logTag Tag for logging. If null defaults to the short class name.
124      */
InputEventConsistencyVerifier(Object caller, int flags, String logTag)125     public InputEventConsistencyVerifier(Object caller, int flags, String logTag) {
126         this.mCaller = caller;
127         this.mFlags = flags;
128         this.mLogTag = (logTag != null) ? logTag : "InputEventConsistencyVerifier";
129     }
130 
131     /**
132      * Determines whether the instrumentation should be enabled.
133      * @return True if it should be enabled.
134      */
isInstrumentationEnabled()135     public static boolean isInstrumentationEnabled() {
136         return IS_ENG_BUILD;
137     }
138 
139     /**
140      * Resets the state of the input event consistency verifier.
141      */
reset()142     public void reset() {
143         mLastEvent = null;
144         mLastNestingLevel = 0;
145         mTrackballDown = false;
146         mTrackballUnhandled = false;
147         mTouchEventStreamPointers = 0;
148         mTouchEventStreamIsTainted = false;
149         mTouchEventStreamUnhandled = false;
150         mHoverEntered = false;
151 
152         while (mKeyStateList != null) {
153             final KeyState state = mKeyStateList;
154             mKeyStateList = state.next;
155             state.recycle();
156         }
157     }
158 
159     /**
160      * Checks an arbitrary input event.
161      * @param event The event.
162      * @param nestingLevel The nesting level: 0 if called from the base class,
163      * or 1 from a subclass.  If the event was already checked by this consistency verifier
164      * at a higher nesting level, it will not be checked again.  Used to handle the situation
165      * where a subclass dispatching method delegates to its superclass's dispatching method
166      * and both dispatching methods call into the consistency verifier.
167      */
onInputEvent(InputEvent event, int nestingLevel)168     public void onInputEvent(InputEvent event, int nestingLevel) {
169         if (event instanceof KeyEvent) {
170             final KeyEvent keyEvent = (KeyEvent)event;
171             onKeyEvent(keyEvent, nestingLevel);
172         } else {
173             final MotionEvent motionEvent = (MotionEvent)event;
174             if (motionEvent.isTouchEvent()) {
175                 onTouchEvent(motionEvent, nestingLevel);
176             } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
177                 onTrackballEvent(motionEvent, nestingLevel);
178             } else {
179                 onGenericMotionEvent(motionEvent, nestingLevel);
180             }
181         }
182     }
183 
184     /**
185      * Checks a key event.
186      * @param event The event.
187      * @param nestingLevel The nesting level: 0 if called from the base class,
188      * or 1 from a subclass.  If the event was already checked by this consistency verifier
189      * at a higher nesting level, it will not be checked again.  Used to handle the situation
190      * where a subclass dispatching method delegates to its superclass's dispatching method
191      * and both dispatching methods call into the consistency verifier.
192      */
onKeyEvent(KeyEvent event, int nestingLevel)193     public void onKeyEvent(KeyEvent event, int nestingLevel) {
194         if (!startEvent(event, nestingLevel, EVENT_TYPE_KEY)) {
195             return;
196         }
197 
198         try {
199             ensureMetaStateIsNormalized(event.getMetaState());
200 
201             final int action = event.getAction();
202             final int deviceId = event.getDeviceId();
203             final int source = event.getSource();
204             final int keyCode = event.getKeyCode();
205             switch (action) {
206                 case KeyEvent.ACTION_DOWN: {
207                     KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
208                     if (state != null) {
209                         // If the key is already down, ensure it is a repeat.
210                         // We don't perform this check when processing raw device input
211                         // because the input dispatcher itself is responsible for setting
212                         // the key repeat count before it delivers input events.
213                         if (state.unhandled) {
214                             state.unhandled = false;
215                         } else if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0
216                                 && event.getRepeatCount() == 0) {
217                             problem("ACTION_DOWN but key is already down and this event "
218                                     + "is not a key repeat.");
219                         }
220                     } else {
221                         addKeyState(deviceId, source, keyCode);
222                     }
223                     break;
224                 }
225                 case KeyEvent.ACTION_UP: {
226                     KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ true);
227                     if (state == null) {
228                         problem("ACTION_UP but key was not down.");
229                     } else {
230                         state.recycle();
231                     }
232                     break;
233                 }
234                 case KeyEvent.ACTION_MULTIPLE:
235                     break;
236                 default:
237                     problem("Invalid action " + KeyEvent.actionToString(action)
238                             + " for key event.");
239                     break;
240             }
241         } finally {
242             finishEvent();
243         }
244     }
245 
246     /**
247      * Checks a trackball event.
248      * @param event The event.
249      * @param nestingLevel The nesting level: 0 if called from the base class,
250      * or 1 from a subclass.  If the event was already checked by this consistency verifier
251      * at a higher nesting level, it will not be checked again.  Used to handle the situation
252      * where a subclass dispatching method delegates to its superclass's dispatching method
253      * and both dispatching methods call into the consistency verifier.
254      */
onTrackballEvent(MotionEvent event, int nestingLevel)255     public void onTrackballEvent(MotionEvent event, int nestingLevel) {
256         if (!startEvent(event, nestingLevel, EVENT_TYPE_TRACKBALL)) {
257             return;
258         }
259 
260         try {
261             ensureMetaStateIsNormalized(event.getMetaState());
262 
263             final int action = event.getAction();
264             final int source = event.getSource();
265             if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
266                 switch (action) {
267                     case MotionEvent.ACTION_DOWN:
268                         if (mTrackballDown && !mTrackballUnhandled) {
269                             problem("ACTION_DOWN but trackball is already down.");
270                         } else {
271                             mTrackballDown = true;
272                             mTrackballUnhandled = false;
273                         }
274                         ensureHistorySizeIsZeroForThisAction(event);
275                         ensurePointerCountIsOneForThisAction(event);
276                         break;
277                     case MotionEvent.ACTION_UP:
278                         if (!mTrackballDown) {
279                             problem("ACTION_UP but trackball is not down.");
280                         } else {
281                             mTrackballDown = false;
282                             mTrackballUnhandled = false;
283                         }
284                         ensureHistorySizeIsZeroForThisAction(event);
285                         ensurePointerCountIsOneForThisAction(event);
286                         break;
287                     case MotionEvent.ACTION_MOVE:
288                         ensurePointerCountIsOneForThisAction(event);
289                         break;
290                     default:
291                         problem("Invalid action " + MotionEvent.actionToString(action)
292                                 + " for trackball event.");
293                         break;
294                 }
295 
296                 if (mTrackballDown && event.getPressure() <= 0) {
297                     problem("Trackball is down but pressure is not greater than 0.");
298                 } else if (!mTrackballDown && event.getPressure() != 0) {
299                     problem("Trackball is up but pressure is not equal to 0.");
300                 }
301             } else {
302                 problem("Source was not SOURCE_CLASS_TRACKBALL.");
303             }
304         } finally {
305             finishEvent();
306         }
307     }
308 
309     /**
310      * Checks a touch event.
311      * @param event The event.
312      * @param nestingLevel The nesting level: 0 if called from the base class,
313      * or 1 from a subclass.  If the event was already checked by this consistency verifier
314      * at a higher nesting level, it will not be checked again.  Used to handle the situation
315      * where a subclass dispatching method delegates to its superclass's dispatching method
316      * and both dispatching methods call into the consistency verifier.
317      */
onTouchEvent(MotionEvent event, int nestingLevel)318     public void onTouchEvent(MotionEvent event, int nestingLevel) {
319         if (!startEvent(event, nestingLevel, EVENT_TYPE_TOUCH)) {
320             return;
321         }
322 
323         final int action = event.getAction();
324         final boolean newStream = action == MotionEvent.ACTION_DOWN
325                 || action == MotionEvent.ACTION_CANCEL;
326         if (newStream && (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled)) {
327             mTouchEventStreamIsTainted = false;
328             mTouchEventStreamUnhandled = false;
329             mTouchEventStreamPointers = 0;
330         }
331         if (mTouchEventStreamIsTainted) {
332             event.setTainted(true);
333         }
334 
335         try {
336             ensureMetaStateIsNormalized(event.getMetaState());
337 
338             final int deviceId = event.getDeviceId();
339             final int source = event.getSource();
340 
341             if (!newStream && mTouchEventStreamDeviceId != -1
342                     && (mTouchEventStreamDeviceId != deviceId
343                             || mTouchEventStreamSource != source)) {
344                 problem("Touch event stream contains events from multiple sources: "
345                         + "previous device id " + mTouchEventStreamDeviceId
346                         + ", previous source " + Integer.toHexString(mTouchEventStreamSource)
347                         + ", new device id " + deviceId
348                         + ", new source " + Integer.toHexString(source));
349             }
350             mTouchEventStreamDeviceId = deviceId;
351             mTouchEventStreamSource = source;
352 
353             final int pointerCount = event.getPointerCount();
354             if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
355                 switch (action) {
356                     case MotionEvent.ACTION_DOWN:
357                         if (mTouchEventStreamPointers != 0) {
358                             problem("ACTION_DOWN but pointers are already down.  "
359                                     + "Probably missing ACTION_UP from previous gesture.");
360                         }
361                         ensureHistorySizeIsZeroForThisAction(event);
362                         ensurePointerCountIsOneForThisAction(event);
363                         mTouchEventStreamPointers = 1 << event.getPointerId(0);
364                         break;
365                     case MotionEvent.ACTION_UP:
366                         ensureHistorySizeIsZeroForThisAction(event);
367                         ensurePointerCountIsOneForThisAction(event);
368                         mTouchEventStreamPointers = 0;
369                         mTouchEventStreamIsTainted = false;
370                         break;
371                     case MotionEvent.ACTION_MOVE: {
372                         final int expectedPointerCount =
373                                 Integer.bitCount(mTouchEventStreamPointers);
374                         if (pointerCount != expectedPointerCount) {
375                             problem("ACTION_MOVE contained " + pointerCount
376                                     + " pointers but there are currently "
377                                     + expectedPointerCount + " pointers down.");
378                             mTouchEventStreamIsTainted = true;
379                         }
380                         break;
381                     }
382                     case MotionEvent.ACTION_CANCEL:
383                         mTouchEventStreamPointers = 0;
384                         mTouchEventStreamIsTainted = false;
385                         break;
386                     case MotionEvent.ACTION_OUTSIDE:
387                         if (mTouchEventStreamPointers != 0) {
388                             problem("ACTION_OUTSIDE but pointers are still down.");
389                         }
390                         ensureHistorySizeIsZeroForThisAction(event);
391                         ensurePointerCountIsOneForThisAction(event);
392                         mTouchEventStreamIsTainted = false;
393                         break;
394                     default: {
395                         final int actionMasked = event.getActionMasked();
396                         final int actionIndex = event.getActionIndex();
397                         if (actionMasked == MotionEvent.ACTION_POINTER_DOWN) {
398                             if (mTouchEventStreamPointers == 0) {
399                                 problem("ACTION_POINTER_DOWN but no other pointers were down.");
400                                 mTouchEventStreamIsTainted = true;
401                             }
402                             if (actionIndex < 0 || actionIndex >= pointerCount) {
403                                 problem("ACTION_POINTER_DOWN index is " + actionIndex
404                                         + " but the pointer count is " + pointerCount + ".");
405                                 mTouchEventStreamIsTainted = true;
406                             } else {
407                                 final int id = event.getPointerId(actionIndex);
408                                 final int idBit = 1 << id;
409                                 if ((mTouchEventStreamPointers & idBit) != 0) {
410                                     problem("ACTION_POINTER_DOWN specified pointer id " + id
411                                             + " which is already down.");
412                                     mTouchEventStreamIsTainted = true;
413                                 } else {
414                                     mTouchEventStreamPointers |= idBit;
415                                 }
416                             }
417                             ensureHistorySizeIsZeroForThisAction(event);
418                         } else if (actionMasked == MotionEvent.ACTION_POINTER_UP) {
419                             if (actionIndex < 0 || actionIndex >= pointerCount) {
420                                 problem("ACTION_POINTER_UP index is " + actionIndex
421                                         + " but the pointer count is " + pointerCount + ".");
422                                 mTouchEventStreamIsTainted = true;
423                             } else {
424                                 final int id = event.getPointerId(actionIndex);
425                                 final int idBit = 1 << id;
426                                 if ((mTouchEventStreamPointers & idBit) == 0) {
427                                     problem("ACTION_POINTER_UP specified pointer id " + id
428                                             + " which is not currently down.");
429                                     mTouchEventStreamIsTainted = true;
430                                 } else {
431                                     mTouchEventStreamPointers &= ~idBit;
432                                 }
433                             }
434                             ensureHistorySizeIsZeroForThisAction(event);
435                         } else {
436                             problem("Invalid action " + MotionEvent.actionToString(action)
437                                     + " for touch event.");
438                         }
439                         break;
440                     }
441                 }
442             } else {
443                 problem("Source was not SOURCE_CLASS_POINTER.");
444             }
445         } finally {
446             finishEvent();
447         }
448     }
449 
450     /**
451      * Checks a generic motion event.
452      * @param event The event.
453      * @param nestingLevel The nesting level: 0 if called from the base class,
454      * or 1 from a subclass.  If the event was already checked by this consistency verifier
455      * at a higher nesting level, it will not be checked again.  Used to handle the situation
456      * where a subclass dispatching method delegates to its superclass's dispatching method
457      * and both dispatching methods call into the consistency verifier.
458      */
onGenericMotionEvent(MotionEvent event, int nestingLevel)459     public void onGenericMotionEvent(MotionEvent event, int nestingLevel) {
460         if (!startEvent(event, nestingLevel, EVENT_TYPE_GENERIC_MOTION)) {
461             return;
462         }
463 
464         try {
465             ensureMetaStateIsNormalized(event.getMetaState());
466 
467             final int action = event.getAction();
468             final int source = event.getSource();
469             if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
470                 switch (action) {
471                     case MotionEvent.ACTION_HOVER_ENTER:
472                         ensurePointerCountIsOneForThisAction(event);
473                         mHoverEntered = true;
474                         break;
475                     case MotionEvent.ACTION_HOVER_MOVE:
476                         ensurePointerCountIsOneForThisAction(event);
477                         break;
478                     case MotionEvent.ACTION_HOVER_EXIT:
479                         ensurePointerCountIsOneForThisAction(event);
480                         if (!mHoverEntered) {
481                             problem("ACTION_HOVER_EXIT without prior ACTION_HOVER_ENTER");
482                         }
483                         mHoverEntered = false;
484                         break;
485                     case MotionEvent.ACTION_SCROLL:
486                         ensureHistorySizeIsZeroForThisAction(event);
487                         ensurePointerCountIsOneForThisAction(event);
488                         break;
489                     default:
490                         problem("Invalid action for generic pointer event.");
491                         break;
492                 }
493             } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
494                 switch (action) {
495                     case MotionEvent.ACTION_MOVE:
496                         ensurePointerCountIsOneForThisAction(event);
497                         break;
498                     default:
499                         problem("Invalid action for generic joystick event.");
500                         break;
501                 }
502             }
503         } finally {
504             finishEvent();
505         }
506     }
507 
508     /**
509      * Notifies the verifier that a given event was unhandled and the rest of the
510      * trace for the event should be ignored.
511      * This method should only be called if the event was previously checked by
512      * the consistency verifier using {@link #onInputEvent} and other methods.
513      * @param event The event.
514      * @param nestingLevel The nesting level: 0 if called from the base class,
515      * or 1 from a subclass.  If the event was already checked by this consistency verifier
516      * at a higher nesting level, it will not be checked again.  Used to handle the situation
517      * where a subclass dispatching method delegates to its superclass's dispatching method
518      * and both dispatching methods call into the consistency verifier.
519      */
onUnhandledEvent(InputEvent event, int nestingLevel)520     public void onUnhandledEvent(InputEvent event, int nestingLevel) {
521         if (nestingLevel != mLastNestingLevel) {
522             return;
523         }
524 
525         if (mRecentEventsUnhandled != null) {
526             mRecentEventsUnhandled[mMostRecentEventIndex] = true;
527         }
528 
529         if (event instanceof KeyEvent) {
530             final KeyEvent keyEvent = (KeyEvent)event;
531             final int deviceId = keyEvent.getDeviceId();
532             final int source = keyEvent.getSource();
533             final int keyCode = keyEvent.getKeyCode();
534             final KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
535             if (state != null) {
536                 state.unhandled = true;
537             }
538         } else {
539             final MotionEvent motionEvent = (MotionEvent)event;
540             if (motionEvent.isTouchEvent()) {
541                 mTouchEventStreamUnhandled = true;
542             } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
543                 if (mTrackballDown) {
544                     mTrackballUnhandled = true;
545                 }
546             }
547         }
548     }
549 
ensureMetaStateIsNormalized(int metaState)550     private void ensureMetaStateIsNormalized(int metaState) {
551         final int normalizedMetaState = KeyEvent.normalizeMetaState(metaState);
552         if (normalizedMetaState != metaState) {
553             problem(String.format("Metastate not normalized.  Was 0x%08x but expected 0x%08x.",
554                     metaState, normalizedMetaState));
555         }
556     }
557 
ensurePointerCountIsOneForThisAction(MotionEvent event)558     private void ensurePointerCountIsOneForThisAction(MotionEvent event) {
559         final int pointerCount = event.getPointerCount();
560         if (pointerCount != 1) {
561             problem("Pointer count is " + pointerCount + " but it should always be 1 for "
562                     + MotionEvent.actionToString(event.getAction()));
563         }
564     }
565 
ensureHistorySizeIsZeroForThisAction(MotionEvent event)566     private void ensureHistorySizeIsZeroForThisAction(MotionEvent event) {
567         final int historySize = event.getHistorySize();
568         if (historySize != 0) {
569             problem("History size is " + historySize + " but it should always be 0 for "
570                     + MotionEvent.actionToString(event.getAction()));
571         }
572     }
573 
startEvent(InputEvent event, int nestingLevel, String eventType)574     private boolean startEvent(InputEvent event, int nestingLevel, String eventType) {
575         // Ignore the event if we already checked it at a higher nesting level.
576         if (event == mLastEvent && nestingLevel < mLastNestingLevel
577                 && eventType == mLastEventType) {
578             return false;
579         }
580 
581         if (nestingLevel > 0) {
582             mLastEvent = event;
583             mLastEventType = eventType;
584             mLastNestingLevel = nestingLevel;
585         } else {
586             mLastEvent = null;
587             mLastEventType = null;
588             mLastNestingLevel = 0;
589         }
590 
591         mCurrentEvent = event;
592         mCurrentEventType = eventType;
593         return true;
594     }
595 
finishEvent()596     private void finishEvent() {
597         if (mViolationMessage != null && mViolationMessage.length() != 0) {
598             if (!mCurrentEvent.isTainted()) {
599                 // Write a log message only if the event was not already tainted.
600                 mViolationMessage.append("\n  in ").append(mCaller);
601                 mViolationMessage.append("\n  ");
602                 appendEvent(mViolationMessage, 0, mCurrentEvent, false);
603 
604                 if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents != null) {
605                     mViolationMessage.append("\n  -- recent events --");
606                     for (int i = 0; i < RECENT_EVENTS_TO_LOG; i++) {
607                         final int index = (mMostRecentEventIndex + RECENT_EVENTS_TO_LOG - i)
608                                 % RECENT_EVENTS_TO_LOG;
609                         final InputEvent event = mRecentEvents[index];
610                         if (event == null) {
611                             break;
612                         }
613                         mViolationMessage.append("\n  ");
614                         appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]);
615                     }
616                 }
617 
618                 Log.d(mLogTag, mViolationMessage.toString());
619 
620                 // Taint the event so that we do not generate additional violations from it
621                 // further downstream.
622                 mCurrentEvent.setTainted(true);
623             }
624             mViolationMessage.setLength(0);
625         }
626 
627         if (RECENT_EVENTS_TO_LOG != 0) {
628             if (mRecentEvents == null) {
629                 mRecentEvents = new InputEvent[RECENT_EVENTS_TO_LOG];
630                 mRecentEventsUnhandled = new boolean[RECENT_EVENTS_TO_LOG];
631             }
632             final int index = (mMostRecentEventIndex + 1) % RECENT_EVENTS_TO_LOG;
633             mMostRecentEventIndex = index;
634             if (mRecentEvents[index] != null) {
635                 mRecentEvents[index].recycle();
636             }
637             mRecentEvents[index] = mCurrentEvent.copy();
638             mRecentEventsUnhandled[index] = false;
639         }
640 
641         mCurrentEvent = null;
642         mCurrentEventType = null;
643     }
644 
appendEvent(StringBuilder message, int index, InputEvent event, boolean unhandled)645     private static void appendEvent(StringBuilder message, int index,
646             InputEvent event, boolean unhandled) {
647         message.append(index).append(": sent at ").append(event.getEventTimeNano());
648         message.append(", ");
649         if (unhandled) {
650             message.append("(unhandled) ");
651         }
652         message.append(event);
653     }
654 
problem(String message)655     private void problem(String message) {
656         if (mViolationMessage == null) {
657             mViolationMessage = new StringBuilder();
658         }
659         if (mViolationMessage.length() == 0) {
660             mViolationMessage.append(mCurrentEventType).append(": ");
661         } else {
662             mViolationMessage.append("\n  ");
663         }
664         mViolationMessage.append(message);
665     }
666 
findKeyState(int deviceId, int source, int keyCode, boolean remove)667     private KeyState findKeyState(int deviceId, int source, int keyCode, boolean remove) {
668         KeyState last = null;
669         KeyState state = mKeyStateList;
670         while (state != null) {
671             if (state.deviceId == deviceId && state.source == source
672                     && state.keyCode == keyCode) {
673                 if (remove) {
674                     if (last != null) {
675                         last.next = state.next;
676                     } else {
677                         mKeyStateList = state.next;
678                     }
679                     state.next = null;
680                 }
681                 return state;
682             }
683             last = state;
684             state = state.next;
685         }
686         return null;
687     }
688 
addKeyState(int deviceId, int source, int keyCode)689     private void addKeyState(int deviceId, int source, int keyCode) {
690         KeyState state = KeyState.obtain(deviceId, source, keyCode);
691         state.next = mKeyStateList;
692         mKeyStateList = state;
693     }
694 
695     private static final class KeyState {
696         private static Object mRecycledListLock = new Object();
697         private static KeyState mRecycledList;
698 
699         public KeyState next;
700         public int deviceId;
701         public int source;
702         public int keyCode;
703         public boolean unhandled;
704 
KeyState()705         private KeyState() {
706         }
707 
obtain(int deviceId, int source, int keyCode)708         public static KeyState obtain(int deviceId, int source, int keyCode) {
709             KeyState state;
710             synchronized (mRecycledListLock) {
711                 state = mRecycledList;
712                 if (state != null) {
713                     mRecycledList = state.next;
714                 } else {
715                     state = new KeyState();
716                 }
717             }
718             state.deviceId = deviceId;
719             state.source = source;
720             state.keyCode = keyCode;
721             state.unhandled = false;
722             return state;
723         }
724 
recycle()725         public void recycle() {
726             synchronized (mRecycledListLock) {
727                 next = mRecycledList;
728                 mRecycledList = next;
729             }
730         }
731     }
732 }
733