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