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