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