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