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