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