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