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