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