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