1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.view; 18 19 import android.os.Build; 20 import android.util.Log; 21 22 /** 23 * Checks whether a sequence of input events is self-consistent. 24 * Logs a description of each problem detected. 25 * <p> 26 * When a problem is detected, the event is tainted. This mechanism prevents the same 27 * error from being reported multiple times. 28 * </p> 29 * 30 * @hide 31 */ 32 public final class InputEventConsistencyVerifier { 33 private static final boolean IS_ENG_BUILD = "eng".equals(Build.TYPE); 34 35 private static final String EVENT_TYPE_KEY = "KeyEvent"; 36 private static final String EVENT_TYPE_TRACKBALL = "TrackballEvent"; 37 private static final String EVENT_TYPE_TOUCH = "TouchEvent"; 38 private static final String EVENT_TYPE_GENERIC_MOTION = "GenericMotionEvent"; 39 40 // The number of recent events to log when a problem is detected. 41 // Can be set to 0 to disable logging recent events but the runtime overhead of 42 // this feature is negligible on current hardware. 43 private static final int RECENT_EVENTS_TO_LOG = 5; 44 45 // The object to which the verifier is attached. 46 private final Object mCaller; 47 48 // Consistency verifier flags. 49 private final int mFlags; 50 51 // Tag for logging which a client can set to help distinguish the output 52 // from different verifiers since several can be active at the same time. 53 // If not provided defaults to the simple class name. 54 private final String mLogTag; 55 56 // The most recently checked event and the nesting level at which it was checked. 57 // This is only set when the verifier is called from a nesting level greater than 0 58 // so that the verifier can detect when it has been asked to verify the same event twice. 59 // It does not make sense to examine the contents of the last event since it may have 60 // been recycled. 61 private int mLastEventSeq; 62 private String mLastEventType; 63 private int mLastNestingLevel; 64 65 // Copy of the most recent events. 66 private InputEvent[] mRecentEvents; 67 private boolean[] mRecentEventsUnhandled; 68 private int mMostRecentEventIndex; 69 70 // Current event and its type. 71 private InputEvent mCurrentEvent; 72 private String mCurrentEventType; 73 74 // Linked list of key state objects. 75 private KeyState mKeyStateList; 76 77 // Current state of the trackball. 78 private boolean mTrackballDown; 79 private boolean mTrackballUnhandled; 80 81 // Bitfield of pointer ids that are currently down. 82 // Assumes that the largest possible pointer id is 31, which is potentially subject to change. 83 // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h) 84 private int mTouchEventStreamPointers; 85 86 // The device id and source of the current stream of touch events. 87 private int mTouchEventStreamDeviceId = -1; 88 private int mTouchEventStreamSource; 89 90 // Set to true when we discover that the touch event stream is inconsistent. 91 // Reset on down or cancel. 92 private boolean mTouchEventStreamIsTainted; 93 94 // Set to true if the touch event stream is partially unhandled. 95 private boolean mTouchEventStreamUnhandled; 96 97 // Set to true if we received hover enter. 98 private boolean mHoverEntered; 99 100 // The bitset of buttons which we've received ACTION_BUTTON_PRESS for. 101 private int mButtonsPressed; 102 103 // The current violation message. 104 private StringBuilder mViolationMessage; 105 106 /** 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. 110 */ 111 public static final int FLAG_RAW_DEVICE_INPUT = 1 << 0; 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 */ 118 public InputEventConsistencyVerifier(Object caller, int flags) { 119 this(caller, flags, null); 120 } 121 122 /** 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. 127 */ 128 public InputEventConsistencyVerifier(Object caller, int flags, String logTag) { 129 this.mCaller = caller; 130 this.mFlags = flags; 131 this.mLogTag = (logTag != null) ? logTag : "InputEventConsistencyVerifier"; 132 } 133 134 /** 135 * Determines whether the instrumentation should be enabled. 136 * @return True if it should be enabled. 137 */ 138 public static boolean isInstrumentationEnabled() { 139 return IS_ENG_BUILD; 140 } 141 142 /** 143 * Resets the state of the input event consistency verifier. 144 */ 145 public void reset() { 146 mLastEventSeq = -1; 147 mLastNestingLevel = 0; 148 mTrackballDown = false; 149 mTrackballUnhandled = false; 150 mTouchEventStreamPointers = 0; 151 mTouchEventStreamIsTainted = false; 152 mTouchEventStreamUnhandled = false; 153 mHoverEntered = false; 154 mButtonsPressed = 0; 155 156 while (mKeyStateList != null) { 157 final KeyState state = mKeyStateList; 158 mKeyStateList = state.next; 159 state.recycle(); 160 } 161 } 162 163 /** 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. 171 */ 172 public void onInputEvent(InputEvent event, int nestingLevel) { 173 if (event instanceof KeyEvent) { 174 final KeyEvent keyEvent = (KeyEvent)event; 175 onKeyEvent(keyEvent, nestingLevel); 176 } else { 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); 182 } else { 183 onGenericMotionEvent(motionEvent, nestingLevel); 184 } 185 } 186 } 187 188 /** 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. 196 */ 197 public void onKeyEvent(KeyEvent event, int nestingLevel) { 198 if (!startEvent(event, nestingLevel, EVENT_TYPE_KEY)) { 199 return; 200 } 201 202 try { 203 ensureMetaStateIsNormalized(event.getMetaState()); 204 205 final int action = event.getAction(); 206 final int deviceId = event.getDeviceId(); 207 final int source = event.getSource(); 208 final int keyCode = event.getKeyCode(); 209 switch (action) { 210 case KeyEvent.ACTION_DOWN: { 211 KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false); 212 if (state != null) { 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."); 223 } 224 } else { 225 addKeyState(deviceId, source, keyCode); 226 } 227 break; 228 } 229 case KeyEvent.ACTION_UP: { 230 KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ true); 231 if (state == null) { 232 problem("ACTION_UP but key was not down."); 233 } else { 234 state.recycle(); 235 } 236 break; 237 } 238 case KeyEvent.ACTION_MULTIPLE: 239 break; 240 default: 241 problem("Invalid action " + KeyEvent.actionToString(action) 242 + " for key event."); 243 break; 244 } 245 } finally { 246 finishEvent(); 247 } 248 } 249 250 /** 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. 258 */ 259 public void onTrackballEvent(MotionEvent event, int nestingLevel) { 260 if (!startEvent(event, nestingLevel, EVENT_TYPE_TRACKBALL)) { 261 return; 262 } 263 264 try { 265 ensureMetaStateIsNormalized(event.getMetaState()); 266 267 final int action = event.getAction(); 268 final int source = event.getSource(); 269 if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { 270 switch (action) { 271 case MotionEvent.ACTION_DOWN: 272 if (mTrackballDown && !mTrackballUnhandled) { 273 problem("ACTION_DOWN but trackball is already down."); 274 } else { 275 mTrackballDown = true; 276 mTrackballUnhandled = false; 277 } 278 ensureHistorySizeIsZeroForThisAction(event); 279 ensurePointerCountIsOneForThisAction(event); 280 break; 281 case MotionEvent.ACTION_UP: 282 if (!mTrackballDown) { 283 problem("ACTION_UP but trackball is not down."); 284 } else { 285 mTrackballDown = false; 286 mTrackballUnhandled = false; 287 } 288 ensureHistorySizeIsZeroForThisAction(event); 289 ensurePointerCountIsOneForThisAction(event); 290 break; 291 case MotionEvent.ACTION_MOVE: 292 ensurePointerCountIsOneForThisAction(event); 293 break; 294 default: 295 problem("Invalid action " + MotionEvent.actionToString(action) 296 + " for trackball event."); 297 break; 298 } 299 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."); 304 } 305 } else { 306 problem("Source was not SOURCE_CLASS_TRACKBALL."); 307 } 308 } finally { 309 finishEvent(); 310 } 311 } 312 313 /** 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. 321 */ 322 public void onTouchEvent(MotionEvent event, int nestingLevel) { 323 if (!startEvent(event, nestingLevel, EVENT_TYPE_TOUCH)) { 324 return; 325 } 326 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; 334 } 335 if (mTouchEventStreamIsTainted) { 336 event.setTainted(true); 337 } 338 339 try { 340 ensureMetaStateIsNormalized(event.getMetaState()); 341 342 final int deviceId = event.getDeviceId(); 343 final int source = event.getSource(); 344 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)); 353 } 354 mTouchEventStreamDeviceId = deviceId; 355 mTouchEventStreamSource = source; 356 357 final int pointerCount = event.getPointerCount(); 358 if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { 359 switch (action) { 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."); 364 } 365 ensureHistorySizeIsZeroForThisAction(event); 366 ensurePointerCountIsOneForThisAction(event); 367 mTouchEventStreamPointers = 1 << event.getPointerId(0); 368 break; 369 case MotionEvent.ACTION_UP: 370 ensureHistorySizeIsZeroForThisAction(event); 371 ensurePointerCountIsOneForThisAction(event); 372 mTouchEventStreamPointers = 0; 373 mTouchEventStreamIsTainted = false; 374 break; 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; 383 } 384 break; 385 } 386 case MotionEvent.ACTION_CANCEL: 387 mTouchEventStreamPointers = 0; 388 mTouchEventStreamIsTainted = false; 389 break; 390 case MotionEvent.ACTION_OUTSIDE: 391 if (mTouchEventStreamPointers != 0) { 392 problem("ACTION_OUTSIDE but pointers are still down."); 393 } 394 ensureHistorySizeIsZeroForThisAction(event); 395 ensurePointerCountIsOneForThisAction(event); 396 mTouchEventStreamIsTainted = false; 397 break; 398 default: { 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; 405 } 406 if (actionIndex < 0 || actionIndex >= pointerCount) { 407 problem("ACTION_POINTER_DOWN index is " + actionIndex 408 + " but the pointer count is " + pointerCount + "."); 409 mTouchEventStreamIsTainted = true; 410 } else { 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; 417 } else { 418 mTouchEventStreamPointers |= idBit; 419 } 420 } 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; 427 } else { 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; 434 } else { 435 mTouchEventStreamPointers &= ~idBit; 436 } 437 } 438 ensureHistorySizeIsZeroForThisAction(event); 439 } else { 440 problem("Invalid action " + MotionEvent.actionToString(action) 441 + " for touch event."); 442 } 443 break; 444 } 445 } 446 } else { 447 problem("Source was not SOURCE_CLASS_POINTER."); 448 } 449 } finally { 450 finishEvent(); 451 } 452 } 453 454 /** 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. 462 */ 463 public void onGenericMotionEvent(MotionEvent event, int nestingLevel) { 464 if (!startEvent(event, nestingLevel, EVENT_TYPE_GENERIC_MOTION)) { 465 return; 466 } 467 468 try { 469 ensureMetaStateIsNormalized(event.getMetaState()); 470 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) { 476 switch (action) { 477 case MotionEvent.ACTION_HOVER_ENTER: 478 ensurePointerCountIsOneForThisAction(event); 479 mHoverEntered = true; 480 break; 481 case MotionEvent.ACTION_HOVER_MOVE: 482 ensurePointerCountIsOneForThisAction(event); 483 break; 484 case MotionEvent.ACTION_HOVER_EXIT: 485 ensurePointerCountIsOneForThisAction(event); 486 if (!mHoverEntered) { 487 problem("ACTION_HOVER_EXIT without prior ACTION_HOVER_ENTER"); 488 } 489 mHoverEntered = false; 490 break; 491 case MotionEvent.ACTION_SCROLL: 492 ensureHistorySizeIsZeroForThisAction(event); 493 ensurePointerCountIsOneForThisAction(event); 494 break; 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."); 501 } 502 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; 514 } 515 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)); 521 } 522 break; 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."); 529 } 530 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; 542 } 543 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)); 549 } 550 break; 551 default: 552 problem("Invalid action for generic pointer event."); 553 break; 554 } 555 } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { 556 switch (action) { 557 case MotionEvent.ACTION_MOVE: 558 ensurePointerCountIsOneForThisAction(event); 559 break; 560 default: 561 problem("Invalid action for generic joystick event."); 562 break; 563 } 564 } 565 } finally { 566 finishEvent(); 567 } 568 } 569 570 /** 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. 581 */ 582 public void onUnhandledEvent(InputEvent event, int nestingLevel) { 583 if (nestingLevel != mLastNestingLevel) { 584 return; 585 } 586 587 if (mRecentEventsUnhandled != null) { 588 mRecentEventsUnhandled[mMostRecentEventIndex] = true; 589 } 590 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); 597 if (state != null) { 598 state.unhandled = true; 599 } 600 } else { 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; 607 } 608 } 609 } 610 } 611 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)); 617 } 618 } 619 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())); 625 } 626 } 627 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())); 633 634 } 635 } 636 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())); 642 } 643 } 644 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) { 650 return false; 651 } 652 653 if (nestingLevel > 0) { 654 mLastEventSeq = seq; 655 mLastEventType = eventType; 656 mLastNestingLevel = nestingLevel; 657 } else { 658 mLastEventSeq = -1; 659 mLastEventType = null; 660 mLastNestingLevel = 0; 661 } 662 663 mCurrentEvent = event; 664 mCurrentEventType = eventType; 665 return true; 666 } 667 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); 675 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]; 682 if (event == null) { 683 break; 684 } 685 mViolationMessage.append("\n "); 686 appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]); 687 } 688 } 689 690 Log.d(mLogTag, mViolationMessage.toString()); 691 692 // Taint the event so that we do not generate additional violations from it 693 // further downstream. 694 mCurrentEvent.setTainted(true); 695 } 696 mViolationMessage.setLength(0); 697 } 698 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]; 703 } 704 final int index = (mMostRecentEventIndex + 1) % RECENT_EVENTS_TO_LOG; 705 mMostRecentEventIndex = index; 706 if (mRecentEvents[index] != null) { 707 mRecentEvents[index].recycle(); 708 } 709 mRecentEvents[index] = mCurrentEvent.copy(); 710 mRecentEventsUnhandled[index] = false; 711 } 712 713 mCurrentEvent = null; 714 mCurrentEventType = null; 715 } 716 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(", "); 721 if (unhandled) { 722 message.append("(unhandled) "); 723 } 724 message.append(event); 725 } 726 727 private void problem(String message) { 728 if (mViolationMessage == null) { 729 mViolationMessage = new StringBuilder(); 730 } 731 if (mViolationMessage.length() == 0) { 732 mViolationMessage.append(mCurrentEventType).append(": "); 733 } else { 734 mViolationMessage.append("\n "); 735 } 736 mViolationMessage.append(message); 737 } 738 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) { 745 if (remove) { 746 if (last != null) { 747 last.next = state.next; 748 } else { 749 mKeyStateList = state.next; 750 } 751 state.next = null; 752 } 753 return state; 754 } 755 last = state; 756 state = state.next; 757 } 758 return null; 759 } 760 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; 765 } 766 767 private static final class KeyState { 768 private static Object mRecycledListLock = new Object(); 769 private static KeyState mRecycledList; 770 771 public KeyState next; 772 public int deviceId; 773 public int source; 774 public int keyCode; 775 public boolean unhandled; 776 777 private KeyState() { 778 } 779 780 public static KeyState obtain(int deviceId, int source, int keyCode) { 781 KeyState state; 782 synchronized (mRecycledListLock) { 783 state = mRecycledList; 784 if (state != null) { 785 mRecycledList = state.next; 786 } else { 787 state = new KeyState(); 788 } 789 } 790 state.deviceId = deviceId; 791 state.source = source; 792 state.keyCode = keyCode; 793 state.unhandled = false; 794 return state; 795 } 796 797 public void recycle() { 798 synchronized (mRecycledListLock) { 799 next = mRecycledList; 800 mRecycledList = next; 801 } 802 } 803 } 804 } 805