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