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