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