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