1 /* 2 ** Copyright 2011, The Android Open Source Project 3 ** 4 ** Licensed under the Apache License, Version 2.0 (the "License"); 5 ** you may not use this file except in compliance with the License. 6 ** You may obtain a copy of the License at 7 ** 8 ** http://www.apache.org/licenses/LICENSE-2.0 9 ** 10 ** Unless required by applicable law or agreed to in writing, software 11 ** distributed under the License is distributed on an "AS IS" BASIS, 12 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 ** See the License for the specific language governing permissions and 14 ** limitations under the License. 15 */ 16 17 package com.android.server.accessibility; 18 19 import android.content.Context; 20 import android.gesture.Gesture; 21 import android.gesture.GestureLibraries; 22 import android.gesture.GestureLibrary; 23 import android.gesture.GesturePoint; 24 import android.gesture.GestureStore; 25 import android.gesture.GestureStroke; 26 import android.gesture.Prediction; 27 import android.graphics.Rect; 28 import android.os.Handler; 29 import android.os.SystemClock; 30 import android.util.Slog; 31 import android.view.MotionEvent; 32 import android.view.MotionEvent.PointerCoords; 33 import android.view.MotionEvent.PointerProperties; 34 import android.view.VelocityTracker; 35 import android.view.ViewConfiguration; 36 import android.view.WindowManagerPolicy; 37 import android.view.accessibility.AccessibilityEvent; 38 import android.view.accessibility.AccessibilityManager; 39 40 import com.android.internal.R; 41 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 45 /** 46 * This class is a strategy for performing touch exploration. It 47 * transforms the motion event stream by modifying, adding, replacing, 48 * and consuming certain events. The interaction model is: 49 * 50 * <ol> 51 * <li>1. One finger moving slow around performs touch exploration.</li> 52 * <li>2. One finger moving fast around performs gestures.</li> 53 * <li>3. Two close fingers moving in the same direction perform a drag.</li> 54 * <li>4. Multi-finger gestures are delivered to view hierarchy.</li> 55 * <li>5. Pointers that have not moved more than a specified distance after they 56 * went down are considered inactive.</li> 57 * <li>6. 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 of 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 */ 67 class 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 minimum 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 // Temporary array for storing pointer IDs. 106 private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT]; 107 108 // Timeout before trying to decide what the user is trying to do. 109 private final int mDetermineUserIntentTimeout; 110 111 // Timeout within which we try to detect a tap. 112 private final int mTapTimeout; 113 114 // Timeout within which we try to detect a double tap. 115 private final int mDoubleTapTimeout; 116 117 // Slop between the down and up tap to be a tap. 118 private final int mTouchSlop; 119 120 // Slop between the first and second tap to be a double tap. 121 private final int mDoubleTapSlop; 122 123 // The current state of the touch explorer. 124 private int mCurrentState = STATE_TOUCH_EXPLORING; 125 126 // The ID of the pointer used for dragging. 127 private int mDraggingPointerId; 128 129 // Handler for performing asynchronous operations. 130 private final Handler mHandler; 131 132 // Command for delayed sending of a hover enter event. 133 private final SendHoverDelayed mSendHoverEnterDelayed; 134 135 // Command for delayed sending of a hover exit event. 136 private final SendHoverDelayed mSendHoverExitDelayed; 137 138 // Command for delayed sending of touch exploration end events. 139 private final SendAccessibilityEventDelayed mSendTouchExplorationEndDelayed; 140 141 // Command for delayed sending of touch interaction end events. 142 private final SendAccessibilityEventDelayed mSendTouchInteractionEndDelayed; 143 144 // Command for delayed sending of a long press. 145 private final PerformLongPressDelayed mPerformLongPressDelayed; 146 147 // Command for exiting gesture detection mode after a timeout. 148 private final ExitGestureDetectionModeDelayed mExitGestureDetectionModeDelayed; 149 150 // Helper to detect and react to double tap in touch explore mode. 151 private final DoubleTapDetector mDoubleTapDetector; 152 153 // The scaled minimal distance before we take the middle of the distance between 154 // the two dragging pointers as opposed to use the location of the primary one. 155 private final int mScaledMinPointerDistanceToUseMiddleLocation; 156 157 // The scaled velocity above which we detect gestures. 158 private final int mScaledGestureDetectionVelocity; 159 160 // The handler to which to delegate events. 161 private EventStreamTransformation mNext; 162 163 // Helper to track gesture velocity. 164 private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); 165 166 // Helper class to track received pointers. 167 private final ReceivedPointerTracker mReceivedPointerTracker; 168 169 // Helper class to track injected pointers. 170 private final InjectedPointerTracker mInjectedPointerTracker; 171 172 // Handle to the accessibility manager service. 173 private final AccessibilityManagerService mAms; 174 175 // Temporary rectangle to avoid instantiation. 176 private final Rect mTempRect = new Rect(); 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(context); 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 mSendHoverEnterDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_ENTER, true); 238 mSendHoverExitDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_EXIT, false); 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 mSendHoverEnterDelayed.remove(); 287 mSendHoverExitDelayed.remove(); 288 mPerformLongPressDelayed.remove(); 289 mExitGestureDetectionModeDelayed.remove(); 290 mSendTouchExplorationEndDelayed.remove(); 291 mSendTouchInteractionEndDelayed.remove(); 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.remove(); 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.remove(); 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 final int activePointerCount = receivedTracker.getActivePointerCount(); 394 395 mVelocityTracker.addMovement(rawEvent); 396 397 mDoubleTapDetector.onMotionEvent(event, policyFlags); 398 399 switch (event.getActionMasked()) { 400 case MotionEvent.ACTION_DOWN: 401 mAms.onTouchInteractionStart(); 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 //$FALL-THROUGH$ 408 case MotionEvent.ACTION_POINTER_DOWN: { 409 switch (activePointerCount) { 410 case 0: { 411 throw new IllegalStateException("The must always be one active pointer in" 412 + "touch exploring state!"); 413 } 414 case 1: { 415 // If we still have not notified the user for the last 416 // touch, we figure out what to do. If were waiting 417 // we resent the delayed callback and wait again. 418 if (mSendHoverEnterDelayed.isPending()) { 419 mSendHoverEnterDelayed.remove(); 420 mSendHoverExitDelayed.remove(); 421 } 422 423 if (mSendTouchExplorationEndDelayed.isPending()) { 424 mSendTouchExplorationEndDelayed.forceSendAndRemove(); 425 } 426 427 if (mSendTouchInteractionEndDelayed.isPending()) { 428 mSendTouchInteractionEndDelayed.forceSendAndRemove(); 429 } 430 431 // Every pointer that goes down is active until it moves or 432 // another one goes down. Hence, having more than one pointer 433 // down we have already send the interaction start event. 434 if (event.getPointerCount() == 1) { 435 sendAccessibilityEvent( 436 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START); 437 } 438 439 mPerformLongPressDelayed.remove(); 440 441 // If we have the first tap schedule a long press and break 442 // since we do not want to schedule hover enter because 443 // the delayed callback will kick in before the long click. 444 // This would lead to a state transition resulting in long 445 // pressing the item below the double taped area which is 446 // not necessary where accessibility focus is. 447 if (mDoubleTapDetector.firstTapDetected()) { 448 // We got a tap now post a long press action. 449 mPerformLongPressDelayed.post(event, policyFlags); 450 break; 451 } 452 if (!mTouchExplorationInProgress) { 453 // Deliver hover enter with a delay to have a chance 454 // to detect what the user is trying to do. 455 final int pointerId = receivedTracker.getPrimaryActivePointerId(); 456 final int pointerIdBits = (1 << pointerId); 457 mSendHoverEnterDelayed.post(event, true, pointerIdBits, policyFlags); 458 } 459 } break; 460 default: { 461 /* do nothing - let the code for ACTION_MOVE decide what to do */ 462 } break; 463 } 464 } break; 465 case MotionEvent.ACTION_MOVE: { 466 final int pointerId = receivedTracker.getPrimaryActivePointerId(); 467 final int pointerIndex = event.findPointerIndex(pointerId); 468 final int pointerIdBits = (1 << pointerId); 469 switch (activePointerCount) { 470 case 0: { 471 /* do nothing - no active pointers so we swallow the event */ 472 } break; 473 case 1: { 474 // We have not started sending events since we try to 475 // figure out what the user is doing. 476 if (mSendHoverEnterDelayed.isPending()) { 477 // Pre-feed the motion events to the gesture detector since we 478 // have a distance slop before getting into gesture detection 479 // mode and not using the points within this slop significantly 480 // decreases the quality of gesture recognition. 481 handleMotionEventGestureDetecting(rawEvent, policyFlags); 482 // It is *important* to use the distance traveled by the pointers 483 // on the screen which may or may not be magnified. 484 final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId) 485 - rawEvent.getX(pointerIndex); 486 final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId) 487 - rawEvent.getY(pointerIndex); 488 final double moveDelta = Math.hypot(deltaX, deltaY); 489 // The user has moved enough for us to decide. 490 if (moveDelta > mDoubleTapSlop) { 491 // Check whether the user is performing a gesture. We 492 // detect gestures if the pointer is moving above a 493 // given velocity. 494 mVelocityTracker.computeCurrentVelocity(1000); 495 final float maxAbsVelocity = Math.max( 496 Math.abs(mVelocityTracker.getXVelocity(pointerId)), 497 Math.abs(mVelocityTracker.getYVelocity(pointerId))); 498 if (maxAbsVelocity > mScaledGestureDetectionVelocity) { 499 // We have to perform gesture detection, so 500 // clear the current state and try to detect. 501 mCurrentState = STATE_GESTURE_DETECTING; 502 mVelocityTracker.clear(); 503 mSendHoverEnterDelayed.remove(); 504 mSendHoverExitDelayed.remove(); 505 mPerformLongPressDelayed.remove(); 506 mExitGestureDetectionModeDelayed.post(); 507 // Send accessibility event to announce the start 508 // of gesture recognition. 509 sendAccessibilityEvent( 510 AccessibilityEvent.TYPE_GESTURE_DETECTION_START); 511 } else { 512 // We have just decided that the user is touch, 513 // exploring so start sending events. 514 mSendHoverEnterDelayed.forceSendAndRemove(); 515 mSendHoverExitDelayed.remove(); 516 mPerformLongPressDelayed.remove(); 517 sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, 518 pointerIdBits, policyFlags); 519 } 520 break; 521 } 522 } else { 523 // Cancel the long press if pending and the user 524 // moved more than the slop. 525 if (mPerformLongPressDelayed.isPending()) { 526 final float deltaX = 527 receivedTracker.getReceivedPointerDownX(pointerId) 528 - rawEvent.getX(pointerIndex); 529 final float deltaY = 530 receivedTracker.getReceivedPointerDownY(pointerId) 531 - rawEvent.getY(pointerIndex); 532 final double moveDelta = Math.hypot(deltaX, deltaY); 533 // The user has moved enough for us to decide. 534 if (moveDelta > mTouchSlop) { 535 mPerformLongPressDelayed.remove(); 536 } 537 } 538 // The user is wither double tapping or performing long 539 // press so do not send move events yet. 540 if (mDoubleTapDetector.firstTapDetected()) { 541 break; 542 } 543 sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); 544 sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, 545 policyFlags); 546 } 547 } break; 548 case 2: { 549 // More than one pointer so the user is not touch exploring 550 // and now we have to decide whether to delegate or drag. 551 if (mSendHoverEnterDelayed.isPending()) { 552 // We have not started sending events so cancel 553 // scheduled sending events. 554 mSendHoverEnterDelayed.remove(); 555 mSendHoverExitDelayed.remove(); 556 mPerformLongPressDelayed.remove(); 557 } else { 558 mPerformLongPressDelayed.remove(); 559 // If the user is touch exploring the second pointer may be 560 // performing a double tap to activate an item without need 561 // for the user to lift his exploring finger. 562 // It is *important* to use the distance traveled by the pointers 563 // on the screen which may or may not be magnified. 564 final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId) 565 - rawEvent.getX(pointerIndex); 566 final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId) 567 - rawEvent.getY(pointerIndex); 568 final double moveDelta = Math.hypot(deltaX, deltaY); 569 if (moveDelta < mDoubleTapSlop) { 570 break; 571 } 572 // We are sending events so send exit and gesture 573 // end since we transition to another state. 574 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); 575 } 576 577 // We know that a new state transition is to happen and the 578 // new state will not be gesture recognition, so clear the 579 // stashed gesture strokes. 580 mStrokeBuffer.clear(); 581 582 if (isDraggingGesture(event)) { 583 // Two pointers moving in the same direction within 584 // a given distance perform a drag. 585 mCurrentState = STATE_DRAGGING; 586 mDraggingPointerId = pointerId; 587 event.setEdgeFlags(receivedTracker.getLastReceivedDownEdgeFlags()); 588 sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits, 589 policyFlags); 590 } else { 591 // Two pointers moving arbitrary are delegated to the view hierarchy. 592 mCurrentState = STATE_DELEGATING; 593 sendDownForAllActiveNotInjectedPointers(event, policyFlags); 594 } 595 mVelocityTracker.clear(); 596 } break; 597 default: { 598 // More than one pointer so the user is not touch exploring 599 // and now we have to decide whether to delegate or drag. 600 if (mSendHoverEnterDelayed.isPending()) { 601 // We have not started sending events so cancel 602 // scheduled sending events. 603 mSendHoverEnterDelayed.remove(); 604 mSendHoverExitDelayed.remove(); 605 mPerformLongPressDelayed.remove(); 606 } else { 607 mPerformLongPressDelayed.remove(); 608 // We are sending events so send exit and gesture 609 // end since we transition to another state. 610 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); 611 } 612 613 // More than two pointers are delegated to the view hierarchy. 614 mCurrentState = STATE_DELEGATING; 615 sendDownForAllActiveNotInjectedPointers(event, policyFlags); 616 mVelocityTracker.clear(); 617 } 618 } 619 } break; 620 case MotionEvent.ACTION_UP: 621 mAms.onTouchInteractionEnd(); 622 // We know that we do not need the pre-fed gesture points are not 623 // needed anymore since the last pointer just went up. 624 mStrokeBuffer.clear(); 625 //$FALL-THROUGH$ 626 case MotionEvent.ACTION_POINTER_UP: { 627 final int pointerId = receivedTracker.getLastReceivedUpPointerId(); 628 final int pointerIdBits = (1 << pointerId); 629 switch (activePointerCount) { 630 case 0: { 631 // If the pointer that went up was not active we have nothing to do. 632 if (!receivedTracker.wasLastReceivedUpPointerActive()) { 633 break; 634 } 635 636 mPerformLongPressDelayed.remove(); 637 638 // If we have not delivered the enter schedule exit. 639 if (mSendHoverEnterDelayed.isPending()) { 640 mSendHoverExitDelayed.post(event, false, pointerIdBits, policyFlags); 641 } else { 642 // The user is touch exploring so we send events for end. 643 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); 644 } 645 646 if (!mSendTouchInteractionEndDelayed.isPending()) { 647 mSendTouchInteractionEndDelayed.post(); 648 } 649 } break; 650 } 651 mVelocityTracker.clear(); 652 } break; 653 case MotionEvent.ACTION_CANCEL: { 654 clear(event, policyFlags); 655 } break; 656 } 657 } 658 659 /** 660 * Handles a motion event in dragging state. 661 * 662 * @param event The event to be handled. 663 * @param policyFlags The policy flags associated with the event. 664 */ 665 private void handleMotionEventStateDragging(MotionEvent event, int policyFlags) { 666 final int pointerIdBits = (1 << mDraggingPointerId); 667 switch (event.getActionMasked()) { 668 case MotionEvent.ACTION_DOWN: { 669 throw new IllegalStateException("Dragging state can be reached only if two " 670 + "pointers are already down"); 671 } 672 case MotionEvent.ACTION_POINTER_DOWN: { 673 // We are in dragging state so we have two pointers and another one 674 // goes down => delegate the three pointers to the view hierarchy 675 mCurrentState = STATE_DELEGATING; 676 if (mDraggingPointerId != INVALID_POINTER_ID) { 677 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); 678 } 679 sendDownForAllActiveNotInjectedPointers(event, policyFlags); 680 } break; 681 case MotionEvent.ACTION_MOVE: { 682 final int activePointerCount = mReceivedPointerTracker.getActivePointerCount(); 683 switch (activePointerCount) { 684 case 1: { 685 // do nothing 686 } break; 687 case 2: { 688 if (isDraggingGesture(event)) { 689 // If the dragging pointer are closer that a given distance we 690 // use the location of the primary one. Otherwise, we take the 691 // middle between the pointers. 692 int[] pointerIds = mTempPointerIds; 693 mReceivedPointerTracker.populateActivePointerIds(pointerIds); 694 695 final int firstPtrIndex = event.findPointerIndex(pointerIds[0]); 696 final int secondPtrIndex = event.findPointerIndex(pointerIds[1]); 697 698 final float firstPtrX = event.getX(firstPtrIndex); 699 final float firstPtrY = event.getY(firstPtrIndex); 700 final float secondPtrX = event.getX(secondPtrIndex); 701 final float secondPtrY = event.getY(secondPtrIndex); 702 703 final float deltaX = firstPtrX - secondPtrX; 704 final float deltaY = firstPtrY - secondPtrY; 705 final double distance = Math.hypot(deltaX, deltaY); 706 707 if (distance > mScaledMinPointerDistanceToUseMiddleLocation) { 708 event.setLocation(deltaX / 2, deltaY / 2); 709 } 710 711 // If still dragging send a drag event. 712 sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits, 713 policyFlags); 714 } else { 715 // The two pointers are moving either in different directions or 716 // no close enough => delegate the gesture to the view hierarchy. 717 mCurrentState = STATE_DELEGATING; 718 // Send an event to the end of the drag gesture. 719 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, 720 policyFlags); 721 // Deliver all active pointers to the view hierarchy. 722 sendDownForAllActiveNotInjectedPointers(event, policyFlags); 723 } 724 } break; 725 default: { 726 mCurrentState = STATE_DELEGATING; 727 // Send an event to the end of the drag gesture. 728 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, 729 policyFlags); 730 // Deliver all active pointers to the view hierarchy. 731 sendDownForAllActiveNotInjectedPointers(event, policyFlags); 732 } 733 } 734 } break; 735 case MotionEvent.ACTION_POINTER_UP: { 736 final int pointerId = event.getPointerId(event.getActionIndex()); 737 if (pointerId == mDraggingPointerId) { 738 mDraggingPointerId = INVALID_POINTER_ID; 739 // Send an event to the end of the drag gesture. 740 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); 741 } 742 } break; 743 case MotionEvent.ACTION_UP: { 744 mAms.onTouchInteractionEnd(); 745 // Announce the end of a new touch interaction. 746 sendAccessibilityEvent( 747 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 748 final int pointerId = event.getPointerId(event.getActionIndex()); 749 if (pointerId == mDraggingPointerId) { 750 mDraggingPointerId = INVALID_POINTER_ID; 751 // Send an event to the end of the drag gesture. 752 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); 753 } 754 mCurrentState = STATE_TOUCH_EXPLORING; 755 } break; 756 case MotionEvent.ACTION_CANCEL: { 757 clear(event, policyFlags); 758 } break; 759 } 760 } 761 762 /** 763 * Handles a motion event in delegating state. 764 * 765 * @param event The event to be handled. 766 * @param policyFlags The policy flags associated with the event. 767 */ 768 private void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) { 769 switch (event.getActionMasked()) { 770 case MotionEvent.ACTION_DOWN: { 771 throw new IllegalStateException("Delegating state can only be reached if " 772 + "there is at least one pointer down!"); 773 } 774 case MotionEvent.ACTION_MOVE: { 775 // Check whether some other pointer became active because they have moved 776 // a given distance and if such exist send them to the view hierarchy 777 final int notInjectedCount = getNotInjectedActivePointerCount( 778 mReceivedPointerTracker, mInjectedPointerTracker); 779 if (notInjectedCount > 0) { 780 MotionEvent prototype = MotionEvent.obtain(event); 781 sendDownForAllActiveNotInjectedPointers(prototype, policyFlags); 782 } 783 } break; 784 case MotionEvent.ACTION_UP: 785 // Announce the end of a new touch interaction. 786 sendAccessibilityEvent( 787 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 788 //$FALL-THROUGH$ 789 case MotionEvent.ACTION_POINTER_UP: { 790 mAms.onTouchInteractionEnd(); 791 mLongPressingPointerId = -1; 792 mLongPressingPointerDeltaX = 0; 793 mLongPressingPointerDeltaY = 0; 794 // No active pointers => go to initial state. 795 if (mReceivedPointerTracker.getActivePointerCount() == 0) { 796 mCurrentState = STATE_TOUCH_EXPLORING; 797 } 798 } break; 799 case MotionEvent.ACTION_CANCEL: { 800 clear(event, policyFlags); 801 } break; 802 } 803 // Deliver the event striping out inactive pointers. 804 sendMotionEventStripInactivePointers(event, policyFlags); 805 } 806 807 private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) { 808 switch (event.getActionMasked()) { 809 case MotionEvent.ACTION_DOWN: { 810 final float x = event.getX(); 811 final float y = event.getY(); 812 mPreviousX = x; 813 mPreviousY = y; 814 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); 815 } break; 816 case MotionEvent.ACTION_MOVE: { 817 final float x = event.getX(); 818 final float y = event.getY(); 819 final float dX = Math.abs(x - mPreviousX); 820 final float dY = Math.abs(y - mPreviousY); 821 if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) { 822 mPreviousX = x; 823 mPreviousY = y; 824 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); 825 } 826 } break; 827 case MotionEvent.ACTION_UP: { 828 mAms.onTouchInteractionEnd(); 829 // Announce the end of gesture recognition. 830 sendAccessibilityEvent( 831 AccessibilityEvent.TYPE_GESTURE_DETECTION_END); 832 // Announce the end of a new touch interaction. 833 sendAccessibilityEvent( 834 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 835 836 float x = event.getX(); 837 float y = event.getY(); 838 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); 839 840 Gesture gesture = new Gesture(); 841 gesture.addStroke(new GestureStroke(mStrokeBuffer)); 842 843 ArrayList<Prediction> predictions = mGestureLibrary.recognize(gesture); 844 if (!predictions.isEmpty()) { 845 Prediction bestPrediction = predictions.get(0); 846 if (bestPrediction.score >= MIN_PREDICTION_SCORE) { 847 if (DEBUG) { 848 Slog.i(LOG_TAG, "gesture: " + bestPrediction.name + " score: " 849 + bestPrediction.score); 850 } 851 try { 852 final int gestureId = Integer.parseInt(bestPrediction.name); 853 mAms.onGesture(gestureId); 854 } catch (NumberFormatException nfe) { 855 Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name); 856 } 857 } 858 } 859 860 mStrokeBuffer.clear(); 861 mExitGestureDetectionModeDelayed.remove(); 862 mCurrentState = STATE_TOUCH_EXPLORING; 863 } break; 864 case MotionEvent.ACTION_CANCEL: { 865 clear(event, policyFlags); 866 } break; 867 } 868 } 869 870 /** 871 * Sends an accessibility event of the given type. 872 * 873 * @param type The event type. 874 */ 875 private void sendAccessibilityEvent(int type) { 876 AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext); 877 if (accessibilityManager.isEnabled()) { 878 AccessibilityEvent event = AccessibilityEvent.obtain(type); 879 accessibilityManager.sendAccessibilityEvent(event); 880 switch (type) { 881 case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: { 882 mTouchExplorationInProgress = true; 883 } break; 884 case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: { 885 mTouchExplorationInProgress = false; 886 } break; 887 } 888 } 889 } 890 891 /** 892 * Sends down events to the view hierarchy for all active pointers which are 893 * not already being delivered i.e. pointers that are not yet injected. 894 * 895 * @param prototype The prototype from which to create the injected events. 896 * @param policyFlags The policy flags associated with the event. 897 */ 898 private void sendDownForAllActiveNotInjectedPointers(MotionEvent prototype, int policyFlags) { 899 ReceivedPointerTracker receivedPointers = mReceivedPointerTracker; 900 InjectedPointerTracker injectedPointers = mInjectedPointerTracker; 901 int pointerIdBits = 0; 902 final int pointerCount = prototype.getPointerCount(); 903 904 // Find which pointers are already injected. 905 for (int i = 0; i < pointerCount; i++) { 906 final int pointerId = prototype.getPointerId(i); 907 if (injectedPointers.isInjectedPointerDown(pointerId)) { 908 pointerIdBits |= (1 << pointerId); 909 } 910 } 911 912 // Inject the active and not injected pointers. 913 for (int i = 0; i < pointerCount; i++) { 914 final int pointerId = prototype.getPointerId(i); 915 // Skip inactive pointers. 916 if (!receivedPointers.isActivePointer(pointerId)) { 917 continue; 918 } 919 // Do not send event for already delivered pointers. 920 if (injectedPointers.isInjectedPointerDown(pointerId)) { 921 continue; 922 } 923 pointerIdBits |= (1 << pointerId); 924 final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i); 925 sendMotionEvent(prototype, action, pointerIdBits, policyFlags); 926 } 927 } 928 929 /** 930 * Sends the exit events if needed. Such events are hover exit and touch explore 931 * gesture end. 932 * 933 * @param policyFlags The policy flags associated with the event. 934 */ 935 private void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) { 936 MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent(); 937 if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) { 938 final int pointerIdBits = event.getPointerIdBits(); 939 if (!mSendTouchExplorationEndDelayed.isPending()) { 940 mSendTouchExplorationEndDelayed.post(); 941 } 942 sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags); 943 } 944 } 945 946 /** 947 * Sends the enter events if needed. Such events are hover enter and touch explore 948 * gesture start. 949 * 950 * @param policyFlags The policy flags associated with the event. 951 */ 952 private void sendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags) { 953 MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent(); 954 if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) { 955 final int pointerIdBits = event.getPointerIdBits(); 956 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); 957 sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags); 958 } 959 } 960 961 /** 962 * Sends up events to the view hierarchy for all active pointers which are 963 * already being delivered i.e. pointers that are injected. 964 * 965 * @param prototype The prototype from which to create the injected events. 966 * @param policyFlags The policy flags associated with the event. 967 */ 968 private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) { 969 final InjectedPointerTracker injectedTracked = mInjectedPointerTracker; 970 int pointerIdBits = 0; 971 final int pointerCount = prototype.getPointerCount(); 972 for (int i = 0; i < pointerCount; i++) { 973 final int pointerId = prototype.getPointerId(i); 974 // Skip non injected down pointers. 975 if (!injectedTracked.isInjectedPointerDown(pointerId)) { 976 continue; 977 } 978 pointerIdBits |= (1 << pointerId); 979 final int action = computeInjectionAction(MotionEvent.ACTION_UP, i); 980 sendMotionEvent(prototype, action, pointerIdBits, policyFlags); 981 } 982 } 983 984 /** 985 * Sends a motion event by first stripping the inactive pointers. 986 * 987 * @param prototype The prototype from which to create the injected event. 988 * @param policyFlags The policy flags associated with the event. 989 */ 990 private void sendMotionEventStripInactivePointers(MotionEvent prototype, int policyFlags) { 991 ReceivedPointerTracker receivedTracker = mReceivedPointerTracker; 992 993 // All pointers active therefore we just inject the event as is. 994 if (prototype.getPointerCount() == receivedTracker.getActivePointerCount()) { 995 sendMotionEvent(prototype, prototype.getAction(), ALL_POINTER_ID_BITS, policyFlags); 996 return; 997 } 998 999 // No active pointers and the one that just went up was not 1000 // active, therefore we have nothing to do. 1001 if (receivedTracker.getActivePointerCount() == 0 1002 && !receivedTracker.wasLastReceivedUpPointerActive()) { 1003 return; 1004 } 1005 1006 // If the action pointer going up/down is not active we have nothing to do. 1007 // However, for moves we keep going to report moves of active pointers. 1008 final int actionMasked = prototype.getActionMasked(); 1009 final int actionPointerId = prototype.getPointerId(prototype.getActionIndex()); 1010 if (actionMasked != MotionEvent.ACTION_MOVE) { 1011 if (!receivedTracker.isActiveOrWasLastActiveUpPointer(actionPointerId)) { 1012 return; 1013 } 1014 } 1015 1016 // If the pointer is active or the pointer that just went up 1017 // was active we keep the pointer data in the event. 1018 int pointerIdBits = 0; 1019 final int pointerCount = prototype.getPointerCount(); 1020 for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) { 1021 final int pointerId = prototype.getPointerId(pointerIndex); 1022 if (receivedTracker.isActiveOrWasLastActiveUpPointer(pointerId)) { 1023 pointerIdBits |= (1 << pointerId); 1024 } 1025 } 1026 sendMotionEvent(prototype, prototype.getAction(), pointerIdBits, policyFlags); 1027 } 1028 1029 /** 1030 * Sends an up and down events. 1031 * 1032 * @param prototype The prototype from which to create the injected events. 1033 * @param policyFlags The policy flags associated with the event. 1034 */ 1035 private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) { 1036 // Tap with the pointer that last explored - we may have inactive pointers. 1037 final int pointerId = prototype.getPointerId(prototype.getActionIndex()); 1038 final int pointerIdBits = (1 << pointerId); 1039 sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags); 1040 sendMotionEvent(prototype, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); 1041 } 1042 1043 /** 1044 * Sends an event. 1045 * 1046 * @param prototype The prototype from which to create the injected events. 1047 * @param action The action of the event. 1048 * @param pointerIdBits The bits of the pointers to send. 1049 * @param policyFlags The policy flags associated with the event. 1050 */ 1051 private void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits, 1052 int policyFlags) { 1053 prototype.setAction(action); 1054 1055 MotionEvent event = null; 1056 if (pointerIdBits == ALL_POINTER_ID_BITS) { 1057 event = prototype; 1058 } else { 1059 event = prototype.split(pointerIdBits); 1060 } 1061 if (action == MotionEvent.ACTION_DOWN) { 1062 event.setDownTime(event.getEventTime()); 1063 } else { 1064 event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime()); 1065 } 1066 1067 // If the user is long pressing but the long pressing pointer 1068 // was not exactly over the accessibility focused item we need 1069 // to remap the location of that pointer so the user does not 1070 // have to explicitly touch explore something to be able to 1071 // long press it, or even worse to avoid the user long pressing 1072 // on the wrong item since click and long press behave differently. 1073 if (mLongPressingPointerId >= 0) { 1074 final int remappedIndex = event.findPointerIndex(mLongPressingPointerId); 1075 final int pointerCount = event.getPointerCount(); 1076 PointerProperties[] props = PointerProperties.createArray(pointerCount); 1077 PointerCoords[] coords = PointerCoords.createArray(pointerCount); 1078 for (int i = 0; i < pointerCount; i++) { 1079 event.getPointerProperties(i, props[i]); 1080 event.getPointerCoords(i, coords[i]); 1081 if (i == remappedIndex) { 1082 coords[i].x -= mLongPressingPointerDeltaX; 1083 coords[i].y -= mLongPressingPointerDeltaY; 1084 } 1085 } 1086 MotionEvent remapped = MotionEvent.obtain(event.getDownTime(), 1087 event.getEventTime(), event.getAction(), event.getPointerCount(), 1088 props, coords, event.getMetaState(), event.getButtonState(), 1089 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(), 1090 event.getSource(), event.getFlags()); 1091 if (event != prototype) { 1092 event.recycle(); 1093 } 1094 event = remapped; 1095 } 1096 1097 if (DEBUG) { 1098 Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x" 1099 + Integer.toHexString(policyFlags)); 1100 } 1101 1102 // Make sure that the user will see the event. 1103 policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER; 1104 if (mNext != null) { 1105 // TODO: For now pass null for the raw event since the touch 1106 // explorer is the last event transformation and it does 1107 // not care about the raw event. 1108 mNext.onMotionEvent(event, null, policyFlags); 1109 } 1110 1111 mInjectedPointerTracker.onMotionEvent(event); 1112 1113 if (event != prototype) { 1114 event.recycle(); 1115 } 1116 } 1117 1118 /** 1119 * Computes the action for an injected event based on a masked action 1120 * and a pointer index. 1121 * 1122 * @param actionMasked The masked action. 1123 * @param pointerIndex The index of the pointer which has changed. 1124 * @return The action to be used for injection. 1125 */ 1126 private int computeInjectionAction(int actionMasked, int pointerIndex) { 1127 switch (actionMasked) { 1128 case MotionEvent.ACTION_DOWN: 1129 case MotionEvent.ACTION_POINTER_DOWN: { 1130 InjectedPointerTracker injectedTracker = mInjectedPointerTracker; 1131 // Compute the action based on how many down pointers are injected. 1132 if (injectedTracker.getInjectedPointerDownCount() == 0) { 1133 return MotionEvent.ACTION_DOWN; 1134 } else { 1135 return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) 1136 | MotionEvent.ACTION_POINTER_DOWN; 1137 } 1138 } 1139 case MotionEvent.ACTION_POINTER_UP: { 1140 InjectedPointerTracker injectedTracker = mInjectedPointerTracker; 1141 // Compute the action based on how many down pointers are injected. 1142 if (injectedTracker.getInjectedPointerDownCount() == 1) { 1143 return MotionEvent.ACTION_UP; 1144 } else { 1145 return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) 1146 | MotionEvent.ACTION_POINTER_UP; 1147 } 1148 } 1149 default: 1150 return actionMasked; 1151 } 1152 } 1153 1154 private class DoubleTapDetector { 1155 private MotionEvent mDownEvent; 1156 private MotionEvent mFirstTapEvent; 1157 1158 public void onMotionEvent(MotionEvent event, int policyFlags) { 1159 final int actionIndex = event.getActionIndex(); 1160 final int action = event.getActionMasked(); 1161 switch (action) { 1162 case MotionEvent.ACTION_DOWN: 1163 case MotionEvent.ACTION_POINTER_DOWN: { 1164 if (mFirstTapEvent != null 1165 && !GestureUtils.isSamePointerContext(mFirstTapEvent, event)) { 1166 clear(); 1167 } 1168 mDownEvent = MotionEvent.obtain(event); 1169 } break; 1170 case MotionEvent.ACTION_UP: 1171 case MotionEvent.ACTION_POINTER_UP: { 1172 if (mDownEvent == null) { 1173 return; 1174 } 1175 if (!GestureUtils.isSamePointerContext(mDownEvent, event)) { 1176 clear(); 1177 return; 1178 } 1179 if (GestureUtils.isTap(mDownEvent, event, mTapTimeout, mTouchSlop, 1180 actionIndex)) { 1181 if (mFirstTapEvent == null || GestureUtils.isTimedOut(mFirstTapEvent, 1182 event, mDoubleTapTimeout)) { 1183 mFirstTapEvent = MotionEvent.obtain(event); 1184 mDownEvent.recycle(); 1185 mDownEvent = null; 1186 return; 1187 } 1188 if (GestureUtils.isMultiTap(mFirstTapEvent, event, mDoubleTapTimeout, 1189 mDoubleTapSlop, actionIndex)) { 1190 onDoubleTap(event, policyFlags); 1191 mFirstTapEvent.recycle(); 1192 mFirstTapEvent = null; 1193 mDownEvent.recycle(); 1194 mDownEvent = null; 1195 return; 1196 } 1197 mFirstTapEvent.recycle(); 1198 mFirstTapEvent = null; 1199 } else { 1200 if (mFirstTapEvent != null) { 1201 mFirstTapEvent.recycle(); 1202 mFirstTapEvent = null; 1203 } 1204 } 1205 mDownEvent.recycle(); 1206 mDownEvent = null; 1207 } break; 1208 } 1209 } 1210 1211 public void onDoubleTap(MotionEvent secondTapUp, int policyFlags) { 1212 // This should never be called when more than two pointers are down. 1213 if (secondTapUp.getPointerCount() > 2) { 1214 return; 1215 } 1216 1217 // Remove pending event deliveries. 1218 mSendHoverEnterDelayed.remove(); 1219 mSendHoverExitDelayed.remove(); 1220 mPerformLongPressDelayed.remove(); 1221 1222 if (mSendTouchExplorationEndDelayed.isPending()) { 1223 mSendTouchExplorationEndDelayed.forceSendAndRemove(); 1224 } 1225 if (mSendTouchInteractionEndDelayed.isPending()) { 1226 mSendTouchInteractionEndDelayed.forceSendAndRemove(); 1227 } 1228 1229 int clickLocationX; 1230 int clickLocationY; 1231 1232 final int pointerId = secondTapUp.getPointerId(secondTapUp.getActionIndex()); 1233 final int pointerIndex = secondTapUp.findPointerIndex(pointerId); 1234 1235 MotionEvent lastExploreEvent = 1236 mInjectedPointerTracker.getLastInjectedHoverEventForClick(); 1237 if (lastExploreEvent == null) { 1238 // No last touch explored event but there is accessibility focus in 1239 // the active window. We click in the middle of the focus bounds. 1240 Rect focusBounds = mTempRect; 1241 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) { 1242 clickLocationX = focusBounds.centerX(); 1243 clickLocationY = focusBounds.centerY(); 1244 } else { 1245 // Out of luck - do nothing. 1246 return; 1247 } 1248 } else { 1249 // If the click is within the active window but not within the 1250 // accessibility focus bounds we click in the focus center. 1251 final int lastExplorePointerIndex = lastExploreEvent.getActionIndex(); 1252 clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex); 1253 clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex); 1254 Rect activeWindowBounds = mTempRect; 1255 if (mLastTouchedWindowId == mAms.getActiveWindowId()) { 1256 mAms.getActiveWindowBounds(activeWindowBounds); 1257 if (activeWindowBounds.contains(clickLocationX, clickLocationY)) { 1258 Rect focusBounds = mTempRect; 1259 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) { 1260 if (!focusBounds.contains(clickLocationX, clickLocationY)) { 1261 clickLocationX = focusBounds.centerX(); 1262 clickLocationY = focusBounds.centerY(); 1263 } 1264 } 1265 } 1266 } 1267 } 1268 1269 // Do the click. 1270 PointerProperties[] properties = new PointerProperties[1]; 1271 properties[0] = new PointerProperties(); 1272 secondTapUp.getPointerProperties(pointerIndex, properties[0]); 1273 PointerCoords[] coords = new PointerCoords[1]; 1274 coords[0] = new PointerCoords(); 1275 coords[0].x = clickLocationX; 1276 coords[0].y = clickLocationY; 1277 MotionEvent event = MotionEvent.obtain(secondTapUp.getDownTime(), 1278 secondTapUp.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties, 1279 coords, 0, 0, 1.0f, 1.0f, secondTapUp.getDeviceId(), 0, 1280 secondTapUp.getSource(), secondTapUp.getFlags()); 1281 sendActionDownAndUp(event, policyFlags); 1282 event.recycle(); 1283 } 1284 1285 public void clear() { 1286 if (mDownEvent != null) { 1287 mDownEvent.recycle(); 1288 mDownEvent = null; 1289 } 1290 if (mFirstTapEvent != null) { 1291 mFirstTapEvent.recycle(); 1292 mFirstTapEvent = null; 1293 } 1294 } 1295 1296 public boolean firstTapDetected() { 1297 return mFirstTapEvent != null 1298 && SystemClock.uptimeMillis() - mFirstTapEvent.getEventTime() < mDoubleTapTimeout; 1299 } 1300 } 1301 1302 /** 1303 * Determines whether a two pointer gesture is a dragging one. 1304 * 1305 * @param event The event with the pointer data. 1306 * @return True if the gesture is a dragging one. 1307 */ 1308 private boolean isDraggingGesture(MotionEvent event) { 1309 ReceivedPointerTracker receivedTracker = mReceivedPointerTracker; 1310 int[] pointerIds = mTempPointerIds; 1311 receivedTracker.populateActivePointerIds(pointerIds); 1312 1313 final int firstPtrIndex = event.findPointerIndex(pointerIds[0]); 1314 final int secondPtrIndex = event.findPointerIndex(pointerIds[1]); 1315 1316 final float firstPtrX = event.getX(firstPtrIndex); 1317 final float firstPtrY = event.getY(firstPtrIndex); 1318 final float secondPtrX = event.getX(secondPtrIndex); 1319 final float secondPtrY = event.getY(secondPtrIndex); 1320 1321 final float firstPtrDownX = receivedTracker.getReceivedPointerDownX(firstPtrIndex); 1322 final float firstPtrDownY = receivedTracker.getReceivedPointerDownY(firstPtrIndex); 1323 final float secondPtrDownX = receivedTracker.getReceivedPointerDownX(secondPtrIndex); 1324 final float secondPtrDownY = receivedTracker.getReceivedPointerDownY(secondPtrIndex); 1325 1326 return GestureUtils.isDraggingGesture(firstPtrDownX, firstPtrDownY, secondPtrDownX, 1327 secondPtrDownY, firstPtrX, firstPtrY, secondPtrX, secondPtrY, 1328 MAX_DRAGGING_ANGLE_COS); 1329 } 1330 1331 /** 1332 * Gets the symbolic name of a state. 1333 * 1334 * @param state A state. 1335 * @return The state symbolic name. 1336 */ 1337 private static String getStateSymbolicName(int state) { 1338 switch (state) { 1339 case STATE_TOUCH_EXPLORING: 1340 return "STATE_TOUCH_EXPLORING"; 1341 case STATE_DRAGGING: 1342 return "STATE_DRAGGING"; 1343 case STATE_DELEGATING: 1344 return "STATE_DELEGATING"; 1345 case STATE_GESTURE_DETECTING: 1346 return "STATE_GESTURE_DETECTING"; 1347 default: 1348 throw new IllegalArgumentException("Unknown state: " + state); 1349 } 1350 } 1351 1352 /** 1353 * @return The number of non injected active pointers. 1354 */ 1355 private int getNotInjectedActivePointerCount(ReceivedPointerTracker receivedTracker, 1356 InjectedPointerTracker injectedTracker) { 1357 final int pointerState = receivedTracker.getActivePointers() 1358 & ~injectedTracker.getInjectedPointersDown(); 1359 return Integer.bitCount(pointerState); 1360 } 1361 1362 /** 1363 * Class for delayed exiting from gesture detecting mode. 1364 */ 1365 private final class ExitGestureDetectionModeDelayed implements Runnable { 1366 1367 public void post() { 1368 mHandler.postDelayed(this, EXIT_GESTURE_DETECTION_TIMEOUT); 1369 } 1370 1371 public void remove() { 1372 mHandler.removeCallbacks(this); 1373 } 1374 1375 @Override 1376 public void run() { 1377 // Announce the end of gesture recognition. 1378 sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); 1379 // Clearing puts is in touch exploration state with a finger already 1380 // down, so announce the transition to exploration state. 1381 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); 1382 clear(); 1383 } 1384 } 1385 1386 /** 1387 * Class for delayed sending of long press. 1388 */ 1389 private final class PerformLongPressDelayed implements Runnable { 1390 private MotionEvent mEvent; 1391 private int mPolicyFlags; 1392 1393 public void post(MotionEvent prototype, int policyFlags) { 1394 mEvent = MotionEvent.obtain(prototype); 1395 mPolicyFlags = policyFlags; 1396 mHandler.postDelayed(this, ViewConfiguration.getLongPressTimeout()); 1397 } 1398 1399 public void remove() { 1400 if (isPending()) { 1401 mHandler.removeCallbacks(this); 1402 clear(); 1403 } 1404 } 1405 1406 public boolean isPending() { 1407 return (mEvent != null); 1408 } 1409 1410 @Override 1411 public void run() { 1412 // Active pointers should not be zero when running this command. 1413 if (mReceivedPointerTracker.getActivePointerCount() == 0) { 1414 return; 1415 } 1416 1417 int clickLocationX; 1418 int clickLocationY; 1419 1420 final int pointerId = mEvent.getPointerId(mEvent.getActionIndex()); 1421 final int pointerIndex = mEvent.findPointerIndex(pointerId); 1422 1423 MotionEvent lastExploreEvent = 1424 mInjectedPointerTracker.getLastInjectedHoverEventForClick(); 1425 if (lastExploreEvent == null) { 1426 // No last touch explored event but there is accessibility focus in 1427 // the active window. We click in the middle of the focus bounds. 1428 Rect focusBounds = mTempRect; 1429 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) { 1430 clickLocationX = focusBounds.centerX(); 1431 clickLocationY = focusBounds.centerY(); 1432 } else { 1433 // Out of luck - do nothing. 1434 return; 1435 } 1436 } else { 1437 // If the click is within the active window but not within the 1438 // accessibility focus bounds we click in the focus center. 1439 final int lastExplorePointerIndex = lastExploreEvent.getActionIndex(); 1440 clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex); 1441 clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex); 1442 Rect activeWindowBounds = mTempRect; 1443 if (mLastTouchedWindowId == mAms.getActiveWindowId()) { 1444 mAms.getActiveWindowBounds(activeWindowBounds); 1445 if (activeWindowBounds.contains(clickLocationX, clickLocationY)) { 1446 Rect focusBounds = mTempRect; 1447 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) { 1448 if (!focusBounds.contains(clickLocationX, clickLocationY)) { 1449 clickLocationX = focusBounds.centerX(); 1450 clickLocationY = focusBounds.centerY(); 1451 } 1452 } 1453 } 1454 } 1455 } 1456 1457 mLongPressingPointerId = pointerId; 1458 mLongPressingPointerDeltaX = (int) mEvent.getX(pointerIndex) - clickLocationX; 1459 mLongPressingPointerDeltaY = (int) mEvent.getY(pointerIndex) - clickLocationY; 1460 1461 sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags); 1462 1463 mCurrentState = STATE_DELEGATING; 1464 sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags); 1465 clear(); 1466 } 1467 1468 private void clear() { 1469 if (!isPending()) { 1470 return; 1471 } 1472 mEvent.recycle(); 1473 mEvent = null; 1474 mPolicyFlags = 0; 1475 } 1476 } 1477 1478 /** 1479 * Class for delayed sending of hover events. 1480 */ 1481 class SendHoverDelayed implements Runnable { 1482 private final String LOG_TAG_SEND_HOVER_DELAYED = SendHoverDelayed.class.getName(); 1483 1484 private final int mHoverAction; 1485 private final boolean mGestureStarted; 1486 1487 private MotionEvent mPrototype; 1488 private int mPointerIdBits; 1489 private int mPolicyFlags; 1490 1491 public SendHoverDelayed(int hoverAction, boolean gestureStarted) { 1492 mHoverAction = hoverAction; 1493 mGestureStarted = gestureStarted; 1494 } 1495 1496 public void post(MotionEvent prototype, boolean touchExplorationInProgress, 1497 int pointerIdBits, int policyFlags) { 1498 remove(); 1499 mPrototype = MotionEvent.obtain(prototype); 1500 mPointerIdBits = pointerIdBits; 1501 mPolicyFlags = policyFlags; 1502 mHandler.postDelayed(this, mDetermineUserIntentTimeout); 1503 } 1504 1505 public float getX() { 1506 if (isPending()) { 1507 return mPrototype.getX(); 1508 } 1509 return 0; 1510 } 1511 1512 public float getY() { 1513 if (isPending()) { 1514 return mPrototype.getY(); 1515 } 1516 return 0; 1517 } 1518 1519 public void remove() { 1520 mHandler.removeCallbacks(this); 1521 clear(); 1522 } 1523 1524 private boolean isPending() { 1525 return (mPrototype != null); 1526 } 1527 1528 private void clear() { 1529 if (!isPending()) { 1530 return; 1531 } 1532 mPrototype.recycle(); 1533 mPrototype = null; 1534 mPointerIdBits = -1; 1535 mPolicyFlags = 0; 1536 } 1537 1538 public void forceSendAndRemove() { 1539 if (isPending()) { 1540 run(); 1541 remove(); 1542 } 1543 } 1544 1545 public void run() { 1546 if (DEBUG) { 1547 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event: " 1548 + MotionEvent.actionToString(mHoverAction)); 1549 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, mGestureStarted ? 1550 "touchExplorationGestureStarted" : "touchExplorationGestureEnded"); 1551 } 1552 if (mGestureStarted) { 1553 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); 1554 } else { 1555 if (!mSendTouchExplorationEndDelayed.isPending()) { 1556 mSendTouchExplorationEndDelayed.post(); 1557 } 1558 if (mSendTouchInteractionEndDelayed.isPending()) { 1559 mSendTouchInteractionEndDelayed.remove(); 1560 mSendTouchInteractionEndDelayed.post(); 1561 } 1562 } 1563 sendMotionEvent(mPrototype, mHoverAction, mPointerIdBits, mPolicyFlags); 1564 clear(); 1565 } 1566 } 1567 1568 private class SendAccessibilityEventDelayed implements Runnable { 1569 private final int mEventType; 1570 private final int mDelay; 1571 1572 public SendAccessibilityEventDelayed(int eventType, int delay) { 1573 mEventType = eventType; 1574 mDelay = delay; 1575 } 1576 1577 public void remove() { 1578 mHandler.removeCallbacks(this); 1579 } 1580 1581 public void post() { 1582 mHandler.postDelayed(this, mDelay); 1583 } 1584 1585 public boolean isPending() { 1586 return mHandler.hasCallbacks(this); 1587 } 1588 1589 public void forceSendAndRemove() { 1590 if (isPending()) { 1591 run(); 1592 remove(); 1593 } 1594 } 1595 1596 @Override 1597 public void run() { 1598 sendAccessibilityEvent(mEventType); 1599 } 1600 } 1601 1602 @Override 1603 public String toString() { 1604 return LOG_TAG; 1605 } 1606 1607 class InjectedPointerTracker { 1608 private static final String LOG_TAG_INJECTED_POINTER_TRACKER = "InjectedPointerTracker"; 1609 1610 // Keep track of which pointers sent to the system are down. 1611 private int mInjectedPointersDown; 1612 1613 // The time of the last injected down. 1614 private long mLastInjectedDownEventTime; 1615 1616 // The last injected hover event. 1617 private MotionEvent mLastInjectedHoverEvent; 1618 1619 // The last injected hover event used for performing clicks. 1620 private MotionEvent mLastInjectedHoverEventForClick; 1621 1622 /** 1623 * Processes an injected {@link MotionEvent} event. 1624 * 1625 * @param event The event to process. 1626 */ 1627 public void onMotionEvent(MotionEvent event) { 1628 final int action = event.getActionMasked(); 1629 switch (action) { 1630 case MotionEvent.ACTION_DOWN: 1631 case MotionEvent.ACTION_POINTER_DOWN: { 1632 final int pointerId = event.getPointerId(event.getActionIndex()); 1633 final int pointerFlag = (1 << pointerId); 1634 mInjectedPointersDown |= pointerFlag; 1635 mLastInjectedDownEventTime = event.getDownTime(); 1636 } break; 1637 case MotionEvent.ACTION_UP: 1638 case MotionEvent.ACTION_POINTER_UP: { 1639 final int pointerId = event.getPointerId(event.getActionIndex()); 1640 final int pointerFlag = (1 << pointerId); 1641 mInjectedPointersDown &= ~pointerFlag; 1642 if (mInjectedPointersDown == 0) { 1643 mLastInjectedDownEventTime = 0; 1644 } 1645 } break; 1646 case MotionEvent.ACTION_HOVER_ENTER: 1647 case MotionEvent.ACTION_HOVER_MOVE: 1648 case MotionEvent.ACTION_HOVER_EXIT: { 1649 if (mLastInjectedHoverEvent != null) { 1650 mLastInjectedHoverEvent.recycle(); 1651 } 1652 mLastInjectedHoverEvent = MotionEvent.obtain(event); 1653 if (mLastInjectedHoverEventForClick != null) { 1654 mLastInjectedHoverEventForClick.recycle(); 1655 } 1656 mLastInjectedHoverEventForClick = MotionEvent.obtain(event); 1657 } break; 1658 } 1659 if (DEBUG) { 1660 Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer:\n" + toString()); 1661 } 1662 } 1663 1664 /** 1665 * Clears the internals state. 1666 */ 1667 public void clear() { 1668 mInjectedPointersDown = 0; 1669 } 1670 1671 /** 1672 * @return The time of the last injected down event. 1673 */ 1674 public long getLastInjectedDownEventTime() { 1675 return mLastInjectedDownEventTime; 1676 } 1677 1678 /** 1679 * @return The number of down pointers injected to the view hierarchy. 1680 */ 1681 public int getInjectedPointerDownCount() { 1682 return Integer.bitCount(mInjectedPointersDown); 1683 } 1684 1685 /** 1686 * @return The bits of the injected pointers that are down. 1687 */ 1688 public int getInjectedPointersDown() { 1689 return mInjectedPointersDown; 1690 } 1691 1692 /** 1693 * Whether an injected pointer is down. 1694 * 1695 * @param pointerId The unique pointer id. 1696 * @return True if the pointer is down. 1697 */ 1698 public boolean isInjectedPointerDown(int pointerId) { 1699 final int pointerFlag = (1 << pointerId); 1700 return (mInjectedPointersDown & pointerFlag) != 0; 1701 } 1702 1703 /** 1704 * @return The the last injected hover event. 1705 */ 1706 public MotionEvent getLastInjectedHoverEvent() { 1707 return mLastInjectedHoverEvent; 1708 } 1709 1710 /** 1711 * @return The the last injected hover event. 1712 */ 1713 public MotionEvent getLastInjectedHoverEventForClick() { 1714 return mLastInjectedHoverEventForClick; 1715 } 1716 1717 @Override 1718 public String toString() { 1719 StringBuilder builder = new StringBuilder(); 1720 builder.append("========================="); 1721 builder.append("\nDown pointers #"); 1722 builder.append(Integer.bitCount(mInjectedPointersDown)); 1723 builder.append(" [ "); 1724 for (int i = 0; i < MAX_POINTER_COUNT; i++) { 1725 if ((mInjectedPointersDown & i) != 0) { 1726 builder.append(i); 1727 builder.append(" "); 1728 } 1729 } 1730 builder.append("]"); 1731 builder.append("\n========================="); 1732 return builder.toString(); 1733 } 1734 } 1735 1736 class ReceivedPointerTracker { 1737 private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker"; 1738 1739 // The coefficient by which to multiply 1740 // ViewConfiguration.#getScaledTouchSlop() 1741 // to compute #mThresholdActivePointer. 1742 private static final int COEFFICIENT_ACTIVE_POINTER = 2; 1743 1744 // Pointers that moved less than mThresholdActivePointer 1745 // are considered active i.e. are ignored. 1746 private final double mThresholdActivePointer; 1747 1748 // Keep track of where and when a pointer went down. 1749 private final float[] mReceivedPointerDownX = new float[MAX_POINTER_COUNT]; 1750 private final float[] mReceivedPointerDownY = new float[MAX_POINTER_COUNT]; 1751 private final long[] mReceivedPointerDownTime = new long[MAX_POINTER_COUNT]; 1752 1753 // Which pointers are down. 1754 private int mReceivedPointersDown; 1755 1756 // The edge flags of the last received down event. 1757 private int mLastReceivedDownEdgeFlags; 1758 1759 // Which down pointers are active. 1760 private int mActivePointers; 1761 1762 // Primary active pointer which is either the first that went down 1763 // or if it goes up the next active that most recently went down. 1764 private int mPrimaryActivePointerId; 1765 1766 // Flag indicating that there is at least one active pointer moving. 1767 private boolean mHasMovingActivePointer; 1768 1769 // Keep track of the last up pointer data. 1770 private long mLastReceivedUpPointerDownTime; 1771 private int mLastReceivedUpPointerId; 1772 private boolean mLastReceivedUpPointerActive; 1773 private float mLastReceivedUpPointerDownX; 1774 private float mLastReceivedUpPointerDownY; 1775 1776 private MotionEvent mLastReceivedEvent; 1777 1778 /** 1779 * Creates a new instance. 1780 * 1781 * @param context Context for looking up resources. 1782 */ 1783 public ReceivedPointerTracker(Context context) { 1784 mThresholdActivePointer = 1785 ViewConfiguration.get(context).getScaledTouchSlop() * COEFFICIENT_ACTIVE_POINTER; 1786 } 1787 1788 /** 1789 * Clears the internals state. 1790 */ 1791 public void clear() { 1792 Arrays.fill(mReceivedPointerDownX, 0); 1793 Arrays.fill(mReceivedPointerDownY, 0); 1794 Arrays.fill(mReceivedPointerDownTime, 0); 1795 mReceivedPointersDown = 0; 1796 mActivePointers = 0; 1797 mPrimaryActivePointerId = 0; 1798 mHasMovingActivePointer = false; 1799 mLastReceivedUpPointerDownTime = 0; 1800 mLastReceivedUpPointerId = 0; 1801 mLastReceivedUpPointerActive = false; 1802 mLastReceivedUpPointerDownX = 0; 1803 mLastReceivedUpPointerDownY = 0; 1804 } 1805 1806 /** 1807 * Processes a received {@link MotionEvent} event. 1808 * 1809 * @param event The event to process. 1810 */ 1811 public void onMotionEvent(MotionEvent event) { 1812 if (mLastReceivedEvent != null) { 1813 mLastReceivedEvent.recycle(); 1814 } 1815 mLastReceivedEvent = MotionEvent.obtain(event); 1816 1817 final int action = event.getActionMasked(); 1818 switch (action) { 1819 case MotionEvent.ACTION_DOWN: { 1820 handleReceivedPointerDown(event.getActionIndex(), event); 1821 } break; 1822 case MotionEvent.ACTION_POINTER_DOWN: { 1823 handleReceivedPointerDown(event.getActionIndex(), event); 1824 } break; 1825 case MotionEvent.ACTION_MOVE: { 1826 handleReceivedPointerMove(event); 1827 } break; 1828 case MotionEvent.ACTION_UP: { 1829 handleReceivedPointerUp(event.getActionIndex(), event); 1830 } break; 1831 case MotionEvent.ACTION_POINTER_UP: { 1832 handleReceivedPointerUp(event.getActionIndex(), event); 1833 } break; 1834 } 1835 if (DEBUG) { 1836 Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer: " + toString()); 1837 } 1838 } 1839 1840 /** 1841 * @return The last received event. 1842 */ 1843 public MotionEvent getLastReceivedEvent() { 1844 return mLastReceivedEvent; 1845 } 1846 1847 /** 1848 * @return The number of received pointers that are down. 1849 */ 1850 public int getReceivedPointerDownCount() { 1851 return Integer.bitCount(mReceivedPointersDown); 1852 } 1853 1854 /** 1855 * @return The bits of the pointers that are active. 1856 */ 1857 public int getActivePointers() { 1858 return mActivePointers; 1859 } 1860 1861 /** 1862 * @return The number of down input pointers that are active. 1863 */ 1864 public int getActivePointerCount() { 1865 return Integer.bitCount(mActivePointers); 1866 } 1867 1868 /** 1869 * Whether an received pointer is down. 1870 * 1871 * @param pointerId The unique pointer id. 1872 * @return True if the pointer is down. 1873 */ 1874 public boolean isReceivedPointerDown(int pointerId) { 1875 final int pointerFlag = (1 << pointerId); 1876 return (mReceivedPointersDown & pointerFlag) != 0; 1877 } 1878 1879 /** 1880 * Whether an input pointer is active. 1881 * 1882 * @param pointerId The unique pointer id. 1883 * @return True if the pointer is active. 1884 */ 1885 public boolean isActivePointer(int pointerId) { 1886 final int pointerFlag = (1 << pointerId); 1887 return (mActivePointers & pointerFlag) != 0; 1888 } 1889 1890 /** 1891 * @param pointerId The unique pointer id. 1892 * @return The X coordinate where the pointer went down. 1893 */ 1894 public float getReceivedPointerDownX(int pointerId) { 1895 return mReceivedPointerDownX[pointerId]; 1896 } 1897 1898 /** 1899 * @param pointerId The unique pointer id. 1900 * @return The Y coordinate where the pointer went down. 1901 */ 1902 public float getReceivedPointerDownY(int pointerId) { 1903 return mReceivedPointerDownY[pointerId]; 1904 } 1905 1906 /** 1907 * @param pointerId The unique pointer id. 1908 * @return The time when the pointer went down. 1909 */ 1910 public long getReceivedPointerDownTime(int pointerId) { 1911 return mReceivedPointerDownTime[pointerId]; 1912 } 1913 1914 /** 1915 * @return The id of the primary pointer. 1916 */ 1917 public int getPrimaryActivePointerId() { 1918 if (mPrimaryActivePointerId == INVALID_POINTER_ID) { 1919 mPrimaryActivePointerId = findPrimaryActivePointer(); 1920 } 1921 return mPrimaryActivePointerId; 1922 } 1923 1924 /** 1925 * @return The time when the last up received pointer went down. 1926 */ 1927 public long getLastReceivedUpPointerDownTime() { 1928 return mLastReceivedUpPointerDownTime; 1929 } 1930 1931 /** 1932 * @return The id of the last received pointer that went up. 1933 */ 1934 public int getLastReceivedUpPointerId() { 1935 return mLastReceivedUpPointerId; 1936 } 1937 1938 1939 /** 1940 * @return The down X of the last received pointer that went up. 1941 */ 1942 public float getLastReceivedUpPointerDownX() { 1943 return mLastReceivedUpPointerDownX; 1944 } 1945 1946 /** 1947 * @return The down Y of the last received pointer that went up. 1948 */ 1949 public float getLastReceivedUpPointerDownY() { 1950 return mLastReceivedUpPointerDownY; 1951 } 1952 1953 /** 1954 * @return The edge flags of the last received down event. 1955 */ 1956 public int getLastReceivedDownEdgeFlags() { 1957 return mLastReceivedDownEdgeFlags; 1958 } 1959 1960 /** 1961 * @return Whether the last received pointer that went up was active. 1962 */ 1963 public boolean wasLastReceivedUpPointerActive() { 1964 return mLastReceivedUpPointerActive; 1965 } 1966 /** 1967 * Populates the active pointer IDs to the given array. 1968 * <p> 1969 * Note: The client is responsible for providing large enough array. 1970 * 1971 * @param outPointerIds The array to which to write the active pointers. 1972 */ 1973 public void populateActivePointerIds(int[] outPointerIds) { 1974 int index = 0; 1975 for (int idBits = mActivePointers; idBits != 0; ) { 1976 final int id = Integer.numberOfTrailingZeros(idBits); 1977 idBits &= ~(1 << id); 1978 outPointerIds[index] = id; 1979 index++; 1980 } 1981 } 1982 1983 /** 1984 * @param pointerId The unique pointer id. 1985 * @return Whether the pointer is active or was the last active than went up. 1986 */ 1987 public boolean isActiveOrWasLastActiveUpPointer(int pointerId) { 1988 return (isActivePointer(pointerId) 1989 || (mLastReceivedUpPointerId == pointerId 1990 && mLastReceivedUpPointerActive)); 1991 } 1992 1993 /** 1994 * Handles a received pointer down event. 1995 * 1996 * @param pointerIndex The index of the pointer that has changed. 1997 * @param event The event to be handled. 1998 */ 1999 private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) { 2000 final int pointerId = event.getPointerId(pointerIndex); 2001 final int pointerFlag = (1 << pointerId); 2002 2003 mLastReceivedUpPointerId = 0; 2004 mLastReceivedUpPointerDownTime = 0; 2005 mLastReceivedUpPointerActive = false; 2006 mLastReceivedUpPointerDownX = 0; 2007 mLastReceivedUpPointerDownX = 0; 2008 2009 mLastReceivedDownEdgeFlags = event.getEdgeFlags(); 2010 2011 mReceivedPointersDown |= pointerFlag; 2012 mReceivedPointerDownX[pointerId] = event.getX(pointerIndex); 2013 mReceivedPointerDownY[pointerId] = event.getY(pointerIndex); 2014 mReceivedPointerDownTime[pointerId] = event.getEventTime(); 2015 2016 if (!mHasMovingActivePointer) { 2017 // If still no moving active pointers every 2018 // down pointer is the only active one. 2019 mActivePointers = pointerFlag; 2020 mPrimaryActivePointerId = pointerId; 2021 } else { 2022 // If at least one moving active pointer every 2023 // subsequent down pointer is active. 2024 mActivePointers |= pointerFlag; 2025 } 2026 } 2027 2028 /** 2029 * Handles a received pointer move event. 2030 * 2031 * @param event The event to be handled. 2032 */ 2033 private void handleReceivedPointerMove(MotionEvent event) { 2034 detectActivePointers(event); 2035 } 2036 2037 /** 2038 * Handles a received pointer up event. 2039 * 2040 * @param pointerIndex The index of the pointer that has changed. 2041 * @param event The event to be handled. 2042 */ 2043 private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) { 2044 final int pointerId = event.getPointerId(pointerIndex); 2045 final int pointerFlag = (1 << pointerId); 2046 2047 mLastReceivedUpPointerId = pointerId; 2048 mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId); 2049 mLastReceivedUpPointerActive = isActivePointer(pointerId); 2050 mLastReceivedUpPointerDownX = mReceivedPointerDownX[pointerId]; 2051 mLastReceivedUpPointerDownY = mReceivedPointerDownY[pointerId]; 2052 2053 mReceivedPointersDown &= ~pointerFlag; 2054 mActivePointers &= ~pointerFlag; 2055 mReceivedPointerDownX[pointerId] = 0; 2056 mReceivedPointerDownY[pointerId] = 0; 2057 mReceivedPointerDownTime[pointerId] = 0; 2058 2059 if (mActivePointers == 0) { 2060 mHasMovingActivePointer = false; 2061 } 2062 if (mPrimaryActivePointerId == pointerId) { 2063 mPrimaryActivePointerId = INVALID_POINTER_ID; 2064 } 2065 } 2066 2067 /** 2068 * Detects the active pointers in an event. 2069 * 2070 * @param event The event to examine. 2071 */ 2072 private void detectActivePointers(MotionEvent event) { 2073 for (int i = 0, count = event.getPointerCount(); i < count; i++) { 2074 final int pointerId = event.getPointerId(i); 2075 if (mHasMovingActivePointer) { 2076 // If already active => nothing to do. 2077 if (isActivePointer(pointerId)) { 2078 continue; 2079 } 2080 } 2081 // Active pointers are ones that moved more than a given threshold. 2082 final float pointerDeltaMove = computePointerDeltaMove(i, event); 2083 if (pointerDeltaMove > mThresholdActivePointer) { 2084 final int pointerFlag = (1 << pointerId); 2085 mActivePointers |= pointerFlag; 2086 mHasMovingActivePointer = true; 2087 } 2088 } 2089 } 2090 2091 /** 2092 * @return The primary active pointer. 2093 */ 2094 private int findPrimaryActivePointer() { 2095 int primaryActivePointerId = INVALID_POINTER_ID; 2096 long minDownTime = Long.MAX_VALUE; 2097 // Find the active pointer that went down first. 2098 for (int i = 0, count = mReceivedPointerDownTime.length; i < count; i++) { 2099 if (isActivePointer(i)) { 2100 final long downPointerTime = mReceivedPointerDownTime[i]; 2101 if (downPointerTime < minDownTime) { 2102 minDownTime = downPointerTime; 2103 primaryActivePointerId = i; 2104 } 2105 } 2106 } 2107 return primaryActivePointerId; 2108 } 2109 2110 /** 2111 * Computes the move for a given action pointer index since the 2112 * corresponding pointer went down. 2113 * 2114 * @param pointerIndex The action pointer index. 2115 * @param event The event to examine. 2116 * @return The distance the pointer has moved. 2117 */ 2118 private float computePointerDeltaMove(int pointerIndex, MotionEvent event) { 2119 final int pointerId = event.getPointerId(pointerIndex); 2120 final float deltaX = event.getX(pointerIndex) - mReceivedPointerDownX[pointerId]; 2121 final float deltaY = event.getY(pointerIndex) - mReceivedPointerDownY[pointerId]; 2122 return (float) Math.hypot(deltaX, deltaY); 2123 } 2124 2125 @Override 2126 public String toString() { 2127 StringBuilder builder = new StringBuilder(); 2128 builder.append("========================="); 2129 builder.append("\nDown pointers #"); 2130 builder.append(getReceivedPointerDownCount()); 2131 builder.append(" [ "); 2132 for (int i = 0; i < MAX_POINTER_COUNT; i++) { 2133 if (isReceivedPointerDown(i)) { 2134 builder.append(i); 2135 builder.append(" "); 2136 } 2137 } 2138 builder.append("]"); 2139 builder.append("\nActive pointers #"); 2140 builder.append(getActivePointerCount()); 2141 builder.append(" [ "); 2142 for (int i = 0; i < MAX_POINTER_COUNT; i++) { 2143 if (isActivePointer(i)) { 2144 builder.append(i); 2145 builder.append(" "); 2146 } 2147 } 2148 builder.append("]"); 2149 builder.append("\nPrimary active pointer id [ "); 2150 builder.append(getPrimaryActivePointerId()); 2151 builder.append(" ]"); 2152 builder.append("\n========================="); 2153 return builder.toString(); 2154 } 2155 } 2156 } 2157