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