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