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