Home | History | Annotate | Download | only in accessibility
      1 /*
      2  ** Copyright 2011, 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 com.android.server.accessibility;
     18 
     19 import android.content.Context;
     20 import android.gesture.Gesture;
     21 import android.gesture.GestureLibraries;
     22 import android.gesture.GestureLibrary;
     23 import android.gesture.GesturePoint;
     24 import android.gesture.GestureStore;
     25 import android.gesture.GestureStroke;
     26 import android.gesture.Prediction;
     27 import android.graphics.Rect;
     28 import android.os.Handler;
     29 import android.os.SystemClock;
     30 import android.util.Slog;
     31 import android.view.MotionEvent;
     32 import android.view.MotionEvent.PointerCoords;
     33 import android.view.MotionEvent.PointerProperties;
     34 import android.view.VelocityTracker;
     35 import android.view.ViewConfiguration;
     36 import android.view.WindowManagerPolicy;
     37 import android.view.accessibility.AccessibilityEvent;
     38 import android.view.accessibility.AccessibilityManager;
     39 
     40 import com.android.internal.R;
     41 
     42 import java.util.ArrayList;
     43 import java.util.Arrays;
     44 
     45 /**
     46  * This class is a strategy for performing touch exploration. It
     47  * transforms the motion event stream by modifying, adding, replacing,
     48  * and consuming certain events. The interaction model is:
     49  *
     50  * <ol>
     51  *   <li>1. One finger moving slow around performs touch exploration.</li>
     52  *   <li>2. One finger moving fast around performs gestures.</li>
     53  *   <li>3. Two close fingers moving in the same direction perform a drag.</li>
     54  *   <li>4. Multi-finger gestures are delivered to view hierarchy.</li>
     55  *   <li>5. Pointers that have not moved more than a specified distance after they
     56  *          went down are considered inactive.</li>
     57  *   <li>6. Two fingers moving in different directions are considered a multi-finger gesture.</li>
     58  *   <li>7. Double tapping clicks on the on the last touch explored location of it was in
     59  *          a window that does not take focus, otherwise the click is within the accessibility
     60  *          focused rectangle.</li>
     61  *   <li>7. Tapping and holding for a while performs a long press in a similar fashion
     62  *          as the click above.</li>
     63  * <ol>
     64  *
     65  * @hide
     66  */
     67 class TouchExplorer implements EventStreamTransformation {
     68 
     69     private static final boolean DEBUG = false;
     70 
     71     // Tag for logging received events.
     72     private static final String LOG_TAG = "TouchExplorer";
     73 
     74     // States this explorer can be in.
     75     private static final int STATE_TOUCH_EXPLORING = 0x00000001;
     76     private static final int STATE_DRAGGING = 0x00000002;
     77     private static final int STATE_DELEGATING = 0x00000004;
     78     private static final int STATE_GESTURE_DETECTING = 0x00000005;
     79 
     80     // The minimum of the cosine between the vectors of two moving
     81     // pointers so they can be considered moving in the same direction.
     82     private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4)
     83 
     84     // Constant referring to the ids bits of all pointers.
     85     private static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF;
     86 
     87     // This constant captures the current implementation detail that
     88     // pointer IDs are between 0 and 31 inclusive (subject to change).
     89     // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
     90     private static final int MAX_POINTER_COUNT = 32;
     91 
     92     // Invalid pointer ID.
     93     private static final int INVALID_POINTER_ID = -1;
     94 
     95     // The velocity above which we detect gestures.
     96     private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000;
     97 
     98     // The minimal distance before we take the middle of the distance between
     99     // the two dragging pointers as opposed to use the location of the primary one.
    100     private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200;
    101 
    102     // The timeout after which we are no longer trying to detect a gesture.
    103     private static final int EXIT_GESTURE_DETECTION_TIMEOUT = 2000;
    104 
    105     // Temporary array for storing pointer IDs.
    106     private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT];
    107 
    108     // Timeout before trying to decide what the user is trying to do.
    109     private final int mDetermineUserIntentTimeout;
    110 
    111     // Timeout within which we try to detect a tap.
    112     private final int mTapTimeout;
    113 
    114     // Timeout within which we try to detect a double tap.
    115     private final int mDoubleTapTimeout;
    116 
    117     // Slop between the down and up tap to be a tap.
    118     private final int mTouchSlop;
    119 
    120     // Slop between the first and second tap to be a double tap.
    121     private final int mDoubleTapSlop;
    122 
    123     // The current state of the touch explorer.
    124     private int mCurrentState = STATE_TOUCH_EXPLORING;
    125 
    126     // The ID of the pointer used for dragging.
    127     private int mDraggingPointerId;
    128 
    129     // Handler for performing asynchronous operations.
    130     private final Handler mHandler;
    131 
    132     // Command for delayed sending of a hover enter event.
    133     private final SendHoverDelayed mSendHoverEnterDelayed;
    134 
    135     // Command for delayed sending of a hover exit event.
    136     private final SendHoverDelayed mSendHoverExitDelayed;
    137 
    138     // Command for delayed sending of touch exploration end events.
    139     private final SendAccessibilityEventDelayed mSendTouchExplorationEndDelayed;
    140 
    141     // Command for delayed sending of touch interaction end events.
    142     private final SendAccessibilityEventDelayed mSendTouchInteractionEndDelayed;
    143 
    144     // Command for delayed sending of a long press.
    145     private final PerformLongPressDelayed mPerformLongPressDelayed;
    146 
    147     // Command for exiting gesture detection mode after a timeout.
    148     private final ExitGestureDetectionModeDelayed mExitGestureDetectionModeDelayed;
    149 
    150     // Helper to detect and react to double tap in touch explore mode.
    151     private final DoubleTapDetector mDoubleTapDetector;
    152 
    153     // The scaled minimal distance before we take the middle of the distance between
    154     // the two dragging pointers as opposed to use the location of the primary one.
    155     private final int mScaledMinPointerDistanceToUseMiddleLocation;
    156 
    157     // The scaled velocity above which we detect gestures.
    158     private final int mScaledGestureDetectionVelocity;
    159 
    160     // The handler to which to delegate events.
    161     private EventStreamTransformation mNext;
    162 
    163     // Helper to track gesture velocity.
    164     private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
    165 
    166     // Helper class to track received pointers.
    167     private final ReceivedPointerTracker mReceivedPointerTracker;
    168 
    169     // Helper class to track injected pointers.
    170     private final InjectedPointerTracker mInjectedPointerTracker;
    171 
    172     // Handle to the accessibility manager service.
    173     private final AccessibilityManagerService mAms;
    174 
    175     // Temporary rectangle to avoid instantiation.
    176     private final Rect mTempRect = new Rect();
    177 
    178     // Context in which this explorer operates.
    179     private final Context mContext;
    180 
    181     // The X of the previous event.
    182     private float mPreviousX;
    183 
    184     // The Y of the previous event.
    185     private float mPreviousY;
    186 
    187     // Buffer for storing points for gesture detection.
    188     private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
    189 
    190     // The minimal delta between moves to add a gesture point.
    191     private static final int TOUCH_TOLERANCE = 3;
    192 
    193     // The minimal score for accepting a predicted gesture.
    194     private static final float MIN_PREDICTION_SCORE = 2.0f;
    195 
    196     // The library for gesture detection.
    197     private GestureLibrary mGestureLibrary;
    198 
    199     // The long pressing pointer id if coordinate remapping is needed.
    200     private int mLongPressingPointerId = -1;
    201 
    202     // The long pressing pointer X if coordinate remapping is needed.
    203     private int mLongPressingPointerDeltaX;
    204 
    205     // The long pressing pointer Y if coordinate remapping is needed.
    206     private int mLongPressingPointerDeltaY;
    207 
    208     // The id of the last touch explored window.
    209     private int mLastTouchedWindowId;
    210 
    211     // Whether touch exploration is in progress.
    212     private boolean mTouchExplorationInProgress;
    213 
    214     /**
    215      * Creates a new instance.
    216      *
    217      * @param inputFilter The input filter associated with this explorer.
    218      * @param context A context handle for accessing resources.
    219      */
    220     public TouchExplorer(Context context, AccessibilityManagerService service) {
    221         mContext = context;
    222         mAms = service;
    223         mReceivedPointerTracker = new ReceivedPointerTracker(context);
    224         mInjectedPointerTracker = new InjectedPointerTracker();
    225         mTapTimeout = ViewConfiguration.getTapTimeout();
    226         mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout();
    227         mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
    228         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    229         mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
    230         mHandler = new Handler(context.getMainLooper());
    231         mPerformLongPressDelayed = new PerformLongPressDelayed();
    232         mExitGestureDetectionModeDelayed = new ExitGestureDetectionModeDelayed();
    233         mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures);
    234         mGestureLibrary.setOrientationStyle(8);
    235         mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE);
    236         mGestureLibrary.load();
    237         mSendHoverEnterDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_ENTER, true);
    238         mSendHoverExitDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_EXIT, false);
    239         mSendTouchExplorationEndDelayed = new SendAccessibilityEventDelayed(
    240                 AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END,
    241                 mDetermineUserIntentTimeout);
    242         mSendTouchInteractionEndDelayed = new SendAccessibilityEventDelayed(
    243                 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END,
    244                 mDetermineUserIntentTimeout);
    245         mDoubleTapDetector = new DoubleTapDetector();
    246         final float density = context.getResources().getDisplayMetrics().density;
    247         mScaledMinPointerDistanceToUseMiddleLocation =
    248             (int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density);
    249         mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
    250     }
    251 
    252     public void clear() {
    253         // If we have not received an event then we are in initial
    254         // state. Therefore, there is not need to clean anything.
    255         MotionEvent event = mReceivedPointerTracker.getLastReceivedEvent();
    256         if (event != null) {
    257             clear(mReceivedPointerTracker.getLastReceivedEvent(), WindowManagerPolicy.FLAG_TRUSTED);
    258         }
    259     }
    260 
    261     public void onDestroy() {
    262         // TODO: Implement
    263     }
    264 
    265     private void clear(MotionEvent event, int policyFlags) {
    266         switch (mCurrentState) {
    267             case STATE_TOUCH_EXPLORING: {
    268                 // If a touch exploration gesture is in progress send events for its end.
    269                 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
    270             } break;
    271             case STATE_DRAGGING: {
    272                 mDraggingPointerId = INVALID_POINTER_ID;
    273                 // Send exit to all pointers that we have delivered.
    274                 sendUpForInjectedDownPointers(event, policyFlags);
    275             } break;
    276             case STATE_DELEGATING: {
    277                 // Send exit to all pointers that we have delivered.
    278                 sendUpForInjectedDownPointers(event, policyFlags);
    279             } break;
    280             case STATE_GESTURE_DETECTING: {
    281                 // Clear the current stroke.
    282                 mStrokeBuffer.clear();
    283             } break;
    284         }
    285         // Remove all pending callbacks.
    286         mSendHoverEnterDelayed.remove();
    287         mSendHoverExitDelayed.remove();
    288         mPerformLongPressDelayed.remove();
    289         mExitGestureDetectionModeDelayed.remove();
    290         mSendTouchExplorationEndDelayed.remove();
    291         mSendTouchInteractionEndDelayed.remove();
    292         // Reset the pointer trackers.
    293         mReceivedPointerTracker.clear();
    294         mInjectedPointerTracker.clear();
    295         // Clear the double tap detector
    296         mDoubleTapDetector.clear();
    297         // Go to initial state.
    298         // Clear the long pressing pointer remap data.
    299         mLongPressingPointerId = -1;
    300         mLongPressingPointerDeltaX = 0;
    301         mLongPressingPointerDeltaY = 0;
    302         mCurrentState = STATE_TOUCH_EXPLORING;
    303         if (mNext != null) {
    304             mNext.clear();
    305         }
    306         mTouchExplorationInProgress = false;
    307         mAms.onTouchInteractionEnd();
    308     }
    309 
    310     @Override
    311     public void setNext(EventStreamTransformation next) {
    312         mNext = next;
    313     }
    314 
    315     @Override
    316     public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
    317         if (DEBUG) {
    318             Slog.d(LOG_TAG, "Received event: " + event + ", policyFlags=0x"
    319                     + Integer.toHexString(policyFlags));
    320             Slog.d(LOG_TAG, getStateSymbolicName(mCurrentState));
    321         }
    322 
    323         mReceivedPointerTracker.onMotionEvent(rawEvent);
    324 
    325         switch(mCurrentState) {
    326             case STATE_TOUCH_EXPLORING: {
    327                 handleMotionEventStateTouchExploring(event, rawEvent, policyFlags);
    328             } break;
    329             case STATE_DRAGGING: {
    330                 handleMotionEventStateDragging(event, policyFlags);
    331             } break;
    332             case STATE_DELEGATING: {
    333                 handleMotionEventStateDelegating(event, policyFlags);
    334             } break;
    335             case STATE_GESTURE_DETECTING: {
    336                 handleMotionEventGestureDetecting(rawEvent, policyFlags);
    337             } break;
    338             default:
    339                 throw new IllegalStateException("Illegal state: " + mCurrentState);
    340         }
    341     }
    342 
    343     public void onAccessibilityEvent(AccessibilityEvent event) {
    344         final int eventType = event.getEventType();
    345 
    346         // The event for gesture end should be strictly after the
    347         // last hover exit event.
    348         if (mSendTouchExplorationEndDelayed.isPending()
    349                 && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
    350                     mSendTouchExplorationEndDelayed.remove();
    351             sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
    352         }
    353 
    354         // The event for touch interaction end should be strictly after the
    355         // last hover exit and the touch exploration gesture end events.
    356         if (mSendTouchInteractionEndDelayed.isPending()
    357                 && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
    358             mSendTouchInteractionEndDelayed.remove();
    359             sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
    360         }
    361 
    362         // If a new window opens or the accessibility focus moves we no longer
    363         // want to click/long press on the last touch explored location.
    364         switch (eventType) {
    365             case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
    366             case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
    367                 if (mInjectedPointerTracker.mLastInjectedHoverEventForClick != null) {
    368                     mInjectedPointerTracker.mLastInjectedHoverEventForClick.recycle();
    369                     mInjectedPointerTracker.mLastInjectedHoverEventForClick = null;
    370                 }
    371                 mLastTouchedWindowId = -1;
    372             } break;
    373             case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
    374             case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: {
    375                 mLastTouchedWindowId = event.getWindowId();
    376             } break;
    377         }
    378         if (mNext != null) {
    379             mNext.onAccessibilityEvent(event);
    380         }
    381     }
    382 
    383     /**
    384      * Handles a motion event in touch exploring state.
    385      *
    386      * @param event The event to be handled.
    387      * @param rawEvent The raw (unmodified) motion event.
    388      * @param policyFlags The policy flags associated with the event.
    389      */
    390     private void handleMotionEventStateTouchExploring(MotionEvent event, MotionEvent rawEvent,
    391             int policyFlags) {
    392         ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
    393         final int activePointerCount = receivedTracker.getActivePointerCount();
    394 
    395         mVelocityTracker.addMovement(rawEvent);
    396 
    397         mDoubleTapDetector.onMotionEvent(event, policyFlags);
    398 
    399         switch (event.getActionMasked()) {
    400             case MotionEvent.ACTION_DOWN:
    401                 mAms.onTouchInteractionStart();
    402                 // Pre-feed the motion events to the gesture detector since we
    403                 // have a distance slop before getting into gesture detection
    404                 // mode and not using the points within this slop significantly
    405                 // decreases the quality of gesture recognition.
    406                 handleMotionEventGestureDetecting(rawEvent, policyFlags);
    407                 //$FALL-THROUGH$
    408             case MotionEvent.ACTION_POINTER_DOWN: {
    409                 switch (activePointerCount) {
    410                     case 0: {
    411                         throw new IllegalStateException("The must always be one active pointer in"
    412                                 + "touch exploring state!");
    413                     }
    414                     case 1: {
    415                         // If we still have not notified the user for the last
    416                         // touch, we figure out what to do. If were waiting
    417                         // we resent the delayed callback and wait again.
    418                         if (mSendHoverEnterDelayed.isPending()) {
    419                             mSendHoverEnterDelayed.remove();
    420                             mSendHoverExitDelayed.remove();
    421                         }
    422 
    423                         if (mSendTouchExplorationEndDelayed.isPending()) {
    424                             mSendTouchExplorationEndDelayed.forceSendAndRemove();
    425                         }
    426 
    427                         if (mSendTouchInteractionEndDelayed.isPending()) {
    428                             mSendTouchInteractionEndDelayed.forceSendAndRemove();
    429                         }
    430 
    431                         // Every pointer that goes down is active until it moves or
    432                         // another one goes down. Hence, having more than one pointer
    433                         // down we have already send the interaction start event.
    434                         if (event.getPointerCount() == 1) {
    435                             sendAccessibilityEvent(
    436                                     AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
    437                         }
    438 
    439                         mPerformLongPressDelayed.remove();
    440 
    441                         // If we have the first tap schedule a long press and break
    442                         // since we do not want to schedule hover enter because
    443                         // the delayed callback will kick in before the long click.
    444                         // This would lead to a state transition resulting in long
    445                         // pressing the item below the double taped area which is
    446                         // not necessary where accessibility focus is.
    447                         if (mDoubleTapDetector.firstTapDetected()) {
    448                             // We got a tap now post a long press action.
    449                             mPerformLongPressDelayed.post(event, policyFlags);
    450                             break;
    451                         }
    452                         if (!mTouchExplorationInProgress) {
    453                             // Deliver hover enter with a delay to have a chance
    454                             // to detect what the user is trying to do.
    455                             final int pointerId = receivedTracker.getPrimaryActivePointerId();
    456                             final int pointerIdBits = (1 << pointerId);
    457                             mSendHoverEnterDelayed.post(event, true, pointerIdBits, policyFlags);
    458                         }
    459                     } break;
    460                     default: {
    461                         /* do nothing - let the code for ACTION_MOVE decide what to do */
    462                     } break;
    463                 }
    464             } break;
    465             case MotionEvent.ACTION_MOVE: {
    466                 final int pointerId = receivedTracker.getPrimaryActivePointerId();
    467                 final int pointerIndex = event.findPointerIndex(pointerId);
    468                 final int pointerIdBits = (1 << pointerId);
    469                 switch (activePointerCount) {
    470                     case 0: {
    471                         /* do nothing - no active pointers so we swallow the event */
    472                     } break;
    473                     case 1: {
    474                         // We have not started sending events since we try to
    475                         // figure out what the user is doing.
    476                         if (mSendHoverEnterDelayed.isPending()) {
    477                             // Pre-feed the motion events to the gesture detector since we
    478                             // have a distance slop before getting into gesture detection
    479                             // mode and not using the points within this slop significantly
    480                             // decreases the quality of gesture recognition.
    481                             handleMotionEventGestureDetecting(rawEvent, policyFlags);
    482                             // It is *important* to use the distance traveled by the pointers
    483                             // on the screen which may or may not be magnified.
    484                             final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
    485                                 - rawEvent.getX(pointerIndex);
    486                             final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
    487                                 - rawEvent.getY(pointerIndex);
    488                             final double moveDelta = Math.hypot(deltaX, deltaY);
    489                             // The user has moved enough for us to decide.
    490                             if (moveDelta > mDoubleTapSlop) {
    491                                 // Check whether the user is performing a gesture. We
    492                                 // detect gestures if the pointer is moving above a
    493                                 // given velocity.
    494                                 mVelocityTracker.computeCurrentVelocity(1000);
    495                                 final float maxAbsVelocity = Math.max(
    496                                         Math.abs(mVelocityTracker.getXVelocity(pointerId)),
    497                                         Math.abs(mVelocityTracker.getYVelocity(pointerId)));
    498                                 if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
    499                                     // We have to perform gesture detection, so
    500                                     // clear the current state and try to detect.
    501                                     mCurrentState = STATE_GESTURE_DETECTING;
    502                                     mVelocityTracker.clear();
    503                                     mSendHoverEnterDelayed.remove();
    504                                     mSendHoverExitDelayed.remove();
    505                                     mPerformLongPressDelayed.remove();
    506                                     mExitGestureDetectionModeDelayed.post();
    507                                     // Send accessibility event to announce the start
    508                                     // of gesture recognition.
    509                                     sendAccessibilityEvent(
    510                                             AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
    511                                 } else {
    512                                     // We have just decided that the user is touch,
    513                                     // exploring so start sending events.
    514                                     mSendHoverEnterDelayed.forceSendAndRemove();
    515                                     mSendHoverExitDelayed.remove();
    516                                     mPerformLongPressDelayed.remove();
    517                                     sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE,
    518                                             pointerIdBits, policyFlags);
    519                                 }
    520                                 break;
    521                             }
    522                         } else {
    523                             // Cancel the long press if pending and the user
    524                             // moved more than the slop.
    525                             if (mPerformLongPressDelayed.isPending()) {
    526                                 final float deltaX =
    527                                         receivedTracker.getReceivedPointerDownX(pointerId)
    528                                         - rawEvent.getX(pointerIndex);
    529                                 final float deltaY =
    530                                         receivedTracker.getReceivedPointerDownY(pointerId)
    531                                         - rawEvent.getY(pointerIndex);
    532                                 final double moveDelta = Math.hypot(deltaX, deltaY);
    533                                 // The user has moved enough for us to decide.
    534                                 if (moveDelta > mTouchSlop) {
    535                                     mPerformLongPressDelayed.remove();
    536                                 }
    537                             }
    538                             // The user is wither double tapping or performing long
    539                             // press so do not send move events yet.
    540                             if (mDoubleTapDetector.firstTapDetected()) {
    541                                 break;
    542                             }
    543                             sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
    544                             sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
    545                                     policyFlags);
    546                         }
    547                     } break;
    548                     case 2: {
    549                         // More than one pointer so the user is not touch exploring
    550                         // and now we have to decide whether to delegate or drag.
    551                         if (mSendHoverEnterDelayed.isPending()) {
    552                             // We have not started sending events so cancel
    553                             // scheduled sending events.
    554                             mSendHoverEnterDelayed.remove();
    555                             mSendHoverExitDelayed.remove();
    556                             mPerformLongPressDelayed.remove();
    557                         } else {
    558                             mPerformLongPressDelayed.remove();
    559                             // If the user is touch exploring the second pointer may be
    560                             // performing a double tap to activate an item without need
    561                             // for the user to lift his exploring finger.
    562                             // It is *important* to use the distance traveled by the pointers
    563                             // on the screen which may or may not be magnified.
    564                             final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
    565                                     - rawEvent.getX(pointerIndex);
    566                             final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
    567                                     - rawEvent.getY(pointerIndex);
    568                             final double moveDelta = Math.hypot(deltaX, deltaY);
    569                             if (moveDelta < mDoubleTapSlop) {
    570                                 break;
    571                             }
    572                             // We are sending events so send exit and gesture
    573                             // end since we transition to another state.
    574                             sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
    575                         }
    576 
    577                         // We know that a new state transition is to happen and the
    578                         // new state will not be gesture recognition, so clear the
    579                         // stashed gesture strokes.
    580                         mStrokeBuffer.clear();
    581 
    582                         if (isDraggingGesture(event)) {
    583                             // Two pointers moving in the same direction within
    584                             // a given distance perform a drag.
    585                             mCurrentState = STATE_DRAGGING;
    586                             mDraggingPointerId = pointerId;
    587                             event.setEdgeFlags(receivedTracker.getLastReceivedDownEdgeFlags());
    588                             sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits,
    589                                     policyFlags);
    590                         } else {
    591                             // Two pointers moving arbitrary are delegated to the view hierarchy.
    592                             mCurrentState = STATE_DELEGATING;
    593                             sendDownForAllActiveNotInjectedPointers(event, policyFlags);
    594                         }
    595                         mVelocityTracker.clear();
    596                     } break;
    597                     default: {
    598                         // More than one pointer so the user is not touch exploring
    599                         // and now we have to decide whether to delegate or drag.
    600                         if (mSendHoverEnterDelayed.isPending()) {
    601                             // We have not started sending events so cancel
    602                             // scheduled sending events.
    603                             mSendHoverEnterDelayed.remove();
    604                             mSendHoverExitDelayed.remove();
    605                             mPerformLongPressDelayed.remove();
    606                         } else {
    607                             mPerformLongPressDelayed.remove();
    608                             // We are sending events so send exit and gesture
    609                             // end since we transition to another state.
    610                             sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
    611                         }
    612 
    613                         // More than two pointers are delegated to the view hierarchy.
    614                         mCurrentState = STATE_DELEGATING;
    615                         sendDownForAllActiveNotInjectedPointers(event, policyFlags);
    616                         mVelocityTracker.clear();
    617                     }
    618                 }
    619             } break;
    620             case MotionEvent.ACTION_UP:
    621                 mAms.onTouchInteractionEnd();
    622                 // We know that we do not need the pre-fed gesture points are not
    623                 // needed anymore since the last pointer just went up.
    624                 mStrokeBuffer.clear();
    625                 //$FALL-THROUGH$
    626             case MotionEvent.ACTION_POINTER_UP: {
    627                 final int pointerId = receivedTracker.getLastReceivedUpPointerId();
    628                 final int pointerIdBits = (1 << pointerId);
    629                 switch (activePointerCount) {
    630                     case 0: {
    631                         // If the pointer that went up was not active we have nothing to do.
    632                         if (!receivedTracker.wasLastReceivedUpPointerActive()) {
    633                             break;
    634                         }
    635 
    636                         mPerformLongPressDelayed.remove();
    637 
    638                         // If we have not delivered the enter schedule exit.
    639                         if (mSendHoverEnterDelayed.isPending()) {
    640                             mSendHoverExitDelayed.post(event, false, pointerIdBits, policyFlags);
    641                         } else {
    642                             // The user is touch exploring so we send events for end.
    643                             sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
    644                         }
    645 
    646                         if (!mSendTouchInteractionEndDelayed.isPending()) {
    647                             mSendTouchInteractionEndDelayed.post();
    648                         }
    649                     } break;
    650                 }
    651                 mVelocityTracker.clear();
    652             } break;
    653             case MotionEvent.ACTION_CANCEL: {
    654                 clear(event, policyFlags);
    655             } break;
    656         }
    657     }
    658 
    659     /**
    660      * Handles a motion event in dragging state.
    661      *
    662      * @param event The event to be handled.
    663      * @param policyFlags The policy flags associated with the event.
    664      */
    665     private void handleMotionEventStateDragging(MotionEvent event, int policyFlags) {
    666         final int pointerIdBits = (1 << mDraggingPointerId);
    667         switch (event.getActionMasked()) {
    668             case MotionEvent.ACTION_DOWN: {
    669                 throw new IllegalStateException("Dragging state can be reached only if two "
    670                         + "pointers are already down");
    671             }
    672             case MotionEvent.ACTION_POINTER_DOWN: {
    673                 // We are in dragging state so we have two pointers and another one
    674                 // goes down => delegate the three pointers to the view hierarchy
    675                 mCurrentState = STATE_DELEGATING;
    676                 if (mDraggingPointerId != INVALID_POINTER_ID) {
    677                     sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
    678                 }
    679                 sendDownForAllActiveNotInjectedPointers(event, policyFlags);
    680             } break;
    681             case MotionEvent.ACTION_MOVE: {
    682                 final int activePointerCount = mReceivedPointerTracker.getActivePointerCount();
    683                 switch (activePointerCount) {
    684                     case 1: {
    685                         // do nothing
    686                     } break;
    687                     case 2: {
    688                         if (isDraggingGesture(event)) {
    689                             // If the dragging pointer are closer that a given distance we
    690                             // use the location of the primary one. Otherwise, we take the
    691                             // middle between the pointers.
    692                             int[] pointerIds = mTempPointerIds;
    693                             mReceivedPointerTracker.populateActivePointerIds(pointerIds);
    694 
    695                             final int firstPtrIndex = event.findPointerIndex(pointerIds[0]);
    696                             final int secondPtrIndex = event.findPointerIndex(pointerIds[1]);
    697 
    698                             final float firstPtrX = event.getX(firstPtrIndex);
    699                             final float firstPtrY = event.getY(firstPtrIndex);
    700                             final float secondPtrX = event.getX(secondPtrIndex);
    701                             final float secondPtrY = event.getY(secondPtrIndex);
    702 
    703                             final float deltaX = firstPtrX - secondPtrX;
    704                             final float deltaY = firstPtrY - secondPtrY;
    705                             final double distance = Math.hypot(deltaX, deltaY);
    706 
    707                             if (distance > mScaledMinPointerDistanceToUseMiddleLocation) {
    708                                 event.setLocation(deltaX / 2, deltaY / 2);
    709                             }
    710 
    711                             // If still dragging send a drag event.
    712                             sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits,
    713                                     policyFlags);
    714                         } else {
    715                             // The two pointers are moving either in different directions or
    716                             // no close enough => delegate the gesture to the view hierarchy.
    717                             mCurrentState = STATE_DELEGATING;
    718                             // Send an event to the end of the drag gesture.
    719                             sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
    720                                     policyFlags);
    721                             // Deliver all active pointers to the view hierarchy.
    722                             sendDownForAllActiveNotInjectedPointers(event, policyFlags);
    723                         }
    724                     } break;
    725                     default: {
    726                         mCurrentState = STATE_DELEGATING;
    727                         // Send an event to the end of the drag gesture.
    728                         sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
    729                                 policyFlags);
    730                         // Deliver all active pointers to the view hierarchy.
    731                         sendDownForAllActiveNotInjectedPointers(event, policyFlags);
    732                     }
    733                 }
    734             } break;
    735             case MotionEvent.ACTION_POINTER_UP: {
    736                  final int pointerId = event.getPointerId(event.getActionIndex());
    737                  if (pointerId == mDraggingPointerId) {
    738                      mDraggingPointerId = INVALID_POINTER_ID;
    739                      // Send an event to the end of the drag gesture.
    740                      sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
    741                  }
    742             } break;
    743             case MotionEvent.ACTION_UP: {
    744                 mAms.onTouchInteractionEnd();
    745                 // Announce the end of a new touch interaction.
    746                 sendAccessibilityEvent(
    747                         AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
    748                 final int pointerId = event.getPointerId(event.getActionIndex());
    749                 if (pointerId == mDraggingPointerId) {
    750                     mDraggingPointerId = INVALID_POINTER_ID;
    751                     // Send an event to the end of the drag gesture.
    752                     sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
    753                 }
    754                 mCurrentState = STATE_TOUCH_EXPLORING;
    755             } break;
    756             case MotionEvent.ACTION_CANCEL: {
    757                 clear(event, policyFlags);
    758             } break;
    759         }
    760     }
    761 
    762     /**
    763      * Handles a motion event in delegating state.
    764      *
    765      * @param event The event to be handled.
    766      * @param policyFlags The policy flags associated with the event.
    767      */
    768     private void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) {
    769         switch (event.getActionMasked()) {
    770             case MotionEvent.ACTION_DOWN: {
    771                 throw new IllegalStateException("Delegating state can only be reached if "
    772                         + "there is at least one pointer down!");
    773             }
    774             case MotionEvent.ACTION_MOVE: {
    775                 // Check  whether some other pointer became active because they have moved
    776                 // a given distance and if such exist send them to the view hierarchy
    777                 final int notInjectedCount = getNotInjectedActivePointerCount(
    778                         mReceivedPointerTracker, mInjectedPointerTracker);
    779                 if (notInjectedCount > 0) {
    780                     MotionEvent prototype = MotionEvent.obtain(event);
    781                     sendDownForAllActiveNotInjectedPointers(prototype, policyFlags);
    782                 }
    783             } break;
    784             case MotionEvent.ACTION_UP:
    785                 // Announce the end of a new touch interaction.
    786                 sendAccessibilityEvent(
    787                         AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
    788                 //$FALL-THROUGH$
    789             case MotionEvent.ACTION_POINTER_UP: {
    790                 mAms.onTouchInteractionEnd();
    791                 mLongPressingPointerId = -1;
    792                 mLongPressingPointerDeltaX = 0;
    793                 mLongPressingPointerDeltaY = 0;
    794                 // No active pointers => go to initial state.
    795                 if (mReceivedPointerTracker.getActivePointerCount() == 0) {
    796                     mCurrentState = STATE_TOUCH_EXPLORING;
    797                 }
    798             } break;
    799             case MotionEvent.ACTION_CANCEL: {
    800                 clear(event, policyFlags);
    801             } break;
    802         }
    803         // Deliver the event striping out inactive pointers.
    804         sendMotionEventStripInactivePointers(event, policyFlags);
    805     }
    806 
    807     private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) {
    808         switch (event.getActionMasked()) {
    809             case MotionEvent.ACTION_DOWN: {
    810                 final float x = event.getX();
    811                 final float y = event.getY();
    812                 mPreviousX = x;
    813                 mPreviousY = y;
    814                 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
    815             } break;
    816             case MotionEvent.ACTION_MOVE: {
    817                 final float x = event.getX();
    818                 final float y = event.getY();
    819                 final float dX = Math.abs(x - mPreviousX);
    820                 final float dY = Math.abs(y - mPreviousY);
    821                 if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
    822                     mPreviousX = x;
    823                     mPreviousY = y;
    824                     mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
    825                 }
    826             } break;
    827             case MotionEvent.ACTION_UP: {
    828                 mAms.onTouchInteractionEnd();
    829                 // Announce the end of gesture recognition.
    830                 sendAccessibilityEvent(
    831                         AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
    832                 // Announce the end of a new touch interaction.
    833                 sendAccessibilityEvent(
    834                         AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
    835 
    836                 float x = event.getX();
    837                 float y = event.getY();
    838                 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
    839 
    840                 Gesture gesture = new Gesture();
    841                 gesture.addStroke(new GestureStroke(mStrokeBuffer));
    842 
    843                 ArrayList<Prediction> predictions = mGestureLibrary.recognize(gesture);
    844                 if (!predictions.isEmpty()) {
    845                     Prediction bestPrediction = predictions.get(0);
    846                     if (bestPrediction.score >= MIN_PREDICTION_SCORE) {
    847                         if (DEBUG) {
    848                             Slog.i(LOG_TAG, "gesture: " + bestPrediction.name + " score: "
    849                                     + bestPrediction.score);
    850                         }
    851                         try {
    852                             final int gestureId = Integer.parseInt(bestPrediction.name);
    853                             mAms.onGesture(gestureId);
    854                         } catch (NumberFormatException nfe) {
    855                             Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name);
    856                         }
    857                     }
    858                 }
    859 
    860                 mStrokeBuffer.clear();
    861                 mExitGestureDetectionModeDelayed.remove();
    862                 mCurrentState = STATE_TOUCH_EXPLORING;
    863             } break;
    864             case MotionEvent.ACTION_CANCEL: {
    865                 clear(event, policyFlags);
    866             } break;
    867         }
    868     }
    869 
    870     /**
    871      * Sends an accessibility event of the given type.
    872      *
    873      * @param type The event type.
    874      */
    875     private void sendAccessibilityEvent(int type) {
    876         AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
    877         if (accessibilityManager.isEnabled()) {
    878             AccessibilityEvent event = AccessibilityEvent.obtain(type);
    879             accessibilityManager.sendAccessibilityEvent(event);
    880             switch (type) {
    881                 case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: {
    882                     mTouchExplorationInProgress = true;
    883                 } break;
    884                 case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: {
    885                     mTouchExplorationInProgress = false;
    886                 } break;
    887             }
    888         }
    889     }
    890 
    891     /**
    892      * Sends down events to the view hierarchy for all active pointers which are
    893      * not already being delivered i.e. pointers that are not yet injected.
    894      *
    895      * @param prototype The prototype from which to create the injected events.
    896      * @param policyFlags The policy flags associated with the event.
    897      */
    898     private void sendDownForAllActiveNotInjectedPointers(MotionEvent prototype, int policyFlags) {
    899         ReceivedPointerTracker receivedPointers = mReceivedPointerTracker;
    900         InjectedPointerTracker injectedPointers = mInjectedPointerTracker;
    901         int pointerIdBits = 0;
    902         final int pointerCount = prototype.getPointerCount();
    903 
    904         // Find which pointers are already injected.
    905         for (int i = 0; i < pointerCount; i++) {
    906             final int pointerId = prototype.getPointerId(i);
    907             if (injectedPointers.isInjectedPointerDown(pointerId)) {
    908                 pointerIdBits |= (1 << pointerId);
    909             }
    910         }
    911 
    912         // Inject the active and not injected pointers.
    913         for (int i = 0; i < pointerCount; i++) {
    914             final int pointerId = prototype.getPointerId(i);
    915             // Skip inactive pointers.
    916             if (!receivedPointers.isActivePointer(pointerId)) {
    917                 continue;
    918             }
    919             // Do not send event for already delivered pointers.
    920             if (injectedPointers.isInjectedPointerDown(pointerId)) {
    921                 continue;
    922             }
    923             pointerIdBits |= (1 << pointerId);
    924             final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
    925             sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
    926         }
    927     }
    928 
    929     /**
    930      * Sends the exit events if needed. Such events are hover exit and touch explore
    931      * gesture end.
    932      *
    933      * @param policyFlags The policy flags associated with the event.
    934      */
    935     private void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) {
    936         MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
    937         if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
    938             final int pointerIdBits = event.getPointerIdBits();
    939             if (!mSendTouchExplorationEndDelayed.isPending()) {
    940                 mSendTouchExplorationEndDelayed.post();
    941             }
    942             sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
    943         }
    944     }
    945 
    946     /**
    947      * Sends the enter events if needed. Such events are hover enter and touch explore
    948      * gesture start.
    949      *
    950      * @param policyFlags The policy flags associated with the event.
    951      */
    952     private void sendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags) {
    953         MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
    954         if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
    955             final int pointerIdBits = event.getPointerIdBits();
    956             sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
    957             sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
    958         }
    959     }
    960 
    961     /**
    962      * Sends up events to the view hierarchy for all active pointers which are
    963      * already being delivered i.e. pointers that are injected.
    964      *
    965      * @param prototype The prototype from which to create the injected events.
    966      * @param policyFlags The policy flags associated with the event.
    967      */
    968     private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
    969         final InjectedPointerTracker injectedTracked = mInjectedPointerTracker;
    970         int pointerIdBits = 0;
    971         final int pointerCount = prototype.getPointerCount();
    972         for (int i = 0; i < pointerCount; i++) {
    973             final int pointerId = prototype.getPointerId(i);
    974             // Skip non injected down pointers.
    975             if (!injectedTracked.isInjectedPointerDown(pointerId)) {
    976                 continue;
    977             }
    978             pointerIdBits |= (1 << pointerId);
    979             final int action = computeInjectionAction(MotionEvent.ACTION_UP, i);
    980             sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
    981         }
    982     }
    983 
    984     /**
    985      * Sends a motion event by first stripping the inactive pointers.
    986      *
    987      * @param prototype The prototype from which to create the injected event.
    988      * @param policyFlags The policy flags associated with the event.
    989      */
    990     private void sendMotionEventStripInactivePointers(MotionEvent prototype, int policyFlags) {
    991         ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
    992 
    993         // All pointers active therefore we just inject the event as is.
    994         if (prototype.getPointerCount() == receivedTracker.getActivePointerCount()) {
    995             sendMotionEvent(prototype, prototype.getAction(), ALL_POINTER_ID_BITS, policyFlags);
    996             return;
    997         }
    998 
    999         // No active pointers and the one that just went up was not
   1000         // active, therefore we have nothing to do.
   1001         if (receivedTracker.getActivePointerCount() == 0
   1002                 && !receivedTracker.wasLastReceivedUpPointerActive()) {
   1003             return;
   1004         }
   1005 
   1006         // If the action pointer going up/down is not active we have nothing to do.
   1007         // However, for moves we keep going to report moves of active pointers.
   1008         final int actionMasked = prototype.getActionMasked();
   1009         final int actionPointerId = prototype.getPointerId(prototype.getActionIndex());
   1010         if (actionMasked != MotionEvent.ACTION_MOVE) {
   1011             if (!receivedTracker.isActiveOrWasLastActiveUpPointer(actionPointerId)) {
   1012                 return;
   1013             }
   1014         }
   1015 
   1016         // If the pointer is active or the pointer that just went up
   1017         // was active we keep the pointer data in the event.
   1018         int pointerIdBits = 0;
   1019         final int pointerCount = prototype.getPointerCount();
   1020         for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) {
   1021             final int pointerId = prototype.getPointerId(pointerIndex);
   1022             if (receivedTracker.isActiveOrWasLastActiveUpPointer(pointerId)) {
   1023                 pointerIdBits |= (1 << pointerId);
   1024             }
   1025         }
   1026         sendMotionEvent(prototype, prototype.getAction(), pointerIdBits, policyFlags);
   1027     }
   1028 
   1029     /**
   1030      * Sends an up and down events.
   1031      *
   1032      * @param prototype The prototype from which to create the injected events.
   1033      * @param policyFlags The policy flags associated with the event.
   1034      */
   1035     private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) {
   1036         // Tap with the pointer that last explored - we may have inactive pointers.
   1037         final int pointerId = prototype.getPointerId(prototype.getActionIndex());
   1038         final int pointerIdBits = (1 << pointerId);
   1039         sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags);
   1040         sendMotionEvent(prototype, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
   1041     }
   1042 
   1043     /**
   1044      * Sends an event.
   1045      *
   1046      * @param prototype The prototype from which to create the injected events.
   1047      * @param action The action of the event.
   1048      * @param pointerIdBits The bits of the pointers to send.
   1049      * @param policyFlags The policy flags associated with the event.
   1050      */
   1051     private void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits,
   1052             int policyFlags) {
   1053         prototype.setAction(action);
   1054 
   1055         MotionEvent event = null;
   1056         if (pointerIdBits == ALL_POINTER_ID_BITS) {
   1057             event = prototype;
   1058         } else {
   1059             event = prototype.split(pointerIdBits);
   1060         }
   1061         if (action == MotionEvent.ACTION_DOWN) {
   1062             event.setDownTime(event.getEventTime());
   1063         } else {
   1064             event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime());
   1065         }
   1066 
   1067         // If the user is long pressing but the long pressing pointer
   1068         // was not exactly over the accessibility focused item we need
   1069         // to remap the location of that pointer so the user does not
   1070         // have to explicitly touch explore something to be able to
   1071         // long press it, or even worse to avoid the user long pressing
   1072         // on the wrong item since click and long press behave differently.
   1073         if (mLongPressingPointerId >= 0) {
   1074             final int remappedIndex = event.findPointerIndex(mLongPressingPointerId);
   1075             final int pointerCount = event.getPointerCount();
   1076             PointerProperties[] props = PointerProperties.createArray(pointerCount);
   1077             PointerCoords[] coords = PointerCoords.createArray(pointerCount);
   1078             for (int i = 0; i < pointerCount; i++) {
   1079                 event.getPointerProperties(i, props[i]);
   1080                 event.getPointerCoords(i, coords[i]);
   1081                 if (i == remappedIndex) {
   1082                     coords[i].x -= mLongPressingPointerDeltaX;
   1083                     coords[i].y -= mLongPressingPointerDeltaY;
   1084                 }
   1085             }
   1086             MotionEvent remapped = MotionEvent.obtain(event.getDownTime(),
   1087                     event.getEventTime(), event.getAction(), event.getPointerCount(),
   1088                     props, coords, event.getMetaState(), event.getButtonState(),
   1089                     1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
   1090                     event.getSource(), event.getFlags());
   1091             if (event != prototype) {
   1092                 event.recycle();
   1093             }
   1094             event = remapped;
   1095         }
   1096 
   1097         if (DEBUG) {
   1098             Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x"
   1099                     + Integer.toHexString(policyFlags));
   1100         }
   1101 
   1102         // Make sure that the user will see the event.
   1103         policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
   1104         if (mNext != null) {
   1105             // TODO: For now pass null for the raw event since the touch
   1106             //       explorer is the last event transformation and it does
   1107             //       not care about the raw event.
   1108             mNext.onMotionEvent(event, null, policyFlags);
   1109         }
   1110 
   1111         mInjectedPointerTracker.onMotionEvent(event);
   1112 
   1113         if (event != prototype) {
   1114             event.recycle();
   1115         }
   1116     }
   1117 
   1118     /**
   1119      * Computes the action for an injected event based on a masked action
   1120      * and a pointer index.
   1121      *
   1122      * @param actionMasked The masked action.
   1123      * @param pointerIndex The index of the pointer which has changed.
   1124      * @return The action to be used for injection.
   1125      */
   1126     private int computeInjectionAction(int actionMasked, int pointerIndex) {
   1127         switch (actionMasked) {
   1128             case MotionEvent.ACTION_DOWN:
   1129             case MotionEvent.ACTION_POINTER_DOWN: {
   1130                 InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
   1131                 // Compute the action based on how many down pointers are injected.
   1132                 if (injectedTracker.getInjectedPointerDownCount() == 0) {
   1133                     return MotionEvent.ACTION_DOWN;
   1134                 } else {
   1135                     return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
   1136                         | MotionEvent.ACTION_POINTER_DOWN;
   1137                 }
   1138             }
   1139             case MotionEvent.ACTION_POINTER_UP: {
   1140                 InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
   1141                 // Compute the action based on how many down pointers are injected.
   1142                 if (injectedTracker.getInjectedPointerDownCount() == 1) {
   1143                     return MotionEvent.ACTION_UP;
   1144                 } else {
   1145                     return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
   1146                         | MotionEvent.ACTION_POINTER_UP;
   1147                 }
   1148             }
   1149             default:
   1150                 return actionMasked;
   1151         }
   1152     }
   1153 
   1154     private class DoubleTapDetector {
   1155         private MotionEvent mDownEvent;
   1156         private MotionEvent mFirstTapEvent;
   1157 
   1158         public void onMotionEvent(MotionEvent event, int policyFlags) {
   1159             final int actionIndex = event.getActionIndex();
   1160             final int action = event.getActionMasked();
   1161             switch (action) {
   1162                 case MotionEvent.ACTION_DOWN:
   1163                 case MotionEvent.ACTION_POINTER_DOWN: {
   1164                     if (mFirstTapEvent != null
   1165                             && !GestureUtils.isSamePointerContext(mFirstTapEvent, event)) {
   1166                         clear();
   1167                     }
   1168                     mDownEvent = MotionEvent.obtain(event);
   1169                 } break;
   1170                 case MotionEvent.ACTION_UP:
   1171                 case MotionEvent.ACTION_POINTER_UP: {
   1172                     if (mDownEvent == null) {
   1173                         return;
   1174                     }
   1175                     if (!GestureUtils.isSamePointerContext(mDownEvent, event)) {
   1176                         clear();
   1177                         return;
   1178                     }
   1179                     if (GestureUtils.isTap(mDownEvent, event, mTapTimeout, mTouchSlop,
   1180                             actionIndex)) {
   1181                         if (mFirstTapEvent == null || GestureUtils.isTimedOut(mFirstTapEvent,
   1182                                 event, mDoubleTapTimeout)) {
   1183                             mFirstTapEvent = MotionEvent.obtain(event);
   1184                             mDownEvent.recycle();
   1185                             mDownEvent = null;
   1186                             return;
   1187                         }
   1188                         if (GestureUtils.isMultiTap(mFirstTapEvent, event, mDoubleTapTimeout,
   1189                                 mDoubleTapSlop, actionIndex)) {
   1190                             onDoubleTap(event, policyFlags);
   1191                             mFirstTapEvent.recycle();
   1192                             mFirstTapEvent = null;
   1193                             mDownEvent.recycle();
   1194                             mDownEvent = null;
   1195                             return;
   1196                         }
   1197                         mFirstTapEvent.recycle();
   1198                         mFirstTapEvent = null;
   1199                     } else {
   1200                         if (mFirstTapEvent != null) {
   1201                             mFirstTapEvent.recycle();
   1202                             mFirstTapEvent = null;
   1203                         }
   1204                     }
   1205                     mDownEvent.recycle();
   1206                     mDownEvent = null;
   1207                 } break;
   1208             }
   1209         }
   1210 
   1211         public void onDoubleTap(MotionEvent secondTapUp, int policyFlags) {
   1212             // This should never be called when more than two pointers are down.
   1213             if (secondTapUp.getPointerCount() > 2) {
   1214                 return;
   1215             }
   1216 
   1217             // Remove pending event deliveries.
   1218             mSendHoverEnterDelayed.remove();
   1219             mSendHoverExitDelayed.remove();
   1220             mPerformLongPressDelayed.remove();
   1221 
   1222             if (mSendTouchExplorationEndDelayed.isPending()) {
   1223                 mSendTouchExplorationEndDelayed.forceSendAndRemove();
   1224             }
   1225             if (mSendTouchInteractionEndDelayed.isPending()) {
   1226                 mSendTouchInteractionEndDelayed.forceSendAndRemove();
   1227             }
   1228 
   1229             int clickLocationX;
   1230             int clickLocationY;
   1231 
   1232             final int pointerId = secondTapUp.getPointerId(secondTapUp.getActionIndex());
   1233             final int pointerIndex = secondTapUp.findPointerIndex(pointerId);
   1234 
   1235             MotionEvent lastExploreEvent =
   1236                 mInjectedPointerTracker.getLastInjectedHoverEventForClick();
   1237             if (lastExploreEvent == null) {
   1238                 // No last touch explored event but there is accessibility focus in
   1239                 // the active window. We click in the middle of the focus bounds.
   1240                 Rect focusBounds = mTempRect;
   1241                 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) {
   1242                     clickLocationX = focusBounds.centerX();
   1243                     clickLocationY = focusBounds.centerY();
   1244                 } else {
   1245                     // Out of luck - do nothing.
   1246                     return;
   1247                 }
   1248             } else {
   1249                 // If the click is within the active window but not within the
   1250                 // accessibility focus bounds we click in the focus center.
   1251                 final int lastExplorePointerIndex = lastExploreEvent.getActionIndex();
   1252                 clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex);
   1253                 clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex);
   1254                 Rect activeWindowBounds = mTempRect;
   1255                 if (mLastTouchedWindowId == mAms.getActiveWindowId()) {
   1256                     mAms.getActiveWindowBounds(activeWindowBounds);
   1257                     if (activeWindowBounds.contains(clickLocationX, clickLocationY)) {
   1258                         Rect focusBounds = mTempRect;
   1259                         if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) {
   1260                             if (!focusBounds.contains(clickLocationX, clickLocationY)) {
   1261                                 clickLocationX = focusBounds.centerX();
   1262                                 clickLocationY = focusBounds.centerY();
   1263                             }
   1264                         }
   1265                     }
   1266                 }
   1267             }
   1268 
   1269             // Do the click.
   1270             PointerProperties[] properties = new PointerProperties[1];
   1271             properties[0] = new PointerProperties();
   1272             secondTapUp.getPointerProperties(pointerIndex, properties[0]);
   1273             PointerCoords[] coords = new PointerCoords[1];
   1274             coords[0] = new PointerCoords();
   1275             coords[0].x = clickLocationX;
   1276             coords[0].y = clickLocationY;
   1277             MotionEvent event = MotionEvent.obtain(secondTapUp.getDownTime(),
   1278                     secondTapUp.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties,
   1279                     coords, 0, 0, 1.0f, 1.0f, secondTapUp.getDeviceId(), 0,
   1280                     secondTapUp.getSource(), secondTapUp.getFlags());
   1281             sendActionDownAndUp(event, policyFlags);
   1282             event.recycle();
   1283         }
   1284 
   1285         public void clear() {
   1286             if (mDownEvent != null) {
   1287                 mDownEvent.recycle();
   1288                 mDownEvent = null;
   1289             }
   1290             if (mFirstTapEvent != null) {
   1291                 mFirstTapEvent.recycle();
   1292                 mFirstTapEvent = null;
   1293             }
   1294         }
   1295 
   1296         public boolean firstTapDetected() {
   1297             return mFirstTapEvent != null
   1298                 && SystemClock.uptimeMillis() - mFirstTapEvent.getEventTime() < mDoubleTapTimeout;
   1299         }
   1300     }
   1301 
   1302     /**
   1303      * Determines whether a two pointer gesture is a dragging one.
   1304      *
   1305      * @param event The event with the pointer data.
   1306      * @return True if the gesture is a dragging one.
   1307      */
   1308     private boolean isDraggingGesture(MotionEvent event) {
   1309         ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
   1310         int[] pointerIds = mTempPointerIds;
   1311         receivedTracker.populateActivePointerIds(pointerIds);
   1312 
   1313         final int firstPtrIndex = event.findPointerIndex(pointerIds[0]);
   1314         final int secondPtrIndex = event.findPointerIndex(pointerIds[1]);
   1315 
   1316         final float firstPtrX = event.getX(firstPtrIndex);
   1317         final float firstPtrY = event.getY(firstPtrIndex);
   1318         final float secondPtrX = event.getX(secondPtrIndex);
   1319         final float secondPtrY = event.getY(secondPtrIndex);
   1320 
   1321         final float firstPtrDownX = receivedTracker.getReceivedPointerDownX(firstPtrIndex);
   1322         final float firstPtrDownY = receivedTracker.getReceivedPointerDownY(firstPtrIndex);
   1323         final float secondPtrDownX = receivedTracker.getReceivedPointerDownX(secondPtrIndex);
   1324         final float secondPtrDownY = receivedTracker.getReceivedPointerDownY(secondPtrIndex);
   1325 
   1326         return GestureUtils.isDraggingGesture(firstPtrDownX, firstPtrDownY, secondPtrDownX,
   1327                 secondPtrDownY, firstPtrX, firstPtrY, secondPtrX, secondPtrY,
   1328                 MAX_DRAGGING_ANGLE_COS);
   1329     }
   1330 
   1331     /**
   1332      * Gets the symbolic name of a state.
   1333      *
   1334      * @param state A state.
   1335      * @return The state symbolic name.
   1336      */
   1337     private static String getStateSymbolicName(int state) {
   1338         switch (state) {
   1339             case STATE_TOUCH_EXPLORING:
   1340                 return "STATE_TOUCH_EXPLORING";
   1341             case STATE_DRAGGING:
   1342                 return "STATE_DRAGGING";
   1343             case STATE_DELEGATING:
   1344                 return "STATE_DELEGATING";
   1345             case STATE_GESTURE_DETECTING:
   1346                 return "STATE_GESTURE_DETECTING";
   1347             default:
   1348                 throw new IllegalArgumentException("Unknown state: " + state);
   1349         }
   1350     }
   1351 
   1352     /**
   1353      * @return The number of non injected active pointers.
   1354      */
   1355     private int getNotInjectedActivePointerCount(ReceivedPointerTracker receivedTracker,
   1356             InjectedPointerTracker injectedTracker) {
   1357         final int pointerState = receivedTracker.getActivePointers()
   1358                 & ~injectedTracker.getInjectedPointersDown();
   1359         return Integer.bitCount(pointerState);
   1360     }
   1361 
   1362     /**
   1363      * Class for delayed exiting from gesture detecting mode.
   1364      */
   1365     private final class ExitGestureDetectionModeDelayed implements Runnable {
   1366 
   1367         public void post() {
   1368             mHandler.postDelayed(this, EXIT_GESTURE_DETECTION_TIMEOUT);
   1369         }
   1370 
   1371         public void remove() {
   1372             mHandler.removeCallbacks(this);
   1373         }
   1374 
   1375         @Override
   1376         public void run() {
   1377             // Announce the end of gesture recognition.
   1378             sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
   1379             // Clearing puts is in touch exploration state with a finger already
   1380             // down, so announce the transition to exploration state.
   1381             sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
   1382             clear();
   1383         }
   1384     }
   1385 
   1386     /**
   1387      * Class for delayed sending of long press.
   1388      */
   1389     private final class PerformLongPressDelayed implements Runnable {
   1390         private MotionEvent mEvent;
   1391         private int mPolicyFlags;
   1392 
   1393         public void post(MotionEvent prototype, int policyFlags) {
   1394             mEvent = MotionEvent.obtain(prototype);
   1395             mPolicyFlags = policyFlags;
   1396             mHandler.postDelayed(this, ViewConfiguration.getLongPressTimeout());
   1397         }
   1398 
   1399         public void remove() {
   1400             if (isPending()) {
   1401                 mHandler.removeCallbacks(this);
   1402                 clear();
   1403             }
   1404         }
   1405 
   1406         public boolean isPending() {
   1407             return (mEvent != null);
   1408         }
   1409 
   1410         @Override
   1411         public void run() {
   1412             // Active pointers should not be zero when running this command.
   1413             if (mReceivedPointerTracker.getActivePointerCount() == 0) {
   1414                 return;
   1415             }
   1416 
   1417             int clickLocationX;
   1418             int clickLocationY;
   1419 
   1420             final int pointerId = mEvent.getPointerId(mEvent.getActionIndex());
   1421             final int pointerIndex = mEvent.findPointerIndex(pointerId);
   1422 
   1423             MotionEvent lastExploreEvent =
   1424                 mInjectedPointerTracker.getLastInjectedHoverEventForClick();
   1425             if (lastExploreEvent == null) {
   1426                 // No last touch explored event but there is accessibility focus in
   1427                 // the active window. We click in the middle of the focus bounds.
   1428                 Rect focusBounds = mTempRect;
   1429                 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) {
   1430                     clickLocationX = focusBounds.centerX();
   1431                     clickLocationY = focusBounds.centerY();
   1432                 } else {
   1433                     // Out of luck - do nothing.
   1434                     return;
   1435                 }
   1436             } else {
   1437                 // If the click is within the active window but not within the
   1438                 // accessibility focus bounds we click in the focus center.
   1439                 final int lastExplorePointerIndex = lastExploreEvent.getActionIndex();
   1440                 clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex);
   1441                 clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex);
   1442                 Rect activeWindowBounds = mTempRect;
   1443                 if (mLastTouchedWindowId == mAms.getActiveWindowId()) {
   1444                     mAms.getActiveWindowBounds(activeWindowBounds);
   1445                     if (activeWindowBounds.contains(clickLocationX, clickLocationY)) {
   1446                         Rect focusBounds = mTempRect;
   1447                         if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) {
   1448                             if (!focusBounds.contains(clickLocationX, clickLocationY)) {
   1449                                 clickLocationX = focusBounds.centerX();
   1450                                 clickLocationY = focusBounds.centerY();
   1451                             }
   1452                         }
   1453                     }
   1454                 }
   1455             }
   1456 
   1457             mLongPressingPointerId = pointerId;
   1458             mLongPressingPointerDeltaX = (int) mEvent.getX(pointerIndex) - clickLocationX;
   1459             mLongPressingPointerDeltaY = (int) mEvent.getY(pointerIndex) - clickLocationY;
   1460 
   1461             sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags);
   1462 
   1463             mCurrentState = STATE_DELEGATING;
   1464             sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags);
   1465             clear();
   1466         }
   1467 
   1468         private void clear() {
   1469             if (!isPending()) {
   1470                 return;
   1471             }
   1472             mEvent.recycle();
   1473             mEvent = null;
   1474             mPolicyFlags = 0;
   1475         }
   1476     }
   1477 
   1478     /**
   1479      * Class for delayed sending of hover events.
   1480      */
   1481     class SendHoverDelayed implements Runnable {
   1482         private final String LOG_TAG_SEND_HOVER_DELAYED = SendHoverDelayed.class.getName();
   1483 
   1484         private final int mHoverAction;
   1485         private final boolean mGestureStarted;
   1486 
   1487         private MotionEvent mPrototype;
   1488         private int mPointerIdBits;
   1489         private int mPolicyFlags;
   1490 
   1491         public SendHoverDelayed(int hoverAction, boolean gestureStarted) {
   1492             mHoverAction = hoverAction;
   1493             mGestureStarted = gestureStarted;
   1494         }
   1495 
   1496         public void post(MotionEvent prototype, boolean touchExplorationInProgress,
   1497                 int pointerIdBits, int policyFlags) {
   1498             remove();
   1499             mPrototype = MotionEvent.obtain(prototype);
   1500             mPointerIdBits = pointerIdBits;
   1501             mPolicyFlags = policyFlags;
   1502             mHandler.postDelayed(this, mDetermineUserIntentTimeout);
   1503         }
   1504 
   1505         public float getX() {
   1506             if (isPending()) {
   1507                 return mPrototype.getX();
   1508             }
   1509             return 0;
   1510         }
   1511 
   1512         public float getY() {
   1513             if (isPending()) {
   1514                 return mPrototype.getY();
   1515             }
   1516             return 0;
   1517         }
   1518 
   1519         public void remove() {
   1520             mHandler.removeCallbacks(this);
   1521             clear();
   1522         }
   1523 
   1524         private boolean isPending() {
   1525             return (mPrototype != null);
   1526         }
   1527 
   1528         private void clear() {
   1529             if (!isPending()) {
   1530                 return;
   1531             }
   1532             mPrototype.recycle();
   1533             mPrototype = null;
   1534             mPointerIdBits = -1;
   1535             mPolicyFlags = 0;
   1536         }
   1537 
   1538         public void forceSendAndRemove() {
   1539             if (isPending()) {
   1540                 run();
   1541                 remove();
   1542             }
   1543         }
   1544 
   1545         public void run() {
   1546             if (DEBUG) {
   1547                 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event: "
   1548                         + MotionEvent.actionToString(mHoverAction));
   1549                 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, mGestureStarted ?
   1550                         "touchExplorationGestureStarted" : "touchExplorationGestureEnded");
   1551             }
   1552             if (mGestureStarted) {
   1553                 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
   1554             } else {
   1555                 if (!mSendTouchExplorationEndDelayed.isPending()) {
   1556                     mSendTouchExplorationEndDelayed.post();
   1557                 }
   1558                 if (mSendTouchInteractionEndDelayed.isPending()) {
   1559                     mSendTouchInteractionEndDelayed.remove();
   1560                     mSendTouchInteractionEndDelayed.post();
   1561                 }
   1562             }
   1563             sendMotionEvent(mPrototype, mHoverAction, mPointerIdBits, mPolicyFlags);
   1564             clear();
   1565         }
   1566     }
   1567 
   1568     private class SendAccessibilityEventDelayed implements Runnable {
   1569         private final int mEventType;
   1570         private final int mDelay;
   1571 
   1572         public SendAccessibilityEventDelayed(int eventType, int delay) {
   1573             mEventType = eventType;
   1574             mDelay = delay;
   1575         }
   1576 
   1577         public void remove() {
   1578             mHandler.removeCallbacks(this);
   1579         }
   1580 
   1581         public void post() {
   1582             mHandler.postDelayed(this, mDelay);
   1583         }
   1584 
   1585         public boolean isPending() {
   1586             return mHandler.hasCallbacks(this);
   1587         }
   1588 
   1589         public void forceSendAndRemove() {
   1590             if (isPending()) {
   1591                 run();
   1592                 remove();
   1593             }
   1594         }
   1595 
   1596         @Override
   1597         public void run() {
   1598             sendAccessibilityEvent(mEventType);
   1599         }
   1600     }
   1601 
   1602     @Override
   1603     public String toString() {
   1604         return LOG_TAG;
   1605     }
   1606 
   1607     class InjectedPointerTracker {
   1608         private static final String LOG_TAG_INJECTED_POINTER_TRACKER = "InjectedPointerTracker";
   1609 
   1610         // Keep track of which pointers sent to the system are down.
   1611         private int mInjectedPointersDown;
   1612 
   1613         // The time of the last injected down.
   1614         private long mLastInjectedDownEventTime;
   1615 
   1616         // The last injected hover event.
   1617         private MotionEvent mLastInjectedHoverEvent;
   1618 
   1619         // The last injected hover event used for performing clicks.
   1620         private MotionEvent mLastInjectedHoverEventForClick;
   1621 
   1622         /**
   1623          * Processes an injected {@link MotionEvent} event.
   1624          *
   1625          * @param event The event to process.
   1626          */
   1627         public void onMotionEvent(MotionEvent event) {
   1628             final int action = event.getActionMasked();
   1629             switch (action) {
   1630                 case MotionEvent.ACTION_DOWN:
   1631                 case MotionEvent.ACTION_POINTER_DOWN: {
   1632                     final int pointerId = event.getPointerId(event.getActionIndex());
   1633                     final int pointerFlag = (1 << pointerId);
   1634                     mInjectedPointersDown |= pointerFlag;
   1635                     mLastInjectedDownEventTime = event.getDownTime();
   1636                 } break;
   1637                 case MotionEvent.ACTION_UP:
   1638                 case MotionEvent.ACTION_POINTER_UP: {
   1639                     final int pointerId = event.getPointerId(event.getActionIndex());
   1640                     final int pointerFlag = (1 << pointerId);
   1641                     mInjectedPointersDown &= ~pointerFlag;
   1642                     if (mInjectedPointersDown == 0) {
   1643                         mLastInjectedDownEventTime = 0;
   1644                     }
   1645                 } break;
   1646                 case MotionEvent.ACTION_HOVER_ENTER:
   1647                 case MotionEvent.ACTION_HOVER_MOVE:
   1648                 case MotionEvent.ACTION_HOVER_EXIT: {
   1649                     if (mLastInjectedHoverEvent != null) {
   1650                         mLastInjectedHoverEvent.recycle();
   1651                     }
   1652                     mLastInjectedHoverEvent = MotionEvent.obtain(event);
   1653                     if (mLastInjectedHoverEventForClick != null) {
   1654                         mLastInjectedHoverEventForClick.recycle();
   1655                     }
   1656                     mLastInjectedHoverEventForClick = MotionEvent.obtain(event);
   1657                 } break;
   1658             }
   1659             if (DEBUG) {
   1660                 Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer:\n" + toString());
   1661             }
   1662         }
   1663 
   1664         /**
   1665          * Clears the internals state.
   1666          */
   1667         public void clear() {
   1668             mInjectedPointersDown = 0;
   1669         }
   1670 
   1671         /**
   1672          * @return The time of the last injected down event.
   1673          */
   1674         public long getLastInjectedDownEventTime() {
   1675             return mLastInjectedDownEventTime;
   1676         }
   1677 
   1678         /**
   1679          * @return The number of down pointers injected to the view hierarchy.
   1680          */
   1681         public int getInjectedPointerDownCount() {
   1682             return Integer.bitCount(mInjectedPointersDown);
   1683         }
   1684 
   1685         /**
   1686          * @return The bits of the injected pointers that are down.
   1687          */
   1688         public int getInjectedPointersDown() {
   1689             return mInjectedPointersDown;
   1690         }
   1691 
   1692         /**
   1693          * Whether an injected pointer is down.
   1694          *
   1695          * @param pointerId The unique pointer id.
   1696          * @return True if the pointer is down.
   1697          */
   1698         public boolean isInjectedPointerDown(int pointerId) {
   1699             final int pointerFlag = (1 << pointerId);
   1700             return (mInjectedPointersDown & pointerFlag) != 0;
   1701         }
   1702 
   1703         /**
   1704          * @return The the last injected hover event.
   1705          */
   1706         public MotionEvent getLastInjectedHoverEvent() {
   1707             return mLastInjectedHoverEvent;
   1708         }
   1709 
   1710         /**
   1711          * @return The the last injected hover event.
   1712          */
   1713         public MotionEvent getLastInjectedHoverEventForClick() {
   1714             return mLastInjectedHoverEventForClick;
   1715         }
   1716 
   1717         @Override
   1718         public String toString() {
   1719             StringBuilder builder = new StringBuilder();
   1720             builder.append("=========================");
   1721             builder.append("\nDown pointers #");
   1722             builder.append(Integer.bitCount(mInjectedPointersDown));
   1723             builder.append(" [ ");
   1724             for (int i = 0; i < MAX_POINTER_COUNT; i++) {
   1725                 if ((mInjectedPointersDown & i) != 0) {
   1726                     builder.append(i);
   1727                     builder.append(" ");
   1728                 }
   1729             }
   1730             builder.append("]");
   1731             builder.append("\n=========================");
   1732             return builder.toString();
   1733         }
   1734     }
   1735 
   1736     class ReceivedPointerTracker {
   1737         private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker";
   1738 
   1739         // The coefficient by which to multiply
   1740         // ViewConfiguration.#getScaledTouchSlop()
   1741         // to compute #mThresholdActivePointer.
   1742         private static final int COEFFICIENT_ACTIVE_POINTER = 2;
   1743 
   1744         // Pointers that moved less than mThresholdActivePointer
   1745         // are considered active i.e. are ignored.
   1746         private final double mThresholdActivePointer;
   1747 
   1748         // Keep track of where and when a pointer went down.
   1749         private final float[] mReceivedPointerDownX = new float[MAX_POINTER_COUNT];
   1750         private final float[] mReceivedPointerDownY = new float[MAX_POINTER_COUNT];
   1751         private final long[] mReceivedPointerDownTime = new long[MAX_POINTER_COUNT];
   1752 
   1753         // Which pointers are down.
   1754         private int mReceivedPointersDown;
   1755 
   1756         // The edge flags of the last received down event.
   1757         private int mLastReceivedDownEdgeFlags;
   1758 
   1759         // Which down pointers are active.
   1760         private int mActivePointers;
   1761 
   1762         // Primary active pointer which is either the first that went down
   1763         // or if it goes up the next active that most recently went down.
   1764         private int mPrimaryActivePointerId;
   1765 
   1766         // Flag indicating that there is at least one active pointer moving.
   1767         private boolean mHasMovingActivePointer;
   1768 
   1769         // Keep track of the last up pointer data.
   1770         private long mLastReceivedUpPointerDownTime;
   1771         private int mLastReceivedUpPointerId;
   1772         private boolean mLastReceivedUpPointerActive;
   1773         private float mLastReceivedUpPointerDownX;
   1774         private float mLastReceivedUpPointerDownY;
   1775 
   1776         private MotionEvent mLastReceivedEvent;
   1777 
   1778         /**
   1779          * Creates a new instance.
   1780          *
   1781          * @param context Context for looking up resources.
   1782          */
   1783         public ReceivedPointerTracker(Context context) {
   1784             mThresholdActivePointer =
   1785                 ViewConfiguration.get(context).getScaledTouchSlop() * COEFFICIENT_ACTIVE_POINTER;
   1786         }
   1787 
   1788         /**
   1789          * Clears the internals state.
   1790          */
   1791         public void clear() {
   1792             Arrays.fill(mReceivedPointerDownX, 0);
   1793             Arrays.fill(mReceivedPointerDownY, 0);
   1794             Arrays.fill(mReceivedPointerDownTime, 0);
   1795             mReceivedPointersDown = 0;
   1796             mActivePointers = 0;
   1797             mPrimaryActivePointerId = 0;
   1798             mHasMovingActivePointer = false;
   1799             mLastReceivedUpPointerDownTime = 0;
   1800             mLastReceivedUpPointerId = 0;
   1801             mLastReceivedUpPointerActive = false;
   1802             mLastReceivedUpPointerDownX = 0;
   1803             mLastReceivedUpPointerDownY = 0;
   1804         }
   1805 
   1806         /**
   1807          * Processes a received {@link MotionEvent} event.
   1808          *
   1809          * @param event The event to process.
   1810          */
   1811         public void onMotionEvent(MotionEvent event) {
   1812             if (mLastReceivedEvent != null) {
   1813                 mLastReceivedEvent.recycle();
   1814             }
   1815             mLastReceivedEvent = MotionEvent.obtain(event);
   1816 
   1817             final int action = event.getActionMasked();
   1818             switch (action) {
   1819                 case MotionEvent.ACTION_DOWN: {
   1820                     handleReceivedPointerDown(event.getActionIndex(), event);
   1821                 } break;
   1822                 case MotionEvent.ACTION_POINTER_DOWN: {
   1823                     handleReceivedPointerDown(event.getActionIndex(), event);
   1824                 } break;
   1825                 case MotionEvent.ACTION_MOVE: {
   1826                     handleReceivedPointerMove(event);
   1827                 } break;
   1828                 case MotionEvent.ACTION_UP: {
   1829                     handleReceivedPointerUp(event.getActionIndex(), event);
   1830                 } break;
   1831                 case MotionEvent.ACTION_POINTER_UP: {
   1832                     handleReceivedPointerUp(event.getActionIndex(), event);
   1833                 } break;
   1834             }
   1835             if (DEBUG) {
   1836                 Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer: " + toString());
   1837             }
   1838         }
   1839 
   1840         /**
   1841          * @return The last received event.
   1842          */
   1843         public MotionEvent getLastReceivedEvent() {
   1844             return mLastReceivedEvent;
   1845         }
   1846 
   1847         /**
   1848          * @return The number of received pointers that are down.
   1849          */
   1850         public int getReceivedPointerDownCount() {
   1851             return Integer.bitCount(mReceivedPointersDown);
   1852         }
   1853 
   1854         /**
   1855          * @return The bits of the pointers that are active.
   1856          */
   1857         public int getActivePointers() {
   1858             return mActivePointers;
   1859         }
   1860 
   1861         /**
   1862          * @return The number of down input  pointers that are active.
   1863          */
   1864         public int getActivePointerCount() {
   1865             return Integer.bitCount(mActivePointers);
   1866         }
   1867 
   1868         /**
   1869          * Whether an received pointer is down.
   1870          *
   1871          * @param pointerId The unique pointer id.
   1872          * @return True if the pointer is down.
   1873          */
   1874         public boolean isReceivedPointerDown(int pointerId) {
   1875             final int pointerFlag = (1 << pointerId);
   1876             return (mReceivedPointersDown & pointerFlag) != 0;
   1877         }
   1878 
   1879         /**
   1880          * Whether an input pointer is active.
   1881          *
   1882          * @param pointerId The unique pointer id.
   1883          * @return True if the pointer is active.
   1884          */
   1885         public boolean isActivePointer(int pointerId) {
   1886             final int pointerFlag = (1 << pointerId);
   1887             return (mActivePointers & pointerFlag) != 0;
   1888         }
   1889 
   1890         /**
   1891          * @param pointerId The unique pointer id.
   1892          * @return The X coordinate where the pointer went down.
   1893          */
   1894         public float getReceivedPointerDownX(int pointerId) {
   1895             return mReceivedPointerDownX[pointerId];
   1896         }
   1897 
   1898         /**
   1899          * @param pointerId The unique pointer id.
   1900          * @return The Y coordinate where the pointer went down.
   1901          */
   1902         public float getReceivedPointerDownY(int pointerId) {
   1903             return mReceivedPointerDownY[pointerId];
   1904         }
   1905 
   1906         /**
   1907          * @param pointerId The unique pointer id.
   1908          * @return The time when the pointer went down.
   1909          */
   1910         public long getReceivedPointerDownTime(int pointerId) {
   1911             return mReceivedPointerDownTime[pointerId];
   1912         }
   1913 
   1914         /**
   1915          * @return The id of the primary pointer.
   1916          */
   1917         public int getPrimaryActivePointerId() {
   1918             if (mPrimaryActivePointerId == INVALID_POINTER_ID) {
   1919                 mPrimaryActivePointerId = findPrimaryActivePointer();
   1920             }
   1921             return mPrimaryActivePointerId;
   1922         }
   1923 
   1924         /**
   1925          * @return The time when the last up received pointer went down.
   1926          */
   1927         public long getLastReceivedUpPointerDownTime() {
   1928             return mLastReceivedUpPointerDownTime;
   1929         }
   1930 
   1931         /**
   1932          * @return The id of the last received pointer that went up.
   1933          */
   1934         public int getLastReceivedUpPointerId() {
   1935             return mLastReceivedUpPointerId;
   1936         }
   1937 
   1938 
   1939         /**
   1940          * @return The down X of the last received pointer that went up.
   1941          */
   1942         public float getLastReceivedUpPointerDownX() {
   1943             return mLastReceivedUpPointerDownX;
   1944         }
   1945 
   1946         /**
   1947          * @return The down Y of the last received pointer that went up.
   1948          */
   1949         public float getLastReceivedUpPointerDownY() {
   1950             return mLastReceivedUpPointerDownY;
   1951         }
   1952 
   1953         /**
   1954          * @return The edge flags of the last received down event.
   1955          */
   1956         public int getLastReceivedDownEdgeFlags() {
   1957             return mLastReceivedDownEdgeFlags;
   1958         }
   1959 
   1960         /**
   1961          * @return Whether the last received pointer that went up was active.
   1962          */
   1963         public boolean wasLastReceivedUpPointerActive() {
   1964             return mLastReceivedUpPointerActive;
   1965         }
   1966         /**
   1967          * Populates the active pointer IDs to the given array.
   1968          * <p>
   1969          * Note: The client is responsible for providing large enough array.
   1970          *
   1971          * @param outPointerIds The array to which to write the active pointers.
   1972          */
   1973         public void populateActivePointerIds(int[] outPointerIds) {
   1974             int index = 0;
   1975             for (int idBits = mActivePointers; idBits != 0; ) {
   1976                 final int id = Integer.numberOfTrailingZeros(idBits);
   1977                 idBits &= ~(1 << id);
   1978                 outPointerIds[index] = id;
   1979                 index++;
   1980             }
   1981         }
   1982 
   1983         /**
   1984          * @param pointerId The unique pointer id.
   1985          * @return Whether the pointer is active or was the last active than went up.
   1986          */
   1987         public boolean isActiveOrWasLastActiveUpPointer(int pointerId) {
   1988             return (isActivePointer(pointerId)
   1989                     || (mLastReceivedUpPointerId == pointerId
   1990                             && mLastReceivedUpPointerActive));
   1991         }
   1992 
   1993         /**
   1994          * Handles a received pointer down event.
   1995          *
   1996          * @param pointerIndex The index of the pointer that has changed.
   1997          * @param event The event to be handled.
   1998          */
   1999         private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) {
   2000             final int pointerId = event.getPointerId(pointerIndex);
   2001             final int pointerFlag = (1 << pointerId);
   2002 
   2003             mLastReceivedUpPointerId = 0;
   2004             mLastReceivedUpPointerDownTime = 0;
   2005             mLastReceivedUpPointerActive = false;
   2006             mLastReceivedUpPointerDownX = 0;
   2007             mLastReceivedUpPointerDownX = 0;
   2008 
   2009             mLastReceivedDownEdgeFlags = event.getEdgeFlags();
   2010 
   2011             mReceivedPointersDown |= pointerFlag;
   2012             mReceivedPointerDownX[pointerId] = event.getX(pointerIndex);
   2013             mReceivedPointerDownY[pointerId] = event.getY(pointerIndex);
   2014             mReceivedPointerDownTime[pointerId] = event.getEventTime();
   2015 
   2016             if (!mHasMovingActivePointer) {
   2017                 // If still no moving active pointers every
   2018                 // down pointer is the only active one.
   2019                 mActivePointers = pointerFlag;
   2020                 mPrimaryActivePointerId = pointerId;
   2021             } else {
   2022                 // If at least one moving active pointer every
   2023                 // subsequent down pointer is active.
   2024                 mActivePointers |= pointerFlag;
   2025             }
   2026         }
   2027 
   2028         /**
   2029          * Handles a received pointer move event.
   2030          *
   2031          * @param event The event to be handled.
   2032          */
   2033         private void handleReceivedPointerMove(MotionEvent event) {
   2034             detectActivePointers(event);
   2035         }
   2036 
   2037         /**
   2038          * Handles a received pointer up event.
   2039          *
   2040          * @param pointerIndex The index of the pointer that has changed.
   2041          * @param event The event to be handled.
   2042          */
   2043         private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) {
   2044             final int pointerId = event.getPointerId(pointerIndex);
   2045             final int pointerFlag = (1 << pointerId);
   2046 
   2047             mLastReceivedUpPointerId = pointerId;
   2048             mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId);
   2049             mLastReceivedUpPointerActive = isActivePointer(pointerId);
   2050             mLastReceivedUpPointerDownX = mReceivedPointerDownX[pointerId];
   2051             mLastReceivedUpPointerDownY = mReceivedPointerDownY[pointerId];
   2052 
   2053             mReceivedPointersDown &= ~pointerFlag;
   2054             mActivePointers &= ~pointerFlag;
   2055             mReceivedPointerDownX[pointerId] = 0;
   2056             mReceivedPointerDownY[pointerId] = 0;
   2057             mReceivedPointerDownTime[pointerId] = 0;
   2058 
   2059             if (mActivePointers == 0) {
   2060                 mHasMovingActivePointer = false;
   2061             }
   2062             if (mPrimaryActivePointerId == pointerId) {
   2063                 mPrimaryActivePointerId = INVALID_POINTER_ID;
   2064             }
   2065         }
   2066 
   2067         /**
   2068          * Detects the active pointers in an event.
   2069          *
   2070          * @param event The event to examine.
   2071          */
   2072         private void detectActivePointers(MotionEvent event) {
   2073             for (int i = 0, count = event.getPointerCount(); i < count; i++) {
   2074                 final int pointerId = event.getPointerId(i);
   2075                 if (mHasMovingActivePointer) {
   2076                     // If already active => nothing to do.
   2077                     if (isActivePointer(pointerId)) {
   2078                         continue;
   2079                     }
   2080                 }
   2081                 // Active pointers are ones that moved more than a given threshold.
   2082                 final float pointerDeltaMove = computePointerDeltaMove(i, event);
   2083                 if (pointerDeltaMove > mThresholdActivePointer) {
   2084                     final int pointerFlag = (1 << pointerId);
   2085                     mActivePointers |= pointerFlag;
   2086                     mHasMovingActivePointer = true;
   2087                 }
   2088             }
   2089         }
   2090 
   2091         /**
   2092          * @return The primary active pointer.
   2093          */
   2094         private int findPrimaryActivePointer() {
   2095             int primaryActivePointerId = INVALID_POINTER_ID;
   2096             long minDownTime = Long.MAX_VALUE;
   2097             // Find the active pointer that went down first.
   2098             for (int i = 0, count = mReceivedPointerDownTime.length; i < count; i++) {
   2099                 if (isActivePointer(i)) {
   2100                     final long downPointerTime = mReceivedPointerDownTime[i];
   2101                     if (downPointerTime < minDownTime) {
   2102                         minDownTime = downPointerTime;
   2103                         primaryActivePointerId = i;
   2104                     }
   2105                 }
   2106             }
   2107             return primaryActivePointerId;
   2108         }
   2109 
   2110         /**
   2111          * Computes the move for a given action pointer index since the
   2112          * corresponding pointer went down.
   2113          *
   2114          * @param pointerIndex The action pointer index.
   2115          * @param event The event to examine.
   2116          * @return The distance the pointer has moved.
   2117          */
   2118         private float computePointerDeltaMove(int pointerIndex, MotionEvent event) {
   2119             final int pointerId = event.getPointerId(pointerIndex);
   2120             final float deltaX = event.getX(pointerIndex) - mReceivedPointerDownX[pointerId];
   2121             final float deltaY = event.getY(pointerIndex) - mReceivedPointerDownY[pointerId];
   2122             return (float) Math.hypot(deltaX, deltaY);
   2123         }
   2124 
   2125         @Override
   2126         public String toString() {
   2127             StringBuilder builder = new StringBuilder();
   2128             builder.append("=========================");
   2129             builder.append("\nDown pointers #");
   2130             builder.append(getReceivedPointerDownCount());
   2131             builder.append(" [ ");
   2132             for (int i = 0; i < MAX_POINTER_COUNT; i++) {
   2133                 if (isReceivedPointerDown(i)) {
   2134                     builder.append(i);
   2135                     builder.append(" ");
   2136                 }
   2137             }
   2138             builder.append("]");
   2139             builder.append("\nActive pointers #");
   2140             builder.append(getActivePointerCount());
   2141             builder.append(" [ ");
   2142             for (int i = 0; i < MAX_POINTER_COUNT; i++) {
   2143                 if (isActivePointer(i)) {
   2144                     builder.append(i);
   2145                     builder.append(" ");
   2146                 }
   2147             }
   2148             builder.append("]");
   2149             builder.append("\nPrimary active pointer id [ ");
   2150             builder.append(getPrimaryActivePointerId());
   2151             builder.append(" ]");
   2152             builder.append("\n=========================");
   2153             return builder.toString();
   2154         }
   2155     }
   2156 }
   2157