TouchExplorer.java revision 12a024ca681d877fe16b7e087356f7aff175a218
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 static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END;
20import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START;
21
22import android.content.Context;
23import android.os.Handler;
24import android.util.Slog;
25import android.view.MotionEvent;
26import android.view.ViewConfiguration;
27import android.view.WindowManagerPolicy;
28import android.view.accessibility.AccessibilityEvent;
29import android.view.accessibility.AccessibilityManager;
30
31import com.android.server.accessibility.AccessibilityInputFilter.Explorer;
32import com.android.server.wm.InputFilter;
33
34import java.util.Arrays;
35
36/**
37 * This class is a strategy for performing touch exploration. It
38 * transforms the motion event stream by modifying, adding, replacing,
39 * and consuming certain events. The interaction model is:
40 *
41 * <ol>
42 *   <li>1. One finger moving around performs touch exploration.</li>
43 *   <li>2. Two close fingers moving in the same direction perform a drag.</li>
44 *   <li>3. Multi-finger gestures are delivered to view hierarchy.</li>
45 *   <li>4. Pointers that have not moved more than a specified distance after they
46 *          went down are considered inactive.</li>
47 *   <li>5. Two fingers moving too far from each other or in different directions
48 *          are considered a multi-finger gesture.</li>
49 *   <li>6. Tapping on the last touch explored location within given time and
50 *          distance slop performs a click.</li>
51 *   <li>7. Tapping and holding for a while on the last touch explored location within
52 *          given time and distance slop performs a long press.</li>
53 * <ol>
54 *
55 * @hide
56 */
57public class TouchExplorer implements Explorer {
58    private static final boolean DEBUG = false;
59
60    // Tag for logging received events.
61    private static final String LOG_TAG_RECEIVED = "TouchExplorer-RECEIVED";
62    // Tag for logging injected events.
63    private static final String LOG_TAG_INJECTED = "TouchExplorer-INJECTED";
64    // Tag for logging the current state.
65    private static final String LOG_TAG_STATE = "TouchExplorer-STATE";
66
67    // States this explorer can be in.
68    private static final int STATE_TOUCH_EXPLORING = 0x00000001;
69    private static final int STATE_DRAGGING = 0x00000002;
70    private static final int STATE_DELEGATING = 0x00000004;
71
72    // Invalid pointer ID.
73    private static final int INVALID_POINTER_ID = -1;
74
75    // The time slop in milliseconds for activating an item after it has
76    // been touch explored. Tapping on an item within this slop will perform
77    // a click and tapping and holding down a long press.
78    private static final long ACTIVATION_TIME_SLOP = 2000;
79
80    // This constant captures the current implementation detail that
81    // pointer IDs are between 0 and 31 inclusive (subject to change).
82    // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
83    private static final int MAX_POINTER_COUNT = 32;
84
85    // The minimum of the cosine between the vectors of two moving
86    // pointers so they can be considered moving in the same direction.
87    private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4)
88
89    // The delay for sending a hover enter event.
90    private static final long DELAY_SEND_HOVER_ENTER = 200;
91
92    // Constant referring to the ids bits of all pointers.
93    private static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF;
94
95    // Temporary array for storing pointer IDs.
96    private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT];
97
98    // The distance from the last touch explored location tapping within
99    // which would perform a click and tapping and holding a long press.
100    private final int mTouchExplorationTapSlop;
101
102    // The InputFilter this tracker is associated with i.e. the filter
103    // which delegates event processing to this touch explorer.
104    private final InputFilter mInputFilter;
105
106    // Helper class for tracking pointers on the screen, for example which
107    // pointers are down, which are active, etc.
108    private final PointerTracker mPointerTracker;
109
110    // Handle to the accessibility manager for firing accessibility events
111    // announcing touch exploration gesture start and end.
112    private final AccessibilityManager mAccessibilityManager;
113
114    // The last event that was received while performing touch exploration.
115    private MotionEvent mLastTouchExploreEvent;
116
117    // The current state of the touch explorer.
118    private int mCurrentState = STATE_TOUCH_EXPLORING;
119
120    // Flag whether a touch exploration gesture is in progress.
121    private boolean mTouchExploreGestureInProgress;
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 event.
130    private final SendHoverDelayed mSendHoverDelayed;
131
132    // Command for delayed sending of a long press.
133    private final PerformLongPressDelayed mPerformLongPressDelayed;
134
135    /**
136     * Creates a new instance.
137     *
138     * @param inputFilter The input filter associated with this explorer.
139     * @param context A context handle for accessing resources.
140     */
141    public TouchExplorer(InputFilter inputFilter, Context context) {
142        mInputFilter = inputFilter;
143        mTouchExplorationTapSlop =
144            ViewConfiguration.get(context).getScaledTouchExplorationTapSlop();
145        mPointerTracker = new PointerTracker(context);
146        mHandler = new Handler(context.getMainLooper());
147        mSendHoverDelayed = new SendHoverDelayed();
148        mPerformLongPressDelayed = new PerformLongPressDelayed();
149        mAccessibilityManager = AccessibilityManager.getInstance(context);
150    }
151
152    public void clear(MotionEvent event, int policyFlags) {
153        sendUpForInjectedDownPointers(event, policyFlags);
154        clear();
155    }
156
157    /**
158     * {@inheritDoc}
159     */
160    public void onMotionEvent(MotionEvent event, int policyFlags) {
161        if (DEBUG) {
162            Slog.d(LOG_TAG_RECEIVED, "Received event: " + event + ", policyFlags=0x"
163                    + Integer.toHexString(policyFlags));
164            Slog.d(LOG_TAG_STATE, getStateSymbolicName(mCurrentState));
165        }
166
167        // Keep track of the pointers's state.
168        mPointerTracker.onReceivedMotionEvent(event);
169
170        switch(mCurrentState) {
171            case STATE_TOUCH_EXPLORING: {
172                handleMotionEventStateTouchExploring(event, policyFlags);
173            } break;
174            case STATE_DRAGGING: {
175                handleMotionEventStateDragging(event, policyFlags);
176            } break;
177            case STATE_DELEGATING: {
178                handleMotionEventStateDelegating(event, policyFlags);
179            } break;
180            default: {
181                throw new IllegalStateException("Illegal state: " + mCurrentState);
182            }
183        }
184    }
185
186    /**
187     * Handles a motion event in touch exploring state.
188     *
189     * @param event The event to be handled.
190     * @param policyFlags The policy flags associated with the event.
191     */
192    private void handleMotionEventStateTouchExploring(MotionEvent event, int policyFlags) {
193        PointerTracker pointerTracker = mPointerTracker;
194        final int activePointerCount = pointerTracker.getActivePointerCount();
195
196        switch (event.getActionMasked()) {
197            case MotionEvent.ACTION_DOWN:
198            case MotionEvent.ACTION_POINTER_DOWN: {
199                switch (activePointerCount) {
200                    case 0: {
201                        throw new IllegalStateException("The must always be one active pointer in"
202                                + "touch exploring state!");
203                    }
204                    case 1: {
205                        mSendHoverDelayed.remove();
206                        // Send a hover for every finger down so the user gets feedback.
207                        final int pointerId = pointerTracker.getPrimaryActivePointerId();
208                        final int pointerIdBits = (1 << pointerId);
209                        final int lastAction = pointerTracker.getLastInjectedHoverAction();
210
211                        // Deliver hover enter with a delay to have a change to detect
212                        // whether the user actually starts a scrolling gesture.
213                        if (lastAction == MotionEvent.ACTION_HOVER_EXIT) {
214                            mSendHoverDelayed.post(event, MotionEvent.ACTION_HOVER_ENTER,
215                                    pointerIdBits, policyFlags, DELAY_SEND_HOVER_ENTER);
216                        } else {
217                            sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
218                                    policyFlags);
219                        }
220
221                        if (mLastTouchExploreEvent == null) {
222                            break;
223                        }
224
225                        // If more pointers down on the screen since the last touch
226                        // exploration we discard the last cached touch explore event.
227                        if (event.getPointerCount() != mLastTouchExploreEvent.getPointerCount()) {
228                            mLastTouchExploreEvent = null;
229                            break;
230                        }
231
232                        // If the down is in the time slop => schedule a long press.
233                        final long pointerDownTime =
234                            pointerTracker.getReceivedPointerDownTime(pointerId);
235                        final long lastExploreTime = mLastTouchExploreEvent.getEventTime();
236                        final long deltaTimeExplore = pointerDownTime - lastExploreTime;
237                        if (deltaTimeExplore <= ACTIVATION_TIME_SLOP) {
238                            mPerformLongPressDelayed.post(event, policyFlags,
239                                    ViewConfiguration.getLongPressTimeout());
240                            break;
241                        }
242                    } break;
243                    default: {
244                        /* do nothing - let the code for ACTION_MOVE decide what to do */
245                    } break;
246                }
247            } break;
248            case MotionEvent.ACTION_MOVE: {
249                final int pointerId = pointerTracker.getPrimaryActivePointerId();
250                final int pointerIndex = event.findPointerIndex(pointerId);
251                final int pointerIdBits = (1 << pointerId);
252                switch (activePointerCount) {
253                    case 0: {
254                        /* do nothing - no active pointers so we swallow the event */
255                    } break;
256                    case 1: {
257                        // Detect touch exploration gesture start by having one active pointer
258                        // that moved more than a given distance.
259                        if (!mTouchExploreGestureInProgress) {
260                            final float deltaX = pointerTracker.getReceivedPointerDownX(pointerId)
261                                - event.getX(pointerIndex);
262                            final float deltaY = pointerTracker.getReceivedPointerDownY(pointerId)
263                                - event.getY(pointerIndex);
264                            final double moveDelta = Math.hypot(deltaX, deltaY);
265
266                            if (moveDelta > mTouchExplorationTapSlop) {
267                                mTouchExploreGestureInProgress = true;
268                                sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
269                                // Make sure the scheduled down/move event is sent.
270                                mSendHoverDelayed.forceSendAndRemove();
271                                mPerformLongPressDelayed.remove();
272                                // If we have transitioned to exploring state from another one
273                                // we need to send a hover enter event here.
274                                final int lastAction = mPointerTracker.getLastInjectedHoverAction();
275                                if (lastAction == MotionEvent.ACTION_HOVER_EXIT) {
276                                    sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER,
277                                            pointerIdBits, policyFlags);
278                                }
279                                sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
280                                        policyFlags);
281                            }
282                        } else {
283                            // Touch exploration gesture in progress so send a hover event.
284                            sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
285                                    policyFlags);
286                        }
287
288                        // If the exploring pointer moved enough => cancel the long press.
289                        if (!mTouchExploreGestureInProgress && mLastTouchExploreEvent != null
290                                && mPerformLongPressDelayed.isPenidng()) {
291
292                            // If the pointer moved more than the tap slop => cancel long press.
293                            final float deltaX = mLastTouchExploreEvent.getX(pointerIndex)
294                                    - event.getX(pointerIndex);
295                            final float deltaY = mLastTouchExploreEvent.getY(pointerIndex)
296                                    - event.getY(pointerIndex);
297                            final float moveDelta = (float) Math.hypot(deltaX, deltaY);
298                            if (moveDelta > mTouchExplorationTapSlop) {
299                                mLastTouchExploreEvent = null;
300                                mPerformLongPressDelayed.remove();
301                                break;
302                            }
303                        }
304                    } break;
305                    case 2: {
306                        mSendHoverDelayed.remove();
307                        mPerformLongPressDelayed.remove();
308                        // We want to no longer hover over the location so subsequent
309                        // touch at the same spot will generate a hover enter.
310                        ensureHoverExitSent(event, pointerIdBits, policyFlags);
311
312                        if (isDraggingGesture(event)) {
313                            // Two pointers moving in the same direction within
314                            // a given distance perform a drag.
315                            mCurrentState = STATE_DRAGGING;
316                            if (mTouchExploreGestureInProgress) {
317                                sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
318                                mTouchExploreGestureInProgress = false;
319                            }
320                            mLastTouchExploreEvent = null;
321                            mDraggingPointerId = pointerId;
322                            sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits,
323                                    policyFlags);
324                        } else {
325                            // Two pointers moving arbitrary are delegated to the view hierarchy.
326                            mCurrentState = STATE_DELEGATING;
327                            mSendHoverDelayed.remove();
328                            if (mTouchExploreGestureInProgress) {
329                                sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
330                                mTouchExploreGestureInProgress = false;
331                            }
332                            mLastTouchExploreEvent = null;
333                            sendDownForAllActiveNotInjectedPointers(event, policyFlags);
334                        }
335                    } break;
336                    default: {
337                        mSendHoverDelayed.remove();
338                        mPerformLongPressDelayed.remove();
339                        // We want to no longer hover over the location so subsequent
340                        // touch at the same spot will generate a hover enter.
341                        ensureHoverExitSent(event, pointerIdBits, policyFlags);
342
343                        // More than two pointers are delegated to the view hierarchy.
344                        mCurrentState = STATE_DELEGATING;
345                        mSendHoverDelayed.remove();
346                        if (mTouchExploreGestureInProgress) {
347                            sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
348                            mTouchExploreGestureInProgress = false;
349                        }
350                        mLastTouchExploreEvent = null;
351                        sendDownForAllActiveNotInjectedPointers(event, policyFlags);
352                    }
353                }
354            } break;
355            case MotionEvent.ACTION_UP:
356            case MotionEvent.ACTION_POINTER_UP: {
357                final int pointerId = pointerTracker.getLastReceivedUpPointerId();
358                final int pointerIdBits = (1 << pointerId);
359                switch (activePointerCount) {
360                    case 0: {
361                        // If the pointer that went up was not active we have nothing to do.
362                        if (!pointerTracker.wasLastReceivedUpPointerActive()) {
363                            break;
364                        }
365
366                        mPerformLongPressDelayed.remove();
367
368                        // If touch exploring announce the end of the gesture.
369                        // Also do not click on the last explored location.
370                        if (mTouchExploreGestureInProgress) {
371                            mTouchExploreGestureInProgress = false;
372                            mSendHoverDelayed.forceSendAndRemove();
373                            ensureHoverExitSent(event, pointerIdBits, policyFlags);
374                            mLastTouchExploreEvent = MotionEvent.obtain(event);
375                            sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END);
376                            break;
377                        }
378
379                        // Detect whether to activate i.e. click on the last explored location.
380                        if (mLastTouchExploreEvent != null) {
381                            // If the down was not in the time slop => nothing else to do.
382                            final long eventTime =
383                                pointerTracker.getLastReceivedUpPointerDownTime();
384                            final long exploreTime = mLastTouchExploreEvent.getEventTime();
385                            final long deltaTime = eventTime - exploreTime;
386                            if (deltaTime > ACTIVATION_TIME_SLOP) {
387                                mSendHoverDelayed.forceSendAndRemove();
388                                ensureHoverExitSent(event, pointerIdBits, policyFlags);
389                                mLastTouchExploreEvent = MotionEvent.obtain(event);
390                                break;
391                            }
392
393                            // If a tap is farther than the tap slop => nothing to do.
394                            final int pointerIndex = event.findPointerIndex(pointerId);
395                            final float deltaX = mLastTouchExploreEvent.getX(pointerIndex)
396                                    - event.getX(pointerIndex);
397                            final float deltaY = mLastTouchExploreEvent.getY(pointerIndex)
398                                    - event.getY(pointerIndex);
399                            final float deltaMove = (float) Math.hypot(deltaX, deltaY);
400                            if (deltaMove > mTouchExplorationTapSlop) {
401                                mSendHoverDelayed.forceSendAndRemove();
402                                ensureHoverExitSent(event, pointerIdBits, policyFlags);
403                                mLastTouchExploreEvent = MotionEvent.obtain(event);
404                                break;
405                            }
406
407                            // This is a tap so do not send hover events since
408                            // this events will result in firing the corresponding
409                            // accessibility events confusing the user about what
410                            // is actually clicked.
411                            mSendHoverDelayed.remove();
412                            ensureHoverExitSent(event, pointerIdBits, policyFlags);
413
414                            // All preconditions are met, so click the last explored location.
415                            sendActionDownAndUp(mLastTouchExploreEvent, policyFlags);
416                            mLastTouchExploreEvent = null;
417                        } else {
418                            mSendHoverDelayed.forceSendAndRemove();
419                            ensureHoverExitSent(event, pointerIdBits, policyFlags);
420                            mLastTouchExploreEvent = MotionEvent.obtain(event);
421                        }
422                    } break;
423                }
424            } break;
425            case MotionEvent.ACTION_CANCEL: {
426                mSendHoverDelayed.remove();
427                mPerformLongPressDelayed.remove();
428                final int pointerId = pointerTracker.getPrimaryActivePointerId();
429                final int pointerIdBits = (1 << pointerId);
430                ensureHoverExitSent(event, pointerIdBits, policyFlags);
431                clear();
432            } break;
433        }
434    }
435
436    /**
437     * Handles a motion event in dragging state.
438     *
439     * @param event The event to be handled.
440     * @param policyFlags The policy flags associated with the event.
441     */
442    private void handleMotionEventStateDragging(MotionEvent event, int policyFlags) {
443        final int pointerIdBits = (1 << mDraggingPointerId);
444        switch (event.getActionMasked()) {
445            case MotionEvent.ACTION_DOWN: {
446                throw new IllegalStateException("Dragging state can be reached only if two "
447                        + "pointers are already down");
448            }
449            case MotionEvent.ACTION_POINTER_DOWN: {
450                // We are in dragging state so we have two pointers and another one
451                // goes down => delegate the three pointers to the view hierarchy
452                mCurrentState = STATE_DELEGATING;
453                sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
454                sendDownForAllActiveNotInjectedPointers(event, policyFlags);
455            } break;
456            case MotionEvent.ACTION_MOVE: {
457                final int activePointerCount = mPointerTracker.getActivePointerCount();
458                switch (activePointerCount) {
459                    case 2: {
460                        if (isDraggingGesture(event)) {
461                            // If still dragging send a drag event.
462                            sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits,
463                                    policyFlags);
464                        } else {
465                            // The two pointers are moving either in different directions or
466                            // no close enough => delegate the gesture to the view hierarchy.
467                            mCurrentState = STATE_DELEGATING;
468                            // Send an event to the end of the drag gesture.
469                            sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
470                                    policyFlags);
471                            // Deliver all active pointers to the view hierarchy.
472                            sendDownForAllActiveNotInjectedPointers(event, policyFlags);
473                        }
474                    } break;
475                    default: {
476                        mCurrentState = STATE_DELEGATING;
477                        // Send an event to the end of the drag gesture.
478                        sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
479                                policyFlags);
480                        // Deliver all active pointers to the view hierarchy.
481                        sendDownForAllActiveNotInjectedPointers(event, policyFlags);
482                    }
483                }
484            } break;
485            case MotionEvent.ACTION_POINTER_UP: {
486                mCurrentState = STATE_TOUCH_EXPLORING;
487                // Send an event to the end of the drag gesture.
488                sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
489             } break;
490            case MotionEvent.ACTION_CANCEL: {
491                clear();
492            } break;
493        }
494    }
495
496    /**
497     * Handles a motion event in delegating state.
498     *
499     * @param event The event to be handled.
500     * @param policyFlags The policy flags associated with the event.
501     */
502    public void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) {
503        switch (event.getActionMasked()) {
504            case MotionEvent.ACTION_DOWN: {
505                throw new IllegalStateException("Delegating state can only be reached if "
506                        + "there is at least one pointer down!");
507            }
508            case MotionEvent.ACTION_UP: {
509                mCurrentState = STATE_TOUCH_EXPLORING;
510            } break;
511            case MotionEvent.ACTION_MOVE: {
512                // Check  whether some other pointer became active because they have moved
513                // a given distance and if such exist send them to the view hierarchy
514                final int notInjectedCount = mPointerTracker.getNotInjectedActivePointerCount();
515                if (notInjectedCount > 0) {
516                    MotionEvent prototype = MotionEvent.obtain(event);
517                    sendDownForAllActiveNotInjectedPointers(prototype, policyFlags);
518                }
519            } break;
520            case MotionEvent.ACTION_POINTER_UP: {
521                // No active pointers => go to initial state.
522                if (mPointerTracker.getActivePointerCount() == 0) {
523                    mCurrentState = STATE_TOUCH_EXPLORING;
524                }
525            } break;
526            case MotionEvent.ACTION_CANCEL: {
527                clear();
528            } break;
529        }
530        // Deliver the event striping out inactive pointers.
531        sendMotionEventStripInactivePointers(event, policyFlags);
532    }
533
534    /**
535     * Sends down events to the view hierarchy for all active pointers which are
536     * not already being delivered i.e. pointers that are not yet injected.
537     *
538     * @param prototype The prototype from which to create the injected events.
539     * @param policyFlags The policy flags associated with the event.
540     */
541    private void sendDownForAllActiveNotInjectedPointers(MotionEvent prototype, int policyFlags) {
542        final PointerTracker pointerTracker = mPointerTracker;
543        int pointerIdBits = 0;
544        final int pointerCount = prototype.getPointerCount();
545
546        // Find which pointers are already injected.
547        for (int i = 0; i < pointerCount; i++) {
548            final int pointerId = prototype.getPointerId(i);
549            if (pointerTracker.isInjectedPointerDown(pointerId)) {
550                pointerIdBits |= (1 << pointerId);
551            }
552        }
553
554        // Inject the active and not injected pointers.
555        for (int i = 0; i < pointerCount; i++) {
556            final int pointerId = prototype.getPointerId(i);
557            // Skip inactive pointers.
558            if (!pointerTracker.isActivePointer(pointerId)) {
559                continue;
560            }
561            // Do not send event for already delivered pointers.
562            if (pointerTracker.isInjectedPointerDown(pointerId)) {
563                continue;
564            }
565            pointerIdBits |= (1 << pointerId);
566            final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
567            sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
568        }
569    }
570
571    /**
572     * Ensures that hover exit has been sent.
573     *
574     * @param prototype The prototype from which to create the injected events.
575     * @param pointerIdBits The bits of the pointers to send.
576     * @param policyFlags The policy flags associated with the event.
577     */
578    private void ensureHoverExitSent(MotionEvent prototype, int pointerIdBits, int policyFlags) {
579        final int lastAction = mPointerTracker.getLastInjectedHoverAction();
580        if (lastAction != MotionEvent.ACTION_HOVER_EXIT) {
581            sendMotionEvent(prototype, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits,
582                    policyFlags);
583        }
584    }
585
586    /**
587     * Sends up events to the view hierarchy for all active pointers which are
588     * already being delivered i.e. pointers that are injected.
589     *
590     * @param prototype The prototype from which to create the injected events.
591     * @param policyFlags The policy flags associated with the event.
592     */
593    private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
594        final PointerTracker pointerTracker = mPointerTracker;
595        int pointerIdBits = 0;
596        final int pointerCount = prototype.getPointerCount();
597        for (int i = 0; i < pointerCount; i++) {
598            final int pointerId = prototype.getPointerId(i);
599            // Skip non injected down pointers.
600            if (!pointerTracker.isInjectedPointerDown(pointerId)) {
601                continue;
602            }
603            pointerIdBits |= (1 << pointerId);
604            final int action = computeInjectionAction(MotionEvent.ACTION_UP, i);
605            sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
606        }
607    }
608
609    /**
610     * Sends a motion event by first stripping the inactive pointers.
611     *
612     * @param prototype The prototype from which to create the injected event.
613     * @param policyFlags The policy flags associated with the event.
614     */
615    private void sendMotionEventStripInactivePointers(MotionEvent prototype, int policyFlags) {
616        PointerTracker pointerTracker = mPointerTracker;
617
618        // All pointers active therefore we just inject the event as is.
619        if (prototype.getPointerCount() == pointerTracker.getActivePointerCount()) {
620            sendMotionEvent(prototype, prototype.getAction(), ALL_POINTER_ID_BITS, policyFlags);
621            return;
622        }
623
624        // No active pointers and the one that just went up was not
625        // active, therefore we have nothing to do.
626        if (pointerTracker.getActivePointerCount() == 0
627                && !pointerTracker.wasLastReceivedUpPointerActive()) {
628            return;
629        }
630
631        // If the action pointer going up/down is not active we have nothing to do.
632        // However, for moves we keep going to report moves of active pointers.
633        final int actionMasked = prototype.getActionMasked();
634        final int actionPointerId = prototype.getPointerId(prototype.getActionIndex());
635        if (actionMasked != MotionEvent.ACTION_MOVE) {
636            if (!pointerTracker.isActiveOrWasLastActiveUpPointer(actionPointerId)) {
637                return;
638            }
639        }
640
641        // If the pointer is active or the pointer that just went up
642        // was active we keep the pointer data in the event.
643        int pointerIdBits = 0;
644        final int pointerCount = prototype.getPointerCount();
645        for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) {
646            final int pointerId = prototype.getPointerId(pointerIndex);
647            if (pointerTracker.isActiveOrWasLastActiveUpPointer(pointerId)) {
648                pointerIdBits |= (1 << pointerId);
649            }
650        }
651        sendMotionEvent(prototype, prototype.getAction(), pointerIdBits, policyFlags);
652    }
653
654    /**
655     * Sends an up and down events.
656     *
657     * @param prototype The prototype from which to create the injected events.
658     * @param policyFlags The policy flags associated with the event.
659     */
660    private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) {
661        // Tap with the pointer that last went up - we may have inactive pointers.
662        final int pointerId = mPointerTracker.getLastReceivedUpPointerId();
663        final int pointerIdBits = (1 << pointerId);
664        sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags);
665        sendMotionEvent(prototype, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
666    }
667
668    /**
669     * Sends an event.
670     *
671     * @param prototype The prototype from which to create the injected events.
672     * @param action The action of the event.
673     * @param pointerIdBits The bits of the pointers to send.
674     * @param policyFlags The policy flags associated with the event.
675     */
676    private void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits,
677            int policyFlags) {
678        prototype.setAction(action);
679
680        MotionEvent event = null;
681        if (pointerIdBits == ALL_POINTER_ID_BITS) {
682            event = prototype;
683        } else {
684            event = prototype.split(pointerIdBits);
685        }
686        if (action == MotionEvent.ACTION_DOWN) {
687            event.setDownTime(event.getEventTime());
688        } else {
689            event.setDownTime(mPointerTracker.getLastInjectedDownEventTime());
690        }
691
692        if (DEBUG) {
693            Slog.d(LOG_TAG_INJECTED, "Injecting event: " + event + ", policyFlags=0x"
694                    + Integer.toHexString(policyFlags));
695        }
696
697        // Make sure that the user will see the event.
698        policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
699        mPointerTracker.onInjectedMotionEvent(event);
700        mInputFilter.sendInputEvent(event, policyFlags);
701
702        if (event != prototype) {
703            event.recycle();
704        }
705    }
706
707    /**
708     * Computes the action for an injected event based on a masked action
709     * and a pointer index.
710     *
711     * @param actionMasked The masked action.
712     * @param pointerIndex The index of the pointer which has changed.
713     * @return The action to be used for injection.
714     */
715    private int computeInjectionAction(int actionMasked, int pointerIndex) {
716        switch (actionMasked) {
717            case MotionEvent.ACTION_DOWN:
718            case MotionEvent.ACTION_POINTER_DOWN: {
719                PointerTracker pointerTracker = mPointerTracker;
720                // Compute the action based on how many down pointers are injected.
721                if (pointerTracker.getInjectedPointerDownCount() == 0) {
722                    return MotionEvent.ACTION_DOWN;
723                } else {
724                    return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
725                        | MotionEvent.ACTION_POINTER_DOWN;
726                }
727            }
728            case MotionEvent.ACTION_POINTER_UP: {
729                PointerTracker pointerTracker = mPointerTracker;
730                // Compute the action based on how many down pointers are injected.
731                if (pointerTracker.getInjectedPointerDownCount() == 1) {
732                    return MotionEvent.ACTION_UP;
733                } else {
734                    return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
735                        | MotionEvent.ACTION_POINTER_UP;
736                }
737            }
738            default:
739                return actionMasked;
740        }
741    }
742
743    /**
744     * Determines whether a two pointer gesture is a dragging one.
745     *
746     * @param event The event with the pointer data.
747     * @return True if the gesture is a dragging one.
748     */
749    private boolean isDraggingGesture(MotionEvent event) {
750        PointerTracker pointerTracker = mPointerTracker;
751        int[] pointerIds = mTempPointerIds;
752        pointerTracker.populateActivePointerIds(pointerIds);
753
754        final int firstPtrIndex = event.findPointerIndex(pointerIds[0]);
755        final int secondPtrIndex = event.findPointerIndex(pointerIds[1]);
756
757        final float firstPtrX = event.getX(firstPtrIndex);
758        final float firstPtrY = event.getY(firstPtrIndex);
759        final float secondPtrX = event.getX(secondPtrIndex);
760        final float secondPtrY = event.getY(secondPtrIndex);
761
762        // Check if the pointers are moving in the same direction.
763        final float firstDeltaX =
764            firstPtrX - pointerTracker.getReceivedPointerDownX(firstPtrIndex);
765        final float firstDeltaY =
766            firstPtrY - pointerTracker.getReceivedPointerDownY(firstPtrIndex);
767
768        if (firstDeltaX == 0 && firstDeltaY == 0) {
769            return true;
770        }
771
772        final float firstMagnitude =
773            (float) Math.sqrt(firstDeltaX * firstDeltaX + firstDeltaY * firstDeltaY);
774        final float firstXNormalized =
775            (firstMagnitude > 0) ? firstDeltaX / firstMagnitude : firstDeltaX;
776        final float firstYNormalized =
777            (firstMagnitude > 0) ? firstDeltaY / firstMagnitude : firstDeltaY;
778
779        final float secondDeltaX =
780            secondPtrX - pointerTracker.getReceivedPointerDownX(secondPtrIndex);
781        final float secondDeltaY =
782            secondPtrY - pointerTracker.getReceivedPointerDownY(secondPtrIndex);
783
784        if (secondDeltaX == 0 && secondDeltaY == 0) {
785            return true;
786        }
787
788        final float secondMagnitude =
789            (float) Math.sqrt(secondDeltaX * secondDeltaX + secondDeltaY * secondDeltaY);
790        final float secondXNormalized =
791            (secondMagnitude > 0) ? secondDeltaX / secondMagnitude : secondDeltaX;
792        final float secondYNormalized =
793            (secondMagnitude > 0) ? secondDeltaY / secondMagnitude : secondDeltaY;
794
795        final float angleCos =
796            firstXNormalized * secondXNormalized + firstYNormalized * secondYNormalized;
797
798        if (angleCos < MAX_DRAGGING_ANGLE_COS) {
799            return false;
800        }
801
802        return true;
803    }
804
805   /**
806    * Sends an event announcing the start/end of a touch exploration gesture.
807    *
808    * @param eventType The type of the event to send.
809    */
810    private void sendAccessibilityEvent(int eventType) {
811        AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
812        mAccessibilityManager.sendAccessibilityEvent(event);
813    }
814
815    /**
816     * Clears the internal state of this explorer.
817     */
818    public void clear() {
819        mSendHoverDelayed.remove();
820        mPerformLongPressDelayed.remove();
821        mPointerTracker.clear();
822        mLastTouchExploreEvent = null;
823        mCurrentState = STATE_TOUCH_EXPLORING;
824        mTouchExploreGestureInProgress = false;
825        mDraggingPointerId = INVALID_POINTER_ID;
826    }
827
828    /**
829     * Gets the symbolic name of a state.
830     *
831     * @param state A state.
832     * @return The state symbolic name.
833     */
834    private static String getStateSymbolicName(int state) {
835        switch (state) {
836            case STATE_TOUCH_EXPLORING:
837                return "STATE_TOUCH_EXPLORING";
838            case STATE_DRAGGING:
839                return "STATE_DRAGGING";
840            case STATE_DELEGATING:
841                return "STATE_DELEGATING";
842            default:
843                throw new IllegalArgumentException("Unknown state: " + state);
844        }
845    }
846
847    /**
848     * Helper class for tracking pointers and more specifically which of
849     * them are currently down, which are active, and which are delivered
850     * to the view hierarchy. The enclosing {@link TouchExplorer} uses the
851     * pointer state reported by this class to perform touch exploration.
852     * <p>
853     * The main purpose of this class is to allow the touch explorer to
854     * disregard pointers put down by accident by the user and not being
855     * involved in the interaction. For example, a blind user grabs the
856     * device with her left hand such that she touches the screen and she
857     * uses her right hand's index finger to explore the screen content.
858     * In this scenario the touches generated by the left hand are to be
859     * ignored.
860     */
861    class PointerTracker {
862        private static final String LOG_TAG = "PointerTracker";
863
864        // The coefficient by which to multiply
865        // ViewConfiguration.#getScaledTouchSlop()
866        // to compute #mThresholdActivePointer.
867        private static final int COEFFICIENT_ACTIVE_POINTER = 2;
868
869        // Pointers that moved less than mThresholdActivePointer
870        // are considered active i.e. are ignored.
871        private final double mThresholdActivePointer;
872
873        // Keep track of where and when a pointer went down.
874        private final float[] mReceivedPointerDownX = new float[MAX_POINTER_COUNT];
875        private final float[] mReceivedPointerDownY = new float[MAX_POINTER_COUNT];
876        private final long[] mReceivedPointerDownTime = new long[MAX_POINTER_COUNT];
877
878        // Which pointers are down.
879        private int mReceivedPointersDown;
880
881        // Which down pointers are active.
882        private int mActivePointers;
883
884        // Primary active pointer which is either the first that went down
885        // or if it goes up the next active that most recently went down.
886        private int mPrimaryActivePointerId;
887
888        // Flag indicating that there is at least one active pointer moving.
889        private boolean mHasMovingActivePointer;
890
891        // Keep track of which pointers sent to the system are down.
892        private int mInjectedPointersDown;
893
894        // Keep track of the last up pointer data.
895        private long mLastReceivedUpPointerDownTime;
896        private int mLastReceivedUpPointerId;
897        private boolean mLastReceivedUpPointerActive;
898
899        // The time of the last injected down.
900        private long mLastInjectedDownEventTime;
901
902        // The action of the last injected hover event.
903        private int mLastInjectedHoverEventAction = MotionEvent.ACTION_HOVER_EXIT;
904
905        /**
906         * Creates a new instance.
907         *
908         * @param context Context for looking up resources.
909         */
910        public PointerTracker(Context context) {
911            mThresholdActivePointer =
912                ViewConfiguration.get(context).getScaledTouchSlop() * COEFFICIENT_ACTIVE_POINTER;
913        }
914
915        /**
916         * Clears the internals state.
917         */
918        public void clear() {
919            Arrays.fill(mReceivedPointerDownX, 0);
920            Arrays.fill(mReceivedPointerDownY, 0);
921            Arrays.fill(mReceivedPointerDownTime, 0);
922            mReceivedPointersDown = 0;
923            mActivePointers = 0;
924            mPrimaryActivePointerId = 0;
925            mHasMovingActivePointer = false;
926            mInjectedPointersDown = 0;
927            mLastReceivedUpPointerDownTime = 0;
928            mLastReceivedUpPointerId = 0;
929            mLastReceivedUpPointerActive = false;
930        }
931
932        /**
933         * Processes a received {@link MotionEvent} event.
934         *
935         * @param event The event to process.
936         */
937        public void onReceivedMotionEvent(MotionEvent event) {
938            final int action = event.getActionMasked();
939            switch (action) {
940                case MotionEvent.ACTION_DOWN: {
941                    // New gesture so restart tracking injected down pointers.
942                    mInjectedPointersDown = 0;
943                    handleReceivedPointerDown(event.getActionIndex(), event);
944                } break;
945                case MotionEvent.ACTION_POINTER_DOWN: {
946                    handleReceivedPointerDown(event.getActionIndex(), event);
947                } break;
948                case MotionEvent.ACTION_MOVE: {
949                    handleReceivedPointerMove(event);
950                } break;
951                case MotionEvent.ACTION_UP: {
952                    handleReceivedPointerUp(event.getActionIndex(), event);
953                } break;
954                case MotionEvent.ACTION_POINTER_UP: {
955                    handleReceivedPointerUp(event.getActionIndex(), event);
956                } break;
957            }
958            if (DEBUG) {
959                Slog.i(LOG_TAG, "Received pointer: " + toString());
960            }
961        }
962
963        /**
964         * Processes an injected {@link MotionEvent} event.
965         *
966         * @param event The event to process.
967         */
968        public void onInjectedMotionEvent(MotionEvent event) {
969            final int action = event.getActionMasked();
970            switch (action) {
971                case MotionEvent.ACTION_DOWN: {
972                    handleInjectedPointerDown(event.getActionIndex(), event);
973                    mLastInjectedDownEventTime = event.getDownTime();
974                } break;
975                case MotionEvent.ACTION_POINTER_DOWN: {
976                    handleInjectedPointerDown(event.getActionIndex(), event);
977                } break;
978                case MotionEvent.ACTION_UP: {
979                    handleInjectedPointerUp(event.getActionIndex(), event);
980                } break;
981                case MotionEvent.ACTION_POINTER_UP: {
982                    handleInjectedPointerUp(event.getActionIndex(), event);
983                } break;
984                case MotionEvent.ACTION_HOVER_ENTER:
985                case MotionEvent.ACTION_HOVER_MOVE:
986                case MotionEvent.ACTION_HOVER_EXIT: {
987                    mLastInjectedHoverEventAction = event.getActionMasked();
988                } break;
989            }
990            if (DEBUG) {
991                Slog.i(LOG_TAG, "Injected pointer: " + toString());
992            }
993        }
994
995        /**
996         * @return The number of received pointers that are down.
997         */
998        public int getReceivedPointerDownCount() {
999            return Integer.bitCount(mReceivedPointersDown);
1000        }
1001
1002        /**
1003         * @return The number of down input  pointers that are active.
1004         */
1005        public int getActivePointerCount() {
1006            return Integer.bitCount(mActivePointers);
1007        }
1008
1009        /**
1010         * Whether an received pointer is down.
1011         *
1012         * @param pointerId The unique pointer id.
1013         * @return True if the pointer is down.
1014         */
1015        public boolean isReceivedPointerDown(int pointerId) {
1016            final int pointerFlag = (1 << pointerId);
1017            return (mReceivedPointersDown & pointerFlag) != 0;
1018        }
1019
1020        /**
1021         * Whether an injected pointer is down.
1022         *
1023         * @param pointerId The unique pointer id.
1024         * @return True if the pointer is down.
1025         */
1026        public boolean isInjectedPointerDown(int pointerId) {
1027            final int pointerFlag = (1 << pointerId);
1028            return (mInjectedPointersDown & pointerFlag) != 0;
1029        }
1030
1031        /**
1032         * @return The number of down pointers injected to the view hierarchy.
1033         */
1034        public int getInjectedPointerDownCount() {
1035            return Integer.bitCount(mInjectedPointersDown);
1036        }
1037
1038        /**
1039         * Whether an input pointer is active.
1040         *
1041         * @param pointerId The unique pointer id.
1042         * @return True if the pointer is active.
1043         */
1044        public boolean isActivePointer(int pointerId) {
1045            final int pointerFlag = (1 << pointerId);
1046            return (mActivePointers & pointerFlag) != 0;
1047        }
1048
1049        /**
1050         * @param pointerId The unique pointer id.
1051         * @return The X coordinate where the pointer went down.
1052         */
1053        public float getReceivedPointerDownX(int pointerId) {
1054            return mReceivedPointerDownX[pointerId];
1055        }
1056
1057        /**
1058         * @param pointerId The unique pointer id.
1059         * @return The Y coordinate where the pointer went down.
1060         */
1061        public float getReceivedPointerDownY(int pointerId) {
1062            return mReceivedPointerDownY[pointerId];
1063        }
1064
1065        /**
1066         * @param pointerId The unique pointer id.
1067         * @return The time when the pointer went down.
1068         */
1069        public long getReceivedPointerDownTime(int pointerId) {
1070            return mReceivedPointerDownTime[pointerId];
1071        }
1072
1073        /**
1074         * @return The id of the primary pointer.
1075         */
1076        public int getPrimaryActivePointerId() {
1077            if (mPrimaryActivePointerId == INVALID_POINTER_ID) {
1078                mPrimaryActivePointerId = findPrimaryActivePointer();
1079            }
1080            return mPrimaryActivePointerId;
1081        }
1082
1083        /**
1084         * @return The time when the last up received pointer went down.
1085         */
1086        public long getLastReceivedUpPointerDownTime() {
1087            return mLastReceivedUpPointerDownTime;
1088        }
1089
1090        /**
1091         * @return The id of the last received pointer that went up.
1092         */
1093        public int getLastReceivedUpPointerId() {
1094            return mLastReceivedUpPointerId;
1095        }
1096
1097        /**
1098         * @return Whether the last received pointer that went up was active.
1099         */
1100        public boolean wasLastReceivedUpPointerActive() {
1101            return mLastReceivedUpPointerActive;
1102        }
1103
1104        /**
1105         * @return The time of the last injected down event.
1106         */
1107        public long getLastInjectedDownEventTime() {
1108            return mLastInjectedDownEventTime;
1109        }
1110
1111        /**
1112         * @return The action of the last injected hover event.
1113         */
1114        public int getLastInjectedHoverAction() {
1115            return mLastInjectedHoverEventAction;
1116        }
1117
1118        /**
1119         * Populates the active pointer IDs to the given array.
1120         * <p>
1121         * Note: The client is responsible for providing large enough array.
1122         *
1123         * @param outPointerIds The array to which to write the active pointers.
1124         */
1125        public void populateActivePointerIds(int[] outPointerIds) {
1126            int index = 0;
1127            for (int idBits = mActivePointers; idBits != 0; ) {
1128                final int id = Integer.numberOfTrailingZeros(idBits);
1129                idBits &= ~(1 << id);
1130                outPointerIds[index] = id;
1131                index++;
1132            }
1133        }
1134
1135        /**
1136         * @return The number of non injected active pointers.
1137         */
1138        public int getNotInjectedActivePointerCount() {
1139            final int pointerState = mActivePointers & ~mInjectedPointersDown;
1140            return Integer.bitCount(pointerState);
1141        }
1142
1143        /**
1144         * @param pointerId The unique pointer id.
1145         * @return Whether the pointer is active or was the last active than went up.
1146         */
1147        private boolean isActiveOrWasLastActiveUpPointer(int pointerId) {
1148            return (isActivePointer(pointerId)
1149                    || (mLastReceivedUpPointerId == pointerId
1150                            && mLastReceivedUpPointerActive));
1151        }
1152
1153        /**
1154         * Handles a received pointer down event.
1155         *
1156         * @param pointerIndex The index of the pointer that has changed.
1157         * @param event The event to be handled.
1158         */
1159        private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) {
1160            final int pointerId = event.getPointerId(pointerIndex);
1161            final int pointerFlag = (1 << pointerId);
1162
1163            mLastReceivedUpPointerId = 0;
1164            mLastReceivedUpPointerDownTime = 0;
1165            mLastReceivedUpPointerActive = false;
1166
1167            mReceivedPointersDown |= pointerFlag;
1168            mReceivedPointerDownX[pointerId] = event.getX(pointerIndex);
1169            mReceivedPointerDownY[pointerId] = event.getY(pointerIndex);
1170            mReceivedPointerDownTime[pointerId] = event.getEventTime();
1171
1172            if (!mHasMovingActivePointer) {
1173                // If still no moving active pointers every
1174                // down pointer is the only active one.
1175                mActivePointers = pointerFlag;
1176                mPrimaryActivePointerId = pointerId;
1177            } else {
1178                // If at least one moving active pointer every
1179                // subsequent down pointer is active.
1180                mActivePointers |= pointerFlag;
1181            }
1182        }
1183
1184        /**
1185         * Handles a received pointer move event.
1186         *
1187         * @param event The event to be handled.
1188         */
1189        private void handleReceivedPointerMove(MotionEvent event) {
1190            detectActivePointers(event);
1191        }
1192
1193        /**
1194         * Handles a received pointer up event.
1195         *
1196         * @param pointerIndex The index of the pointer that has changed.
1197         * @param event The event to be handled.
1198         */
1199        private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) {
1200            final int pointerId = event.getPointerId(pointerIndex);
1201            final int pointerFlag = (1 << pointerId);
1202
1203            mLastReceivedUpPointerId = pointerId;
1204            mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId);
1205            mLastReceivedUpPointerActive = isActivePointer(pointerId);
1206
1207            mReceivedPointersDown &= ~pointerFlag;
1208            mActivePointers &= ~pointerFlag;
1209            mReceivedPointerDownX[pointerId] = 0;
1210            mReceivedPointerDownY[pointerId] = 0;
1211            mReceivedPointerDownTime[pointerId] = 0;
1212
1213            if (mActivePointers == 0) {
1214                mHasMovingActivePointer = false;
1215            }
1216            if (mPrimaryActivePointerId == pointerId) {
1217                mPrimaryActivePointerId = INVALID_POINTER_ID;
1218            }
1219        }
1220
1221        /**
1222         * Handles a injected pointer down event.
1223         *
1224         * @param pointerIndex The index of the pointer that has changed.
1225         * @param event The event to be handled.
1226         */
1227        private void handleInjectedPointerDown(int pointerIndex, MotionEvent event) {
1228            final int pointerId = event.getPointerId(pointerIndex);
1229            final int pointerFlag = (1 << pointerId);
1230            mInjectedPointersDown |= pointerFlag;
1231        }
1232
1233        /**
1234         * Handles a injected pointer up event.
1235         *
1236         * @param pointerIndex The index of the pointer that has changed.
1237         * @param event The event to be handled.
1238         */
1239        private void handleInjectedPointerUp(int pointerIndex, MotionEvent event) {
1240            final int pointerId = event.getPointerId(pointerIndex);
1241            final int pointerFlag = (1 << pointerId);
1242            mInjectedPointersDown &= ~pointerFlag;
1243            if (mInjectedPointersDown == 0) {
1244                mLastInjectedDownEventTime = 0;
1245            }
1246        }
1247
1248        /**
1249         * Detects the active pointers in an event.
1250         *
1251         * @param event The event to examine.
1252         */
1253        private void detectActivePointers(MotionEvent event) {
1254            for (int i = 0, count = event.getPointerCount(); i < count; i++) {
1255                final int pointerId = event.getPointerId(i);
1256                if (mHasMovingActivePointer) {
1257                    // If already active => nothing to do.
1258                    if (isActivePointer(pointerId)) {
1259                        continue;
1260                    }
1261                }
1262                // Active pointers are ones that moved more than a given threshold.
1263                final float pointerDeltaMove = computePointerDeltaMove(i, event);
1264                if (pointerDeltaMove > mThresholdActivePointer) {
1265                    final int pointerFlag = (1 << pointerId);
1266                    mActivePointers |= pointerFlag;
1267                    mHasMovingActivePointer = true;
1268                }
1269            }
1270        }
1271
1272        /**
1273         * @return The primary active pointer.
1274         */
1275        private int findPrimaryActivePointer() {
1276            int primaryActivePointerId = INVALID_POINTER_ID;
1277            long minDownTime = Long.MAX_VALUE;
1278            // Find the active pointer that went down first.
1279            for (int i = 0, count = mReceivedPointerDownTime.length; i < count; i++) {
1280                if (isActivePointer(i)) {
1281                    final long downPointerTime = mReceivedPointerDownTime[i];
1282                    if (downPointerTime < minDownTime) {
1283                        minDownTime = downPointerTime;
1284                        primaryActivePointerId = i;
1285                    }
1286                }
1287            }
1288            return primaryActivePointerId;
1289        }
1290
1291        /**
1292         * Computes the move for a given action pointer index since the
1293         * corresponding pointer went down.
1294         *
1295         * @param pointerIndex The action pointer index.
1296         * @param event The event to examine.
1297         * @return The distance the pointer has moved.
1298         */
1299        private float computePointerDeltaMove(int pointerIndex, MotionEvent event) {
1300            final int pointerId = event.getPointerId(pointerIndex);
1301            final float deltaX = event.getX(pointerIndex) - mReceivedPointerDownX[pointerId];
1302            final float deltaY = event.getY(pointerIndex) - mReceivedPointerDownY[pointerId];
1303            return (float) Math.hypot(deltaX, deltaY);
1304        }
1305
1306        @Override
1307        public String toString() {
1308            StringBuilder builder = new StringBuilder();
1309            builder.append("=========================");
1310            builder.append("\nDown pointers #");
1311            builder.append(getReceivedPointerDownCount());
1312            builder.append(" [ ");
1313            for (int i = 0; i < MAX_POINTER_COUNT; i++) {
1314                if (isReceivedPointerDown(i)) {
1315                    builder.append(i);
1316                    builder.append(" ");
1317                }
1318            }
1319            builder.append("]");
1320            builder.append("\nActive pointers #");
1321            builder.append(getActivePointerCount());
1322            builder.append(" [ ");
1323            for (int i = 0; i < MAX_POINTER_COUNT; i++) {
1324                if (isActivePointer(i)) {
1325                    builder.append(i);
1326                    builder.append(" ");
1327                }
1328            }
1329            builder.append("]");
1330            builder.append("\nPrimary active pointer id [ ");
1331            builder.append(getPrimaryActivePointerId());
1332            builder.append(" ]");
1333            builder.append("\n=========================");
1334            return builder.toString();
1335        }
1336    }
1337
1338    /**
1339     * Class for delayed sending of long press.
1340     */
1341    private final class PerformLongPressDelayed implements Runnable {
1342        private MotionEvent mEvent;
1343        private int mPolicyFlags;
1344
1345        public void post(MotionEvent prototype, int policyFlags, long delay) {
1346            mEvent = MotionEvent.obtain(prototype);
1347            mPolicyFlags = policyFlags;
1348            mHandler.postDelayed(this, delay);
1349        }
1350
1351        public void remove() {
1352            if (isPenidng()) {
1353                mHandler.removeCallbacks(this);
1354                clear();
1355            }
1356        }
1357
1358        private boolean isPenidng() {
1359            return (mEvent != null);
1360        }
1361
1362        @Override
1363        public void run() {
1364            mCurrentState = STATE_DELEGATING;
1365            // Make sure the scheduled hover exit is delivered.
1366            mSendHoverDelayed.remove();
1367            final int pointerId = mPointerTracker.getPrimaryActivePointerId();
1368            final int pointerIdBits = (1 << pointerId);
1369            ensureHoverExitSent(mEvent, pointerIdBits, mPolicyFlags);
1370
1371            sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags);
1372            mTouchExploreGestureInProgress = false;
1373            mLastTouchExploreEvent = null;
1374            clear();
1375        }
1376
1377        private void clear() {
1378            if (!isPenidng()) {
1379                return;
1380            }
1381            mEvent.recycle();
1382            mEvent = null;
1383            mPolicyFlags = 0;
1384        }
1385    }
1386
1387    /**
1388     * Class for delayed sending of hover events.
1389     */
1390    private final class SendHoverDelayed implements Runnable {
1391        private static final String LOG_TAG = "SendHoverEnterOrExitDelayed";
1392
1393        private MotionEvent mEvent;
1394        private int mAction;
1395        private int mPointerIdBits;
1396        private int mPolicyFlags;
1397
1398        public void post(MotionEvent prototype, int action, int pointerIdBits, int policyFlags,
1399                long delay) {
1400            remove();
1401            mEvent = MotionEvent.obtain(prototype);
1402            mAction = action;
1403            mPointerIdBits = pointerIdBits;
1404            mPolicyFlags = policyFlags;
1405            mHandler.postDelayed(this, delay);
1406        }
1407
1408        public void remove() {
1409            mHandler.removeCallbacks(this);
1410            clear();
1411        }
1412
1413        private boolean isPenidng() {
1414            return (mEvent != null);
1415        }
1416
1417        private void clear() {
1418            if (!isPenidng()) {
1419                return;
1420            }
1421            mEvent.recycle();
1422            mEvent = null;
1423            mAction = 0;
1424            mPointerIdBits = -1;
1425            mPolicyFlags = 0;
1426        }
1427
1428        public void forceSendAndRemove() {
1429            if (isPenidng()) {
1430                run();
1431                remove();
1432            }
1433        }
1434
1435        public void run() {
1436            if (DEBUG) {
1437                if (mAction == MotionEvent.ACTION_HOVER_ENTER) {
1438                    Slog.d(LOG_TAG, "Injecting: " + MotionEvent.ACTION_HOVER_ENTER);
1439                } else if (mAction == MotionEvent.ACTION_HOVER_MOVE) {
1440                    Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_MOVE");
1441                } else if (mAction == MotionEvent.ACTION_HOVER_EXIT) {
1442                    Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_EXIT");
1443                }
1444            }
1445
1446            sendMotionEvent(mEvent, mAction, mPointerIdBits, mPolicyFlags);
1447            clear();
1448        }
1449    }
1450}
1451