2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 import android.os.Build;
20 import android.util.Log;
23 * Checks whether a sequence of input events is self-consistent.
24 * Logs a description of each problem detected.
26 * When a problem is detected, the event is tainted. This mechanism prevents the same
27 * error from being reported multiple times.
32 public final class InputEventConsistencyVerifier {
33 private static final boolean IS_ENG_BUILD = "eng".equals(Build.TYPE);
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";
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;
45 // The object to which the verifier is attached.
46 private final Object mCaller;
48 // Consistency verifier flags.
49 private final int mFlags;
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;
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
61 private int mLastEventSeq;
62 private String mLastEventType;
63 private int mLastNestingLevel;
65 // Copy of the most recent events.
66 private InputEvent[] mRecentEvents;
67 private boolean[] mRecentEventsUnhandled;
68 private int mMostRecentEventIndex;
70 // Current event and its type.
71 private InputEvent mCurrentEvent;
72 private String mCurrentEventType;
74 // Linked list of key state objects.
75 private KeyState mKeyStateList;
77 // Current state of the trackball.
78 private boolean mTrackballDown;
79 private boolean mTrackballUnhandled;
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;
86 // The device id and source of the current stream of touch events.
87 private int mTouchEventStreamDeviceId = -1;
88 private int mTouchEventStreamSource;
90 // Set to true when we discover that the touch event stream is inconsistent.
91 // Reset on down or cancel.
92 private boolean mTouchEventStreamIsTainted;
94 // Set to true if the touch event stream is partially unhandled.
95 private boolean mTouchEventStreamUnhandled;
97 // Set to true if we received hover enter.
98 private boolean mHoverEntered;
100 // The bitset of buttons which we've received ACTION_BUTTON_PRESS for.
101 private int mButtonsPressed;
103 // The current violation message.
104 private StringBuilder mViolationMessage;
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.
111 public static final int FLAG_RAW_DEVICE_INPUT = 1 << 0;
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.
118 public InputEventConsistencyVerifier(Object caller, int flags) {
119 this(caller, flags, null);
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.
128 public InputEventConsistencyVerifier(Object caller, int flags, String logTag) {
129 this.mCaller = caller;
131 this.mLogTag = (logTag != null) ? logTag : "InputEventConsistencyVerifier";
135 * Determines whether the instrumentation should be enabled.
136 * @return True if it should be enabled.
138 public static boolean isInstrumentationEnabled() {
143 * Resets the state of the input event consistency verifier.
145 public void reset() {
147 mLastNestingLevel = 0;
148 mTrackballDown = false;
149 mTrackballUnhandled = false;
150 mTouchEventStreamPointers = 0;
151 mTouchEventStreamIsTainted = false;
152 mTouchEventStreamUnhandled = false;
153 mHoverEntered = false;
156 while (mKeyStateList != null) {
157 final KeyState state = mKeyStateList;
158 mKeyStateList = state.next;
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.
172 public void onInputEvent(InputEvent event, int nestingLevel) {
173 if (event instanceof KeyEvent) {
174 final KeyEvent keyEvent = (KeyEvent)event;
175 onKeyEvent(keyEvent, nestingLevel);
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);
183 onGenericMotionEvent(motionEvent, nestingLevel);
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.
197 public void onKeyEvent(KeyEvent event, int nestingLevel) {
198 if (!startEvent(event, nestingLevel, EVENT_TYPE_KEY)) {
203 ensureMetaStateIsNormalized(event.getMetaState());
205 final int action = event.getAction();
206 final int deviceId = event.getDeviceId();
207 final int source = event.getSource();
208 final int keyCode = event.getKeyCode();
210 case KeyEvent.ACTION_DOWN: {
211 KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
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.");
225 addKeyState(deviceId, source, keyCode);
229 case KeyEvent.ACTION_UP: {
230 KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ true);
232 problem("ACTION_UP but key was not down.");
238 case KeyEvent.ACTION_MULTIPLE:
241 problem("Invalid action " + KeyEvent.actionToString(action)
242 + " for key event.");
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.
259 public void onTrackballEvent(MotionEvent event, int nestingLevel) {
260 if (!startEvent(event, nestingLevel, EVENT_TYPE_TRACKBALL)) {
265 ensureMetaStateIsNormalized(event.getMetaState());
267 final int action = event.getAction();
268 final int source = event.getSource();
269 if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
271 case MotionEvent.ACTION_DOWN:
272 if (mTrackballDown && !mTrackballUnhandled) {
273 problem("ACTION_DOWN but trackball is already down.");
275 mTrackballDown = true;
276 mTrackballUnhandled = false;
278 ensureHistorySizeIsZeroForThisAction(event);
279 ensurePointerCountIsOneForThisAction(event);
281 case MotionEvent.ACTION_UP:
282 if (!mTrackballDown) {
283 problem("ACTION_UP but trackball is not down.");
285 mTrackballDown = false;
286 mTrackballUnhandled = false;
288 ensureHistorySizeIsZeroForThisAction(event);
289 ensurePointerCountIsOneForThisAction(event);
291 case MotionEvent.ACTION_MOVE:
292 ensurePointerCountIsOneForThisAction(event);
295 problem("Invalid action " + MotionEvent.actionToString(action)
296 + " for trackball event.");
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.");
306 problem("Source was not SOURCE_CLASS_TRACKBALL.");
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.
322 public void onTouchEvent(MotionEvent event, int nestingLevel) {
323 if (!startEvent(event, nestingLevel, EVENT_TYPE_TOUCH)) {
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;
335 if (mTouchEventStreamIsTainted) {
336 event.setTainted(true);
340 ensureMetaStateIsNormalized(event.getMetaState());
342 final int deviceId = event.getDeviceId();
343 final int source = event.getSource();
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));
354 mTouchEventStreamDeviceId = deviceId;
355 mTouchEventStreamSource = source;
357 final int pointerCount = event.getPointerCount();
358 if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
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.");
365 ensureHistorySizeIsZeroForThisAction(event);
366 ensurePointerCountIsOneForThisAction(event);
367 mTouchEventStreamPointers = 1 << event.getPointerId(0);
369 case MotionEvent.ACTION_UP:
370 ensureHistorySizeIsZeroForThisAction(event);
371 ensurePointerCountIsOneForThisAction(event);
372 mTouchEventStreamPointers = 0;
373 mTouchEventStreamIsTainted = false;
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;
386 case MotionEvent.ACTION_CANCEL:
387 mTouchEventStreamPointers = 0;
388 mTouchEventStreamIsTainted = false;
390 case MotionEvent.ACTION_OUTSIDE:
391 if (mTouchEventStreamPointers != 0) {
392 problem("ACTION_OUTSIDE but pointers are still down.");
394 ensureHistorySizeIsZeroForThisAction(event);
395 ensurePointerCountIsOneForThisAction(event);
396 mTouchEventStreamIsTainted = false;
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;
406 if (actionIndex < 0 || actionIndex >= pointerCount) {
407 problem("ACTION_POINTER_DOWN index is " + actionIndex
408 + " but the pointer count is " + pointerCount + ".");
409 mTouchEventStreamIsTainted = true;
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;
418 mTouchEventStreamPointers |= idBit;
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;
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;
435 mTouchEventStreamPointers &= ~idBit;
438 ensureHistorySizeIsZeroForThisAction(event);
440 problem("Invalid action " + MotionEvent.actionToString(action)
441 + " for touch event.");
447 problem("Source was not SOURCE_CLASS_POINTER.");
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.
463 public void onGenericMotionEvent(MotionEvent event, int nestingLevel) {
464 if (!startEvent(event, nestingLevel, EVENT_TYPE_GENERIC_MOTION)) {
469 ensureMetaStateIsNormalized(event.getMetaState());
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) {
477 case MotionEvent.ACTION_HOVER_ENTER:
478 ensurePointerCountIsOneForThisAction(event);
479 mHoverEntered = true;
481 case MotionEvent.ACTION_HOVER_MOVE:
482 ensurePointerCountIsOneForThisAction(event);
484 case MotionEvent.ACTION_HOVER_EXIT:
485 ensurePointerCountIsOneForThisAction(event);
486 if (!mHoverEntered) {
487 problem("ACTION_HOVER_EXIT without prior ACTION_HOVER_ENTER");
489 mHoverEntered = false;
491 case MotionEvent.ACTION_SCROLL:
492 ensureHistorySizeIsZeroForThisAction(event);
493 ensurePointerCountIsOneForThisAction(event);
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.");
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;
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));
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.");
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;
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));
552 problem("Invalid action for generic pointer event.");
555 } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
557 case MotionEvent.ACTION_MOVE:
558 ensurePointerCountIsOneForThisAction(event);
561 problem("Invalid action for generic joystick event.");
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.
582 public void onUnhandledEvent(InputEvent event, int nestingLevel) {
583 if (nestingLevel != mLastNestingLevel) {
587 if (mRecentEventsUnhandled != null) {
588 mRecentEventsUnhandled[mMostRecentEventIndex] = true;
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);
598 state.unhandled = true;
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;
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));
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()));
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()));
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()));
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) {
653 if (nestingLevel > 0) {
655 mLastEventType = eventType;
656 mLastNestingLevel = nestingLevel;
659 mLastEventType = null;
660 mLastNestingLevel = 0;
663 mCurrentEvent = event;
664 mCurrentEventType = eventType;
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);
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];
685 mViolationMessage.append("\n ");
686 appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]);
690 Log.d(mLogTag, mViolationMessage.toString());
692 // Taint the event so that we do not generate additional violations from it
693 // further downstream.
694 mCurrentEvent.setTainted(true);
696 mViolationMessage.setLength(0);
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];
704 final int index = (mMostRecentEventIndex + 1) % RECENT_EVENTS_TO_LOG;
705 mMostRecentEventIndex = index;
706 if (mRecentEvents[index] != null) {
707 mRecentEvents[index].recycle();
709 mRecentEvents[index] = mCurrentEvent.copy();
710 mRecentEventsUnhandled[index] = false;
713 mCurrentEvent = null;
714 mCurrentEventType = null;
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(", ");
722 message.append("(unhandled) ");
724 message.append(event);
727 private void problem(String message) {
728 if (mViolationMessage == null) {
729 mViolationMessage = new StringBuilder();
731 if (mViolationMessage.length() == 0) {
732 mViolationMessage.append(mCurrentEventType).append(": ");
734 mViolationMessage.append("\n ");
736 mViolationMessage.append(message);
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) {
747 last.next = state.next;
749 mKeyStateList = state.next;
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;
767 private static final class KeyState {
768 private static Object mRecycledListLock = new Object();
769 private static KeyState mRecycledList;
771 public KeyState next;
775 public boolean unhandled;
780 public static KeyState obtain(int deviceId, int source, int keyCode) {
782 synchronized (mRecycledListLock) {
783 state = mRecycledList;
785 mRecycledList = state.next;
787 state = new KeyState();
790 state.deviceId = deviceId;
791 state.source = source;
792 state.keyCode = keyCode;
793 state.unhandled = false;
797 public void recycle() {
798 synchronized (mRecycledListLock) {
799 next = mRecycledList;
800 mRecycledList = next;