Home | History | Annotate | Download | only in view
      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.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
    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      */
    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
    141     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() {
    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      */
    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) {
    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      */
    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
    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      */
    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
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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;
    780         public boolean unhandled;
    781 
    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;
    798             state.unhandled = false;
    799             return state;
    800         }
    801 
    802         public void recycle() {
    803             synchronized (mRecycledListLock) {
    804                 next = mRecycledList;
    805                 mRecycledList = next;
    806             }
    807         }
    808     }
    809 }
    810