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