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