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