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.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