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