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