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