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