TouchExplorer.java revision 4532e6158474a263d9d26c2b42240bcf7ce9b172
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.input.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                final int activePointerCount = mPointerTracker.getActivePointerCount();
491                switch (activePointerCount) {
492                    case 1: {
493                        // Send an event to the end of the drag gesture.
494                        sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
495                    } break;
496                    default: {
497                        mCurrentState = STATE_TOUCH_EXPLORING;
498                    }
499                }
500             } break;
501            case MotionEvent.ACTION_UP: {
502                mCurrentState = STATE_TOUCH_EXPLORING;
503            } break;
504            case MotionEvent.ACTION_CANCEL: {
505                clear();
506            } break;
507        }
508    }
509
510    /**
511     * Handles a motion event in delegating state.
512     *
513     * @param event The event to be handled.
514     * @param policyFlags The policy flags associated with the event.
515     */
516    private void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) {
517        switch (event.getActionMasked()) {
518            case MotionEvent.ACTION_DOWN: {
519                throw new IllegalStateException("Delegating state can only be reached if "
520                        + "there is at least one pointer down!");
521            }
522            case MotionEvent.ACTION_UP: {
523                mCurrentState = STATE_TOUCH_EXPLORING;
524            } break;
525            case MotionEvent.ACTION_MOVE: {
526                // Check  whether some other pointer became active because they have moved
527                // a given distance and if such exist send them to the view hierarchy
528                final int notInjectedCount = mPointerTracker.getNotInjectedActivePointerCount();
529                if (notInjectedCount > 0) {
530                    MotionEvent prototype = MotionEvent.obtain(event);
531                    sendDownForAllActiveNotInjectedPointers(prototype, policyFlags);
532                }
533            } break;
534            case MotionEvent.ACTION_POINTER_UP: {
535                // No active pointers => go to initial state.
536                if (mPointerTracker.getActivePointerCount() == 0) {
537                    mCurrentState = STATE_TOUCH_EXPLORING;
538                }
539            } break;
540            case MotionEvent.ACTION_CANCEL: {
541                clear();
542            } break;
543        }
544        // Deliver the event striping out inactive pointers.
545        sendMotionEventStripInactivePointers(event, policyFlags);
546    }
547
548    /**
549     * Sends down events to the view hierarchy for all active pointers which are
550     * not already being delivered i.e. pointers that are not yet injected.
551     *
552     * @param prototype The prototype from which to create the injected events.
553     * @param policyFlags The policy flags associated with the event.
554     */
555    private void sendDownForAllActiveNotInjectedPointers(MotionEvent prototype, int policyFlags) {
556        final PointerTracker pointerTracker = mPointerTracker;
557        int pointerIdBits = 0;
558        final int pointerCount = prototype.getPointerCount();
559
560        // Find which pointers are already injected.
561        for (int i = 0; i < pointerCount; i++) {
562            final int pointerId = prototype.getPointerId(i);
563            if (pointerTracker.isInjectedPointerDown(pointerId)) {
564                pointerIdBits |= (1 << pointerId);
565            }
566        }
567
568        // Inject the active and not injected pointers.
569        for (int i = 0; i < pointerCount; i++) {
570            final int pointerId = prototype.getPointerId(i);
571            // Skip inactive pointers.
572            if (!pointerTracker.isActivePointer(pointerId)) {
573                continue;
574            }
575            // Do not send event for already delivered pointers.
576            if (pointerTracker.isInjectedPointerDown(pointerId)) {
577                continue;
578            }
579            pointerIdBits |= (1 << pointerId);
580            final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
581            sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
582        }
583    }
584
585    /**
586     * Ensures that hover exit has been sent.
587     *
588     * @param prototype The prototype from which to create the injected events.
589     * @param pointerIdBits The bits of the pointers to send.
590     * @param policyFlags The policy flags associated with the event.
591     */
592    private void ensureHoverExitSent(MotionEvent prototype, int pointerIdBits, int policyFlags) {
593        final int lastAction = mPointerTracker.getLastInjectedHoverAction();
594        if (lastAction != MotionEvent.ACTION_HOVER_EXIT) {
595            sendMotionEvent(prototype, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits,
596                    policyFlags);
597        }
598    }
599
600    /**
601     * Sends up events to the view hierarchy for all active pointers which are
602     * already being delivered i.e. pointers that are injected.
603     *
604     * @param prototype The prototype from which to create the injected events.
605     * @param policyFlags The policy flags associated with the event.
606     */
607    private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
608        final PointerTracker pointerTracker = mPointerTracker;
609        int pointerIdBits = 0;
610        final int pointerCount = prototype.getPointerCount();
611        for (int i = 0; i < pointerCount; i++) {
612            final int pointerId = prototype.getPointerId(i);
613            // Skip non injected down pointers.
614            if (!pointerTracker.isInjectedPointerDown(pointerId)) {
615                continue;
616            }
617            pointerIdBits |= (1 << pointerId);
618            final int action = computeInjectionAction(MotionEvent.ACTION_UP, i);
619            sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
620        }
621    }
622
623    /**
624     * Sends a motion event by first stripping the inactive pointers.
625     *
626     * @param prototype The prototype from which to create the injected event.
627     * @param policyFlags The policy flags associated with the event.
628     */
629    private void sendMotionEventStripInactivePointers(MotionEvent prototype, int policyFlags) {
630        PointerTracker pointerTracker = mPointerTracker;
631
632        // All pointers active therefore we just inject the event as is.
633        if (prototype.getPointerCount() == pointerTracker.getActivePointerCount()) {
634            sendMotionEvent(prototype, prototype.getAction(), ALL_POINTER_ID_BITS, policyFlags);
635            return;
636        }
637
638        // No active pointers and the one that just went up was not
639        // active, therefore we have nothing to do.
640        if (pointerTracker.getActivePointerCount() == 0
641                && !pointerTracker.wasLastReceivedUpPointerActive()) {
642            return;
643        }
644
645        // If the action pointer going up/down is not active we have nothing to do.
646        // However, for moves we keep going to report moves of active pointers.
647        final int actionMasked = prototype.getActionMasked();
648        final int actionPointerId = prototype.getPointerId(prototype.getActionIndex());
649        if (actionMasked != MotionEvent.ACTION_MOVE) {
650            if (!pointerTracker.isActiveOrWasLastActiveUpPointer(actionPointerId)) {
651                return;
652            }
653        }
654
655        // If the pointer is active or the pointer that just went up
656        // was active we keep the pointer data in the event.
657        int pointerIdBits = 0;
658        final int pointerCount = prototype.getPointerCount();
659        for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) {
660            final int pointerId = prototype.getPointerId(pointerIndex);
661            if (pointerTracker.isActiveOrWasLastActiveUpPointer(pointerId)) {
662                pointerIdBits |= (1 << pointerId);
663            }
664        }
665        sendMotionEvent(prototype, prototype.getAction(), pointerIdBits, policyFlags);
666    }
667
668    /**
669     * Sends an up and down events.
670     *
671     * @param prototype The prototype from which to create the injected events.
672     * @param policyFlags The policy flags associated with the event.
673     */
674    private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) {
675        // Tap with the pointer that last explored - we may have inactive pointers.
676        final int pointerId = prototype.getPointerId(prototype.getActionIndex());
677        final int pointerIdBits = (1 << pointerId);
678        sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags);
679        sendMotionEvent(prototype, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
680    }
681
682    /**
683     * Sends an event.
684     *
685     * @param prototype The prototype from which to create the injected events.
686     * @param action The action of the event.
687     * @param pointerIdBits The bits of the pointers to send.
688     * @param policyFlags The policy flags associated with the event.
689     */
690    private void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits,
691            int policyFlags) {
692        prototype.setAction(action);
693
694        MotionEvent event = null;
695        if (pointerIdBits == ALL_POINTER_ID_BITS) {
696            event = prototype;
697        } else {
698            event = prototype.split(pointerIdBits);
699        }
700        if (action == MotionEvent.ACTION_DOWN) {
701            event.setDownTime(event.getEventTime());
702        } else {
703            event.setDownTime(mPointerTracker.getLastInjectedDownEventTime());
704        }
705
706        if (DEBUG) {
707            Slog.d(LOG_TAG_INJECTED, "Injecting event: " + event + ", policyFlags=0x"
708                    + Integer.toHexString(policyFlags));
709        }
710
711        // Make sure that the user will see the event.
712        policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
713        mPointerTracker.onInjectedMotionEvent(event);
714        mInputFilter.sendInputEvent(event, policyFlags);
715
716        if (event != prototype) {
717            event.recycle();
718        }
719    }
720
721    /**
722     * Computes the action for an injected event based on a masked action
723     * and a pointer index.
724     *
725     * @param actionMasked The masked action.
726     * @param pointerIndex The index of the pointer which has changed.
727     * @return The action to be used for injection.
728     */
729    private int computeInjectionAction(int actionMasked, int pointerIndex) {
730        switch (actionMasked) {
731            case MotionEvent.ACTION_DOWN:
732            case MotionEvent.ACTION_POINTER_DOWN: {
733                PointerTracker pointerTracker = mPointerTracker;
734                // Compute the action based on how many down pointers are injected.
735                if (pointerTracker.getInjectedPointerDownCount() == 0) {
736                    return MotionEvent.ACTION_DOWN;
737                } else {
738                    return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
739                        | MotionEvent.ACTION_POINTER_DOWN;
740                }
741            }
742            case MotionEvent.ACTION_POINTER_UP: {
743                PointerTracker pointerTracker = mPointerTracker;
744                // Compute the action based on how many down pointers are injected.
745                if (pointerTracker.getInjectedPointerDownCount() == 1) {
746                    return MotionEvent.ACTION_UP;
747                } else {
748                    return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
749                        | MotionEvent.ACTION_POINTER_UP;
750                }
751            }
752            default:
753                return actionMasked;
754        }
755    }
756
757    /**
758     * Determines whether a two pointer gesture is a dragging one.
759     *
760     * @param event The event with the pointer data.
761     * @return True if the gesture is a dragging one.
762     */
763    private boolean isDraggingGesture(MotionEvent event) {
764        PointerTracker pointerTracker = mPointerTracker;
765        int[] pointerIds = mTempPointerIds;
766        pointerTracker.populateActivePointerIds(pointerIds);
767
768        final int firstPtrIndex = event.findPointerIndex(pointerIds[0]);
769        final int secondPtrIndex = event.findPointerIndex(pointerIds[1]);
770
771        final float firstPtrX = event.getX(firstPtrIndex);
772        final float firstPtrY = event.getY(firstPtrIndex);
773        final float secondPtrX = event.getX(secondPtrIndex);
774        final float secondPtrY = event.getY(secondPtrIndex);
775
776        // Check if the pointers are moving in the same direction.
777        final float firstDeltaX =
778            firstPtrX - pointerTracker.getReceivedPointerDownX(firstPtrIndex);
779        final float firstDeltaY =
780            firstPtrY - pointerTracker.getReceivedPointerDownY(firstPtrIndex);
781
782        if (firstDeltaX == 0 && firstDeltaY == 0) {
783            return true;
784        }
785
786        final float firstMagnitude =
787            (float) Math.sqrt(firstDeltaX * firstDeltaX + firstDeltaY * firstDeltaY);
788        final float firstXNormalized =
789            (firstMagnitude > 0) ? firstDeltaX / firstMagnitude : firstDeltaX;
790        final float firstYNormalized =
791            (firstMagnitude > 0) ? firstDeltaY / firstMagnitude : firstDeltaY;
792
793        final float secondDeltaX =
794            secondPtrX - pointerTracker.getReceivedPointerDownX(secondPtrIndex);
795        final float secondDeltaY =
796            secondPtrY - pointerTracker.getReceivedPointerDownY(secondPtrIndex);
797
798        if (secondDeltaX == 0 && secondDeltaY == 0) {
799            return true;
800        }
801
802        final float secondMagnitude =
803            (float) Math.sqrt(secondDeltaX * secondDeltaX + secondDeltaY * secondDeltaY);
804        final float secondXNormalized =
805            (secondMagnitude > 0) ? secondDeltaX / secondMagnitude : secondDeltaX;
806        final float secondYNormalized =
807            (secondMagnitude > 0) ? secondDeltaY / secondMagnitude : secondDeltaY;
808
809        final float angleCos =
810            firstXNormalized * secondXNormalized + firstYNormalized * secondYNormalized;
811
812        if (angleCos < MAX_DRAGGING_ANGLE_COS) {
813            return false;
814        }
815
816        return true;
817    }
818
819   /**
820    * Sends an event announcing the start/end of a touch exploration gesture.
821    *
822    * @param eventType The type of the event to send.
823    */
824    private void sendAccessibilityEvent(int eventType) {
825        AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
826        mAccessibilityManager.sendAccessibilityEvent(event);
827    }
828
829    /**
830     * Clears the internal state of this explorer.
831     */
832    public void clear() {
833        mSendHoverDelayed.remove();
834        mPerformLongPressDelayed.remove();
835        mPointerTracker.clear();
836        mLastTouchExploreEvent = null;
837        mCurrentState = STATE_TOUCH_EXPLORING;
838        mTouchExploreGestureInProgress = false;
839        mDraggingPointerId = INVALID_POINTER_ID;
840    }
841
842    /**
843     * Gets the symbolic name of a state.
844     *
845     * @param state A state.
846     * @return The state symbolic name.
847     */
848    private static String getStateSymbolicName(int state) {
849        switch (state) {
850            case STATE_TOUCH_EXPLORING:
851                return "STATE_TOUCH_EXPLORING";
852            case STATE_DRAGGING:
853                return "STATE_DRAGGING";
854            case STATE_DELEGATING:
855                return "STATE_DELEGATING";
856            default:
857                throw new IllegalArgumentException("Unknown state: " + state);
858        }
859    }
860
861    /**
862     * Helper class for tracking pointers and more specifically which of
863     * them are currently down, which are active, and which are delivered
864     * to the view hierarchy. The enclosing {@link TouchExplorer} uses the
865     * pointer state reported by this class to perform touch exploration.
866     * <p>
867     * The main purpose of this class is to allow the touch explorer to
868     * disregard pointers put down by accident by the user and not being
869     * involved in the interaction. For example, a blind user grabs the
870     * device with her left hand such that she touches the screen and she
871     * uses her right hand's index finger to explore the screen content.
872     * In this scenario the touches generated by the left hand are to be
873     * ignored.
874     */
875    class PointerTracker {
876        private static final String LOG_TAG = "PointerTracker";
877
878        // The coefficient by which to multiply
879        // ViewConfiguration.#getScaledTouchSlop()
880        // to compute #mThresholdActivePointer.
881        private static final int COEFFICIENT_ACTIVE_POINTER = 2;
882
883        // Pointers that moved less than mThresholdActivePointer
884        // are considered active i.e. are ignored.
885        private final double mThresholdActivePointer;
886
887        // Keep track of where and when a pointer went down.
888        private final float[] mReceivedPointerDownX = new float[MAX_POINTER_COUNT];
889        private final float[] mReceivedPointerDownY = new float[MAX_POINTER_COUNT];
890        private final long[] mReceivedPointerDownTime = new long[MAX_POINTER_COUNT];
891
892        // Which pointers are down.
893        private int mReceivedPointersDown;
894
895        // Which down pointers are active.
896        private int mActivePointers;
897
898        // Primary active pointer which is either the first that went down
899        // or if it goes up the next active that most recently went down.
900        private int mPrimaryActivePointerId;
901
902        // Flag indicating that there is at least one active pointer moving.
903        private boolean mHasMovingActivePointer;
904
905        // Keep track of which pointers sent to the system are down.
906        private int mInjectedPointersDown;
907
908        // Keep track of the last up pointer data.
909        private long mLastReceivedUpPointerDownTime;
910        private int mLastReceivedUpPointerId;
911        private boolean mLastReceivedUpPointerActive;
912
913        // The time of the last injected down.
914        private long mLastInjectedDownEventTime;
915
916        // The action of the last injected hover event.
917        private int mLastInjectedHoverEventAction = MotionEvent.ACTION_HOVER_EXIT;
918
919        /**
920         * Creates a new instance.
921         *
922         * @param context Context for looking up resources.
923         */
924        public PointerTracker(Context context) {
925            mThresholdActivePointer =
926                ViewConfiguration.get(context).getScaledTouchSlop() * COEFFICIENT_ACTIVE_POINTER;
927        }
928
929        /**
930         * Clears the internals state.
931         */
932        public void clear() {
933            Arrays.fill(mReceivedPointerDownX, 0);
934            Arrays.fill(mReceivedPointerDownY, 0);
935            Arrays.fill(mReceivedPointerDownTime, 0);
936            mReceivedPointersDown = 0;
937            mActivePointers = 0;
938            mPrimaryActivePointerId = 0;
939            mHasMovingActivePointer = false;
940            mInjectedPointersDown = 0;
941            mLastReceivedUpPointerDownTime = 0;
942            mLastReceivedUpPointerId = 0;
943            mLastReceivedUpPointerActive = false;
944        }
945
946        /**
947         * Processes a received {@link MotionEvent} event.
948         *
949         * @param event The event to process.
950         */
951        public void onReceivedMotionEvent(MotionEvent event) {
952            final int action = event.getActionMasked();
953            switch (action) {
954                case MotionEvent.ACTION_DOWN: {
955                    // New gesture so restart tracking injected down pointers.
956                    mInjectedPointersDown = 0;
957                    handleReceivedPointerDown(event.getActionIndex(), event);
958                } break;
959                case MotionEvent.ACTION_POINTER_DOWN: {
960                    handleReceivedPointerDown(event.getActionIndex(), event);
961                } break;
962                case MotionEvent.ACTION_MOVE: {
963                    handleReceivedPointerMove(event);
964                } break;
965                case MotionEvent.ACTION_UP: {
966                    handleReceivedPointerUp(event.getActionIndex(), event);
967                } break;
968                case MotionEvent.ACTION_POINTER_UP: {
969                    handleReceivedPointerUp(event.getActionIndex(), event);
970                } break;
971            }
972            if (DEBUG) {
973                Slog.i(LOG_TAG, "Received pointer: " + toString());
974            }
975        }
976
977        /**
978         * Processes an injected {@link MotionEvent} event.
979         *
980         * @param event The event to process.
981         */
982        public void onInjectedMotionEvent(MotionEvent event) {
983            final int action = event.getActionMasked();
984            switch (action) {
985                case MotionEvent.ACTION_DOWN: {
986                    handleInjectedPointerDown(event.getActionIndex(), event);
987                    mLastInjectedDownEventTime = event.getDownTime();
988                } break;
989                case MotionEvent.ACTION_POINTER_DOWN: {
990                    handleInjectedPointerDown(event.getActionIndex(), event);
991                } break;
992                case MotionEvent.ACTION_UP: {
993                    handleInjectedPointerUp(event.getActionIndex(), event);
994                } break;
995                case MotionEvent.ACTION_POINTER_UP: {
996                    handleInjectedPointerUp(event.getActionIndex(), event);
997                } break;
998                case MotionEvent.ACTION_HOVER_ENTER:
999                case MotionEvent.ACTION_HOVER_MOVE:
1000                case MotionEvent.ACTION_HOVER_EXIT: {
1001                    mLastInjectedHoverEventAction = event.getActionMasked();
1002                } break;
1003            }
1004            if (DEBUG) {
1005                Slog.i(LOG_TAG, "Injected pointer: " + toString());
1006            }
1007        }
1008
1009        /**
1010         * @return The number of received pointers that are down.
1011         */
1012        public int getReceivedPointerDownCount() {
1013            return Integer.bitCount(mReceivedPointersDown);
1014        }
1015
1016        /**
1017         * @return The number of down input  pointers that are active.
1018         */
1019        public int getActivePointerCount() {
1020            return Integer.bitCount(mActivePointers);
1021        }
1022
1023        /**
1024         * Whether an received pointer is down.
1025         *
1026         * @param pointerId The unique pointer id.
1027         * @return True if the pointer is down.
1028         */
1029        public boolean isReceivedPointerDown(int pointerId) {
1030            final int pointerFlag = (1 << pointerId);
1031            return (mReceivedPointersDown & pointerFlag) != 0;
1032        }
1033
1034        /**
1035         * Whether an injected pointer is down.
1036         *
1037         * @param pointerId The unique pointer id.
1038         * @return True if the pointer is down.
1039         */
1040        public boolean isInjectedPointerDown(int pointerId) {
1041            final int pointerFlag = (1 << pointerId);
1042            return (mInjectedPointersDown & pointerFlag) != 0;
1043        }
1044
1045        /**
1046         * @return The number of down pointers injected to the view hierarchy.
1047         */
1048        public int getInjectedPointerDownCount() {
1049            return Integer.bitCount(mInjectedPointersDown);
1050        }
1051
1052        /**
1053         * Whether an input pointer is active.
1054         *
1055         * @param pointerId The unique pointer id.
1056         * @return True if the pointer is active.
1057         */
1058        public boolean isActivePointer(int pointerId) {
1059            final int pointerFlag = (1 << pointerId);
1060            return (mActivePointers & pointerFlag) != 0;
1061        }
1062
1063        /**
1064         * @param pointerId The unique pointer id.
1065         * @return The X coordinate where the pointer went down.
1066         */
1067        public float getReceivedPointerDownX(int pointerId) {
1068            return mReceivedPointerDownX[pointerId];
1069        }
1070
1071        /**
1072         * @param pointerId The unique pointer id.
1073         * @return The Y coordinate where the pointer went down.
1074         */
1075        public float getReceivedPointerDownY(int pointerId) {
1076            return mReceivedPointerDownY[pointerId];
1077        }
1078
1079        /**
1080         * @param pointerId The unique pointer id.
1081         * @return The time when the pointer went down.
1082         */
1083        public long getReceivedPointerDownTime(int pointerId) {
1084            return mReceivedPointerDownTime[pointerId];
1085        }
1086
1087        /**
1088         * @return The id of the primary pointer.
1089         */
1090        public int getPrimaryActivePointerId() {
1091            if (mPrimaryActivePointerId == INVALID_POINTER_ID) {
1092                mPrimaryActivePointerId = findPrimaryActivePointer();
1093            }
1094            return mPrimaryActivePointerId;
1095        }
1096
1097        /**
1098         * @return The time when the last up received pointer went down.
1099         */
1100        public long getLastReceivedUpPointerDownTime() {
1101            return mLastReceivedUpPointerDownTime;
1102        }
1103
1104        /**
1105         * @return The id of the last received pointer that went up.
1106         */
1107        public int getLastReceivedUpPointerId() {
1108            return mLastReceivedUpPointerId;
1109        }
1110
1111        /**
1112         * @return Whether the last received pointer that went up was active.
1113         */
1114        public boolean wasLastReceivedUpPointerActive() {
1115            return mLastReceivedUpPointerActive;
1116        }
1117
1118        /**
1119         * @return The time of the last injected down event.
1120         */
1121        public long getLastInjectedDownEventTime() {
1122            return mLastInjectedDownEventTime;
1123        }
1124
1125        /**
1126         * @return The action of the last injected hover event.
1127         */
1128        public int getLastInjectedHoverAction() {
1129            return mLastInjectedHoverEventAction;
1130        }
1131
1132        /**
1133         * Populates the active pointer IDs to the given array.
1134         * <p>
1135         * Note: The client is responsible for providing large enough array.
1136         *
1137         * @param outPointerIds The array to which to write the active pointers.
1138         */
1139        public void populateActivePointerIds(int[] outPointerIds) {
1140            int index = 0;
1141            for (int idBits = mActivePointers; idBits != 0; ) {
1142                final int id = Integer.numberOfTrailingZeros(idBits);
1143                idBits &= ~(1 << id);
1144                outPointerIds[index] = id;
1145                index++;
1146            }
1147        }
1148
1149        /**
1150         * @return The number of non injected active pointers.
1151         */
1152        public int getNotInjectedActivePointerCount() {
1153            final int pointerState = mActivePointers & ~mInjectedPointersDown;
1154            return Integer.bitCount(pointerState);
1155        }
1156
1157        /**
1158         * @param pointerId The unique pointer id.
1159         * @return Whether the pointer is active or was the last active than went up.
1160         */
1161        private boolean isActiveOrWasLastActiveUpPointer(int pointerId) {
1162            return (isActivePointer(pointerId)
1163                    || (mLastReceivedUpPointerId == pointerId
1164                            && mLastReceivedUpPointerActive));
1165        }
1166
1167        /**
1168         * Handles a received pointer down event.
1169         *
1170         * @param pointerIndex The index of the pointer that has changed.
1171         * @param event The event to be handled.
1172         */
1173        private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) {
1174            final int pointerId = event.getPointerId(pointerIndex);
1175            final int pointerFlag = (1 << pointerId);
1176
1177            mLastReceivedUpPointerId = 0;
1178            mLastReceivedUpPointerDownTime = 0;
1179            mLastReceivedUpPointerActive = false;
1180
1181            mReceivedPointersDown |= pointerFlag;
1182            mReceivedPointerDownX[pointerId] = event.getX(pointerIndex);
1183            mReceivedPointerDownY[pointerId] = event.getY(pointerIndex);
1184            mReceivedPointerDownTime[pointerId] = event.getEventTime();
1185
1186            if (!mHasMovingActivePointer) {
1187                // If still no moving active pointers every
1188                // down pointer is the only active one.
1189                mActivePointers = pointerFlag;
1190                mPrimaryActivePointerId = pointerId;
1191            } else {
1192                // If at least one moving active pointer every
1193                // subsequent down pointer is active.
1194                mActivePointers |= pointerFlag;
1195            }
1196        }
1197
1198        /**
1199         * Handles a received pointer move event.
1200         *
1201         * @param event The event to be handled.
1202         */
1203        private void handleReceivedPointerMove(MotionEvent event) {
1204            detectActivePointers(event);
1205        }
1206
1207        /**
1208         * Handles a received pointer up event.
1209         *
1210         * @param pointerIndex The index of the pointer that has changed.
1211         * @param event The event to be handled.
1212         */
1213        private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) {
1214            final int pointerId = event.getPointerId(pointerIndex);
1215            final int pointerFlag = (1 << pointerId);
1216
1217            mLastReceivedUpPointerId = pointerId;
1218            mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId);
1219            mLastReceivedUpPointerActive = isActivePointer(pointerId);
1220
1221            mReceivedPointersDown &= ~pointerFlag;
1222            mActivePointers &= ~pointerFlag;
1223            mReceivedPointerDownX[pointerId] = 0;
1224            mReceivedPointerDownY[pointerId] = 0;
1225            mReceivedPointerDownTime[pointerId] = 0;
1226
1227            if (mActivePointers == 0) {
1228                mHasMovingActivePointer = false;
1229            }
1230            if (mPrimaryActivePointerId == pointerId) {
1231                mPrimaryActivePointerId = INVALID_POINTER_ID;
1232            }
1233        }
1234
1235        /**
1236         * Handles a injected pointer down event.
1237         *
1238         * @param pointerIndex The index of the pointer that has changed.
1239         * @param event The event to be handled.
1240         */
1241        private void handleInjectedPointerDown(int pointerIndex, MotionEvent event) {
1242            final int pointerId = event.getPointerId(pointerIndex);
1243            final int pointerFlag = (1 << pointerId);
1244            mInjectedPointersDown |= pointerFlag;
1245        }
1246
1247        /**
1248         * Handles a injected 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 handleInjectedPointerUp(int pointerIndex, MotionEvent event) {
1254            final int pointerId = event.getPointerId(pointerIndex);
1255            final int pointerFlag = (1 << pointerId);
1256            mInjectedPointersDown &= ~pointerFlag;
1257            if (mInjectedPointersDown == 0) {
1258                mLastInjectedDownEventTime = 0;
1259            }
1260        }
1261
1262        /**
1263         * Detects the active pointers in an event.
1264         *
1265         * @param event The event to examine.
1266         */
1267        private void detectActivePointers(MotionEvent event) {
1268            for (int i = 0, count = event.getPointerCount(); i < count; i++) {
1269                final int pointerId = event.getPointerId(i);
1270                if (mHasMovingActivePointer) {
1271                    // If already active => nothing to do.
1272                    if (isActivePointer(pointerId)) {
1273                        continue;
1274                    }
1275                }
1276                // Active pointers are ones that moved more than a given threshold.
1277                final float pointerDeltaMove = computePointerDeltaMove(i, event);
1278                if (pointerDeltaMove > mThresholdActivePointer) {
1279                    final int pointerFlag = (1 << pointerId);
1280                    mActivePointers |= pointerFlag;
1281                    mHasMovingActivePointer = true;
1282                }
1283            }
1284        }
1285
1286        /**
1287         * @return The primary active pointer.
1288         */
1289        private int findPrimaryActivePointer() {
1290            int primaryActivePointerId = INVALID_POINTER_ID;
1291            long minDownTime = Long.MAX_VALUE;
1292            // Find the active pointer that went down first.
1293            for (int i = 0, count = mReceivedPointerDownTime.length; i < count; i++) {
1294                if (isActivePointer(i)) {
1295                    final long downPointerTime = mReceivedPointerDownTime[i];
1296                    if (downPointerTime < minDownTime) {
1297                        minDownTime = downPointerTime;
1298                        primaryActivePointerId = i;
1299                    }
1300                }
1301            }
1302            return primaryActivePointerId;
1303        }
1304
1305        /**
1306         * Computes the move for a given action pointer index since the
1307         * corresponding pointer went down.
1308         *
1309         * @param pointerIndex The action pointer index.
1310         * @param event The event to examine.
1311         * @return The distance the pointer has moved.
1312         */
1313        private float computePointerDeltaMove(int pointerIndex, MotionEvent event) {
1314            final int pointerId = event.getPointerId(pointerIndex);
1315            final float deltaX = event.getX(pointerIndex) - mReceivedPointerDownX[pointerId];
1316            final float deltaY = event.getY(pointerIndex) - mReceivedPointerDownY[pointerId];
1317            return (float) Math.hypot(deltaX, deltaY);
1318        }
1319
1320        @Override
1321        public String toString() {
1322            StringBuilder builder = new StringBuilder();
1323            builder.append("=========================");
1324            builder.append("\nDown pointers #");
1325            builder.append(getReceivedPointerDownCount());
1326            builder.append(" [ ");
1327            for (int i = 0; i < MAX_POINTER_COUNT; i++) {
1328                if (isReceivedPointerDown(i)) {
1329                    builder.append(i);
1330                    builder.append(" ");
1331                }
1332            }
1333            builder.append("]");
1334            builder.append("\nActive pointers #");
1335            builder.append(getActivePointerCount());
1336            builder.append(" [ ");
1337            for (int i = 0; i < MAX_POINTER_COUNT; i++) {
1338                if (isActivePointer(i)) {
1339                    builder.append(i);
1340                    builder.append(" ");
1341                }
1342            }
1343            builder.append("]");
1344            builder.append("\nPrimary active pointer id [ ");
1345            builder.append(getPrimaryActivePointerId());
1346            builder.append(" ]");
1347            builder.append("\n=========================");
1348            return builder.toString();
1349        }
1350    }
1351
1352    /**
1353     * Class for delayed sending of long press.
1354     */
1355    private final class PerformLongPressDelayed implements Runnable {
1356        private MotionEvent mEvent;
1357        private int mPolicyFlags;
1358
1359        public void post(MotionEvent prototype, int policyFlags, long delay) {
1360            mEvent = MotionEvent.obtain(prototype);
1361            mPolicyFlags = policyFlags;
1362            mHandler.postDelayed(this, delay);
1363        }
1364
1365        public void remove() {
1366            if (isPenidng()) {
1367                mHandler.removeCallbacks(this);
1368                clear();
1369            }
1370        }
1371
1372        private boolean isPenidng() {
1373            return (mEvent != null);
1374        }
1375
1376        @Override
1377        public void run() {
1378            mCurrentState = STATE_DELEGATING;
1379            // Make sure the scheduled hover exit is delivered.
1380            mSendHoverDelayed.remove();
1381            final int pointerId = mPointerTracker.getPrimaryActivePointerId();
1382            final int pointerIdBits = (1 << pointerId);
1383            ensureHoverExitSent(mEvent, pointerIdBits, mPolicyFlags);
1384
1385            sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags);
1386            mTouchExploreGestureInProgress = false;
1387            mLastTouchExploreEvent = null;
1388            clear();
1389        }
1390
1391        private void clear() {
1392            if (!isPenidng()) {
1393                return;
1394            }
1395            mEvent.recycle();
1396            mEvent = null;
1397            mPolicyFlags = 0;
1398        }
1399    }
1400
1401    /**
1402     * Class for delayed sending of hover events.
1403     */
1404    private final class SendHoverDelayed implements Runnable {
1405        private static final String LOG_TAG = "SendHoverEnterOrExitDelayed";
1406
1407        private MotionEvent mEvent;
1408        private int mAction;
1409        private int mPointerIdBits;
1410        private int mPolicyFlags;
1411
1412        public void post(MotionEvent prototype, int action, int pointerIdBits, int policyFlags,
1413                long delay) {
1414            remove();
1415            mEvent = MotionEvent.obtain(prototype);
1416            mAction = action;
1417            mPointerIdBits = pointerIdBits;
1418            mPolicyFlags = policyFlags;
1419            mHandler.postDelayed(this, delay);
1420        }
1421
1422        public void remove() {
1423            mHandler.removeCallbacks(this);
1424            clear();
1425        }
1426
1427        private boolean isPenidng() {
1428            return (mEvent != null);
1429        }
1430
1431        private void clear() {
1432            if (!isPenidng()) {
1433                return;
1434            }
1435            mEvent.recycle();
1436            mEvent = null;
1437            mAction = 0;
1438            mPointerIdBits = -1;
1439            mPolicyFlags = 0;
1440        }
1441
1442        public void forceSendAndRemove() {
1443            if (isPenidng()) {
1444                run();
1445                remove();
1446            }
1447        }
1448
1449        public void run() {
1450            if (DEBUG) {
1451                if (mAction == MotionEvent.ACTION_HOVER_ENTER) {
1452                    Slog.d(LOG_TAG, "Injecting: " + MotionEvent.ACTION_HOVER_ENTER);
1453                } else if (mAction == MotionEvent.ACTION_HOVER_MOVE) {
1454                    Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_MOVE");
1455                } else if (mAction == MotionEvent.ACTION_HOVER_EXIT) {
1456                    Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_EXIT");
1457                }
1458            }
1459
1460            sendMotionEvent(mEvent, mAction, mPointerIdBits, mPolicyFlags);
1461            clear();
1462        }
1463    }
1464}
1465