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