FileDocCategorySizeDatePackage
TouchExplorer.javaAPI DocAndroid 5.1 API81294Thu Mar 12 22:22:42 GMT 2015com.android.server.accessibility

TouchExplorer

public class TouchExplorer extends Object implements EventStreamTransformation
This class is a strategy for performing touch exploration. It transforms the motion event stream by modifying, adding, replacing, and consuming certain events. The interaction model is:
  1. 1. One finger moving slow around performs touch exploration.
  2. 2. One finger moving fast around performs gestures.
  3. 3. Two close fingers moving in the same direction perform a drag.
  4. 4. Multi-finger gestures are delivered to view hierarchy.
  5. 5. Two fingers moving in different directions are considered a multi-finger gesture.
  6. 7. Double tapping clicks on the on the last touch explored location if it was in a window that does not take focus, otherwise the click is within the accessibility focused rectangle.
  7. 7. Tapping and holding for a while performs a long press in a similar fashion as the click above.
    hide

    Fields Summary
    private static final boolean
    DEBUG
    private static final String
    LOG_TAG
    private static final int
    STATE_TOUCH_EXPLORING
    private static final int
    STATE_DRAGGING
    private static final int
    STATE_DELEGATING
    private static final int
    STATE_GESTURE_DETECTING
    private static final int
    CLICK_LOCATION_NONE
    private static final int
    CLICK_LOCATION_ACCESSIBILITY_FOCUS
    private static final int
    CLICK_LOCATION_LAST_TOUCH_EXPLORED
    private static final float
    MAX_DRAGGING_ANGLE_COS
    private static final int
    ALL_POINTER_ID_BITS
    private static final int
    MAX_POINTER_COUNT
    private static final int
    INVALID_POINTER_ID
    private static final int
    GESTURE_DETECTION_VELOCITY_DIP
    private static final int
    MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP
    private static final int
    EXIT_GESTURE_DETECTION_TIMEOUT
    private final int
    mDetermineUserIntentTimeout
    private final int
    mTapTimeout
    private final int
    mDoubleTapTimeout
    private final int
    mTouchSlop
    private final int
    mDoubleTapSlop
    private int
    mCurrentState
    private int
    mDraggingPointerId
    private final android.os.Handler
    mHandler
    private final SendHoverEnterAndMoveDelayed
    mSendHoverEnterAndMoveDelayed
    private final SendHoverExitDelayed
    mSendHoverExitDelayed
    private final SendAccessibilityEventDelayed
    mSendTouchExplorationEndDelayed
    private final SendAccessibilityEventDelayed
    mSendTouchInteractionEndDelayed
    private final PerformLongPressDelayed
    mPerformLongPressDelayed
    private final ExitGestureDetectionModeDelayed
    mExitGestureDetectionModeDelayed
    private final DoubleTapDetector
    mDoubleTapDetector
    private final int
    mScaledMinPointerDistanceToUseMiddleLocation
    private final int
    mScaledGestureDetectionVelocity
    private EventStreamTransformation
    mNext
    private final android.view.VelocityTracker
    mVelocityTracker
    private final ReceivedPointerTracker
    mReceivedPointerTracker
    private final InjectedPointerTracker
    mInjectedPointerTracker
    private final AccessibilityManagerService
    mAms
    private final android.graphics.Rect
    mTempRect
    private final android.graphics.Point
    mTempPoint
    private final android.content.Context
    mContext
    private float
    mPreviousX
    private float
    mPreviousY
    private final ArrayList
    mStrokeBuffer
    private static final int
    TOUCH_TOLERANCE
    private static final float
    MIN_PREDICTION_SCORE
    private android.gesture.GestureLibrary
    mGestureLibrary
    private int
    mLongPressingPointerId
    private int
    mLongPressingPointerDeltaX
    private int
    mLongPressingPointerDeltaY
    private int
    mLastTouchedWindowId
    private boolean
    mTouchExplorationInProgress
    Constructors Summary
    public TouchExplorer(android.content.Context context, AccessibilityManagerService service)
    Creates a new instance.

    param
    inputFilter The input filter associated with this explorer.
    param
    context A context handle for accessing resources.

    
                                  
             
            mContext = context;
            mAms = service;
            mReceivedPointerTracker = new ReceivedPointerTracker();
            mInjectedPointerTracker = new InjectedPointerTracker();
            mTapTimeout = ViewConfiguration.getTapTimeout();
            mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout();
            mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
            mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
            mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
            mHandler = new Handler(context.getMainLooper());
            mPerformLongPressDelayed = new PerformLongPressDelayed();
            mExitGestureDetectionModeDelayed = new ExitGestureDetectionModeDelayed();
            mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures);
            mGestureLibrary.setOrientationStyle(8);
            mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE);
            mGestureLibrary.load();
            mSendHoverEnterAndMoveDelayed = new SendHoverEnterAndMoveDelayed();
            mSendHoverExitDelayed = new SendHoverExitDelayed();
            mSendTouchExplorationEndDelayed = new SendAccessibilityEventDelayed(
                    AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END,
                    mDetermineUserIntentTimeout);
            mSendTouchInteractionEndDelayed = new SendAccessibilityEventDelayed(
                    AccessibilityEvent.TYPE_TOUCH_INTERACTION_END,
                    mDetermineUserIntentTimeout);
            mDoubleTapDetector = new DoubleTapDetector();
            final float density = context.getResources().getDisplayMetrics().density;
            mScaledMinPointerDistanceToUseMiddleLocation =
                (int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density);
            mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
        
    Methods Summary
    public voidclear()

            // If we have not received an event then we are in initial
            // state. Therefore, there is not need to clean anything.
            MotionEvent event = mReceivedPointerTracker.getLastReceivedEvent();
            if (event != null) {
                clear(mReceivedPointerTracker.getLastReceivedEvent(), WindowManagerPolicy.FLAG_TRUSTED);
            }
        
    private voidclear(android.view.MotionEvent event, int policyFlags)

            switch (mCurrentState) {
                case STATE_TOUCH_EXPLORING: {
                    // If a touch exploration gesture is in progress send events for its end.
                    sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
                } break;
                case STATE_DRAGGING: {
                    mDraggingPointerId = INVALID_POINTER_ID;
                    // Send exit to all pointers that we have delivered.
                    sendUpForInjectedDownPointers(event, policyFlags);
                } break;
                case STATE_DELEGATING: {
                    // Send exit to all pointers that we have delivered.
                    sendUpForInjectedDownPointers(event, policyFlags);
                } break;
                case STATE_GESTURE_DETECTING: {
                    // Clear the current stroke.
                    mStrokeBuffer.clear();
                } break;
            }
            // Remove all pending callbacks.
            mSendHoverEnterAndMoveDelayed.cancel();
            mSendHoverExitDelayed.cancel();
            mPerformLongPressDelayed.cancel();
            mExitGestureDetectionModeDelayed.cancel();
            mSendTouchExplorationEndDelayed.cancel();
            mSendTouchInteractionEndDelayed.cancel();
            // Reset the pointer trackers.
            mReceivedPointerTracker.clear();
            mInjectedPointerTracker.clear();
            // Clear the double tap detector
            mDoubleTapDetector.clear();
            // Go to initial state.
            // Clear the long pressing pointer remap data.
            mLongPressingPointerId = -1;
            mLongPressingPointerDeltaX = 0;
            mLongPressingPointerDeltaY = 0;
            mCurrentState = STATE_TOUCH_EXPLORING;
            if (mNext != null) {
                mNext.clear();
            }
            mTouchExplorationInProgress = false;
            mAms.onTouchInteractionEnd();
        
    private intcomputeClickLocation(android.graphics.Point outLocation)

            MotionEvent lastExploreEvent = mInjectedPointerTracker.getLastInjectedHoverEventForClick();
            if (lastExploreEvent != null) {
                final int lastExplorePointerIndex = lastExploreEvent.getActionIndex();
                outLocation.x = (int) lastExploreEvent.getX(lastExplorePointerIndex);
                outLocation.y = (int) lastExploreEvent.getY(lastExplorePointerIndex);
                if (!mAms.accessibilityFocusOnlyInActiveWindow()
                        || mLastTouchedWindowId == mAms.getActiveWindowId()) {
                    if (mAms.getAccessibilityFocusClickPointInScreen(outLocation)) {
                        return CLICK_LOCATION_ACCESSIBILITY_FOCUS;
                    } else {
                        return CLICK_LOCATION_LAST_TOUCH_EXPLORED;
                    }
                }
            }
            if (mAms.getAccessibilityFocusClickPointInScreen(outLocation)) {
                return CLICK_LOCATION_ACCESSIBILITY_FOCUS;
            }
            return CLICK_LOCATION_NONE;
        
    private intcomputeInjectionAction(int actionMasked, int pointerIndex)
    Computes the action for an injected event based on a masked action and a pointer index.

    param
    actionMasked The masked action.
    param
    pointerIndex The index of the pointer which has changed.
    return
    The action to be used for injection.

            switch (actionMasked) {
                case MotionEvent.ACTION_DOWN:
                case MotionEvent.ACTION_POINTER_DOWN: {
                    InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
                    // Compute the action based on how many down pointers are injected.
                    if (injectedTracker.getInjectedPointerDownCount() == 0) {
                        return MotionEvent.ACTION_DOWN;
                    } else {
                        return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
                            | MotionEvent.ACTION_POINTER_DOWN;
                    }
                }
                case MotionEvent.ACTION_POINTER_UP: {
                    InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
                    // Compute the action based on how many down pointers are injected.
                    if (injectedTracker.getInjectedPointerDownCount() == 1) {
                        return MotionEvent.ACTION_UP;
                    } else {
                        return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
                            | MotionEvent.ACTION_POINTER_UP;
                    }
                }
                default:
                    return actionMasked;
            }
        
    private static java.lang.StringgetStateSymbolicName(int state)
    Gets the symbolic name of a state.

    param
    state A state.
    return
    The state symbolic name.

            switch (state) {
                case STATE_TOUCH_EXPLORING:
                    return "STATE_TOUCH_EXPLORING";
                case STATE_DRAGGING:
                    return "STATE_DRAGGING";
                case STATE_DELEGATING:
                    return "STATE_DELEGATING";
                case STATE_GESTURE_DETECTING:
                    return "STATE_GESTURE_DETECTING";
                default:
                    throw new IllegalArgumentException("Unknown state: " + state);
            }
        
    private voidhandleMotionEventGestureDetecting(android.view.MotionEvent event, int policyFlags)

            switch (event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN: {
                    final float x = event.getX();
                    final float y = event.getY();
                    mPreviousX = x;
                    mPreviousY = y;
                    mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
                } break;
                case MotionEvent.ACTION_MOVE: {
                    final float x = event.getX();
                    final float y = event.getY();
                    final float dX = Math.abs(x - mPreviousX);
                    final float dY = Math.abs(y - mPreviousY);
                    if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
                        mPreviousX = x;
                        mPreviousY = y;
                        mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
                    }
                } break;
                case MotionEvent.ACTION_UP: {
                    mAms.onTouchInteractionEnd();
                    // Announce the end of the gesture recognition.
                    sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
                    // Announce the end of a the touch interaction.
                    sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
    
                    float x = event.getX();
                    float y = event.getY();
                    mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
    
                    Gesture gesture = new Gesture();
                    gesture.addStroke(new GestureStroke(mStrokeBuffer));
    
                    ArrayList<Prediction> predictions = mGestureLibrary.recognize(gesture);
                    if (!predictions.isEmpty()) {
                        Prediction bestPrediction = predictions.get(0);
                        if (bestPrediction.score >= MIN_PREDICTION_SCORE) {
                            if (DEBUG) {
                                Slog.i(LOG_TAG, "gesture: " + bestPrediction.name + " score: "
                                        + bestPrediction.score);
                            }
                            try {
                                final int gestureId = Integer.parseInt(bestPrediction.name);
                                mAms.onGesture(gestureId);
                            } catch (NumberFormatException nfe) {
                                Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name);
                            }
                        }
                    }
    
                    mStrokeBuffer.clear();
                    mExitGestureDetectionModeDelayed.cancel();
                    mCurrentState = STATE_TOUCH_EXPLORING;
                } break;
                case MotionEvent.ACTION_CANCEL: {
                    clear(event, policyFlags);
                } break;
            }
        
    private voidhandleMotionEventStateDelegating(android.view.MotionEvent event, int policyFlags)
    Handles a motion event in delegating state.

    param
    event The event to be handled.
    param
    policyFlags The policy flags associated with the event.

            switch (event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN: {
                    throw new IllegalStateException("Delegating state can only be reached if "
                            + "there is at least one pointer down!");
                }
                case MotionEvent.ACTION_UP: {
                    // Offset the event if we are doing a long press as the
                    // target is not necessarily under the user's finger.
                    if (mLongPressingPointerId >= 0) {
                        event = offsetEvent(event, - mLongPressingPointerDeltaX,
                                - mLongPressingPointerDeltaY);
                        // Clear the long press state.
                        mLongPressingPointerId = -1;
                        mLongPressingPointerDeltaX = 0;
                        mLongPressingPointerDeltaY = 0;
                    }
    
                    // Deliver the event.
                    sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
    
                    // Announce the end of a the touch interaction.
                    mAms.onTouchInteractionEnd();
                    sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
    
                    mCurrentState = STATE_TOUCH_EXPLORING;
                } break;
                case MotionEvent.ACTION_CANCEL: {
                    clear(event, policyFlags);
                } break;
                default: {
                    // Deliver the event.
                    sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
                }
            }
        
    private voidhandleMotionEventStateDragging(android.view.MotionEvent event, int policyFlags)
    Handles a motion event in dragging state.

    param
    event The event to be handled.
    param
    policyFlags The policy flags associated with the event.

            final int pointerIdBits = (1 << mDraggingPointerId);
            switch (event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN: {
                    throw new IllegalStateException("Dragging state can be reached only if two "
                            + "pointers are already down");
                }
                case MotionEvent.ACTION_POINTER_DOWN: {
                    // We are in dragging state so we have two pointers and another one
                    // goes down => delegate the three pointers to the view hierarchy
                    mCurrentState = STATE_DELEGATING;
                    if (mDraggingPointerId != INVALID_POINTER_ID) {
                        sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
                    }
                    sendDownForAllNotInjectedPointers(event, policyFlags);
                } break;
                case MotionEvent.ACTION_MOVE: {
                    switch (event.getPointerCount()) {
                        case 1: {
                            // do nothing
                        } break;
                        case 2: {
                            if (isDraggingGesture(event)) {
                                final float firstPtrX = event.getX(0);
                                final float firstPtrY = event.getY(0);
                                final float secondPtrX = event.getX(1);
                                final float secondPtrY = event.getY(1);
    
                                final float deltaX = firstPtrX - secondPtrX;
                                final float deltaY = firstPtrY - secondPtrY;
                                final double distance = Math.hypot(deltaX, deltaY);
    
                                if (distance > mScaledMinPointerDistanceToUseMiddleLocation) {
                                    event.setLocation(deltaX / 2, deltaY / 2);
                                }
    
                                // If still dragging send a drag event.
                                sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits,
                                        policyFlags);
                            } else {
                                // The two pointers are moving either in different directions or
                                // no close enough => delegate the gesture to the view hierarchy.
                                mCurrentState = STATE_DELEGATING;
                                // Send an event to the end of the drag gesture.
                                sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
                                        policyFlags);
                                // Deliver all pointers to the view hierarchy.
                                sendDownForAllNotInjectedPointers(event, policyFlags);
                            }
                        } break;
                        default: {
                            mCurrentState = STATE_DELEGATING;
                            // Send an event to the end of the drag gesture.
                            sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
                                    policyFlags);
                            // Deliver all pointers to the view hierarchy.
                            sendDownForAllNotInjectedPointers(event, policyFlags);
                        }
                    }
                } break;
                case MotionEvent.ACTION_POINTER_UP: {
                     final int pointerId = event.getPointerId(event.getActionIndex());
                     if (pointerId == mDraggingPointerId) {
                         mDraggingPointerId = INVALID_POINTER_ID;
                         // Send an event to the end of the drag gesture.
                         sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
                     }
                } break;
                case MotionEvent.ACTION_UP: {
                    mAms.onTouchInteractionEnd();
                    // Announce the end of a new touch interaction.
                    sendAccessibilityEvent(
                            AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
                    final int pointerId = event.getPointerId(event.getActionIndex());
                    if (pointerId == mDraggingPointerId) {
                        mDraggingPointerId = INVALID_POINTER_ID;
                        // Send an event to the end of the drag gesture.
                        sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
                    }
                    mCurrentState = STATE_TOUCH_EXPLORING;
                } break;
                case MotionEvent.ACTION_CANCEL: {
                    clear(event, policyFlags);
                } break;
            }
        
    private voidhandleMotionEventStateTouchExploring(android.view.MotionEvent event, android.view.MotionEvent rawEvent, int policyFlags)
    Handles a motion event in touch exploring state.

    param
    event The event to be handled.
    param
    rawEvent The raw (unmodified) motion event.
    param
    policyFlags The policy flags associated with the event.

            ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
    
            mVelocityTracker.addMovement(rawEvent);
    
            mDoubleTapDetector.onMotionEvent(event, policyFlags);
    
            switch (event.getActionMasked()) {
                case MotionEvent.ACTION_DOWN: {
                    mAms.onTouchInteractionStart();
    
                    // Pre-feed the motion events to the gesture detector since we
                    // have a distance slop before getting into gesture detection
                    // mode and not using the points within this slop significantly
                    // decreases the quality of gesture recognition.
                    handleMotionEventGestureDetecting(rawEvent, policyFlags);
                    sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
    
                    // If we still have not notified the user for the last
                    // touch, we figure out what to do. If were waiting
                    // we resent the delayed callback and wait again.
                    mSendHoverEnterAndMoveDelayed.cancel();
                    mSendHoverExitDelayed.cancel();
                    mPerformLongPressDelayed.cancel();
    
                    if (mSendTouchExplorationEndDelayed.isPending()) {
                        mSendTouchExplorationEndDelayed.forceSendAndRemove();
                    }
    
                    if (mSendTouchInteractionEndDelayed.isPending()) {
                        mSendTouchInteractionEndDelayed.forceSendAndRemove();
                    }
    
                    // If we have the first tap, schedule a long press and break
                    // since we do not want to schedule hover enter because
                    // the delayed callback will kick in before the long click.
                    // This would lead to a state transition resulting in long
                    // pressing the item below the double taped area which is
                    // not necessary where accessibility focus is.
                    if (mDoubleTapDetector.firstTapDetected()) {
                        // We got a tap now post a long press action.
                        mPerformLongPressDelayed.post(event, policyFlags);
                        break;
                    }
                    if (!mTouchExplorationInProgress) {
                        if (!mSendHoverEnterAndMoveDelayed.isPending()) {
                            // Deliver hover enter with a delay to have a chance
                            // to detect what the user is trying to do.
                            final int pointerId = receivedTracker.getPrimaryPointerId();
                            final int pointerIdBits = (1 << pointerId);
                            mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits,
                                    policyFlags);
                        } else {
                            // Cache the event until we discern exploration from gesturing.
                            mSendHoverEnterAndMoveDelayed.addEvent(event);
                        }
                    }
                } break;
                case MotionEvent.ACTION_POINTER_DOWN: {
                    // Another finger down means that if we have not started to deliver
                    // hover events, we will not have to. The code for ACTION_MOVE will
                    // decide what we will actually do next.
                    mSendHoverEnterAndMoveDelayed.cancel();
                    mSendHoverExitDelayed.cancel();
                    mPerformLongPressDelayed.cancel();
                } break;
                case MotionEvent.ACTION_MOVE: {
                    final int pointerId = receivedTracker.getPrimaryPointerId();
                    final int pointerIndex = event.findPointerIndex(pointerId);
                    final int pointerIdBits = (1 << pointerId);
                    switch (event.getPointerCount()) {
                        case 1: {
                            // We have not started sending events since we try to
                            // figure out what the user is doing.
                            if (mSendHoverEnterAndMoveDelayed.isPending()) {
                                // Pre-feed the motion events to the gesture detector since we
                                // have a distance slop before getting into gesture detection
                                // mode and not using the points within this slop significantly
                                // decreases the quality of gesture recognition.
                                handleMotionEventGestureDetecting(rawEvent, policyFlags);
    
                                // Cache the event until we discern exploration from gesturing.
                                mSendHoverEnterAndMoveDelayed.addEvent(event);
    
                                // It is *important* to use the distance traveled by the pointers
                                // on the screen which may or may not be magnified.
                                final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
                                    - rawEvent.getX(pointerIndex);
                                final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
                                    - rawEvent.getY(pointerIndex);
                                final double moveDelta = Math.hypot(deltaX, deltaY);
                                // The user has moved enough for us to decide.
                                if (moveDelta > mDoubleTapSlop) {
                                    // Check whether the user is performing a gesture. We
                                    // detect gestures if the pointer is moving above a
                                    // given velocity.
                                    mVelocityTracker.computeCurrentVelocity(1000);
                                    final float maxAbsVelocity = Math.max(
                                            Math.abs(mVelocityTracker.getXVelocity(pointerId)),
                                            Math.abs(mVelocityTracker.getYVelocity(pointerId)));
                                    if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
                                        // We have to perform gesture detection, so
                                        // clear the current state and try to detect.
                                        mCurrentState = STATE_GESTURE_DETECTING;
                                        mVelocityTracker.clear();
                                        mSendHoverEnterAndMoveDelayed.cancel();
                                        mSendHoverExitDelayed.cancel();
                                        mPerformLongPressDelayed.cancel();
                                        mExitGestureDetectionModeDelayed.post();
                                        // Send accessibility event to announce the start
                                        // of gesture recognition.
                                        sendAccessibilityEvent(
                                                AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
                                    } else {
                                        // We have just decided that the user is touch,
                                        // exploring so start sending events.
                                        mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
                                        mSendHoverExitDelayed.cancel();
                                        mPerformLongPressDelayed.cancel();
                                        sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE,
                                                pointerIdBits, policyFlags);
                                    }
                                    break;
                                }
                            } else {
                                // Cancel the long press if pending and the user
                                // moved more than the slop.
                                if (mPerformLongPressDelayed.isPending()) {
                                    final float deltaX =
                                            receivedTracker.getReceivedPointerDownX(pointerId)
                                            - rawEvent.getX(pointerIndex);
                                    final float deltaY =
                                            receivedTracker.getReceivedPointerDownY(pointerId)
                                            - rawEvent.getY(pointerIndex);
                                    final double moveDelta = Math.hypot(deltaX, deltaY);
                                    // The user has moved enough for us to decide.
                                    if (moveDelta > mTouchSlop) {
                                        mPerformLongPressDelayed.cancel();
                                    }
                                }
                                if (mTouchExplorationInProgress) {
                                    sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
                                    sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
                                            policyFlags);
                                }
                            }
                        } break;
                        case 2: {
                            // More than one pointer so the user is not touch exploring
                            // and now we have to decide whether to delegate or drag.
                            if (mSendHoverEnterAndMoveDelayed.isPending()) {
                                // We have not started sending events so cancel
                                // scheduled sending events.
                                mSendHoverEnterAndMoveDelayed.cancel();
                                mSendHoverExitDelayed.cancel();
                                mPerformLongPressDelayed.cancel();
                            } else {
                                mPerformLongPressDelayed.cancel();
                                if (mTouchExplorationInProgress) {
                                    // If the user is touch exploring the second pointer may be
                                    // performing a double tap to activate an item without need
                                    // for the user to lift his exploring finger.
                                    // It is *important* to use the distance traveled by the pointers
                                    // on the screen which may or may not be magnified.
                                    final float deltaX = receivedTracker.getReceivedPointerDownX(
                                            pointerId) - rawEvent.getX(pointerIndex);
                                    final float deltaY = receivedTracker.getReceivedPointerDownY(
                                            pointerId) - rawEvent.getY(pointerIndex);
                                    final double moveDelta = Math.hypot(deltaX, deltaY);
                                    if (moveDelta < mDoubleTapSlop) {
                                        break;
                                    }
                                    // We are sending events so send exit and gesture
                                    // end since we transition to another state.
                                    sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
                                }
                            }
    
                            // We know that a new state transition is to happen and the
                            // new state will not be gesture recognition, so clear the
                            // stashed gesture strokes.
                            mStrokeBuffer.clear();
    
                            if (isDraggingGesture(event)) {
                                // Two pointers moving in the same direction within
                                // a given distance perform a drag.
                                mCurrentState = STATE_DRAGGING;
                                mDraggingPointerId = pointerId;
                                event.setEdgeFlags(receivedTracker.getLastReceivedDownEdgeFlags());
                                sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits,
                                        policyFlags);
                            } else {
                                // Two pointers moving arbitrary are delegated to the view hierarchy.
                                mCurrentState = STATE_DELEGATING;
                                sendDownForAllNotInjectedPointers(event, policyFlags);
                            }
                            mVelocityTracker.clear();
                        } break;
                        default: {
                            // More than one pointer so the user is not touch exploring
                            // and now we have to decide whether to delegate or drag.
                            if (mSendHoverEnterAndMoveDelayed.isPending()) {
                                // We have not started sending events so cancel
                                // scheduled sending events.
                                mSendHoverEnterAndMoveDelayed.cancel();
                                mSendHoverExitDelayed.cancel();
                                mPerformLongPressDelayed.cancel();
                            } else {
                                mPerformLongPressDelayed.cancel();
                                // We are sending events so send exit and gesture
                                // end since we transition to another state.
                                sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
                            }
    
                            // More than two pointers are delegated to the view hierarchy.
                            mCurrentState = STATE_DELEGATING;
                            sendDownForAllNotInjectedPointers(event, policyFlags);
                            mVelocityTracker.clear();
                        }
                    }
                } break;
                case MotionEvent.ACTION_UP: {
                    mAms.onTouchInteractionEnd();
                    // We know that we do not need the pre-fed gesture points are not
                    // needed anymore since the last pointer just went up.
                    mStrokeBuffer.clear();
                    final int pointerId = event.getPointerId(event.getActionIndex());
                    final int pointerIdBits = (1 << pointerId);
    
                    mPerformLongPressDelayed.cancel();
                    mVelocityTracker.clear();
    
                    if (mSendHoverEnterAndMoveDelayed.isPending()) {
                        // If we have not delivered the enter schedule an exit.
                        mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags);
                    } else {
                        // The user is touch exploring so we send events for end.
                        sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
                    }
    
                    if (!mSendTouchInteractionEndDelayed.isPending()) {
                        mSendTouchInteractionEndDelayed.post();
                    }
    
                } break;
                case MotionEvent.ACTION_CANCEL: {
                    clear(event, policyFlags);
                } break;
            }
        
    private booleanisDraggingGesture(android.view.MotionEvent event)
    Determines whether a two pointer gesture is a dragging one.

    param
    event The event with the pointer data.
    return
    True if the gesture is a dragging one.

            ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
    
            final float firstPtrX = event.getX(0);
            final float firstPtrY = event.getY(0);
            final float secondPtrX = event.getX(1);
            final float secondPtrY = event.getY(1);
    
            final float firstPtrDownX = receivedTracker.getReceivedPointerDownX(0);
            final float firstPtrDownY = receivedTracker.getReceivedPointerDownY(0);
            final float secondPtrDownX = receivedTracker.getReceivedPointerDownX(1);
            final float secondPtrDownY = receivedTracker.getReceivedPointerDownY(1);
    
            return GestureUtils.isDraggingGesture(firstPtrDownX, firstPtrDownY, secondPtrDownX,
                    secondPtrDownY, firstPtrX, firstPtrY, secondPtrX, secondPtrY,
                    MAX_DRAGGING_ANGLE_COS);
        
    private android.view.MotionEventoffsetEvent(android.view.MotionEvent event, int offsetX, int offsetY)
    Offsets all pointers in the given event by adding the specified X and Y offsets.

    param
    event The event to offset.
    param
    offsetX The X offset.
    param
    offsetY The Y offset.
    return
    An event with the offset pointers or the original event if both offsets are zero.

            if (offsetX == 0 && offsetY == 0) {
                return event;
            }
            final int remappedIndex = event.findPointerIndex(mLongPressingPointerId);
            final int pointerCount = event.getPointerCount();
            PointerProperties[] props = PointerProperties.createArray(pointerCount);
            PointerCoords[] coords = PointerCoords.createArray(pointerCount);
            for (int i = 0; i < pointerCount; i++) {
                event.getPointerProperties(i, props[i]);
                event.getPointerCoords(i, coords[i]);
                if (i == remappedIndex) {
                    coords[i].x += offsetX;
                    coords[i].y += offsetY;
                }
            }
            return MotionEvent.obtain(event.getDownTime(),
                    event.getEventTime(), event.getAction(), event.getPointerCount(),
                    props, coords, event.getMetaState(), event.getButtonState(),
                    1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
                    event.getSource(), event.getFlags());
        
    public voidonAccessibilityEvent(android.view.accessibility.AccessibilityEvent event)

            final int eventType = event.getEventType();
    
            // The event for gesture end should be strictly after the
            // last hover exit event.
            if (mSendTouchExplorationEndDelayed.isPending()
                    && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
                        mSendTouchExplorationEndDelayed.cancel();
                sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
            }
    
            // The event for touch interaction end should be strictly after the
            // last hover exit and the touch exploration gesture end events.
            if (mSendTouchInteractionEndDelayed.isPending()
                    && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
                mSendTouchInteractionEndDelayed.cancel();
                sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
            }
    
            // If a new window opens or the accessibility focus moves we no longer
            // want to click/long press on the last touch explored location.
            switch (eventType) {
                case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
                case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
                    if (mInjectedPointerTracker.mLastInjectedHoverEventForClick != null) {
                        mInjectedPointerTracker.mLastInjectedHoverEventForClick.recycle();
                        mInjectedPointerTracker.mLastInjectedHoverEventForClick = null;
                    }
                    mLastTouchedWindowId = -1;
                } break;
                case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
                case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: {
                    mLastTouchedWindowId = event.getWindowId();
                } break;
            }
            if (mNext != null) {
                mNext.onAccessibilityEvent(event);
            }
        
    public voidonDestroy()

            // TODO: Implement
        
    public voidonMotionEvent(android.view.MotionEvent event, android.view.MotionEvent rawEvent, int policyFlags)

            if (DEBUG) {
                Slog.d(LOG_TAG, "Received event: " + event + ", policyFlags=0x"
                        + Integer.toHexString(policyFlags));
                Slog.d(LOG_TAG, getStateSymbolicName(mCurrentState));
            }
    
            mReceivedPointerTracker.onMotionEvent(rawEvent);
    
            switch(mCurrentState) {
                case STATE_TOUCH_EXPLORING: {
                    handleMotionEventStateTouchExploring(event, rawEvent, policyFlags);
                } break;
                case STATE_DRAGGING: {
                    handleMotionEventStateDragging(event, policyFlags);
                } break;
                case STATE_DELEGATING: {
                    handleMotionEventStateDelegating(event, policyFlags);
                } break;
                case STATE_GESTURE_DETECTING: {
                    handleMotionEventGestureDetecting(rawEvent, policyFlags);
                } break;
                default:
                    throw new IllegalStateException("Illegal state: " + mCurrentState);
            }
        
    private voidsendAccessibilityEvent(int type)
    Sends an accessibility event of the given type.

    param
    type The event type.

            AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
            if (accessibilityManager.isEnabled()) {
                AccessibilityEvent event = AccessibilityEvent.obtain(type);
                event.setWindowId(mAms.getActiveWindowId());
                accessibilityManager.sendAccessibilityEvent(event);
                switch (type) {
                    case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: {
                        mTouchExplorationInProgress = true;
                    } break;
                    case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: {
                        mTouchExplorationInProgress = false;
                    } break;
                }
            }
        
    private voidsendActionDownAndUp(android.view.MotionEvent prototype, int policyFlags, boolean targetAccessibilityFocus)
    Sends an up and down events.

    param
    prototype The prototype from which to create the injected events.
    param
    policyFlags The policy flags associated with the event.
    param
    targetAccessibilityFocus Whether the event targets the accessibility focus.

            // Tap with the pointer that last explored.
            final int pointerId = prototype.getPointerId(prototype.getActionIndex());
            final int pointerIdBits = (1 << pointerId);
            prototype.setTargetAccessibilityFocus(targetAccessibilityFocus);
            sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags);
            prototype.setTargetAccessibilityFocus(targetAccessibilityFocus);
            sendMotionEvent(prototype, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
        
    private voidsendDownForAllNotInjectedPointers(android.view.MotionEvent prototype, int policyFlags)
    Sends down events to the view hierarchy for all pointers which are not already being delivered i.e. pointers that are not yet injected.

    param
    prototype The prototype from which to create the injected events.
    param
    policyFlags The policy flags associated with the event.

            InjectedPointerTracker injectedPointers = mInjectedPointerTracker;
    
            // Inject the injected pointers.
            int pointerIdBits = 0;
            final int pointerCount = prototype.getPointerCount();
            for (int i = 0; i < pointerCount; i++) {
                final int pointerId = prototype.getPointerId(i);
                // Do not send event for already delivered pointers.
                if (!injectedPointers.isInjectedPointerDown(pointerId)) {
                    pointerIdBits |= (1 << pointerId);
                    final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
                    sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
                }
            }
        
    private voidsendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags)
    Sends the exit events if needed. Such events are hover exit and touch explore gesture end.

    param
    policyFlags The policy flags associated with the event.

            MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
            if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
                final int pointerIdBits = event.getPointerIdBits();
                if (!mSendTouchExplorationEndDelayed.isPending()) {
                    mSendTouchExplorationEndDelayed.post();
                }
                sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
            }
        
    private voidsendMotionEvent(android.view.MotionEvent prototype, int action, int pointerIdBits, int policyFlags)
    Sends an event.

    param
    prototype The prototype from which to create the injected events.
    param
    action The action of the event.
    param
    pointerIdBits The bits of the pointers to send.
    param
    policyFlags The policy flags associated with the event.

            prototype.setAction(action);
    
            MotionEvent event = null;
            if (pointerIdBits == ALL_POINTER_ID_BITS) {
                event = prototype;
            } else {
                event = prototype.split(pointerIdBits);
            }
            if (action == MotionEvent.ACTION_DOWN) {
                event.setDownTime(event.getEventTime());
            } else {
                event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime());
            }
    
            // If the user is long pressing but the long pressing pointer
            // was not exactly over the accessibility focused item we need
            // to remap the location of that pointer so the user does not
            // have to explicitly touch explore something to be able to
            // long press it, or even worse to avoid the user long pressing
            // on the wrong item since click and long press behave differently.
            if (mLongPressingPointerId >= 0) {
                event = offsetEvent(event, - mLongPressingPointerDeltaX,
                        - mLongPressingPointerDeltaY);
            }
    
            if (DEBUG) {
                Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x"
                        + Integer.toHexString(policyFlags));
            }
    
            // Make sure that the user will see the event.
            policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
            if (mNext != null) {
                // TODO: For now pass null for the raw event since the touch
                //       explorer is the last event transformation and it does
                //       not care about the raw event.
                mNext.onMotionEvent(event, null, policyFlags);
            }
    
            mInjectedPointerTracker.onMotionEvent(event);
    
            if (event != prototype) {
                event.recycle();
            }
        
    private voidsendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags)
    Sends the enter events if needed. Such events are hover enter and touch explore gesture start.

    param
    policyFlags The policy flags associated with the event.

            MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
            if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
                final int pointerIdBits = event.getPointerIdBits();
                sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
                sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
            }
        
    private voidsendUpForInjectedDownPointers(android.view.MotionEvent prototype, int policyFlags)
    Sends up events to the view hierarchy for all pointers which are already being delivered i.e. pointers that are injected.

    param
    prototype The prototype from which to create the injected events.
    param
    policyFlags The policy flags associated with the event.

            final InjectedPointerTracker injectedTracked = mInjectedPointerTracker;
            int pointerIdBits = 0;
            final int pointerCount = prototype.getPointerCount();
            for (int i = 0; i < pointerCount; i++) {
                final int pointerId = prototype.getPointerId(i);
                // Skip non injected down pointers.
                if (!injectedTracked.isInjectedPointerDown(pointerId)) {
                    continue;
                }
                pointerIdBits |= (1 << pointerId);
                final int action = computeInjectionAction(MotionEvent.ACTION_UP, i);
                sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
            }
        
    public voidsetNext(EventStreamTransformation next)

            mNext = next;
        
    public java.lang.StringtoString()

            return LOG_TAG;