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 17 package com.android.server.accessibility; 18 19 import android.content.Context; 20 import android.graphics.Point; 21 import android.os.Handler; 22 import android.util.Slog; 23 import android.view.InputDevice; 24 import android.view.KeyEvent; 25 import android.view.MotionEvent; 26 import android.view.MotionEvent.PointerCoords; 27 import android.view.MotionEvent.PointerProperties; 28 import android.view.ViewConfiguration; 29 import android.view.WindowManagerPolicy; 30 import android.view.accessibility.AccessibilityEvent; 31 import android.view.accessibility.AccessibilityManager; 32 import android.view.accessibility.AccessibilityNodeInfo; 33 34 import java.util.ArrayList; 35 import java.util.Arrays; 36 import java.util.List; 37 38 /** 39 * This class is a strategy for performing touch exploration. It 40 * transforms the motion event stream by modifying, adding, replacing, 41 * and consuming certain events. The interaction model is: 42 * 43 * <ol> 44 * <li>1. One finger moving slow around performs touch exploration.</li> 45 * <li>2. One finger moving fast around performs gestures.</li> 46 * <li>3. Two close fingers moving in the same direction perform a drag.</li> 47 * <li>4. Multi-finger gestures are delivered to view hierarchy.</li> 48 * <li>5. Two fingers moving in different directions are considered a multi-finger gesture.</li> 49 * <li>7. Double tapping clicks on the on the last touch explored location if it was in 50 * a window that does not take focus, otherwise the click is within the accessibility 51 * focused rectangle.</li> 52 * <li>7. Tapping and holding for a while performs a long press in a similar fashion 53 * as the click above.</li> 54 * <ol> 55 * 56 * @hide 57 */ 58 class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDetector.Listener { 59 60 private static final boolean DEBUG = false; 61 62 // Tag for logging received events. 63 private static final String LOG_TAG = "TouchExplorer"; 64 65 // States this explorer can be in. 66 private static final int STATE_TOUCH_EXPLORING = 0x00000001; 67 private static final int STATE_DRAGGING = 0x00000002; 68 private static final int STATE_DELEGATING = 0x00000004; 69 private static final int STATE_GESTURE_DETECTING = 0x00000005; 70 71 private static final int CLICK_LOCATION_NONE = 0; 72 private static final int CLICK_LOCATION_ACCESSIBILITY_FOCUS = 1; 73 private static final int CLICK_LOCATION_LAST_TOUCH_EXPLORED = 2; 74 75 // The maximum of the cosine between the vectors of two moving 76 // pointers so they can be considered moving in the same direction. 77 private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4) 78 79 // Constant referring to the ids bits of all pointers. 80 private static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF; 81 82 // This constant captures the current implementation detail that 83 // pointer IDs are between 0 and 31 inclusive (subject to change). 84 // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h) 85 private static final int MAX_POINTER_COUNT = 32; 86 87 // Invalid pointer ID. 88 private static final int INVALID_POINTER_ID = -1; 89 90 // The minimal distance before we take the middle of the distance between 91 // the two dragging pointers as opposed to use the location of the primary one. 92 private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200; 93 94 // The timeout after which we are no longer trying to detect a gesture. 95 private static final int EXIT_GESTURE_DETECTION_TIMEOUT = 2000; 96 97 // Timeout before trying to decide what the user is trying to do. 98 private final int mDetermineUserIntentTimeout; 99 100 // Slop between the first and second tap to be a double tap. 101 private final int mDoubleTapSlop; 102 103 // The current state of the touch explorer. 104 private int mCurrentState = STATE_TOUCH_EXPLORING; 105 106 // The ID of the pointer used for dragging. 107 private int mDraggingPointerId; 108 109 // Handler for performing asynchronous operations. 110 private final Handler mHandler; 111 112 // Command for delayed sending of a hover enter and move event. 113 private final SendHoverEnterAndMoveDelayed mSendHoverEnterAndMoveDelayed; 114 115 // Command for delayed sending of a hover exit event. 116 private final SendHoverExitDelayed mSendHoverExitDelayed; 117 118 // Command for delayed sending of touch exploration end events. 119 private final SendAccessibilityEventDelayed mSendTouchExplorationEndDelayed; 120 121 // Command for delayed sending of touch interaction end events. 122 private final SendAccessibilityEventDelayed mSendTouchInteractionEndDelayed; 123 124 // Command for exiting gesture detection mode after a timeout. 125 private final ExitGestureDetectionModeDelayed mExitGestureDetectionModeDelayed; 126 127 // Helper to detect gestures. 128 private final AccessibilityGestureDetector mGestureDetector; 129 130 // The scaled minimal distance before we take the middle of the distance between 131 // the two dragging pointers as opposed to use the location of the primary one. 132 private final int mScaledMinPointerDistanceToUseMiddleLocation; 133 134 // The handler to which to delegate events. 135 private EventStreamTransformation mNext; 136 137 // Helper class to track received pointers. 138 private final ReceivedPointerTracker mReceivedPointerTracker; 139 140 // Helper class to track injected pointers. 141 private final InjectedPointerTracker mInjectedPointerTracker; 142 143 // Handle to the accessibility manager service. 144 private final AccessibilityManagerService mAms; 145 146 // Temporary point to avoid instantiation. 147 private final Point mTempPoint = new Point(); 148 149 // Context in which this explorer operates. 150 private final Context mContext; 151 152 // The long pressing pointer id if coordinate remapping is needed. 153 private int mLongPressingPointerId = -1; 154 155 // The long pressing pointer X if coordinate remapping is needed. 156 private int mLongPressingPointerDeltaX; 157 158 // The long pressing pointer Y if coordinate remapping is needed. 159 private int mLongPressingPointerDeltaY; 160 161 // The id of the last touch explored window. 162 private int mLastTouchedWindowId; 163 164 // Whether touch exploration is in progress. 165 private boolean mTouchExplorationInProgress; 166 167 /** 168 * Creates a new instance. 169 * 170 * @param inputFilter The input filter associated with this explorer. 171 * @param context A context handle for accessing resources. 172 */ 173 public TouchExplorer(Context context, AccessibilityManagerService service) { 174 mContext = context; 175 mAms = service; 176 mReceivedPointerTracker = new ReceivedPointerTracker(); 177 mInjectedPointerTracker = new InjectedPointerTracker(); 178 mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout(); 179 mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); 180 mHandler = new Handler(context.getMainLooper()); 181 mExitGestureDetectionModeDelayed = new ExitGestureDetectionModeDelayed(); 182 mSendHoverEnterAndMoveDelayed = new SendHoverEnterAndMoveDelayed(); 183 mSendHoverExitDelayed = new SendHoverExitDelayed(); 184 mSendTouchExplorationEndDelayed = new SendAccessibilityEventDelayed( 185 AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END, 186 mDetermineUserIntentTimeout); 187 mSendTouchInteractionEndDelayed = new SendAccessibilityEventDelayed( 188 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END, 189 mDetermineUserIntentTimeout); 190 mGestureDetector = new AccessibilityGestureDetector(context, this); 191 final float density = context.getResources().getDisplayMetrics().density; 192 mScaledMinPointerDistanceToUseMiddleLocation = 193 (int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density); 194 } 195 196 @Override 197 public void clearEvents(int inputSource) { 198 if (inputSource == InputDevice.SOURCE_TOUCHSCREEN) { 199 clear(); 200 } 201 if (mNext != null) { 202 mNext.clearEvents(inputSource); 203 } 204 } 205 206 @Override 207 public void onDestroy() { 208 clear(); 209 } 210 211 private void clear() { 212 // If we have not received an event then we are in initial 213 // state. Therefore, there is not need to clean anything. 214 MotionEvent event = mReceivedPointerTracker.getLastReceivedEvent(); 215 if (event != null) { 216 clear(mReceivedPointerTracker.getLastReceivedEvent(), WindowManagerPolicy.FLAG_TRUSTED); 217 } 218 } 219 220 private void clear(MotionEvent event, int policyFlags) { 221 switch (mCurrentState) { 222 case STATE_TOUCH_EXPLORING: { 223 // If a touch exploration gesture is in progress send events for its end. 224 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); 225 } break; 226 case STATE_DRAGGING: { 227 mDraggingPointerId = INVALID_POINTER_ID; 228 // Send exit to all pointers that we have delivered. 229 sendUpForInjectedDownPointers(event, policyFlags); 230 } break; 231 case STATE_DELEGATING: { 232 // Send exit to all pointers that we have delivered. 233 sendUpForInjectedDownPointers(event, policyFlags); 234 } break; 235 case STATE_GESTURE_DETECTING: { 236 // No state specific cleanup required. 237 } break; 238 } 239 // Remove all pending callbacks. 240 mSendHoverEnterAndMoveDelayed.cancel(); 241 mSendHoverExitDelayed.cancel(); 242 mExitGestureDetectionModeDelayed.cancel(); 243 mSendTouchExplorationEndDelayed.cancel(); 244 mSendTouchInteractionEndDelayed.cancel(); 245 // Reset the pointer trackers. 246 mReceivedPointerTracker.clear(); 247 mInjectedPointerTracker.clear(); 248 // Clear the gesture detector 249 mGestureDetector.clear(); 250 // Go to initial state. 251 // Clear the long pressing pointer remap data. 252 mLongPressingPointerId = -1; 253 mLongPressingPointerDeltaX = 0; 254 mLongPressingPointerDeltaY = 0; 255 mCurrentState = STATE_TOUCH_EXPLORING; 256 mTouchExplorationInProgress = false; 257 mAms.onTouchInteractionEnd(); 258 } 259 260 @Override 261 public void setNext(EventStreamTransformation next) { 262 mNext = next; 263 } 264 265 @Override 266 public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { 267 if (!event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) { 268 if (mNext != null) { 269 mNext.onMotionEvent(event, rawEvent, policyFlags); 270 } 271 return; 272 } 273 274 if (DEBUG) { 275 Slog.d(LOG_TAG, "Received event: " + event + ", policyFlags=0x" 276 + Integer.toHexString(policyFlags)); 277 Slog.d(LOG_TAG, getStateSymbolicName(mCurrentState)); 278 } 279 280 mReceivedPointerTracker.onMotionEvent(rawEvent); 281 282 // The motion detector is interested in the movements in physical space, 283 // so it uses the rawEvent to ignore magnification and other 284 // transformations. 285 if (mGestureDetector.onMotionEvent(rawEvent, policyFlags)) { 286 // Event was handled by the gesture detector. 287 return; 288 } 289 290 if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) { 291 clear(event, policyFlags); 292 return; 293 } 294 295 switch(mCurrentState) { 296 case STATE_TOUCH_EXPLORING: { 297 handleMotionEventStateTouchExploring(event, rawEvent, policyFlags); 298 } break; 299 case STATE_DRAGGING: { 300 handleMotionEventStateDragging(event, policyFlags); 301 } break; 302 case STATE_DELEGATING: { 303 handleMotionEventStateDelegating(event, policyFlags); 304 } break; 305 case STATE_GESTURE_DETECTING: { 306 // Already handled. 307 } break; 308 default: 309 throw new IllegalStateException("Illegal state: " + mCurrentState); 310 } 311 } 312 313 @Override 314 public void onKeyEvent(KeyEvent event, int policyFlags) { 315 if (mNext != null) { 316 mNext.onKeyEvent(event, policyFlags); 317 } 318 } 319 320 @Override 321 public void onAccessibilityEvent(AccessibilityEvent event) { 322 final int eventType = event.getEventType(); 323 324 // The event for gesture end should be strictly after the 325 // last hover exit event. 326 if (mSendTouchExplorationEndDelayed.isPending() 327 && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) { 328 mSendTouchExplorationEndDelayed.cancel(); 329 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END); 330 } 331 332 // The event for touch interaction end should be strictly after the 333 // last hover exit and the touch exploration gesture end events. 334 if (mSendTouchInteractionEndDelayed.isPending() 335 && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) { 336 mSendTouchInteractionEndDelayed.cancel(); 337 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 338 } 339 340 // If a new window opens or the accessibility focus moves we no longer 341 // want to click/long press on the last touch explored location. 342 switch (eventType) { 343 case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: 344 case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: { 345 if (mInjectedPointerTracker.mLastInjectedHoverEventForClick != null) { 346 mInjectedPointerTracker.mLastInjectedHoverEventForClick.recycle(); 347 mInjectedPointerTracker.mLastInjectedHoverEventForClick = null; 348 } 349 mLastTouchedWindowId = -1; 350 } break; 351 case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: 352 case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: { 353 mLastTouchedWindowId = event.getWindowId(); 354 } break; 355 } 356 if (mNext != null) { 357 mNext.onAccessibilityEvent(event); 358 } 359 } 360 361 @Override 362 public void onDoubleTapAndHold(MotionEvent event, int policyFlags) { 363 // Ignore the event if we aren't touch exploring. 364 if (mCurrentState != STATE_TOUCH_EXPLORING) { 365 return; 366 } 367 368 // Pointers should not be zero when running this command. 369 if (mReceivedPointerTracker.getLastReceivedEvent().getPointerCount() == 0) { 370 return; 371 } 372 373 final int pointerIndex = event.getActionIndex(); 374 final int pointerId = event.getPointerId(pointerIndex); 375 376 Point clickLocation = mTempPoint; 377 final int result = computeClickLocation(clickLocation); 378 379 if (result == CLICK_LOCATION_NONE) { 380 return; 381 } 382 383 mLongPressingPointerId = pointerId; 384 mLongPressingPointerDeltaX = (int) event.getX(pointerIndex) - clickLocation.x; 385 mLongPressingPointerDeltaY = (int) event.getY(pointerIndex) - clickLocation.y; 386 387 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); 388 389 mCurrentState = STATE_DELEGATING; 390 sendDownForAllNotInjectedPointers(event, policyFlags); 391 } 392 393 @Override 394 public boolean onDoubleTap(MotionEvent event, int policyFlags) { 395 // Ignore the event if we aren't touch exploring. 396 if (mCurrentState != STATE_TOUCH_EXPLORING) { 397 return false; 398 } 399 400 // Remove pending event deliveries. 401 mSendHoverEnterAndMoveDelayed.cancel(); 402 mSendHoverExitDelayed.cancel(); 403 404 if (mSendTouchExplorationEndDelayed.isPending()) { 405 mSendTouchExplorationEndDelayed.forceSendAndRemove(); 406 } 407 if (mSendTouchInteractionEndDelayed.isPending()) { 408 mSendTouchInteractionEndDelayed.forceSendAndRemove(); 409 } 410 411 // Try to use the standard accessibility API to click 412 if (mAms.performActionOnAccessibilityFocusedItem( 413 AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)) { 414 return true; 415 } 416 Slog.e(LOG_TAG, "ACTION_CLICK failed. Dispatching motion events to simulate click."); 417 418 final int pointerIndex = event.getActionIndex(); 419 final int pointerId = event.getPointerId(pointerIndex); 420 421 Point clickLocation = mTempPoint; 422 final int result = computeClickLocation(clickLocation); 423 if (result == CLICK_LOCATION_NONE) { 424 // We can't send a click to no location, but the gesture was still 425 // consumed. 426 return true; 427 } 428 429 // Do the click. 430 PointerProperties[] properties = new PointerProperties[1]; 431 properties[0] = new PointerProperties(); 432 event.getPointerProperties(pointerIndex, properties[0]); 433 PointerCoords[] coords = new PointerCoords[1]; 434 coords[0] = new PointerCoords(); 435 coords[0].x = clickLocation.x; 436 coords[0].y = clickLocation.y; 437 MotionEvent click_event = MotionEvent.obtain(event.getDownTime(), 438 event.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties, 439 coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, 440 event.getSource(), event.getFlags()); 441 final boolean targetAccessibilityFocus = (result == CLICK_LOCATION_ACCESSIBILITY_FOCUS); 442 sendActionDownAndUp(click_event, policyFlags, targetAccessibilityFocus); 443 click_event.recycle(); 444 return true; 445 } 446 447 @Override 448 public boolean onGestureStarted() { 449 // We have to perform gesture detection, so 450 // clear the current state and try to detect. 451 mCurrentState = STATE_GESTURE_DETECTING; 452 mSendHoverEnterAndMoveDelayed.cancel(); 453 mSendHoverExitDelayed.cancel(); 454 mExitGestureDetectionModeDelayed.post(); 455 // Send accessibility event to announce the start 456 // of gesture recognition. 457 sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START); 458 return false; 459 } 460 461 @Override 462 public boolean onGestureCompleted(int gestureId) { 463 if (mCurrentState != STATE_GESTURE_DETECTING) { 464 return false; 465 } 466 467 endGestureDetection(); 468 469 mAms.onGesture(gestureId); 470 471 return true; 472 } 473 474 @Override 475 public boolean onGestureCancelled(MotionEvent event, int policyFlags) { 476 if (mCurrentState == STATE_GESTURE_DETECTING) { 477 endGestureDetection(); 478 return true; 479 } else if (mCurrentState == STATE_TOUCH_EXPLORING) { 480 // If the finger is still moving, pass the event on. 481 if (event.getActionMasked() == MotionEvent.ACTION_MOVE) { 482 final int pointerId = mReceivedPointerTracker.getPrimaryPointerId(); 483 final int pointerIdBits = (1 << pointerId); 484 485 // We have just decided that the user is touch, 486 // exploring so start sending events. 487 mSendHoverEnterAndMoveDelayed.addEvent(event); 488 mSendHoverEnterAndMoveDelayed.forceSendAndRemove(); 489 mSendHoverExitDelayed.cancel(); 490 sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags); 491 return true; 492 } 493 } 494 return false; 495 } 496 497 /** 498 * Handles a motion event in touch exploring state. 499 * 500 * @param event The event to be handled. 501 * @param rawEvent The raw (unmodified) motion event. 502 * @param policyFlags The policy flags associated with the event. 503 */ 504 private void handleMotionEventStateTouchExploring(MotionEvent event, MotionEvent rawEvent, 505 int policyFlags) { 506 ReceivedPointerTracker receivedTracker = mReceivedPointerTracker; 507 508 switch (event.getActionMasked()) { 509 case MotionEvent.ACTION_DOWN: { 510 mAms.onTouchInteractionStart(); 511 512 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START); 513 514 // If we still have not notified the user for the last 515 // touch, we figure out what to do. If were waiting 516 // we resent the delayed callback and wait again. 517 mSendHoverEnterAndMoveDelayed.cancel(); 518 mSendHoverExitDelayed.cancel(); 519 520 if (mSendTouchExplorationEndDelayed.isPending()) { 521 mSendTouchExplorationEndDelayed.forceSendAndRemove(); 522 } 523 524 if (mSendTouchInteractionEndDelayed.isPending()) { 525 mSendTouchInteractionEndDelayed.forceSendAndRemove(); 526 } 527 528 if (!mGestureDetector.firstTapDetected() && !mTouchExplorationInProgress) { 529 if (!mSendHoverEnterAndMoveDelayed.isPending()) { 530 // Deliver hover enter with a delay to have a chance 531 // to detect what the user is trying to do. 532 final int pointerId = receivedTracker.getPrimaryPointerId(); 533 final int pointerIdBits = (1 << pointerId); 534 mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits, 535 policyFlags); 536 } else { 537 // Cache the event until we discern exploration from gesturing. 538 mSendHoverEnterAndMoveDelayed.addEvent(event); 539 } 540 } 541 } break; 542 case MotionEvent.ACTION_POINTER_DOWN: { 543 // Another finger down means that if we have not started to deliver 544 // hover events, we will not have to. The code for ACTION_MOVE will 545 // decide what we will actually do next. 546 mSendHoverEnterAndMoveDelayed.cancel(); 547 mSendHoverExitDelayed.cancel(); 548 } break; 549 case MotionEvent.ACTION_MOVE: { 550 final int pointerId = receivedTracker.getPrimaryPointerId(); 551 final int pointerIndex = event.findPointerIndex(pointerId); 552 final int pointerIdBits = (1 << pointerId); 553 switch (event.getPointerCount()) { 554 case 1: { 555 // We have not started sending events since we try to 556 // figure out what the user is doing. 557 if (mSendHoverEnterAndMoveDelayed.isPending()) { 558 // Cache the event until we discern exploration from gesturing. 559 mSendHoverEnterAndMoveDelayed.addEvent(event); 560 } else { 561 if (mTouchExplorationInProgress) { 562 sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); 563 sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, 564 policyFlags); 565 } 566 } 567 } break; 568 case 2: { 569 // More than one pointer so the user is not touch exploring 570 // and now we have to decide whether to delegate or drag. 571 if (mSendHoverEnterAndMoveDelayed.isPending()) { 572 // We have not started sending events so cancel 573 // scheduled sending events. 574 mSendHoverEnterAndMoveDelayed.cancel(); 575 mSendHoverExitDelayed.cancel(); 576 } else { 577 if (mTouchExplorationInProgress) { 578 // If the user is touch exploring the second pointer may be 579 // performing a double tap to activate an item without need 580 // for the user to lift his exploring finger. 581 // It is *important* to use the distance traveled by the pointers 582 // on the screen which may or may not be magnified. 583 final float deltaX = receivedTracker.getReceivedPointerDownX( 584 pointerId) - rawEvent.getX(pointerIndex); 585 final float deltaY = receivedTracker.getReceivedPointerDownY( 586 pointerId) - rawEvent.getY(pointerIndex); 587 final double moveDelta = Math.hypot(deltaX, deltaY); 588 if (moveDelta < mDoubleTapSlop) { 589 break; 590 } 591 // We are sending events so send exit and gesture 592 // end since we transition to another state. 593 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); 594 } 595 } 596 597 if (isDraggingGesture(event)) { 598 // Two pointers moving in the same direction within 599 // a given distance perform a drag. 600 mCurrentState = STATE_DRAGGING; 601 mDraggingPointerId = pointerId; 602 event.setEdgeFlags(receivedTracker.getLastReceivedDownEdgeFlags()); 603 sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits, 604 policyFlags); 605 } else { 606 // Two pointers moving arbitrary are delegated to the view hierarchy. 607 mCurrentState = STATE_DELEGATING; 608 sendDownForAllNotInjectedPointers(event, policyFlags); 609 } 610 } break; 611 default: { 612 // More than one pointer so the user is not touch exploring 613 // and now we have to decide whether to delegate or drag. 614 if (mSendHoverEnterAndMoveDelayed.isPending()) { 615 // We have not started sending events so cancel 616 // scheduled sending events. 617 mSendHoverEnterAndMoveDelayed.cancel(); 618 mSendHoverExitDelayed.cancel(); 619 } else { 620 // We are sending events so send exit and gesture 621 // end since we transition to another state. 622 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); 623 } 624 625 // More than two pointers are delegated to the view hierarchy. 626 mCurrentState = STATE_DELEGATING; 627 sendDownForAllNotInjectedPointers(event, policyFlags); 628 } 629 } 630 } break; 631 case MotionEvent.ACTION_UP: { 632 mAms.onTouchInteractionEnd(); 633 final int pointerId = event.getPointerId(event.getActionIndex()); 634 final int pointerIdBits = (1 << pointerId); 635 636 if (mSendHoverEnterAndMoveDelayed.isPending()) { 637 // If we have not delivered the enter schedule an exit. 638 mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags); 639 } else { 640 // The user is touch exploring so we send events for end. 641 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); 642 } 643 644 if (!mSendTouchInteractionEndDelayed.isPending()) { 645 mSendTouchInteractionEndDelayed.post(); 646 } 647 648 } break; 649 } 650 } 651 652 /** 653 * Handles a motion event in dragging state. 654 * 655 * @param event The event to be handled. 656 * @param policyFlags The policy flags associated with the event. 657 */ 658 private void handleMotionEventStateDragging(MotionEvent event, int policyFlags) { 659 int pointerIdBits = 0; 660 // Clear the dragging pointer id if it's no longer valid. 661 if (event.findPointerIndex(mDraggingPointerId) == -1) { 662 Slog.e(LOG_TAG, "mDraggingPointerId doesn't match any pointers on current event. " + 663 "mDraggingPointerId: " + Integer.toString(mDraggingPointerId) + 664 ", Event: " + event); 665 mDraggingPointerId = INVALID_POINTER_ID; 666 } else { 667 pointerIdBits = (1 << mDraggingPointerId); 668 } 669 switch (event.getActionMasked()) { 670 case MotionEvent.ACTION_DOWN: { 671 throw new IllegalStateException("Dragging state can be reached only if two " 672 + "pointers are already down"); 673 } 674 case MotionEvent.ACTION_POINTER_DOWN: { 675 // We are in dragging state so we have two pointers and another one 676 // goes down => delegate the three pointers to the view hierarchy 677 mCurrentState = STATE_DELEGATING; 678 if (mDraggingPointerId != INVALID_POINTER_ID) { 679 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); 680 } 681 sendDownForAllNotInjectedPointers(event, policyFlags); 682 } break; 683 case MotionEvent.ACTION_MOVE: { 684 if (mDraggingPointerId == INVALID_POINTER_ID) { 685 break; 686 } 687 switch (event.getPointerCount()) { 688 case 1: { 689 // do nothing 690 } break; 691 case 2: { 692 if (isDraggingGesture(event)) { 693 final float firstPtrX = event.getX(0); 694 final float firstPtrY = event.getY(0); 695 final float secondPtrX = event.getX(1); 696 final float secondPtrY = event.getY(1); 697 698 final float deltaX = firstPtrX - secondPtrX; 699 final float deltaY = firstPtrY - secondPtrY; 700 final double distance = Math.hypot(deltaX, deltaY); 701 702 if (distance > mScaledMinPointerDistanceToUseMiddleLocation) { 703 event.setLocation(deltaX / 2, deltaY / 2); 704 } 705 706 // If still dragging send a drag event. 707 sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits, 708 policyFlags); 709 } else { 710 // The two pointers are moving either in different directions or 711 // no close enough => delegate the gesture to the view hierarchy. 712 mCurrentState = STATE_DELEGATING; 713 // Send an event to the end of the drag gesture. 714 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, 715 policyFlags); 716 // Deliver all pointers to the view hierarchy. 717 sendDownForAllNotInjectedPointers(event, policyFlags); 718 } 719 } break; 720 default: { 721 mCurrentState = STATE_DELEGATING; 722 // Send an event to the end of the drag gesture. 723 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, 724 policyFlags); 725 // Deliver all pointers to the view hierarchy. 726 sendDownForAllNotInjectedPointers(event, policyFlags); 727 } 728 } 729 } break; 730 case MotionEvent.ACTION_POINTER_UP: { 731 final int pointerId = event.getPointerId(event.getActionIndex()); 732 if (pointerId == mDraggingPointerId) { 733 mDraggingPointerId = INVALID_POINTER_ID; 734 // Send an event to the end of the drag gesture. 735 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); 736 } 737 } break; 738 case MotionEvent.ACTION_UP: { 739 mAms.onTouchInteractionEnd(); 740 // Announce the end of a new touch interaction. 741 sendAccessibilityEvent( 742 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 743 final int pointerId = event.getPointerId(event.getActionIndex()); 744 if (pointerId == mDraggingPointerId) { 745 mDraggingPointerId = INVALID_POINTER_ID; 746 // Send an event to the end of the drag gesture. 747 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); 748 } 749 mCurrentState = STATE_TOUCH_EXPLORING; 750 } break; 751 } 752 } 753 754 /** 755 * Handles a motion event in delegating state. 756 * 757 * @param event The event to be handled. 758 * @param policyFlags The policy flags associated with the event. 759 */ 760 private void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) { 761 switch (event.getActionMasked()) { 762 case MotionEvent.ACTION_DOWN: { 763 throw new IllegalStateException("Delegating state can only be reached if " 764 + "there is at least one pointer down!"); 765 } 766 case MotionEvent.ACTION_UP: { 767 // Offset the event if we are doing a long press as the 768 // target is not necessarily under the user's finger. 769 if (mLongPressingPointerId >= 0) { 770 event = offsetEvent(event, - mLongPressingPointerDeltaX, 771 - mLongPressingPointerDeltaY); 772 // Clear the long press state. 773 mLongPressingPointerId = -1; 774 mLongPressingPointerDeltaX = 0; 775 mLongPressingPointerDeltaY = 0; 776 } 777 778 // Deliver the event. 779 sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); 780 781 // Announce the end of a the touch interaction. 782 mAms.onTouchInteractionEnd(); 783 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 784 785 mCurrentState = STATE_TOUCH_EXPLORING; 786 } break; 787 default: { 788 // Deliver the event. 789 sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); 790 } 791 } 792 } 793 794 private void endGestureDetection() { 795 mAms.onTouchInteractionEnd(); 796 797 // Announce the end of the gesture recognition. 798 sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); 799 // Announce the end of a the touch interaction. 800 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 801 802 mExitGestureDetectionModeDelayed.cancel(); 803 mCurrentState = STATE_TOUCH_EXPLORING; 804 } 805 806 /** 807 * Sends an accessibility event of the given type. 808 * 809 * @param type The event type. 810 */ 811 private void sendAccessibilityEvent(int type) { 812 AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext); 813 if (accessibilityManager.isEnabled()) { 814 AccessibilityEvent event = AccessibilityEvent.obtain(type); 815 event.setWindowId(mAms.getActiveWindowId()); 816 accessibilityManager.sendAccessibilityEvent(event); 817 switch (type) { 818 case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: { 819 mTouchExplorationInProgress = true; 820 } break; 821 case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: { 822 mTouchExplorationInProgress = false; 823 } break; 824 } 825 } 826 } 827 828 /** 829 * Sends down events to the view hierarchy for all pointers which are 830 * not already being delivered i.e. pointers that are not yet injected. 831 * 832 * @param prototype The prototype from which to create the injected events. 833 * @param policyFlags The policy flags associated with the event. 834 */ 835 private void sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags) { 836 InjectedPointerTracker injectedPointers = mInjectedPointerTracker; 837 838 // Inject the injected pointers. 839 int pointerIdBits = 0; 840 final int pointerCount = prototype.getPointerCount(); 841 for (int i = 0; i < pointerCount; i++) { 842 final int pointerId = prototype.getPointerId(i); 843 // Do not send event for already delivered pointers. 844 if (!injectedPointers.isInjectedPointerDown(pointerId)) { 845 pointerIdBits |= (1 << pointerId); 846 final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i); 847 sendMotionEvent(prototype, action, pointerIdBits, policyFlags); 848 } 849 } 850 } 851 852 /** 853 * Sends the exit events if needed. Such events are hover exit and touch explore 854 * gesture end. 855 * 856 * @param policyFlags The policy flags associated with the event. 857 */ 858 private void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) { 859 MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent(); 860 if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) { 861 final int pointerIdBits = event.getPointerIdBits(); 862 if (!mSendTouchExplorationEndDelayed.isPending()) { 863 mSendTouchExplorationEndDelayed.post(); 864 } 865 sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags); 866 } 867 } 868 869 /** 870 * Sends the enter events if needed. Such events are hover enter and touch explore 871 * gesture start. 872 * 873 * @param policyFlags The policy flags associated with the event. 874 */ 875 private void sendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags) { 876 MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent(); 877 if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) { 878 final int pointerIdBits = event.getPointerIdBits(); 879 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); 880 sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags); 881 } 882 } 883 884 /** 885 * Sends up events to the view hierarchy for all pointers which are 886 * already being delivered i.e. pointers that are injected. 887 * 888 * @param prototype The prototype from which to create the injected events. 889 * @param policyFlags The policy flags associated with the event. 890 */ 891 private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) { 892 final InjectedPointerTracker injectedTracked = mInjectedPointerTracker; 893 int pointerIdBits = 0; 894 final int pointerCount = prototype.getPointerCount(); 895 for (int i = 0; i < pointerCount; i++) { 896 final int pointerId = prototype.getPointerId(i); 897 // Skip non injected down pointers. 898 if (!injectedTracked.isInjectedPointerDown(pointerId)) { 899 continue; 900 } 901 pointerIdBits |= (1 << pointerId); 902 final int action = computeInjectionAction(MotionEvent.ACTION_UP, i); 903 sendMotionEvent(prototype, action, pointerIdBits, policyFlags); 904 } 905 } 906 907 /** 908 * Sends an up and down events. 909 * 910 * @param prototype The prototype from which to create the injected events. 911 * @param policyFlags The policy flags associated with the event. 912 * @param targetAccessibilityFocus Whether the event targets the accessibility focus. 913 */ 914 private void sendActionDownAndUp(MotionEvent prototype, int policyFlags, 915 boolean targetAccessibilityFocus) { 916 // Tap with the pointer that last explored. 917 final int pointerId = prototype.getPointerId(prototype.getActionIndex()); 918 final int pointerIdBits = (1 << pointerId); 919 prototype.setTargetAccessibilityFocus(targetAccessibilityFocus); 920 sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags); 921 prototype.setTargetAccessibilityFocus(targetAccessibilityFocus); 922 sendMotionEvent(prototype, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); 923 } 924 925 /** 926 * Sends an event. 927 * 928 * @param prototype The prototype from which to create the injected events. 929 * @param action The action of the event. 930 * @param pointerIdBits The bits of the pointers to send. 931 * @param policyFlags The policy flags associated with the event. 932 */ 933 private void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits, 934 int policyFlags) { 935 prototype.setAction(action); 936 937 MotionEvent event = null; 938 if (pointerIdBits == ALL_POINTER_ID_BITS) { 939 event = prototype; 940 } else { 941 try { 942 event = prototype.split(pointerIdBits); 943 } catch (IllegalArgumentException e) { 944 Slog.e(LOG_TAG, "sendMotionEvent: Failed to split motion event: " + e); 945 return; 946 } 947 } 948 if (action == MotionEvent.ACTION_DOWN) { 949 event.setDownTime(event.getEventTime()); 950 } else { 951 event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime()); 952 } 953 954 // If the user is long pressing but the long pressing pointer 955 // was not exactly over the accessibility focused item we need 956 // to remap the location of that pointer so the user does not 957 // have to explicitly touch explore something to be able to 958 // long press it, or even worse to avoid the user long pressing 959 // on the wrong item since click and long press behave differently. 960 if (mLongPressingPointerId >= 0) { 961 event = offsetEvent(event, - mLongPressingPointerDeltaX, 962 - mLongPressingPointerDeltaY); 963 } 964 965 if (DEBUG) { 966 Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x" 967 + Integer.toHexString(policyFlags)); 968 } 969 970 // Make sure that the user will see the event. 971 policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER; 972 if (mNext != null) { 973 // TODO: For now pass null for the raw event since the touch 974 // explorer is the last event transformation and it does 975 // not care about the raw event. 976 mNext.onMotionEvent(event, null, policyFlags); 977 } 978 979 mInjectedPointerTracker.onMotionEvent(event); 980 981 if (event != prototype) { 982 event.recycle(); 983 } 984 } 985 986 /** 987 * Offsets all pointers in the given event by adding the specified X and Y 988 * offsets. 989 * 990 * @param event The event to offset. 991 * @param offsetX The X offset. 992 * @param offsetY The Y offset. 993 * @return An event with the offset pointers or the original event if both 994 * offsets are zero. 995 */ 996 private MotionEvent offsetEvent(MotionEvent event, int offsetX, int offsetY) { 997 if (offsetX == 0 && offsetY == 0) { 998 return event; 999 } 1000 final int remappedIndex = event.findPointerIndex(mLongPressingPointerId); 1001 final int pointerCount = event.getPointerCount(); 1002 PointerProperties[] props = PointerProperties.createArray(pointerCount); 1003 PointerCoords[] coords = PointerCoords.createArray(pointerCount); 1004 for (int i = 0; i < pointerCount; i++) { 1005 event.getPointerProperties(i, props[i]); 1006 event.getPointerCoords(i, coords[i]); 1007 if (i == remappedIndex) { 1008 coords[i].x += offsetX; 1009 coords[i].y += offsetY; 1010 } 1011 } 1012 return MotionEvent.obtain(event.getDownTime(), 1013 event.getEventTime(), event.getAction(), event.getPointerCount(), 1014 props, coords, event.getMetaState(), event.getButtonState(), 1015 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(), 1016 event.getSource(), event.getFlags()); 1017 } 1018 1019 /** 1020 * Computes the action for an injected event based on a masked action 1021 * and a pointer index. 1022 * 1023 * @param actionMasked The masked action. 1024 * @param pointerIndex The index of the pointer which has changed. 1025 * @return The action to be used for injection. 1026 */ 1027 private int computeInjectionAction(int actionMasked, int pointerIndex) { 1028 switch (actionMasked) { 1029 case MotionEvent.ACTION_DOWN: 1030 case MotionEvent.ACTION_POINTER_DOWN: { 1031 InjectedPointerTracker injectedTracker = mInjectedPointerTracker; 1032 // Compute the action based on how many down pointers are injected. 1033 if (injectedTracker.getInjectedPointerDownCount() == 0) { 1034 return MotionEvent.ACTION_DOWN; 1035 } else { 1036 return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) 1037 | MotionEvent.ACTION_POINTER_DOWN; 1038 } 1039 } 1040 case MotionEvent.ACTION_POINTER_UP: { 1041 InjectedPointerTracker injectedTracker = mInjectedPointerTracker; 1042 // Compute the action based on how many down pointers are injected. 1043 if (injectedTracker.getInjectedPointerDownCount() == 1) { 1044 return MotionEvent.ACTION_UP; 1045 } else { 1046 return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) 1047 | MotionEvent.ACTION_POINTER_UP; 1048 } 1049 } 1050 default: 1051 return actionMasked; 1052 } 1053 } 1054 1055 /** 1056 * Determines whether a two pointer gesture is a dragging one. 1057 * 1058 * @param event The event with the pointer data. 1059 * @return True if the gesture is a dragging one. 1060 */ 1061 private boolean isDraggingGesture(MotionEvent event) { 1062 ReceivedPointerTracker receivedTracker = mReceivedPointerTracker; 1063 1064 final float firstPtrX = event.getX(0); 1065 final float firstPtrY = event.getY(0); 1066 final float secondPtrX = event.getX(1); 1067 final float secondPtrY = event.getY(1); 1068 1069 final float firstPtrDownX = receivedTracker.getReceivedPointerDownX(0); 1070 final float firstPtrDownY = receivedTracker.getReceivedPointerDownY(0); 1071 final float secondPtrDownX = receivedTracker.getReceivedPointerDownX(1); 1072 final float secondPtrDownY = receivedTracker.getReceivedPointerDownY(1); 1073 1074 return GestureUtils.isDraggingGesture(firstPtrDownX, firstPtrDownY, secondPtrDownX, 1075 secondPtrDownY, firstPtrX, firstPtrY, secondPtrX, secondPtrY, 1076 MAX_DRAGGING_ANGLE_COS); 1077 } 1078 1079 private int computeClickLocation(Point outLocation) { 1080 MotionEvent lastExploreEvent = mInjectedPointerTracker.getLastInjectedHoverEventForClick(); 1081 if (lastExploreEvent != null) { 1082 final int lastExplorePointerIndex = lastExploreEvent.getActionIndex(); 1083 outLocation.x = (int) lastExploreEvent.getX(lastExplorePointerIndex); 1084 outLocation.y = (int) lastExploreEvent.getY(lastExplorePointerIndex); 1085 if (!mAms.accessibilityFocusOnlyInActiveWindow() 1086 || mLastTouchedWindowId == mAms.getActiveWindowId()) { 1087 if (mAms.getAccessibilityFocusClickPointInScreen(outLocation)) { 1088 return CLICK_LOCATION_ACCESSIBILITY_FOCUS; 1089 } else { 1090 return CLICK_LOCATION_LAST_TOUCH_EXPLORED; 1091 } 1092 } 1093 } 1094 if (mAms.getAccessibilityFocusClickPointInScreen(outLocation)) { 1095 return CLICK_LOCATION_ACCESSIBILITY_FOCUS; 1096 } 1097 return CLICK_LOCATION_NONE; 1098 } 1099 1100 /** 1101 * Gets the symbolic name of a state. 1102 * 1103 * @param state A state. 1104 * @return The state symbolic name. 1105 */ 1106 private static String getStateSymbolicName(int state) { 1107 switch (state) { 1108 case STATE_TOUCH_EXPLORING: 1109 return "STATE_TOUCH_EXPLORING"; 1110 case STATE_DRAGGING: 1111 return "STATE_DRAGGING"; 1112 case STATE_DELEGATING: 1113 return "STATE_DELEGATING"; 1114 case STATE_GESTURE_DETECTING: 1115 return "STATE_GESTURE_DETECTING"; 1116 default: 1117 throw new IllegalArgumentException("Unknown state: " + state); 1118 } 1119 } 1120 1121 /** 1122 * Class for delayed exiting from gesture detecting mode. 1123 */ 1124 private final class ExitGestureDetectionModeDelayed implements Runnable { 1125 1126 public void post() { 1127 mHandler.postDelayed(this, EXIT_GESTURE_DETECTION_TIMEOUT); 1128 } 1129 1130 public void cancel() { 1131 mHandler.removeCallbacks(this); 1132 } 1133 1134 @Override 1135 public void run() { 1136 // Announce the end of gesture recognition. 1137 sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); 1138 // Clearing puts is in touch exploration state with a finger already 1139 // down, so announce the transition to exploration state. 1140 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); 1141 clear(); 1142 } 1143 } 1144 1145 /** 1146 * Class for delayed sending of hover enter and move events. 1147 */ 1148 class SendHoverEnterAndMoveDelayed implements Runnable { 1149 private final String LOG_TAG_SEND_HOVER_DELAYED = "SendHoverEnterAndMoveDelayed"; 1150 1151 private final List<MotionEvent> mEvents = new ArrayList<MotionEvent>(); 1152 1153 private int mPointerIdBits; 1154 private int mPolicyFlags; 1155 1156 public void post(MotionEvent event, boolean touchExplorationInProgress, 1157 int pointerIdBits, int policyFlags) { 1158 cancel(); 1159 addEvent(event); 1160 mPointerIdBits = pointerIdBits; 1161 mPolicyFlags = policyFlags; 1162 mHandler.postDelayed(this, mDetermineUserIntentTimeout); 1163 } 1164 1165 public void addEvent(MotionEvent event) { 1166 mEvents.add(MotionEvent.obtain(event)); 1167 } 1168 1169 public void cancel() { 1170 if (isPending()) { 1171 mHandler.removeCallbacks(this); 1172 clear(); 1173 } 1174 } 1175 1176 private boolean isPending() { 1177 return mHandler.hasCallbacks(this); 1178 } 1179 1180 private void clear() { 1181 mPointerIdBits = -1; 1182 mPolicyFlags = 0; 1183 final int eventCount = mEvents.size(); 1184 for (int i = eventCount - 1; i >= 0; i--) { 1185 mEvents.remove(i).recycle(); 1186 } 1187 } 1188 1189 public void forceSendAndRemove() { 1190 if (isPending()) { 1191 run(); 1192 cancel(); 1193 } 1194 } 1195 1196 public void run() { 1197 // Send an accessibility event to announce the touch exploration start. 1198 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); 1199 1200 if (!mEvents.isEmpty()) { 1201 // Deliver a down event. 1202 sendMotionEvent(mEvents.get(0), MotionEvent.ACTION_HOVER_ENTER, 1203 mPointerIdBits, mPolicyFlags); 1204 if (DEBUG) { 1205 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, 1206 "Injecting motion event: ACTION_HOVER_ENTER"); 1207 } 1208 1209 // Deliver move events. 1210 final int eventCount = mEvents.size(); 1211 for (int i = 1; i < eventCount; i++) { 1212 sendMotionEvent(mEvents.get(i), MotionEvent.ACTION_HOVER_MOVE, 1213 mPointerIdBits, mPolicyFlags); 1214 if (DEBUG) { 1215 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, 1216 "Injecting motion event: ACTION_HOVER_MOVE"); 1217 } 1218 } 1219 } 1220 clear(); 1221 } 1222 } 1223 1224 /** 1225 * Class for delayed sending of hover exit events. 1226 */ 1227 class SendHoverExitDelayed implements Runnable { 1228 private final String LOG_TAG_SEND_HOVER_DELAYED = "SendHoverExitDelayed"; 1229 1230 private MotionEvent mPrototype; 1231 private int mPointerIdBits; 1232 private int mPolicyFlags; 1233 1234 public void post(MotionEvent prototype, int pointerIdBits, int policyFlags) { 1235 cancel(); 1236 mPrototype = MotionEvent.obtain(prototype); 1237 mPointerIdBits = pointerIdBits; 1238 mPolicyFlags = policyFlags; 1239 mHandler.postDelayed(this, mDetermineUserIntentTimeout); 1240 } 1241 1242 public void cancel() { 1243 if (isPending()) { 1244 mHandler.removeCallbacks(this); 1245 clear(); 1246 } 1247 } 1248 1249 private boolean isPending() { 1250 return mHandler.hasCallbacks(this); 1251 } 1252 1253 private void clear() { 1254 mPrototype.recycle(); 1255 mPrototype = null; 1256 mPointerIdBits = -1; 1257 mPolicyFlags = 0; 1258 } 1259 1260 public void forceSendAndRemove() { 1261 if (isPending()) { 1262 run(); 1263 cancel(); 1264 } 1265 } 1266 1267 public void run() { 1268 if (DEBUG) { 1269 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event:" 1270 + " ACTION_HOVER_EXIT"); 1271 } 1272 sendMotionEvent(mPrototype, MotionEvent.ACTION_HOVER_EXIT, 1273 mPointerIdBits, mPolicyFlags); 1274 if (!mSendTouchExplorationEndDelayed.isPending()) { 1275 mSendTouchExplorationEndDelayed.cancel(); 1276 mSendTouchExplorationEndDelayed.post(); 1277 } 1278 if (mSendTouchInteractionEndDelayed.isPending()) { 1279 mSendTouchInteractionEndDelayed.cancel(); 1280 mSendTouchInteractionEndDelayed.post(); 1281 } 1282 clear(); 1283 } 1284 } 1285 1286 private class SendAccessibilityEventDelayed implements Runnable { 1287 private final int mEventType; 1288 private final int mDelay; 1289 1290 public SendAccessibilityEventDelayed(int eventType, int delay) { 1291 mEventType = eventType; 1292 mDelay = delay; 1293 } 1294 1295 public void cancel() { 1296 mHandler.removeCallbacks(this); 1297 } 1298 1299 public void post() { 1300 mHandler.postDelayed(this, mDelay); 1301 } 1302 1303 public boolean isPending() { 1304 return mHandler.hasCallbacks(this); 1305 } 1306 1307 public void forceSendAndRemove() { 1308 if (isPending()) { 1309 run(); 1310 cancel(); 1311 } 1312 } 1313 1314 @Override 1315 public void run() { 1316 sendAccessibilityEvent(mEventType); 1317 } 1318 } 1319 1320 @Override 1321 public String toString() { 1322 return LOG_TAG; 1323 } 1324 1325 class InjectedPointerTracker { 1326 private static final String LOG_TAG_INJECTED_POINTER_TRACKER = "InjectedPointerTracker"; 1327 1328 // Keep track of which pointers sent to the system are down. 1329 private int mInjectedPointersDown; 1330 1331 // The time of the last injected down. 1332 private long mLastInjectedDownEventTime; 1333 1334 // The last injected hover event. 1335 private MotionEvent mLastInjectedHoverEvent; 1336 1337 // The last injected hover event used for performing clicks. 1338 private MotionEvent mLastInjectedHoverEventForClick; 1339 1340 /** 1341 * Processes an injected {@link MotionEvent} event. 1342 * 1343 * @param event The event to process. 1344 */ 1345 public void onMotionEvent(MotionEvent event) { 1346 final int action = event.getActionMasked(); 1347 switch (action) { 1348 case MotionEvent.ACTION_DOWN: 1349 case MotionEvent.ACTION_POINTER_DOWN: { 1350 final int pointerId = event.getPointerId(event.getActionIndex()); 1351 final int pointerFlag = (1 << pointerId); 1352 mInjectedPointersDown |= pointerFlag; 1353 mLastInjectedDownEventTime = event.getDownTime(); 1354 } break; 1355 case MotionEvent.ACTION_UP: 1356 case MotionEvent.ACTION_POINTER_UP: { 1357 final int pointerId = event.getPointerId(event.getActionIndex()); 1358 final int pointerFlag = (1 << pointerId); 1359 mInjectedPointersDown &= ~pointerFlag; 1360 if (mInjectedPointersDown == 0) { 1361 mLastInjectedDownEventTime = 0; 1362 } 1363 } break; 1364 case MotionEvent.ACTION_HOVER_ENTER: 1365 case MotionEvent.ACTION_HOVER_MOVE: 1366 case MotionEvent.ACTION_HOVER_EXIT: { 1367 if (mLastInjectedHoverEvent != null) { 1368 mLastInjectedHoverEvent.recycle(); 1369 } 1370 mLastInjectedHoverEvent = MotionEvent.obtain(event); 1371 if (mLastInjectedHoverEventForClick != null) { 1372 mLastInjectedHoverEventForClick.recycle(); 1373 } 1374 mLastInjectedHoverEventForClick = MotionEvent.obtain(event); 1375 } break; 1376 } 1377 if (DEBUG) { 1378 Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer:\n" + toString()); 1379 } 1380 } 1381 1382 /** 1383 * Clears the internals state. 1384 */ 1385 public void clear() { 1386 mInjectedPointersDown = 0; 1387 } 1388 1389 /** 1390 * @return The time of the last injected down event. 1391 */ 1392 public long getLastInjectedDownEventTime() { 1393 return mLastInjectedDownEventTime; 1394 } 1395 1396 /** 1397 * @return The number of down pointers injected to the view hierarchy. 1398 */ 1399 public int getInjectedPointerDownCount() { 1400 return Integer.bitCount(mInjectedPointersDown); 1401 } 1402 1403 /** 1404 * @return The bits of the injected pointers that are down. 1405 */ 1406 public int getInjectedPointersDown() { 1407 return mInjectedPointersDown; 1408 } 1409 1410 /** 1411 * Whether an injected pointer is down. 1412 * 1413 * @param pointerId The unique pointer id. 1414 * @return True if the pointer is down. 1415 */ 1416 public boolean isInjectedPointerDown(int pointerId) { 1417 final int pointerFlag = (1 << pointerId); 1418 return (mInjectedPointersDown & pointerFlag) != 0; 1419 } 1420 1421 /** 1422 * @return The the last injected hover event. 1423 */ 1424 public MotionEvent getLastInjectedHoverEvent() { 1425 return mLastInjectedHoverEvent; 1426 } 1427 1428 /** 1429 * @return The the last injected hover event. 1430 */ 1431 public MotionEvent getLastInjectedHoverEventForClick() { 1432 return mLastInjectedHoverEventForClick; 1433 } 1434 1435 @Override 1436 public String toString() { 1437 StringBuilder builder = new StringBuilder(); 1438 builder.append("========================="); 1439 builder.append("\nDown pointers #"); 1440 builder.append(Integer.bitCount(mInjectedPointersDown)); 1441 builder.append(" [ "); 1442 for (int i = 0; i < MAX_POINTER_COUNT; i++) { 1443 if ((mInjectedPointersDown & i) != 0) { 1444 builder.append(i); 1445 builder.append(" "); 1446 } 1447 } 1448 builder.append("]"); 1449 builder.append("\n========================="); 1450 return builder.toString(); 1451 } 1452 } 1453 1454 class ReceivedPointerTracker { 1455 private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker"; 1456 1457 // Keep track of where and when a pointer went down. 1458 private final float[] mReceivedPointerDownX = new float[MAX_POINTER_COUNT]; 1459 private final float[] mReceivedPointerDownY = new float[MAX_POINTER_COUNT]; 1460 private final long[] mReceivedPointerDownTime = new long[MAX_POINTER_COUNT]; 1461 1462 // Which pointers are down. 1463 private int mReceivedPointersDown; 1464 1465 // The edge flags of the last received down event. 1466 private int mLastReceivedDownEdgeFlags; 1467 1468 // Primary pointer which is either the first that went down 1469 // or if it goes up the next one that most recently went down. 1470 private int mPrimaryPointerId; 1471 1472 // Keep track of the last up pointer data. 1473 private long mLastReceivedUpPointerDownTime; 1474 private float mLastReceivedUpPointerDownX; 1475 private float mLastReceivedUpPointerDownY; 1476 1477 private MotionEvent mLastReceivedEvent; 1478 1479 /** 1480 * Clears the internals state. 1481 */ 1482 public void clear() { 1483 Arrays.fill(mReceivedPointerDownX, 0); 1484 Arrays.fill(mReceivedPointerDownY, 0); 1485 Arrays.fill(mReceivedPointerDownTime, 0); 1486 mReceivedPointersDown = 0; 1487 mPrimaryPointerId = 0; 1488 mLastReceivedUpPointerDownTime = 0; 1489 mLastReceivedUpPointerDownX = 0; 1490 mLastReceivedUpPointerDownY = 0; 1491 } 1492 1493 /** 1494 * Processes a received {@link MotionEvent} event. 1495 * 1496 * @param event The event to process. 1497 */ 1498 public void onMotionEvent(MotionEvent event) { 1499 if (mLastReceivedEvent != null) { 1500 mLastReceivedEvent.recycle(); 1501 } 1502 mLastReceivedEvent = MotionEvent.obtain(event); 1503 1504 final int action = event.getActionMasked(); 1505 switch (action) { 1506 case MotionEvent.ACTION_DOWN: { 1507 handleReceivedPointerDown(event.getActionIndex(), event); 1508 } break; 1509 case MotionEvent.ACTION_POINTER_DOWN: { 1510 handleReceivedPointerDown(event.getActionIndex(), event); 1511 } break; 1512 case MotionEvent.ACTION_UP: { 1513 handleReceivedPointerUp(event.getActionIndex(), event); 1514 } break; 1515 case MotionEvent.ACTION_POINTER_UP: { 1516 handleReceivedPointerUp(event.getActionIndex(), event); 1517 } break; 1518 } 1519 if (DEBUG) { 1520 Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer:\n" + toString()); 1521 } 1522 } 1523 1524 /** 1525 * @return The last received event. 1526 */ 1527 public MotionEvent getLastReceivedEvent() { 1528 return mLastReceivedEvent; 1529 } 1530 1531 /** 1532 * @return The number of received pointers that are down. 1533 */ 1534 public int getReceivedPointerDownCount() { 1535 return Integer.bitCount(mReceivedPointersDown); 1536 } 1537 1538 /** 1539 * Whether an received pointer is down. 1540 * 1541 * @param pointerId The unique pointer id. 1542 * @return True if the pointer is down. 1543 */ 1544 public boolean isReceivedPointerDown(int pointerId) { 1545 final int pointerFlag = (1 << pointerId); 1546 return (mReceivedPointersDown & pointerFlag) != 0; 1547 } 1548 1549 /** 1550 * @param pointerId The unique pointer id. 1551 * @return The X coordinate where the pointer went down. 1552 */ 1553 public float getReceivedPointerDownX(int pointerId) { 1554 return mReceivedPointerDownX[pointerId]; 1555 } 1556 1557 /** 1558 * @param pointerId The unique pointer id. 1559 * @return The Y coordinate where the pointer went down. 1560 */ 1561 public float getReceivedPointerDownY(int pointerId) { 1562 return mReceivedPointerDownY[pointerId]; 1563 } 1564 1565 /** 1566 * @param pointerId The unique pointer id. 1567 * @return The time when the pointer went down. 1568 */ 1569 public long getReceivedPointerDownTime(int pointerId) { 1570 return mReceivedPointerDownTime[pointerId]; 1571 } 1572 1573 /** 1574 * @return The id of the primary pointer. 1575 */ 1576 public int getPrimaryPointerId() { 1577 if (mPrimaryPointerId == INVALID_POINTER_ID) { 1578 mPrimaryPointerId = findPrimaryPointerId(); 1579 } 1580 return mPrimaryPointerId; 1581 } 1582 1583 /** 1584 * @return The time when the last up received pointer went down. 1585 */ 1586 public long getLastReceivedUpPointerDownTime() { 1587 return mLastReceivedUpPointerDownTime; 1588 } 1589 1590 /** 1591 * @return The down X of the last received pointer that went up. 1592 */ 1593 public float getLastReceivedUpPointerDownX() { 1594 return mLastReceivedUpPointerDownX; 1595 } 1596 1597 /** 1598 * @return The down Y of the last received pointer that went up. 1599 */ 1600 public float getLastReceivedUpPointerDownY() { 1601 return mLastReceivedUpPointerDownY; 1602 } 1603 1604 /** 1605 * @return The edge flags of the last received down event. 1606 */ 1607 public int getLastReceivedDownEdgeFlags() { 1608 return mLastReceivedDownEdgeFlags; 1609 } 1610 1611 /** 1612 * Handles a received pointer down event. 1613 * 1614 * @param pointerIndex The index of the pointer that has changed. 1615 * @param event The event to be handled. 1616 */ 1617 private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) { 1618 final int pointerId = event.getPointerId(pointerIndex); 1619 final int pointerFlag = (1 << pointerId); 1620 1621 mLastReceivedUpPointerDownTime = 0; 1622 mLastReceivedUpPointerDownX = 0; 1623 mLastReceivedUpPointerDownX = 0; 1624 1625 mLastReceivedDownEdgeFlags = event.getEdgeFlags(); 1626 1627 mReceivedPointersDown |= pointerFlag; 1628 mReceivedPointerDownX[pointerId] = event.getX(pointerIndex); 1629 mReceivedPointerDownY[pointerId] = event.getY(pointerIndex); 1630 mReceivedPointerDownTime[pointerId] = event.getEventTime(); 1631 1632 mPrimaryPointerId = pointerId; 1633 } 1634 1635 /** 1636 * Handles a received pointer up event. 1637 * 1638 * @param pointerIndex The index of the pointer that has changed. 1639 * @param event The event to be handled. 1640 */ 1641 private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) { 1642 final int pointerId = event.getPointerId(pointerIndex); 1643 final int pointerFlag = (1 << pointerId); 1644 1645 mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId); 1646 mLastReceivedUpPointerDownX = mReceivedPointerDownX[pointerId]; 1647 mLastReceivedUpPointerDownY = mReceivedPointerDownY[pointerId]; 1648 1649 mReceivedPointersDown &= ~pointerFlag; 1650 mReceivedPointerDownX[pointerId] = 0; 1651 mReceivedPointerDownY[pointerId] = 0; 1652 mReceivedPointerDownTime[pointerId] = 0; 1653 1654 if (mPrimaryPointerId == pointerId) { 1655 mPrimaryPointerId = INVALID_POINTER_ID; 1656 } 1657 } 1658 1659 /** 1660 * @return The primary pointer id. 1661 */ 1662 private int findPrimaryPointerId() { 1663 int primaryPointerId = INVALID_POINTER_ID; 1664 long minDownTime = Long.MAX_VALUE; 1665 1666 // Find the pointer that went down first. 1667 int pointerIdBits = mReceivedPointersDown; 1668 while (pointerIdBits > 0) { 1669 final int pointerId = Integer.numberOfTrailingZeros(pointerIdBits); 1670 pointerIdBits &= ~(1 << pointerId); 1671 final long downPointerTime = mReceivedPointerDownTime[pointerId]; 1672 if (downPointerTime < minDownTime) { 1673 minDownTime = downPointerTime; 1674 primaryPointerId = pointerId; 1675 } 1676 } 1677 return primaryPointerId; 1678 } 1679 1680 @Override 1681 public String toString() { 1682 StringBuilder builder = new StringBuilder(); 1683 builder.append("========================="); 1684 builder.append("\nDown pointers #"); 1685 builder.append(getReceivedPointerDownCount()); 1686 builder.append(" [ "); 1687 for (int i = 0; i < MAX_POINTER_COUNT; i++) { 1688 if (isReceivedPointerDown(i)) { 1689 builder.append(i); 1690 builder.append(" "); 1691 } 1692 } 1693 builder.append("]"); 1694 builder.append("\nPrimary pointer id [ "); 1695 builder.append(getPrimaryPointerId()); 1696 builder.append(" ]"); 1697 builder.append("\n========================="); 1698 return builder.toString(); 1699 } 1700 } 1701 } 1702