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