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