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.compat.annotation.UnsupportedAppUsage; 20 import android.os.Build; 21 import 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 */ 33 public final class InputEventConsistencyVerifier { 34 private static final boolean IS_ENG_BUILD = Build.IS_ENG; 35 36 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 41 // 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 52 // 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 57 // 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. 62 private int mLastEventSeq; 63 private String mLastEventType; 64 private int mLastNestingLevel; 65 66 // Copy of the most recent events. 67 private InputEvent[] mRecentEvents; 68 private boolean[] mRecentEventsUnhandled; 69 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; 80 private boolean mTrackballUnhandled; 81 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 95 // Set to true if the touch event stream is partially unhandled. 96 private boolean mTouchEventStreamUnhandled; 97 98 // Set to true if we received hover enter. 99 private boolean mHoverEntered; 100 101 // The bitset of buttons which we've received ACTION_BUTTON_PRESS for. 102 private int mButtonsPressed; 103 104 // 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 */ 119 @UnsupportedAppUsage InputEventConsistencyVerifier(Object caller, int flags)120 public InputEventConsistencyVerifier(Object caller, int flags) { 121 this(caller, flags, null); 122 } 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 */ InputEventConsistencyVerifier(Object caller, int flags, String logTag)130 public InputEventConsistencyVerifier(Object caller, int flags, String logTag) { 131 this.mCaller = caller; 132 this.mFlags = flags; 133 this.mLogTag = (logTag != null) ? logTag : "InputEventConsistencyVerifier"; 134 } 135 136 /** 137 * Determines whether the instrumentation should be enabled. 138 * @return True if it should be enabled. 139 */ 140 @UnsupportedAppUsage isInstrumentationEnabled()141 public static boolean isInstrumentationEnabled() { 142 return IS_ENG_BUILD; 143 } 144 145 /** 146 * Resets the state of the input event consistency verifier. 147 */ reset()148 public void reset() { 149 mLastEventSeq = -1; 150 mLastNestingLevel = 0; 151 mTrackballDown = false; 152 mTrackballUnhandled = false; 153 mTouchEventStreamPointers = 0; 154 mTouchEventStreamIsTainted = false; 155 mTouchEventStreamUnhandled = false; 156 mHoverEntered = false; 157 mButtonsPressed = 0; 158 159 while (mKeyStateList != null) { 160 final KeyState state = mKeyStateList; 161 mKeyStateList = state.next; 162 state.recycle(); 163 } 164 } 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 */ onInputEvent(InputEvent event, int nestingLevel)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 */ onKeyEvent(KeyEvent event, int nestingLevel)200 public void onKeyEvent(KeyEvent event, int nestingLevel) { 201 if (!startEvent(event, nestingLevel, EVENT_TYPE_KEY)) { 202 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. 220 if (state.unhandled) { 221 state.unhandled = false; 222 } else if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0 223 && 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 { 249 finishEvent(); 250 } 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 */ onTrackballEvent(MotionEvent event, int nestingLevel)262 public void onTrackballEvent(MotionEvent event, int nestingLevel) { 263 if (!startEvent(event, nestingLevel, EVENT_TYPE_TRACKBALL)) { 264 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: 275 if (mTrackballDown && !mTrackballUnhandled) { 276 problem("ACTION_DOWN but trackball is already down."); 277 } else { 278 mTrackballDown = true; 279 mTrackballUnhandled = false; 280 } 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; 289 mTrackballUnhandled = false; 290 } 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 { 312 finishEvent(); 313 } 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 */ 325 @UnsupportedAppUsage onTouchEvent(MotionEvent event, int nestingLevel)326 public void onTouchEvent(MotionEvent event, int nestingLevel) { 327 if (!startEvent(event, nestingLevel, EVENT_TYPE_TOUCH)) { 328 return; 329 } 330 331 final int action = event.getAction(); 332 final boolean newStream = action == MotionEvent.ACTION_DOWN 333 || action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_OUTSIDE; 334 if (newStream && (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled)) { 335 mTouchEventStreamIsTainted = false; 336 mTouchEventStreamUnhandled = false; 337 mTouchEventStreamPointers = 0; 338 } 339 if (mTouchEventStreamIsTainted) { 340 event.setTainted(true); 341 } 342 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 { 454 finishEvent(); 455 } 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 */ onGenericMotionEvent(MotionEvent event, int nestingLevel)467 public void onGenericMotionEvent(MotionEvent event, int nestingLevel) { 468 if (!startEvent(event, nestingLevel, EVENT_TYPE_GENERIC_MOTION)) { 469 return; 470 } 471 472 try { 473 ensureMetaStateIsNormalized(event.getMetaState()); 474 475 final int action = event.getAction(); 476 final int source = event.getSource(); 477 final int buttonState = event.getButtonState(); 478 final int actionButton = event.getActionButton(); 479 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; 499 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; 555 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 { 570 finishEvent(); 571 } 572 } 573 574 /** 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 */ 586 @UnsupportedAppUsage onUnhandledEvent(InputEvent event, int nestingLevel)587 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 ensureMetaStateIsNormalized(int metaState)617 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 ensurePointerCountIsOneForThisAction(MotionEvent event)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 ensureActionButtonIsNonZeroForThisAction(MotionEvent event)633 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 ensureHistorySizeIsZeroForThisAction(MotionEvent event)642 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 startEvent(InputEvent event, int nestingLevel, String eventType)650 private boolean startEvent(InputEvent event, int nestingLevel, String eventType) { 651 // Ignore the event if we already checked it at a higher nesting level. 652 final int seq = event.getSequenceNumber(); 653 if (seq == mLastEventSeq && nestingLevel < mLastNestingLevel 654 && eventType == mLastEventType) { 655 return false; 656 } 657 658 if (nestingLevel > 0) { 659 mLastEventSeq = seq; 660 mLastEventType = eventType; 661 mLastNestingLevel = nestingLevel; 662 } else { 663 mLastEventSeq = -1; 664 mLastEventType = null; 665 mLastNestingLevel = 0; 666 } 667 668 mCurrentEvent = event; 669 mCurrentEventType = eventType; 670 return true; 671 } 672 finishEvent()673 private void finishEvent() { 674 if (mViolationMessage != null && mViolationMessage.length() != 0) { 675 if (!mCurrentEvent.isTainted()) { 676 // 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); 680 681 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]); 692 } 693 } 694 695 Log.d(mLogTag, mViolationMessage.toString()); 696 697 // Taint the event so that we do not generate additional violations from it 698 // further downstream. 699 mCurrentEvent.setTainted(true); 700 } 701 mViolationMessage.setLength(0); 702 } 703 704 if (RECENT_EVENTS_TO_LOG != 0) { 705 if (mRecentEvents == null) { 706 mRecentEvents = new InputEvent[RECENT_EVENTS_TO_LOG]; 707 mRecentEventsUnhandled = new boolean[RECENT_EVENTS_TO_LOG]; 708 } 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(); 715 mRecentEventsUnhandled[index] = false; 716 } 717 718 mCurrentEvent = null; 719 mCurrentEventType = null; 720 } 721 appendEvent(StringBuilder message, int index, InputEvent event, boolean unhandled)722 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 problem(String message)732 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 findKeyState(int deviceId, int source, int keyCode, boolean remove)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 addKeyState(int deviceId, int source, int keyCode)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; 780 public boolean unhandled; 781 KeyState()782 private KeyState() { 783 } 784 obtain(int deviceId, int source, int keyCode)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; 798 state.unhandled = false; 799 return state; 800 } 801 recycle()802 public void recycle() { 803 synchronized (mRecycledListLock) { 804 next = mRecycledList; 805 mRecycledList = next; 806 } 807 } 808 } 809 } 810