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