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 InputEvent mLastEvent; 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 current violation message. 101 private StringBuilder mViolationMessage; 102 103 /** 104 * Indicates that the verifier is intended to act on raw device input event streams. 105 * Disables certain checks for invariants that are established by the input dispatcher 106 * itself as it delivers input events, such as key repeating behavior. 107 */ 108 public static final int FLAG_RAW_DEVICE_INPUT = 1 << 0; 109 110 /** 111 * Creates an input consistency verifier. 112 * @param caller The object to which the verifier is attached. 113 * @param flags Flags to the verifier, or 0 if none. 114 */ 115 public InputEventConsistencyVerifier(Object caller, int flags) { 116 this(caller, flags, InputEventConsistencyVerifier.class.getSimpleName()); 117 } 118 119 /** 120 * Creates an input consistency verifier. 121 * @param caller The object to which the verifier is attached. 122 * @param flags Flags to the verifier, or 0 if none. 123 * @param logTag Tag for logging. If null defaults to the short class name. 124 */ 125 public InputEventConsistencyVerifier(Object caller, int flags, String logTag) { 126 this.mCaller = caller; 127 this.mFlags = flags; 128 this.mLogTag = (logTag != null) ? logTag : "InputEventConsistencyVerifier"; 129 } 130 131 /** 132 * Determines whether the instrumentation should be enabled. 133 * @return True if it should be enabled. 134 */ 135 public static boolean isInstrumentationEnabled() { 136 return IS_ENG_BUILD; 137 } 138 139 /** 140 * Resets the state of the input event consistency verifier. 141 */ 142 public void reset() { 143 mLastEvent = null; 144 mLastNestingLevel = 0; 145 mTrackballDown = false; 146 mTrackballUnhandled = false; 147 mTouchEventStreamPointers = 0; 148 mTouchEventStreamIsTainted = false; 149 mTouchEventStreamUnhandled = false; 150 mHoverEntered = false; 151 152 while (mKeyStateList != null) { 153 final KeyState state = mKeyStateList; 154 mKeyStateList = state.next; 155 state.recycle(); 156 } 157 } 158 159 /** 160 * Checks an arbitrary input event. 161 * @param event The event. 162 * @param nestingLevel The nesting level: 0 if called from the base class, 163 * or 1 from a subclass. If the event was already checked by this consistency verifier 164 * at a higher nesting level, it will not be checked again. Used to handle the situation 165 * where a subclass dispatching method delegates to its superclass's dispatching method 166 * and both dispatching methods call into the consistency verifier. 167 */ 168 public void onInputEvent(InputEvent event, int nestingLevel) { 169 if (event instanceof KeyEvent) { 170 final KeyEvent keyEvent = (KeyEvent)event; 171 onKeyEvent(keyEvent, nestingLevel); 172 } else { 173 final MotionEvent motionEvent = (MotionEvent)event; 174 if (motionEvent.isTouchEvent()) { 175 onTouchEvent(motionEvent, nestingLevel); 176 } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { 177 onTrackballEvent(motionEvent, nestingLevel); 178 } else { 179 onGenericMotionEvent(motionEvent, nestingLevel); 180 } 181 } 182 } 183 184 /** 185 * Checks a key event. 186 * @param event The event. 187 * @param nestingLevel The nesting level: 0 if called from the base class, 188 * or 1 from a subclass. If the event was already checked by this consistency verifier 189 * at a higher nesting level, it will not be checked again. Used to handle the situation 190 * where a subclass dispatching method delegates to its superclass's dispatching method 191 * and both dispatching methods call into the consistency verifier. 192 */ 193 public void onKeyEvent(KeyEvent event, int nestingLevel) { 194 if (!startEvent(event, nestingLevel, EVENT_TYPE_KEY)) { 195 return; 196 } 197 198 try { 199 ensureMetaStateIsNormalized(event.getMetaState()); 200 201 final int action = event.getAction(); 202 final int deviceId = event.getDeviceId(); 203 final int source = event.getSource(); 204 final int keyCode = event.getKeyCode(); 205 switch (action) { 206 case KeyEvent.ACTION_DOWN: { 207 KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false); 208 if (state != null) { 209 // If the key is already down, ensure it is a repeat. 210 // We don't perform this check when processing raw device input 211 // because the input dispatcher itself is responsible for setting 212 // the key repeat count before it delivers input events. 213 if (state.unhandled) { 214 state.unhandled = false; 215 } else if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0 216 && event.getRepeatCount() == 0) { 217 problem("ACTION_DOWN but key is already down and this event " 218 + "is not a key repeat."); 219 } 220 } else { 221 addKeyState(deviceId, source, keyCode); 222 } 223 break; 224 } 225 case KeyEvent.ACTION_UP: { 226 KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ true); 227 if (state == null) { 228 problem("ACTION_UP but key was not down."); 229 } else { 230 state.recycle(); 231 } 232 break; 233 } 234 case KeyEvent.ACTION_MULTIPLE: 235 break; 236 default: 237 problem("Invalid action " + KeyEvent.actionToString(action) 238 + " for key event."); 239 break; 240 } 241 } finally { 242 finishEvent(); 243 } 244 } 245 246 /** 247 * Checks a trackball event. 248 * @param event The event. 249 * @param nestingLevel The nesting level: 0 if called from the base class, 250 * or 1 from a subclass. If the event was already checked by this consistency verifier 251 * at a higher nesting level, it will not be checked again. Used to handle the situation 252 * where a subclass dispatching method delegates to its superclass's dispatching method 253 * and both dispatching methods call into the consistency verifier. 254 */ 255 public void onTrackballEvent(MotionEvent event, int nestingLevel) { 256 if (!startEvent(event, nestingLevel, EVENT_TYPE_TRACKBALL)) { 257 return; 258 } 259 260 try { 261 ensureMetaStateIsNormalized(event.getMetaState()); 262 263 final int action = event.getAction(); 264 final int source = event.getSource(); 265 if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { 266 switch (action) { 267 case MotionEvent.ACTION_DOWN: 268 if (mTrackballDown && !mTrackballUnhandled) { 269 problem("ACTION_DOWN but trackball is already down."); 270 } else { 271 mTrackballDown = true; 272 mTrackballUnhandled = false; 273 } 274 ensureHistorySizeIsZeroForThisAction(event); 275 ensurePointerCountIsOneForThisAction(event); 276 break; 277 case MotionEvent.ACTION_UP: 278 if (!mTrackballDown) { 279 problem("ACTION_UP but trackball is not down."); 280 } else { 281 mTrackballDown = false; 282 mTrackballUnhandled = false; 283 } 284 ensureHistorySizeIsZeroForThisAction(event); 285 ensurePointerCountIsOneForThisAction(event); 286 break; 287 case MotionEvent.ACTION_MOVE: 288 ensurePointerCountIsOneForThisAction(event); 289 break; 290 default: 291 problem("Invalid action " + MotionEvent.actionToString(action) 292 + " for trackball event."); 293 break; 294 } 295 296 if (mTrackballDown && event.getPressure() <= 0) { 297 problem("Trackball is down but pressure is not greater than 0."); 298 } else if (!mTrackballDown && event.getPressure() != 0) { 299 problem("Trackball is up but pressure is not equal to 0."); 300 } 301 } else { 302 problem("Source was not SOURCE_CLASS_TRACKBALL."); 303 } 304 } finally { 305 finishEvent(); 306 } 307 } 308 309 /** 310 * Checks a touch event. 311 * @param event The event. 312 * @param nestingLevel The nesting level: 0 if called from the base class, 313 * or 1 from a subclass. If the event was already checked by this consistency verifier 314 * at a higher nesting level, it will not be checked again. Used to handle the situation 315 * where a subclass dispatching method delegates to its superclass's dispatching method 316 * and both dispatching methods call into the consistency verifier. 317 */ 318 public void onTouchEvent(MotionEvent event, int nestingLevel) { 319 if (!startEvent(event, nestingLevel, EVENT_TYPE_TOUCH)) { 320 return; 321 } 322 323 final int action = event.getAction(); 324 final boolean newStream = action == MotionEvent.ACTION_DOWN 325 || action == MotionEvent.ACTION_CANCEL; 326 if (newStream && (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled)) { 327 mTouchEventStreamIsTainted = false; 328 mTouchEventStreamUnhandled = false; 329 mTouchEventStreamPointers = 0; 330 } 331 if (mTouchEventStreamIsTainted) { 332 event.setTainted(true); 333 } 334 335 try { 336 ensureMetaStateIsNormalized(event.getMetaState()); 337 338 final int deviceId = event.getDeviceId(); 339 final int source = event.getSource(); 340 341 if (!newStream && mTouchEventStreamDeviceId != -1 342 && (mTouchEventStreamDeviceId != deviceId 343 || mTouchEventStreamSource != source)) { 344 problem("Touch event stream contains events from multiple sources: " 345 + "previous device id " + mTouchEventStreamDeviceId 346 + ", previous source " + Integer.toHexString(mTouchEventStreamSource) 347 + ", new device id " + deviceId 348 + ", new source " + Integer.toHexString(source)); 349 } 350 mTouchEventStreamDeviceId = deviceId; 351 mTouchEventStreamSource = source; 352 353 final int pointerCount = event.getPointerCount(); 354 if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { 355 switch (action) { 356 case MotionEvent.ACTION_DOWN: 357 if (mTouchEventStreamPointers != 0) { 358 problem("ACTION_DOWN but pointers are already down. " 359 + "Probably missing ACTION_UP from previous gesture."); 360 } 361 ensureHistorySizeIsZeroForThisAction(event); 362 ensurePointerCountIsOneForThisAction(event); 363 mTouchEventStreamPointers = 1 << event.getPointerId(0); 364 break; 365 case MotionEvent.ACTION_UP: 366 ensureHistorySizeIsZeroForThisAction(event); 367 ensurePointerCountIsOneForThisAction(event); 368 mTouchEventStreamPointers = 0; 369 mTouchEventStreamIsTainted = false; 370 break; 371 case MotionEvent.ACTION_MOVE: { 372 final int expectedPointerCount = 373 Integer.bitCount(mTouchEventStreamPointers); 374 if (pointerCount != expectedPointerCount) { 375 problem("ACTION_MOVE contained " + pointerCount 376 + " pointers but there are currently " 377 + expectedPointerCount + " pointers down."); 378 mTouchEventStreamIsTainted = true; 379 } 380 break; 381 } 382 case MotionEvent.ACTION_CANCEL: 383 mTouchEventStreamPointers = 0; 384 mTouchEventStreamIsTainted = false; 385 break; 386 case MotionEvent.ACTION_OUTSIDE: 387 if (mTouchEventStreamPointers != 0) { 388 problem("ACTION_OUTSIDE but pointers are still down."); 389 } 390 ensureHistorySizeIsZeroForThisAction(event); 391 ensurePointerCountIsOneForThisAction(event); 392 mTouchEventStreamIsTainted = false; 393 break; 394 default: { 395 final int actionMasked = event.getActionMasked(); 396 final int actionIndex = event.getActionIndex(); 397 if (actionMasked == MotionEvent.ACTION_POINTER_DOWN) { 398 if (mTouchEventStreamPointers == 0) { 399 problem("ACTION_POINTER_DOWN but no other pointers were down."); 400 mTouchEventStreamIsTainted = true; 401 } 402 if (actionIndex < 0 || actionIndex >= pointerCount) { 403 problem("ACTION_POINTER_DOWN index is " + actionIndex 404 + " but the pointer count is " + pointerCount + "."); 405 mTouchEventStreamIsTainted = true; 406 } else { 407 final int id = event.getPointerId(actionIndex); 408 final int idBit = 1 << id; 409 if ((mTouchEventStreamPointers & idBit) != 0) { 410 problem("ACTION_POINTER_DOWN specified pointer id " + id 411 + " which is already down."); 412 mTouchEventStreamIsTainted = true; 413 } else { 414 mTouchEventStreamPointers |= idBit; 415 } 416 } 417 ensureHistorySizeIsZeroForThisAction(event); 418 } else if (actionMasked == MotionEvent.ACTION_POINTER_UP) { 419 if (actionIndex < 0 || actionIndex >= pointerCount) { 420 problem("ACTION_POINTER_UP index is " + actionIndex 421 + " but the pointer count is " + pointerCount + "."); 422 mTouchEventStreamIsTainted = true; 423 } else { 424 final int id = event.getPointerId(actionIndex); 425 final int idBit = 1 << id; 426 if ((mTouchEventStreamPointers & idBit) == 0) { 427 problem("ACTION_POINTER_UP specified pointer id " + id 428 + " which is not currently down."); 429 mTouchEventStreamIsTainted = true; 430 } else { 431 mTouchEventStreamPointers &= ~idBit; 432 } 433 } 434 ensureHistorySizeIsZeroForThisAction(event); 435 } else { 436 problem("Invalid action " + MotionEvent.actionToString(action) 437 + " for touch event."); 438 } 439 break; 440 } 441 } 442 } else { 443 problem("Source was not SOURCE_CLASS_POINTER."); 444 } 445 } finally { 446 finishEvent(); 447 } 448 } 449 450 /** 451 * Checks a generic motion event. 452 * @param event The event. 453 * @param nestingLevel The nesting level: 0 if called from the base class, 454 * or 1 from a subclass. If the event was already checked by this consistency verifier 455 * at a higher nesting level, it will not be checked again. Used to handle the situation 456 * where a subclass dispatching method delegates to its superclass's dispatching method 457 * and both dispatching methods call into the consistency verifier. 458 */ 459 public void onGenericMotionEvent(MotionEvent event, int nestingLevel) { 460 if (!startEvent(event, nestingLevel, EVENT_TYPE_GENERIC_MOTION)) { 461 return; 462 } 463 464 try { 465 ensureMetaStateIsNormalized(event.getMetaState()); 466 467 final int action = event.getAction(); 468 final int source = event.getSource(); 469 if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { 470 switch (action) { 471 case MotionEvent.ACTION_HOVER_ENTER: 472 ensurePointerCountIsOneForThisAction(event); 473 mHoverEntered = true; 474 break; 475 case MotionEvent.ACTION_HOVER_MOVE: 476 ensurePointerCountIsOneForThisAction(event); 477 break; 478 case MotionEvent.ACTION_HOVER_EXIT: 479 ensurePointerCountIsOneForThisAction(event); 480 if (!mHoverEntered) { 481 problem("ACTION_HOVER_EXIT without prior ACTION_HOVER_ENTER"); 482 } 483 mHoverEntered = false; 484 break; 485 case MotionEvent.ACTION_SCROLL: 486 ensureHistorySizeIsZeroForThisAction(event); 487 ensurePointerCountIsOneForThisAction(event); 488 break; 489 default: 490 problem("Invalid action for generic pointer event."); 491 break; 492 } 493 } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { 494 switch (action) { 495 case MotionEvent.ACTION_MOVE: 496 ensurePointerCountIsOneForThisAction(event); 497 break; 498 default: 499 problem("Invalid action for generic joystick event."); 500 break; 501 } 502 } 503 } finally { 504 finishEvent(); 505 } 506 } 507 508 /** 509 * Notifies the verifier that a given event was unhandled and the rest of the 510 * trace for the event should be ignored. 511 * This method should only be called if the event was previously checked by 512 * the consistency verifier using {@link #onInputEvent} and other methods. 513 * @param event The event. 514 * @param nestingLevel The nesting level: 0 if called from the base class, 515 * or 1 from a subclass. If the event was already checked by this consistency verifier 516 * at a higher nesting level, it will not be checked again. Used to handle the situation 517 * where a subclass dispatching method delegates to its superclass's dispatching method 518 * and both dispatching methods call into the consistency verifier. 519 */ 520 public void onUnhandledEvent(InputEvent event, int nestingLevel) { 521 if (nestingLevel != mLastNestingLevel) { 522 return; 523 } 524 525 if (mRecentEventsUnhandled != null) { 526 mRecentEventsUnhandled[mMostRecentEventIndex] = true; 527 } 528 529 if (event instanceof KeyEvent) { 530 final KeyEvent keyEvent = (KeyEvent)event; 531 final int deviceId = keyEvent.getDeviceId(); 532 final int source = keyEvent.getSource(); 533 final int keyCode = keyEvent.getKeyCode(); 534 final KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false); 535 if (state != null) { 536 state.unhandled = true; 537 } 538 } else { 539 final MotionEvent motionEvent = (MotionEvent)event; 540 if (motionEvent.isTouchEvent()) { 541 mTouchEventStreamUnhandled = true; 542 } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { 543 if (mTrackballDown) { 544 mTrackballUnhandled = true; 545 } 546 } 547 } 548 } 549 550 private void ensureMetaStateIsNormalized(int metaState) { 551 final int normalizedMetaState = KeyEvent.normalizeMetaState(metaState); 552 if (normalizedMetaState != metaState) { 553 problem(String.format("Metastate not normalized. Was 0x%08x but expected 0x%08x.", 554 metaState, normalizedMetaState)); 555 } 556 } 557 558 private void ensurePointerCountIsOneForThisAction(MotionEvent event) { 559 final int pointerCount = event.getPointerCount(); 560 if (pointerCount != 1) { 561 problem("Pointer count is " + pointerCount + " but it should always be 1 for " 562 + MotionEvent.actionToString(event.getAction())); 563 } 564 } 565 566 private void ensureHistorySizeIsZeroForThisAction(MotionEvent event) { 567 final int historySize = event.getHistorySize(); 568 if (historySize != 0) { 569 problem("History size is " + historySize + " but it should always be 0 for " 570 + MotionEvent.actionToString(event.getAction())); 571 } 572 } 573 574 private boolean startEvent(InputEvent event, int nestingLevel, String eventType) { 575 // Ignore the event if we already checked it at a higher nesting level. 576 if (event == mLastEvent && nestingLevel < mLastNestingLevel 577 && eventType == mLastEventType) { 578 return false; 579 } 580 581 if (nestingLevel > 0) { 582 mLastEvent = event; 583 mLastEventType = eventType; 584 mLastNestingLevel = nestingLevel; 585 } else { 586 mLastEvent = null; 587 mLastEventType = null; 588 mLastNestingLevel = 0; 589 } 590 591 mCurrentEvent = event; 592 mCurrentEventType = eventType; 593 return true; 594 } 595 596 private void finishEvent() { 597 if (mViolationMessage != null && mViolationMessage.length() != 0) { 598 if (!mCurrentEvent.isTainted()) { 599 // Write a log message only if the event was not already tainted. 600 mViolationMessage.append("\n in ").append(mCaller); 601 mViolationMessage.append("\n "); 602 appendEvent(mViolationMessage, 0, mCurrentEvent, false); 603 604 if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents != null) { 605 mViolationMessage.append("\n -- recent events --"); 606 for (int i = 0; i < RECENT_EVENTS_TO_LOG; i++) { 607 final int index = (mMostRecentEventIndex + RECENT_EVENTS_TO_LOG - i) 608 % RECENT_EVENTS_TO_LOG; 609 final InputEvent event = mRecentEvents[index]; 610 if (event == null) { 611 break; 612 } 613 mViolationMessage.append("\n "); 614 appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]); 615 } 616 } 617 618 Log.d(mLogTag, mViolationMessage.toString()); 619 620 // Taint the event so that we do not generate additional violations from it 621 // further downstream. 622 mCurrentEvent.setTainted(true); 623 } 624 mViolationMessage.setLength(0); 625 } 626 627 if (RECENT_EVENTS_TO_LOG != 0) { 628 if (mRecentEvents == null) { 629 mRecentEvents = new InputEvent[RECENT_EVENTS_TO_LOG]; 630 mRecentEventsUnhandled = new boolean[RECENT_EVENTS_TO_LOG]; 631 } 632 final int index = (mMostRecentEventIndex + 1) % RECENT_EVENTS_TO_LOG; 633 mMostRecentEventIndex = index; 634 if (mRecentEvents[index] != null) { 635 mRecentEvents[index].recycle(); 636 } 637 mRecentEvents[index] = mCurrentEvent.copy(); 638 mRecentEventsUnhandled[index] = false; 639 } 640 641 mCurrentEvent = null; 642 mCurrentEventType = null; 643 } 644 645 private static void appendEvent(StringBuilder message, int index, 646 InputEvent event, boolean unhandled) { 647 message.append(index).append(": sent at ").append(event.getEventTimeNano()); 648 message.append(", "); 649 if (unhandled) { 650 message.append("(unhandled) "); 651 } 652 message.append(event); 653 } 654 655 private void problem(String message) { 656 if (mViolationMessage == null) { 657 mViolationMessage = new StringBuilder(); 658 } 659 if (mViolationMessage.length() == 0) { 660 mViolationMessage.append(mCurrentEventType).append(": "); 661 } else { 662 mViolationMessage.append("\n "); 663 } 664 mViolationMessage.append(message); 665 } 666 667 private KeyState findKeyState(int deviceId, int source, int keyCode, boolean remove) { 668 KeyState last = null; 669 KeyState state = mKeyStateList; 670 while (state != null) { 671 if (state.deviceId == deviceId && state.source == source 672 && state.keyCode == keyCode) { 673 if (remove) { 674 if (last != null) { 675 last.next = state.next; 676 } else { 677 mKeyStateList = state.next; 678 } 679 state.next = null; 680 } 681 return state; 682 } 683 last = state; 684 state = state.next; 685 } 686 return null; 687 } 688 689 private void addKeyState(int deviceId, int source, int keyCode) { 690 KeyState state = KeyState.obtain(deviceId, source, keyCode); 691 state.next = mKeyStateList; 692 mKeyStateList = state; 693 } 694 695 private static final class KeyState { 696 private static Object mRecycledListLock = new Object(); 697 private static KeyState mRecycledList; 698 699 public KeyState next; 700 public int deviceId; 701 public int source; 702 public int keyCode; 703 public boolean unhandled; 704 705 private KeyState() { 706 } 707 708 public static KeyState obtain(int deviceId, int source, int keyCode) { 709 KeyState state; 710 synchronized (mRecycledListLock) { 711 state = mRecycledList; 712 if (state != null) { 713 mRecycledList = state.next; 714 } else { 715 state = new KeyState(); 716 } 717 } 718 state.deviceId = deviceId; 719 state.source = source; 720 state.keyCode = keyCode; 721 state.unhandled = false; 722 return state; 723 } 724 725 public void recycle() { 726 synchronized (mRecycledListLock) { 727 next = mRecycledList; 728 mRecycledList = next; 729 } 730 } 731 } 732 } 733