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