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