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