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