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