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