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