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