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