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