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