1 /* 2 ** Copyright 2011, The Android Open Source Project 3 ** 4 ** Licensed under the Apache License, Version 2.0 (the "License"); 5 ** you may not use this file except in compliance with the License. 6 ** You may obtain a copy of the License at 7 ** 8 ** http://www.apache.org/licenses/LICENSE-2.0 9 ** 10 ** Unless required by applicable law or agreed to in writing, software 11 ** distributed under the License is distributed on an "AS IS" BASIS, 12 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 ** See the License for the specific language governing permissions and 14 ** limitations under the License. 15 */ 16 17 package com.android.server.accessibility; 18 19 import android.content.Context; 20 import android.gesture.Gesture; 21 import android.gesture.GestureLibraries; 22 import android.gesture.GestureLibrary; 23 import android.gesture.GesturePoint; 24 import android.gesture.GestureStore; 25 import android.gesture.GestureStroke; 26 import android.gesture.Prediction; 27 import android.graphics.Rect; 28 import android.os.Handler; 29 import android.os.SystemClock; 30 import android.util.Slog; 31 import android.view.MotionEvent; 32 import android.view.MotionEvent.PointerCoords; 33 import android.view.MotionEvent.PointerProperties; 34 import android.view.VelocityTracker; 35 import android.view.ViewConfiguration; 36 import android.view.WindowManagerPolicy; 37 import android.view.accessibility.AccessibilityEvent; 38 import android.view.accessibility.AccessibilityManager; 39 40 import com.android.internal.R; 41 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.List; 45 46 /** 47 * This class is a strategy for performing touch exploration. It 48 * transforms the motion event stream by modifying, adding, replacing, 49 * and consuming certain events. The interaction model is: 50 * 51 * <ol> 52 * <li>1. One finger moving slow around performs touch exploration.</li> 53 * <li>2. One finger moving fast around performs gestures.</li> 54 * <li>3. Two close fingers moving in the same direction perform a drag.</li> 55 * <li>4. Multi-finger gestures are delivered to view hierarchy.</li> 56 * <li>5. Two fingers moving in different directions are considered a multi-finger gesture.</li> 57 * <li>7. Double tapping clicks on the on the last touch explored location if it was in 58 * a window that does not take focus, otherwise the click is within the accessibility 59 * focused rectangle.</li> 60 * <li>7. Tapping and holding for a while performs a long press in a similar fashion 61 * as the click above.</li> 62 * <ol> 63 * 64 * @hide 65 */ 66 class TouchExplorer implements EventStreamTransformation { 67 68 private static final boolean DEBUG = false; 69 70 // Tag for logging received events. 71 private static final String LOG_TAG = "TouchExplorer"; 72 73 // States this explorer can be in. 74 private static final int STATE_TOUCH_EXPLORING = 0x00000001; 75 private static final int STATE_DRAGGING = 0x00000002; 76 private static final int STATE_DELEGATING = 0x00000004; 77 private static final int STATE_GESTURE_DETECTING = 0x00000005; 78 79 // The maximum of the cosine between the vectors of two moving 80 // pointers so they can be considered moving in the same direction. 81 private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4) 82 83 // Constant referring to the ids bits of all pointers. 84 private static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF; 85 86 // This constant captures the current implementation detail that 87 // pointer IDs are between 0 and 31 inclusive (subject to change). 88 // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h) 89 private static final int MAX_POINTER_COUNT = 32; 90 91 // Invalid pointer ID. 92 private static final int INVALID_POINTER_ID = -1; 93 94 // The velocity above which we detect gestures. 95 private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000; 96 97 // The minimal distance before we take the middle of the distance between 98 // the two dragging pointers as opposed to use the location of the primary one. 99 private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200; 100 101 // The timeout after which we are no longer trying to detect a gesture. 102 private static final int EXIT_GESTURE_DETECTION_TIMEOUT = 2000; 103 104 // Timeout before trying to decide what the user is trying to do. 105 private final int mDetermineUserIntentTimeout; 106 107 // Timeout within which we try to detect a tap. 108 private final int mTapTimeout; 109 110 // Timeout within which we try to detect a double tap. 111 private final int mDoubleTapTimeout; 112 113 // Slop between the down and up tap to be a tap. 114 private final int mTouchSlop; 115 116 // Slop between the first and second tap to be a double tap. 117 private final int mDoubleTapSlop; 118 119 // The current state of the touch explorer. 120 private int mCurrentState = STATE_TOUCH_EXPLORING; 121 122 // The ID of the pointer used for dragging. 123 private int mDraggingPointerId; 124 125 // Handler for performing asynchronous operations. 126 private final Handler mHandler; 127 128 // Command for delayed sending of a hover enter and move event. 129 private final SendHoverEnterAndMoveDelayed mSendHoverEnterAndMoveDelayed; 130 131 // Command for delayed sending of a hover exit event. 132 private final SendHoverExitDelayed mSendHoverExitDelayed; 133 134 // Command for delayed sending of touch exploration end events. 135 private final SendAccessibilityEventDelayed mSendTouchExplorationEndDelayed; 136 137 // Command for delayed sending of touch interaction end events. 138 private final SendAccessibilityEventDelayed mSendTouchInteractionEndDelayed; 139 140 // Command for delayed sending of a long press. 141 private final PerformLongPressDelayed mPerformLongPressDelayed; 142 143 // Command for exiting gesture detection mode after a timeout. 144 private final ExitGestureDetectionModeDelayed mExitGestureDetectionModeDelayed; 145 146 // Helper to detect and react to double tap in touch explore mode. 147 private final DoubleTapDetector mDoubleTapDetector; 148 149 // The scaled minimal distance before we take the middle of the distance between 150 // the two dragging pointers as opposed to use the location of the primary one. 151 private final int mScaledMinPointerDistanceToUseMiddleLocation; 152 153 // The scaled velocity above which we detect gestures. 154 private final int mScaledGestureDetectionVelocity; 155 156 // The handler to which to delegate events. 157 private EventStreamTransformation mNext; 158 159 // Helper to track gesture velocity. 160 private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); 161 162 // Helper class to track received pointers. 163 private final ReceivedPointerTracker mReceivedPointerTracker; 164 165 // Helper class to track injected pointers. 166 private final InjectedPointerTracker mInjectedPointerTracker; 167 168 // Handle to the accessibility manager service. 169 private final AccessibilityManagerService mAms; 170 171 // Temporary rectangle to avoid instantiation. 172 private final Rect mTempRect = new Rect(); 173 174 // Context in which this explorer operates. 175 private final Context mContext; 176 177 // The X of the previous event. 178 private float mPreviousX; 179 180 // The Y of the previous event. 181 private float mPreviousY; 182 183 // Buffer for storing points for gesture detection. 184 private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100); 185 186 // The minimal delta between moves to add a gesture point. 187 private static final int TOUCH_TOLERANCE = 3; 188 189 // The minimal score for accepting a predicted gesture. 190 private static final float MIN_PREDICTION_SCORE = 2.0f; 191 192 // The library for gesture detection. 193 private GestureLibrary mGestureLibrary; 194 195 // The long pressing pointer id if coordinate remapping is needed. 196 private int mLongPressingPointerId = -1; 197 198 // The long pressing pointer X if coordinate remapping is needed. 199 private int mLongPressingPointerDeltaX; 200 201 // The long pressing pointer Y if coordinate remapping is needed. 202 private int mLongPressingPointerDeltaY; 203 204 // The id of the last touch explored window. 205 private int mLastTouchedWindowId; 206 207 // Whether touch exploration is in progress. 208 private boolean mTouchExplorationInProgress; 209 210 /** 211 * Creates a new instance. 212 * 213 * @param inputFilter The input filter associated with this explorer. 214 * @param context A context handle for accessing resources. 215 */ 216 public TouchExplorer(Context context, AccessibilityManagerService service) { 217 mContext = context; 218 mAms = service; 219 mReceivedPointerTracker = new ReceivedPointerTracker(); 220 mInjectedPointerTracker = new InjectedPointerTracker(); 221 mTapTimeout = ViewConfiguration.getTapTimeout(); 222 mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout(); 223 mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout(); 224 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 225 mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); 226 mHandler = new Handler(context.getMainLooper()); 227 mPerformLongPressDelayed = new PerformLongPressDelayed(); 228 mExitGestureDetectionModeDelayed = new ExitGestureDetectionModeDelayed(); 229 mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures); 230 mGestureLibrary.setOrientationStyle(8); 231 mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE); 232 mGestureLibrary.load(); 233 mSendHoverEnterAndMoveDelayed = new SendHoverEnterAndMoveDelayed(); 234 mSendHoverExitDelayed = new SendHoverExitDelayed(); 235 mSendTouchExplorationEndDelayed = new SendAccessibilityEventDelayed( 236 AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END, 237 mDetermineUserIntentTimeout); 238 mSendTouchInteractionEndDelayed = new SendAccessibilityEventDelayed( 239 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END, 240 mDetermineUserIntentTimeout); 241 mDoubleTapDetector = new DoubleTapDetector(); 242 final float density = context.getResources().getDisplayMetrics().density; 243 mScaledMinPointerDistanceToUseMiddleLocation = 244 (int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density); 245 mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density); 246 } 247 248 public void clear() { 249 // If we have not received an event then we are in initial 250 // state. Therefore, there is not need to clean anything. 251 MotionEvent event = mReceivedPointerTracker.getLastReceivedEvent(); 252 if (event != null) { 253 clear(mReceivedPointerTracker.getLastReceivedEvent(), WindowManagerPolicy.FLAG_TRUSTED); 254 } 255 } 256 257 public void onDestroy() { 258 // TODO: Implement 259 } 260 261 private void clear(MotionEvent event, int policyFlags) { 262 switch (mCurrentState) { 263 case STATE_TOUCH_EXPLORING: { 264 // If a touch exploration gesture is in progress send events for its end. 265 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); 266 } break; 267 case STATE_DRAGGING: { 268 mDraggingPointerId = INVALID_POINTER_ID; 269 // Send exit to all pointers that we have delivered. 270 sendUpForInjectedDownPointers(event, policyFlags); 271 } break; 272 case STATE_DELEGATING: { 273 // Send exit to all pointers that we have delivered. 274 sendUpForInjectedDownPointers(event, policyFlags); 275 } break; 276 case STATE_GESTURE_DETECTING: { 277 // Clear the current stroke. 278 mStrokeBuffer.clear(); 279 } break; 280 } 281 // Remove all pending callbacks. 282 mSendHoverEnterAndMoveDelayed.cancel(); 283 mSendHoverExitDelayed.cancel(); 284 mPerformLongPressDelayed.cancel(); 285 mExitGestureDetectionModeDelayed.cancel(); 286 mSendTouchExplorationEndDelayed.cancel(); 287 mSendTouchInteractionEndDelayed.cancel(); 288 // Reset the pointer trackers. 289 mReceivedPointerTracker.clear(); 290 mInjectedPointerTracker.clear(); 291 // Clear the double tap detector 292 mDoubleTapDetector.clear(); 293 // Go to initial state. 294 // Clear the long pressing pointer remap data. 295 mLongPressingPointerId = -1; 296 mLongPressingPointerDeltaX = 0; 297 mLongPressingPointerDeltaY = 0; 298 mCurrentState = STATE_TOUCH_EXPLORING; 299 if (mNext != null) { 300 mNext.clear(); 301 } 302 mTouchExplorationInProgress = false; 303 mAms.onTouchInteractionEnd(); 304 } 305 306 @Override 307 public void setNext(EventStreamTransformation next) { 308 mNext = next; 309 } 310 311 @Override 312 public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, 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(rawEvent); 320 321 switch(mCurrentState) { 322 case STATE_TOUCH_EXPLORING: { 323 handleMotionEventStateTouchExploring(event, rawEvent, 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(rawEvent, 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 (mSendTouchExplorationEndDelayed.isPending() 345 && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) { 346 mSendTouchExplorationEndDelayed.cancel(); 347 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END); 348 } 349 350 // The event for touch interaction end should be strictly after the 351 // last hover exit and the touch exploration gesture end events. 352 if (mSendTouchInteractionEndDelayed.isPending() 353 && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) { 354 mSendTouchInteractionEndDelayed.cancel(); 355 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 356 } 357 358 // If a new window opens or the accessibility focus moves we no longer 359 // want to click/long press on the last touch explored location. 360 switch (eventType) { 361 case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: 362 case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: { 363 if (mInjectedPointerTracker.mLastInjectedHoverEventForClick != null) { 364 mInjectedPointerTracker.mLastInjectedHoverEventForClick.recycle(); 365 mInjectedPointerTracker.mLastInjectedHoverEventForClick = null; 366 } 367 mLastTouchedWindowId = -1; 368 } break; 369 case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: 370 case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: { 371 mLastTouchedWindowId = event.getWindowId(); 372 } break; 373 } 374 if (mNext != null) { 375 mNext.onAccessibilityEvent(event); 376 } 377 } 378 379 /** 380 * Handles a motion event in touch exploring state. 381 * 382 * @param event The event to be handled. 383 * @param rawEvent The raw (unmodified) motion event. 384 * @param policyFlags The policy flags associated with the event. 385 */ 386 private void handleMotionEventStateTouchExploring(MotionEvent event, MotionEvent rawEvent, 387 int policyFlags) { 388 ReceivedPointerTracker receivedTracker = mReceivedPointerTracker; 389 390 mVelocityTracker.addMovement(rawEvent); 391 392 mDoubleTapDetector.onMotionEvent(event, policyFlags); 393 394 switch (event.getActionMasked()) { 395 case MotionEvent.ACTION_DOWN: { 396 mAms.onTouchInteractionStart(); 397 398 // Pre-feed the motion events to the gesture detector since we 399 // have a distance slop before getting into gesture detection 400 // mode and not using the points within this slop significantly 401 // decreases the quality of gesture recognition. 402 handleMotionEventGestureDetecting(rawEvent, policyFlags); 403 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START); 404 405 // If we still have not notified the user for the last 406 // touch, we figure out what to do. If were waiting 407 // we resent the delayed callback and wait again. 408 mSendHoverEnterAndMoveDelayed.cancel(); 409 mSendHoverExitDelayed.cancel(); 410 mPerformLongPressDelayed.cancel(); 411 412 if (mSendTouchExplorationEndDelayed.isPending()) { 413 mSendTouchExplorationEndDelayed.forceSendAndRemove(); 414 } 415 416 if (mSendTouchInteractionEndDelayed.isPending()) { 417 mSendTouchInteractionEndDelayed.forceSendAndRemove(); 418 } 419 420 // If we have the first tap, schedule a long press and break 421 // since we do not want to schedule hover enter because 422 // the delayed callback will kick in before the long click. 423 // This would lead to a state transition resulting in long 424 // pressing the item below the double taped area which is 425 // not necessary where accessibility focus is. 426 if (mDoubleTapDetector.firstTapDetected()) { 427 // We got a tap now post a long press action. 428 mPerformLongPressDelayed.post(event, policyFlags); 429 break; 430 } 431 if (!mTouchExplorationInProgress) { 432 if (!mSendHoverEnterAndMoveDelayed.isPending()) { 433 // Deliver hover enter with a delay to have a chance 434 // to detect what the user is trying to do. 435 final int pointerId = receivedTracker.getPrimaryPointerId(); 436 final int pointerIdBits = (1 << pointerId); 437 mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits, 438 policyFlags); 439 } else { 440 // Cache the event until we discern exploration from gesturing. 441 mSendHoverEnterAndMoveDelayed.addEvent(event); 442 } 443 } 444 } break; 445 case MotionEvent.ACTION_POINTER_DOWN: { 446 // Another finger down means that if we have not started to deliver 447 // hover events, we will not have to. The code for ACTION_MOVE will 448 // decide what we will actually do next. 449 mSendHoverEnterAndMoveDelayed.cancel(); 450 mSendHoverExitDelayed.cancel(); 451 mPerformLongPressDelayed.cancel(); 452 } break; 453 case MotionEvent.ACTION_MOVE: { 454 final int pointerId = receivedTracker.getPrimaryPointerId(); 455 final int pointerIndex = event.findPointerIndex(pointerId); 456 final int pointerIdBits = (1 << pointerId); 457 switch (event.getPointerCount()) { 458 case 1: { 459 // We have not started sending events since we try to 460 // figure out what the user is doing. 461 if (mSendHoverEnterAndMoveDelayed.isPending()) { 462 // Pre-feed the motion events to the gesture detector since we 463 // have a distance slop before getting into gesture detection 464 // mode and not using the points within this slop significantly 465 // decreases the quality of gesture recognition. 466 handleMotionEventGestureDetecting(rawEvent, policyFlags); 467 468 // Cache the event until we discern exploration from gesturing. 469 mSendHoverEnterAndMoveDelayed.addEvent(event); 470 471 // It is *important* to use the distance traveled by the pointers 472 // on the screen which may or may not be magnified. 473 final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId) 474 - rawEvent.getX(pointerIndex); 475 final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId) 476 - rawEvent.getY(pointerIndex); 477 final double moveDelta = Math.hypot(deltaX, deltaY); 478 // The user has moved enough for us to decide. 479 if (moveDelta > mDoubleTapSlop) { 480 // Check whether the user is performing a gesture. We 481 // detect gestures if the pointer is moving above a 482 // given velocity. 483 mVelocityTracker.computeCurrentVelocity(1000); 484 final float maxAbsVelocity = Math.max( 485 Math.abs(mVelocityTracker.getXVelocity(pointerId)), 486 Math.abs(mVelocityTracker.getYVelocity(pointerId))); 487 if (maxAbsVelocity > mScaledGestureDetectionVelocity) { 488 // We have to perform gesture detection, so 489 // clear the current state and try to detect. 490 mCurrentState = STATE_GESTURE_DETECTING; 491 mVelocityTracker.clear(); 492 mSendHoverEnterAndMoveDelayed.cancel(); 493 mSendHoverExitDelayed.cancel(); 494 mPerformLongPressDelayed.cancel(); 495 mExitGestureDetectionModeDelayed.post(); 496 // Send accessibility event to announce the start 497 // of gesture recognition. 498 sendAccessibilityEvent( 499 AccessibilityEvent.TYPE_GESTURE_DETECTION_START); 500 } else { 501 // We have just decided that the user is touch, 502 // exploring so start sending events. 503 mSendHoverEnterAndMoveDelayed.forceSendAndRemove(); 504 mSendHoverExitDelayed.cancel(); 505 mPerformLongPressDelayed.cancel(); 506 sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, 507 pointerIdBits, policyFlags); 508 } 509 break; 510 } 511 } else { 512 // Cancel the long press if pending and the user 513 // moved more than the slop. 514 if (mPerformLongPressDelayed.isPending()) { 515 final float deltaX = 516 receivedTracker.getReceivedPointerDownX(pointerId) 517 - rawEvent.getX(pointerIndex); 518 final float deltaY = 519 receivedTracker.getReceivedPointerDownY(pointerId) 520 - rawEvent.getY(pointerIndex); 521 final double moveDelta = Math.hypot(deltaX, deltaY); 522 // The user has moved enough for us to decide. 523 if (moveDelta > mTouchSlop) { 524 mPerformLongPressDelayed.cancel(); 525 } 526 } 527 if (mTouchExplorationInProgress) { 528 sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); 529 sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, 530 policyFlags); 531 } 532 } 533 } break; 534 case 2: { 535 // More than one pointer so the user is not touch exploring 536 // and now we have to decide whether to delegate or drag. 537 if (mSendHoverEnterAndMoveDelayed.isPending()) { 538 // We have not started sending events so cancel 539 // scheduled sending events. 540 mSendHoverEnterAndMoveDelayed.cancel(); 541 mSendHoverExitDelayed.cancel(); 542 mPerformLongPressDelayed.cancel(); 543 } else { 544 mPerformLongPressDelayed.cancel(); 545 if (mTouchExplorationInProgress) { 546 // If the user is touch exploring the second pointer may be 547 // performing a double tap to activate an item without need 548 // for the user to lift his exploring finger. 549 // It is *important* to use the distance traveled by the pointers 550 // on the screen which may or may not be magnified. 551 final float deltaX = receivedTracker.getReceivedPointerDownX( 552 pointerId) - rawEvent.getX(pointerIndex); 553 final float deltaY = receivedTracker.getReceivedPointerDownY( 554 pointerId) - rawEvent.getY(pointerIndex); 555 final double moveDelta = Math.hypot(deltaX, deltaY); 556 if (moveDelta < mDoubleTapSlop) { 557 break; 558 } 559 // We are sending events so send exit and gesture 560 // end since we transition to another state. 561 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); 562 } 563 } 564 565 // We know that a new state transition is to happen and the 566 // new state will not be gesture recognition, so clear the 567 // stashed gesture strokes. 568 mStrokeBuffer.clear(); 569 570 if (isDraggingGesture(event)) { 571 // Two pointers moving in the same direction within 572 // a given distance perform a drag. 573 mCurrentState = STATE_DRAGGING; 574 mDraggingPointerId = pointerId; 575 event.setEdgeFlags(receivedTracker.getLastReceivedDownEdgeFlags()); 576 sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits, 577 policyFlags); 578 } else { 579 // Two pointers moving arbitrary are delegated to the view hierarchy. 580 mCurrentState = STATE_DELEGATING; 581 sendDownForAllNotInjectedPointers(event, policyFlags); 582 } 583 mVelocityTracker.clear(); 584 } break; 585 default: { 586 // More than one pointer so the user is not touch exploring 587 // and now we have to decide whether to delegate or drag. 588 if (mSendHoverEnterAndMoveDelayed.isPending()) { 589 // We have not started sending events so cancel 590 // scheduled sending events. 591 mSendHoverEnterAndMoveDelayed.cancel(); 592 mSendHoverExitDelayed.cancel(); 593 mPerformLongPressDelayed.cancel(); 594 } else { 595 mPerformLongPressDelayed.cancel(); 596 // We are sending events so send exit and gesture 597 // end since we transition to another state. 598 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); 599 } 600 601 // More than two pointers are delegated to the view hierarchy. 602 mCurrentState = STATE_DELEGATING; 603 sendDownForAllNotInjectedPointers(event, policyFlags); 604 mVelocityTracker.clear(); 605 } 606 } 607 } break; 608 case MotionEvent.ACTION_UP: { 609 mAms.onTouchInteractionEnd(); 610 // We know that we do not need the pre-fed gesture points are not 611 // needed anymore since the last pointer just went up. 612 mStrokeBuffer.clear(); 613 final int pointerId = event.getPointerId(event.getActionIndex()); 614 final int pointerIdBits = (1 << pointerId); 615 616 mPerformLongPressDelayed.cancel(); 617 mVelocityTracker.clear(); 618 619 if (mSendHoverEnterAndMoveDelayed.isPending()) { 620 // If we have not delivered the enter schedule an exit. 621 mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags); 622 } else { 623 // The user is touch exploring so we send events for end. 624 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); 625 } 626 627 if (!mSendTouchInteractionEndDelayed.isPending()) { 628 mSendTouchInteractionEndDelayed.post(); 629 } 630 631 } break; 632 case MotionEvent.ACTION_CANCEL: { 633 clear(event, policyFlags); 634 } break; 635 } 636 } 637 638 /** 639 * Handles a motion event in dragging state. 640 * 641 * @param event The event to be handled. 642 * @param policyFlags The policy flags associated with the event. 643 */ 644 private void handleMotionEventStateDragging(MotionEvent event, int policyFlags) { 645 final int pointerIdBits = (1 << mDraggingPointerId); 646 switch (event.getActionMasked()) { 647 case MotionEvent.ACTION_DOWN: { 648 throw new IllegalStateException("Dragging state can be reached only if two " 649 + "pointers are already down"); 650 } 651 case MotionEvent.ACTION_POINTER_DOWN: { 652 // We are in dragging state so we have two pointers and another one 653 // goes down => delegate the three pointers to the view hierarchy 654 mCurrentState = STATE_DELEGATING; 655 if (mDraggingPointerId != INVALID_POINTER_ID) { 656 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); 657 } 658 sendDownForAllNotInjectedPointers(event, policyFlags); 659 } break; 660 case MotionEvent.ACTION_MOVE: { 661 switch (event.getPointerCount()) { 662 case 1: { 663 // do nothing 664 } break; 665 case 2: { 666 if (isDraggingGesture(event)) { 667 final float firstPtrX = event.getX(0); 668 final float firstPtrY = event.getY(0); 669 final float secondPtrX = event.getX(1); 670 final float secondPtrY = event.getY(1); 671 672 final float deltaX = firstPtrX - secondPtrX; 673 final float deltaY = firstPtrY - secondPtrY; 674 final double distance = Math.hypot(deltaX, deltaY); 675 676 if (distance > mScaledMinPointerDistanceToUseMiddleLocation) { 677 event.setLocation(deltaX / 2, deltaY / 2); 678 } 679 680 // If still dragging send a drag event. 681 sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits, 682 policyFlags); 683 } else { 684 // The two pointers are moving either in different directions or 685 // no close enough => delegate the gesture to the view hierarchy. 686 mCurrentState = STATE_DELEGATING; 687 // Send an event to the end of the drag gesture. 688 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, 689 policyFlags); 690 // Deliver all pointers to the view hierarchy. 691 sendDownForAllNotInjectedPointers(event, policyFlags); 692 } 693 } break; 694 default: { 695 mCurrentState = STATE_DELEGATING; 696 // Send an event to the end of the drag gesture. 697 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, 698 policyFlags); 699 // Deliver all pointers to the view hierarchy. 700 sendDownForAllNotInjectedPointers(event, policyFlags); 701 } 702 } 703 } break; 704 case MotionEvent.ACTION_POINTER_UP: { 705 final int pointerId = event.getPointerId(event.getActionIndex()); 706 if (pointerId == mDraggingPointerId) { 707 mDraggingPointerId = INVALID_POINTER_ID; 708 // Send an event to the end of the drag gesture. 709 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); 710 } 711 } break; 712 case MotionEvent.ACTION_UP: { 713 mAms.onTouchInteractionEnd(); 714 // Announce the end of a new touch interaction. 715 sendAccessibilityEvent( 716 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 717 final int pointerId = event.getPointerId(event.getActionIndex()); 718 if (pointerId == mDraggingPointerId) { 719 mDraggingPointerId = INVALID_POINTER_ID; 720 // Send an event to the end of the drag gesture. 721 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); 722 } 723 mCurrentState = STATE_TOUCH_EXPLORING; 724 } break; 725 case MotionEvent.ACTION_CANCEL: { 726 clear(event, policyFlags); 727 } break; 728 } 729 } 730 731 /** 732 * Handles a motion event in delegating state. 733 * 734 * @param event The event to be handled. 735 * @param policyFlags The policy flags associated with the event. 736 */ 737 private void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) { 738 switch (event.getActionMasked()) { 739 case MotionEvent.ACTION_DOWN: { 740 throw new IllegalStateException("Delegating state can only be reached if " 741 + "there is at least one pointer down!"); 742 } 743 case MotionEvent.ACTION_UP: { 744 // Offset the event if we are doing a long press as the 745 // target is not necessarily under the user's finger. 746 if (mLongPressingPointerId >= 0) { 747 event = offsetEvent(event, - mLongPressingPointerDeltaX, 748 - mLongPressingPointerDeltaY); 749 // Clear the long press state. 750 mLongPressingPointerId = -1; 751 mLongPressingPointerDeltaX = 0; 752 mLongPressingPointerDeltaY = 0; 753 } 754 755 // Deliver the event. 756 sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); 757 758 // Announce the end of a the touch interaction. 759 mAms.onTouchInteractionEnd(); 760 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 761 762 mCurrentState = STATE_TOUCH_EXPLORING; 763 } break; 764 case MotionEvent.ACTION_CANCEL: { 765 clear(event, policyFlags); 766 } break; 767 default: { 768 // Deliver the event. 769 sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); 770 } 771 } 772 } 773 774 private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) { 775 switch (event.getActionMasked()) { 776 case MotionEvent.ACTION_DOWN: { 777 final float x = event.getX(); 778 final float y = event.getY(); 779 mPreviousX = x; 780 mPreviousY = y; 781 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); 782 } break; 783 case MotionEvent.ACTION_MOVE: { 784 final float x = event.getX(); 785 final float y = event.getY(); 786 final float dX = Math.abs(x - mPreviousX); 787 final float dY = Math.abs(y - mPreviousY); 788 if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) { 789 mPreviousX = x; 790 mPreviousY = y; 791 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); 792 } 793 } break; 794 case MotionEvent.ACTION_UP: { 795 mAms.onTouchInteractionEnd(); 796 // Announce the end of the gesture recognition. 797 sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); 798 // Announce the end of a the touch interaction. 799 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); 800 801 float x = event.getX(); 802 float y = event.getY(); 803 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); 804 805 Gesture gesture = new Gesture(); 806 gesture.addStroke(new GestureStroke(mStrokeBuffer)); 807 808 ArrayList<Prediction> predictions = mGestureLibrary.recognize(gesture); 809 if (!predictions.isEmpty()) { 810 Prediction bestPrediction = predictions.get(0); 811 if (bestPrediction.score >= MIN_PREDICTION_SCORE) { 812 if (DEBUG) { 813 Slog.i(LOG_TAG, "gesture: " + bestPrediction.name + " score: " 814 + bestPrediction.score); 815 } 816 try { 817 final int gestureId = Integer.parseInt(bestPrediction.name); 818 mAms.onGesture(gestureId); 819 } catch (NumberFormatException nfe) { 820 Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name); 821 } 822 } 823 } 824 825 mStrokeBuffer.clear(); 826 mExitGestureDetectionModeDelayed.cancel(); 827 mCurrentState = STATE_TOUCH_EXPLORING; 828 } break; 829 case MotionEvent.ACTION_CANCEL: { 830 clear(event, policyFlags); 831 } break; 832 } 833 } 834 835 /** 836 * Sends an accessibility event of the given type. 837 * 838 * @param type The event type. 839 */ 840 private void sendAccessibilityEvent(int type) { 841 AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext); 842 if (accessibilityManager.isEnabled()) { 843 AccessibilityEvent event = AccessibilityEvent.obtain(type); 844 event.setWindowId(mAms.getActiveWindowId()); 845 accessibilityManager.sendAccessibilityEvent(event); 846 switch (type) { 847 case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: { 848 mTouchExplorationInProgress = true; 849 } break; 850 case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: { 851 mTouchExplorationInProgress = false; 852 } break; 853 } 854 } 855 } 856 857 /** 858 * Sends down events to the view hierarchy for all pointers which are 859 * not already being delivered i.e. pointers that are not yet injected. 860 * 861 * @param prototype The prototype from which to create the injected events. 862 * @param policyFlags The policy flags associated with the event. 863 */ 864 private void sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags) { 865 InjectedPointerTracker injectedPointers = mInjectedPointerTracker; 866 867 // Inject the injected pointers. 868 int pointerIdBits = 0; 869 final int pointerCount = prototype.getPointerCount(); 870 for (int i = 0; i < pointerCount; i++) { 871 final int pointerId = prototype.getPointerId(i); 872 // Do not send event for already delivered pointers. 873 if (!injectedPointers.isInjectedPointerDown(pointerId)) { 874 pointerIdBits |= (1 << pointerId); 875 final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i); 876 sendMotionEvent(prototype, action, pointerIdBits, policyFlags); 877 } 878 } 879 } 880 881 /** 882 * Sends the exit events if needed. Such events are hover exit and touch explore 883 * gesture end. 884 * 885 * @param policyFlags The policy flags associated with the event. 886 */ 887 private void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) { 888 MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent(); 889 if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) { 890 final int pointerIdBits = event.getPointerIdBits(); 891 if (!mSendTouchExplorationEndDelayed.isPending()) { 892 mSendTouchExplorationEndDelayed.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 sendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags) { 905 MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent(); 906 if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) { 907 final int pointerIdBits = event.getPointerIdBits(); 908 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); 909 sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags); 910 } 911 } 912 913 /** 914 * Sends up events to the view hierarchy for all pointers which are 915 * already being delivered i.e. pointers that are injected. 916 * 917 * @param prototype The prototype from which to create the injected events. 918 * @param policyFlags The policy flags associated with the event. 919 */ 920 private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) { 921 final InjectedPointerTracker injectedTracked = mInjectedPointerTracker; 922 int pointerIdBits = 0; 923 final int pointerCount = prototype.getPointerCount(); 924 for (int i = 0; i < pointerCount; i++) { 925 final int pointerId = prototype.getPointerId(i); 926 // Skip non injected down pointers. 927 if (!injectedTracked.isInjectedPointerDown(pointerId)) { 928 continue; 929 } 930 pointerIdBits |= (1 << pointerId); 931 final int action = computeInjectionAction(MotionEvent.ACTION_UP, i); 932 sendMotionEvent(prototype, action, pointerIdBits, policyFlags); 933 } 934 } 935 936 /** 937 * Sends an up and down events. 938 * 939 * @param prototype The prototype from which to create the injected events. 940 * @param policyFlags The policy flags associated with the event. 941 */ 942 private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) { 943 // Tap with the pointer that last explored. 944 final int pointerId = prototype.getPointerId(prototype.getActionIndex()); 945 final int pointerIdBits = (1 << pointerId); 946 sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags); 947 sendMotionEvent(prototype, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); 948 } 949 950 /** 951 * Sends an event. 952 * 953 * @param prototype The prototype from which to create the injected events. 954 * @param action The action of the event. 955 * @param pointerIdBits The bits of the pointers to send. 956 * @param policyFlags The policy flags associated with the event. 957 */ 958 private void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits, 959 int policyFlags) { 960 prototype.setAction(action); 961 962 MotionEvent event = null; 963 if (pointerIdBits == ALL_POINTER_ID_BITS) { 964 event = prototype; 965 } else { 966 event = prototype.split(pointerIdBits); 967 } 968 if (action == MotionEvent.ACTION_DOWN) { 969 event.setDownTime(event.getEventTime()); 970 } else { 971 event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime()); 972 } 973 974 // If the user is long pressing but the long pressing pointer 975 // was not exactly over the accessibility focused item we need 976 // to remap the location of that pointer so the user does not 977 // have to explicitly touch explore something to be able to 978 // long press it, or even worse to avoid the user long pressing 979 // on the wrong item since click and long press behave differently. 980 if (mLongPressingPointerId >= 0) { 981 event = offsetEvent(event, - mLongPressingPointerDeltaX, 982 - mLongPressingPointerDeltaY); 983 } 984 985 if (DEBUG) { 986 Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x" 987 + Integer.toHexString(policyFlags)); 988 } 989 990 // Make sure that the user will see the event. 991 policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER; 992 if (mNext != null) { 993 // TODO: For now pass null for the raw event since the touch 994 // explorer is the last event transformation and it does 995 // not care about the raw event. 996 mNext.onMotionEvent(event, null, policyFlags); 997 } 998 999 mInjectedPointerTracker.onMotionEvent(event); 1000 1001 if (event != prototype) { 1002 event.recycle(); 1003 } 1004 } 1005 1006 /** 1007 * Offsets all pointers in the given event by adding the specified X and Y 1008 * offsets. 1009 * 1010 * @param event The event to offset. 1011 * @param offsetX The X offset. 1012 * @param offsetY The Y offset. 1013 * @return An event with the offset pointers or the original event if both 1014 * offsets are zero. 1015 */ 1016 private MotionEvent offsetEvent(MotionEvent event, int offsetX, int offsetY) { 1017 if (offsetX == 0 && offsetY == 0) { 1018 return event; 1019 } 1020 final int remappedIndex = event.findPointerIndex(mLongPressingPointerId); 1021 final int pointerCount = event.getPointerCount(); 1022 PointerProperties[] props = PointerProperties.createArray(pointerCount); 1023 PointerCoords[] coords = PointerCoords.createArray(pointerCount); 1024 for (int i = 0; i < pointerCount; i++) { 1025 event.getPointerProperties(i, props[i]); 1026 event.getPointerCoords(i, coords[i]); 1027 if (i == remappedIndex) { 1028 coords[i].x += offsetX; 1029 coords[i].y += offsetY; 1030 } 1031 } 1032 return MotionEvent.obtain(event.getDownTime(), 1033 event.getEventTime(), event.getAction(), event.getPointerCount(), 1034 props, coords, event.getMetaState(), event.getButtonState(), 1035 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(), 1036 event.getSource(), event.getFlags()); 1037 } 1038 1039 /** 1040 * Computes the action for an injected event based on a masked action 1041 * and a pointer index. 1042 * 1043 * @param actionMasked The masked action. 1044 * @param pointerIndex The index of the pointer which has changed. 1045 * @return The action to be used for injection. 1046 */ 1047 private int computeInjectionAction(int actionMasked, int pointerIndex) { 1048 switch (actionMasked) { 1049 case MotionEvent.ACTION_DOWN: 1050 case MotionEvent.ACTION_POINTER_DOWN: { 1051 InjectedPointerTracker injectedTracker = mInjectedPointerTracker; 1052 // Compute the action based on how many down pointers are injected. 1053 if (injectedTracker.getInjectedPointerDownCount() == 0) { 1054 return MotionEvent.ACTION_DOWN; 1055 } else { 1056 return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) 1057 | MotionEvent.ACTION_POINTER_DOWN; 1058 } 1059 } 1060 case MotionEvent.ACTION_POINTER_UP: { 1061 InjectedPointerTracker injectedTracker = mInjectedPointerTracker; 1062 // Compute the action based on how many down pointers are injected. 1063 if (injectedTracker.getInjectedPointerDownCount() == 1) { 1064 return MotionEvent.ACTION_UP; 1065 } else { 1066 return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) 1067 | MotionEvent.ACTION_POINTER_UP; 1068 } 1069 } 1070 default: 1071 return actionMasked; 1072 } 1073 } 1074 1075 private class DoubleTapDetector { 1076 private MotionEvent mDownEvent; 1077 private MotionEvent mFirstTapEvent; 1078 1079 public void onMotionEvent(MotionEvent event, int policyFlags) { 1080 final int actionIndex = event.getActionIndex(); 1081 final int action = event.getActionMasked(); 1082 switch (action) { 1083 case MotionEvent.ACTION_DOWN: 1084 case MotionEvent.ACTION_POINTER_DOWN: { 1085 if (mFirstTapEvent != null 1086 && !GestureUtils.isSamePointerContext(mFirstTapEvent, event)) { 1087 clear(); 1088 } 1089 mDownEvent = MotionEvent.obtain(event); 1090 } break; 1091 case MotionEvent.ACTION_UP: 1092 case MotionEvent.ACTION_POINTER_UP: { 1093 if (mDownEvent == null) { 1094 return; 1095 } 1096 if (!GestureUtils.isSamePointerContext(mDownEvent, event)) { 1097 clear(); 1098 return; 1099 } 1100 if (GestureUtils.isTap(mDownEvent, event, mTapTimeout, mTouchSlop, 1101 actionIndex)) { 1102 if (mFirstTapEvent == null || GestureUtils.isTimedOut(mFirstTapEvent, 1103 event, mDoubleTapTimeout)) { 1104 mFirstTapEvent = MotionEvent.obtain(event); 1105 mDownEvent.recycle(); 1106 mDownEvent = null; 1107 return; 1108 } 1109 if (GestureUtils.isMultiTap(mFirstTapEvent, event, mDoubleTapTimeout, 1110 mDoubleTapSlop, actionIndex)) { 1111 onDoubleTap(event, policyFlags); 1112 mFirstTapEvent.recycle(); 1113 mFirstTapEvent = null; 1114 mDownEvent.recycle(); 1115 mDownEvent = null; 1116 return; 1117 } 1118 mFirstTapEvent.recycle(); 1119 mFirstTapEvent = null; 1120 } else { 1121 if (mFirstTapEvent != null) { 1122 mFirstTapEvent.recycle(); 1123 mFirstTapEvent = null; 1124 } 1125 } 1126 mDownEvent.recycle(); 1127 mDownEvent = null; 1128 } break; 1129 } 1130 } 1131 1132 public void onDoubleTap(MotionEvent secondTapUp, int policyFlags) { 1133 // This should never be called when more than two pointers are down. 1134 if (secondTapUp.getPointerCount() > 2) { 1135 return; 1136 } 1137 1138 // Remove pending event deliveries. 1139 mSendHoverEnterAndMoveDelayed.cancel(); 1140 mSendHoverExitDelayed.cancel(); 1141 mPerformLongPressDelayed.cancel(); 1142 1143 if (mSendTouchExplorationEndDelayed.isPending()) { 1144 mSendTouchExplorationEndDelayed.forceSendAndRemove(); 1145 } 1146 if (mSendTouchInteractionEndDelayed.isPending()) { 1147 mSendTouchInteractionEndDelayed.forceSendAndRemove(); 1148 } 1149 1150 int clickLocationX; 1151 int clickLocationY; 1152 1153 final int pointerId = secondTapUp.getPointerId(secondTapUp.getActionIndex()); 1154 final int pointerIndex = secondTapUp.findPointerIndex(pointerId); 1155 1156 MotionEvent lastExploreEvent = 1157 mInjectedPointerTracker.getLastInjectedHoverEventForClick(); 1158 if (lastExploreEvent == null) { 1159 // No last touch explored event but there is accessibility focus in 1160 // the active window. We click in the middle of the focus bounds. 1161 Rect focusBounds = mTempRect; 1162 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) { 1163 clickLocationX = focusBounds.centerX(); 1164 clickLocationY = focusBounds.centerY(); 1165 } else { 1166 // Out of luck - do nothing. 1167 return; 1168 } 1169 } else { 1170 // If the click is within the active window but not within the 1171 // accessibility focus bounds we click in the focus center. 1172 final int lastExplorePointerIndex = lastExploreEvent.getActionIndex(); 1173 clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex); 1174 clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex); 1175 Rect activeWindowBounds = mTempRect; 1176 if (mLastTouchedWindowId == mAms.getActiveWindowId()) { 1177 mAms.getActiveWindowBounds(activeWindowBounds); 1178 if (activeWindowBounds.contains(clickLocationX, clickLocationY)) { 1179 Rect focusBounds = mTempRect; 1180 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) { 1181 if (!focusBounds.contains(clickLocationX, clickLocationY)) { 1182 clickLocationX = focusBounds.centerX(); 1183 clickLocationY = focusBounds.centerY(); 1184 } 1185 } 1186 } 1187 } 1188 } 1189 1190 // Do the click. 1191 PointerProperties[] properties = new PointerProperties[1]; 1192 properties[0] = new PointerProperties(); 1193 secondTapUp.getPointerProperties(pointerIndex, properties[0]); 1194 PointerCoords[] coords = new PointerCoords[1]; 1195 coords[0] = new PointerCoords(); 1196 coords[0].x = clickLocationX; 1197 coords[0].y = clickLocationY; 1198 MotionEvent event = MotionEvent.obtain(secondTapUp.getDownTime(), 1199 secondTapUp.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties, 1200 coords, 0, 0, 1.0f, 1.0f, secondTapUp.getDeviceId(), 0, 1201 secondTapUp.getSource(), secondTapUp.getFlags()); 1202 sendActionDownAndUp(event, policyFlags); 1203 event.recycle(); 1204 } 1205 1206 public void clear() { 1207 if (mDownEvent != null) { 1208 mDownEvent.recycle(); 1209 mDownEvent = null; 1210 } 1211 if (mFirstTapEvent != null) { 1212 mFirstTapEvent.recycle(); 1213 mFirstTapEvent = null; 1214 } 1215 } 1216 1217 public boolean firstTapDetected() { 1218 return mFirstTapEvent != null 1219 && SystemClock.uptimeMillis() - mFirstTapEvent.getEventTime() < mDoubleTapTimeout; 1220 } 1221 } 1222 1223 /** 1224 * Determines whether a two pointer gesture is a dragging one. 1225 * 1226 * @param event The event with the pointer data. 1227 * @return True if the gesture is a dragging one. 1228 */ 1229 private boolean isDraggingGesture(MotionEvent event) { 1230 ReceivedPointerTracker receivedTracker = mReceivedPointerTracker; 1231 1232 final float firstPtrX = event.getX(0); 1233 final float firstPtrY = event.getY(0); 1234 final float secondPtrX = event.getX(1); 1235 final float secondPtrY = event.getY(1); 1236 1237 final float firstPtrDownX = receivedTracker.getReceivedPointerDownX(0); 1238 final float firstPtrDownY = receivedTracker.getReceivedPointerDownY(0); 1239 final float secondPtrDownX = receivedTracker.getReceivedPointerDownX(1); 1240 final float secondPtrDownY = receivedTracker.getReceivedPointerDownY(1); 1241 1242 return GestureUtils.isDraggingGesture(firstPtrDownX, firstPtrDownY, secondPtrDownX, 1243 secondPtrDownY, firstPtrX, firstPtrY, secondPtrX, secondPtrY, 1244 MAX_DRAGGING_ANGLE_COS); 1245 } 1246 1247 /** 1248 * Gets the symbolic name of a state. 1249 * 1250 * @param state A state. 1251 * @return The state symbolic name. 1252 */ 1253 private static String getStateSymbolicName(int state) { 1254 switch (state) { 1255 case STATE_TOUCH_EXPLORING: 1256 return "STATE_TOUCH_EXPLORING"; 1257 case STATE_DRAGGING: 1258 return "STATE_DRAGGING"; 1259 case STATE_DELEGATING: 1260 return "STATE_DELEGATING"; 1261 case STATE_GESTURE_DETECTING: 1262 return "STATE_GESTURE_DETECTING"; 1263 default: 1264 throw new IllegalArgumentException("Unknown state: " + state); 1265 } 1266 } 1267 1268 /** 1269 * Class for delayed exiting from gesture detecting mode. 1270 */ 1271 private final class ExitGestureDetectionModeDelayed implements Runnable { 1272 1273 public void post() { 1274 mHandler.postDelayed(this, EXIT_GESTURE_DETECTION_TIMEOUT); 1275 } 1276 1277 public void cancel() { 1278 mHandler.removeCallbacks(this); 1279 } 1280 1281 @Override 1282 public void run() { 1283 // Announce the end of gesture recognition. 1284 sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); 1285 // Clearing puts is in touch exploration state with a finger already 1286 // down, so announce the transition to exploration state. 1287 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); 1288 clear(); 1289 } 1290 } 1291 1292 /** 1293 * Class for delayed sending of long press. 1294 */ 1295 private final class PerformLongPressDelayed implements Runnable { 1296 private MotionEvent mEvent; 1297 private int mPolicyFlags; 1298 1299 public void post(MotionEvent prototype, int policyFlags) { 1300 mEvent = MotionEvent.obtain(prototype); 1301 mPolicyFlags = policyFlags; 1302 mHandler.postDelayed(this, ViewConfiguration.getLongPressTimeout()); 1303 } 1304 1305 public void cancel() { 1306 if (mEvent != null) { 1307 mHandler.removeCallbacks(this); 1308 clear(); 1309 } 1310 } 1311 1312 private boolean isPending() { 1313 return mHandler.hasCallbacks(this); 1314 } 1315 1316 @Override 1317 public void run() { 1318 // Pointers should not be zero when running this command. 1319 if (mReceivedPointerTracker.getLastReceivedEvent().getPointerCount() == 0) { 1320 return; 1321 } 1322 1323 int clickLocationX; 1324 int clickLocationY; 1325 1326 final int pointerId = mEvent.getPointerId(mEvent.getActionIndex()); 1327 final int pointerIndex = mEvent.findPointerIndex(pointerId); 1328 1329 MotionEvent lastExploreEvent = 1330 mInjectedPointerTracker.getLastInjectedHoverEventForClick(); 1331 if (lastExploreEvent == null) { 1332 // No last touch explored event but there is accessibility focus in 1333 // the active window. We click in the middle of the focus bounds. 1334 Rect focusBounds = mTempRect; 1335 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) { 1336 clickLocationX = focusBounds.centerX(); 1337 clickLocationY = focusBounds.centerY(); 1338 } else { 1339 // Out of luck - do nothing. 1340 return; 1341 } 1342 } else { 1343 // If the click is within the active window but not within the 1344 // accessibility focus bounds we click in the focus center. 1345 final int lastExplorePointerIndex = lastExploreEvent.getActionIndex(); 1346 clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex); 1347 clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex); 1348 Rect activeWindowBounds = mTempRect; 1349 if (mLastTouchedWindowId == mAms.getActiveWindowId()) { 1350 mAms.getActiveWindowBounds(activeWindowBounds); 1351 if (activeWindowBounds.contains(clickLocationX, clickLocationY)) { 1352 Rect focusBounds = mTempRect; 1353 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) { 1354 if (!focusBounds.contains(clickLocationX, clickLocationY)) { 1355 clickLocationX = focusBounds.centerX(); 1356 clickLocationY = focusBounds.centerY(); 1357 } 1358 } 1359 } 1360 } 1361 } 1362 1363 mLongPressingPointerId = pointerId; 1364 mLongPressingPointerDeltaX = (int) mEvent.getX(pointerIndex) - clickLocationX; 1365 mLongPressingPointerDeltaY = (int) mEvent.getY(pointerIndex) - clickLocationY; 1366 1367 sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags); 1368 1369 mCurrentState = STATE_DELEGATING; 1370 sendDownForAllNotInjectedPointers(mEvent, mPolicyFlags); 1371 clear(); 1372 } 1373 1374 private void clear() { 1375 mEvent.recycle(); 1376 mEvent = null; 1377 mPolicyFlags = 0; 1378 } 1379 } 1380 1381 /** 1382 * Class for delayed sending of hover enter and move events. 1383 */ 1384 class SendHoverEnterAndMoveDelayed implements Runnable { 1385 private final String LOG_TAG_SEND_HOVER_DELAYED = "SendHoverEnterAndMoveDelayed"; 1386 1387 private final List<MotionEvent> mEvents = new ArrayList<MotionEvent>(); 1388 1389 private int mPointerIdBits; 1390 private int mPolicyFlags; 1391 1392 public void post(MotionEvent event, boolean touchExplorationInProgress, 1393 int pointerIdBits, int policyFlags) { 1394 cancel(); 1395 addEvent(event); 1396 mPointerIdBits = pointerIdBits; 1397 mPolicyFlags = policyFlags; 1398 mHandler.postDelayed(this, mDetermineUserIntentTimeout); 1399 } 1400 1401 public void addEvent(MotionEvent event) { 1402 mEvents.add(MotionEvent.obtain(event)); 1403 } 1404 1405 public void cancel() { 1406 if (isPending()) { 1407 mHandler.removeCallbacks(this); 1408 clear(); 1409 } 1410 } 1411 1412 private boolean isPending() { 1413 return mHandler.hasCallbacks(this); 1414 } 1415 1416 private void clear() { 1417 mPointerIdBits = -1; 1418 mPolicyFlags = 0; 1419 final int eventCount = mEvents.size(); 1420 for (int i = eventCount - 1; i >= 0; i--) { 1421 mEvents.remove(i).recycle(); 1422 } 1423 } 1424 1425 public void forceSendAndRemove() { 1426 if (isPending()) { 1427 run(); 1428 cancel(); 1429 } 1430 } 1431 1432 public void run() { 1433 // Send an accessibility event to announce the touch exploration start. 1434 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); 1435 1436 if (!mEvents.isEmpty()) { 1437 // Deliver a down event. 1438 sendMotionEvent(mEvents.get(0), MotionEvent.ACTION_HOVER_ENTER, 1439 mPointerIdBits, mPolicyFlags); 1440 if (DEBUG) { 1441 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, 1442 "Injecting motion event: ACTION_HOVER_ENTER"); 1443 } 1444 1445 // Deliver move events. 1446 final int eventCount = mEvents.size(); 1447 for (int i = 1; i < eventCount; i++) { 1448 sendMotionEvent(mEvents.get(i), MotionEvent.ACTION_HOVER_MOVE, 1449 mPointerIdBits, mPolicyFlags); 1450 if (DEBUG) { 1451 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, 1452 "Injecting motion event: ACTION_HOVER_MOVE"); 1453 } 1454 } 1455 } 1456 clear(); 1457 } 1458 } 1459 1460 /** 1461 * Class for delayed sending of hover exit events. 1462 */ 1463 class SendHoverExitDelayed implements Runnable { 1464 private final String LOG_TAG_SEND_HOVER_DELAYED = "SendHoverExitDelayed"; 1465 1466 private MotionEvent mPrototype; 1467 private int mPointerIdBits; 1468 private int mPolicyFlags; 1469 1470 public void post(MotionEvent prototype, int pointerIdBits, int policyFlags) { 1471 cancel(); 1472 mPrototype = MotionEvent.obtain(prototype); 1473 mPointerIdBits = pointerIdBits; 1474 mPolicyFlags = policyFlags; 1475 mHandler.postDelayed(this, mDetermineUserIntentTimeout); 1476 } 1477 1478 public void cancel() { 1479 if (isPending()) { 1480 mHandler.removeCallbacks(this); 1481 clear(); 1482 } 1483 } 1484 1485 private boolean isPending() { 1486 return mHandler.hasCallbacks(this); 1487 } 1488 1489 private void clear() { 1490 mPrototype.recycle(); 1491 mPrototype = null; 1492 mPointerIdBits = -1; 1493 mPolicyFlags = 0; 1494 } 1495 1496 public void forceSendAndRemove() { 1497 if (isPending()) { 1498 run(); 1499 cancel(); 1500 } 1501 } 1502 1503 public void run() { 1504 if (DEBUG) { 1505 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event:" 1506 + " ACTION_HOVER_EXIT"); 1507 } 1508 sendMotionEvent(mPrototype, MotionEvent.ACTION_HOVER_EXIT, 1509 mPointerIdBits, mPolicyFlags); 1510 if (!mSendTouchExplorationEndDelayed.isPending()) { 1511 mSendTouchExplorationEndDelayed.cancel(); 1512 mSendTouchExplorationEndDelayed.post(); 1513 } 1514 if (mSendTouchInteractionEndDelayed.isPending()) { 1515 mSendTouchInteractionEndDelayed.cancel(); 1516 mSendTouchInteractionEndDelayed.post(); 1517 } 1518 clear(); 1519 } 1520 } 1521 1522 private class SendAccessibilityEventDelayed implements Runnable { 1523 private final int mEventType; 1524 private final int mDelay; 1525 1526 public SendAccessibilityEventDelayed(int eventType, int delay) { 1527 mEventType = eventType; 1528 mDelay = delay; 1529 } 1530 1531 public void cancel() { 1532 mHandler.removeCallbacks(this); 1533 } 1534 1535 public void post() { 1536 mHandler.postDelayed(this, mDelay); 1537 } 1538 1539 public boolean isPending() { 1540 return mHandler.hasCallbacks(this); 1541 } 1542 1543 public void forceSendAndRemove() { 1544 if (isPending()) { 1545 run(); 1546 cancel(); 1547 } 1548 } 1549 1550 @Override 1551 public void run() { 1552 sendAccessibilityEvent(mEventType); 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 // Keep track of where and when a pointer went down. 1694 private final float[] mReceivedPointerDownX = new float[MAX_POINTER_COUNT]; 1695 private final float[] mReceivedPointerDownY = new float[MAX_POINTER_COUNT]; 1696 private final long[] mReceivedPointerDownTime = new long[MAX_POINTER_COUNT]; 1697 1698 // Which pointers are down. 1699 private int mReceivedPointersDown; 1700 1701 // The edge flags of the last received down event. 1702 private int mLastReceivedDownEdgeFlags; 1703 1704 // Primary pointer which is either the first that went down 1705 // or if it goes up the next one that most recently went down. 1706 private int mPrimaryPointerId; 1707 1708 // Keep track of the last up pointer data. 1709 private long mLastReceivedUpPointerDownTime; 1710 private float mLastReceivedUpPointerDownX; 1711 private float mLastReceivedUpPointerDownY; 1712 1713 private MotionEvent mLastReceivedEvent; 1714 1715 /** 1716 * Clears the internals state. 1717 */ 1718 public void clear() { 1719 Arrays.fill(mReceivedPointerDownX, 0); 1720 Arrays.fill(mReceivedPointerDownY, 0); 1721 Arrays.fill(mReceivedPointerDownTime, 0); 1722 mReceivedPointersDown = 0; 1723 mPrimaryPointerId = 0; 1724 mLastReceivedUpPointerDownTime = 0; 1725 mLastReceivedUpPointerDownX = 0; 1726 mLastReceivedUpPointerDownY = 0; 1727 } 1728 1729 /** 1730 * Processes a received {@link MotionEvent} event. 1731 * 1732 * @param event The event to process. 1733 */ 1734 public void onMotionEvent(MotionEvent event) { 1735 if (mLastReceivedEvent != null) { 1736 mLastReceivedEvent.recycle(); 1737 } 1738 mLastReceivedEvent = MotionEvent.obtain(event); 1739 1740 final int action = event.getActionMasked(); 1741 switch (action) { 1742 case MotionEvent.ACTION_DOWN: { 1743 handleReceivedPointerDown(event.getActionIndex(), event); 1744 } break; 1745 case MotionEvent.ACTION_POINTER_DOWN: { 1746 handleReceivedPointerDown(event.getActionIndex(), event); 1747 } break; 1748 case MotionEvent.ACTION_UP: { 1749 handleReceivedPointerUp(event.getActionIndex(), event); 1750 } break; 1751 case MotionEvent.ACTION_POINTER_UP: { 1752 handleReceivedPointerUp(event.getActionIndex(), event); 1753 } break; 1754 } 1755 if (DEBUG) { 1756 Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer:\n" + toString()); 1757 } 1758 } 1759 1760 /** 1761 * @return The last received event. 1762 */ 1763 public MotionEvent getLastReceivedEvent() { 1764 return mLastReceivedEvent; 1765 } 1766 1767 /** 1768 * @return The number of received pointers that are down. 1769 */ 1770 public int getReceivedPointerDownCount() { 1771 return Integer.bitCount(mReceivedPointersDown); 1772 } 1773 1774 /** 1775 * Whether an received pointer is down. 1776 * 1777 * @param pointerId The unique pointer id. 1778 * @return True if the pointer is down. 1779 */ 1780 public boolean isReceivedPointerDown(int pointerId) { 1781 final int pointerFlag = (1 << pointerId); 1782 return (mReceivedPointersDown & pointerFlag) != 0; 1783 } 1784 1785 /** 1786 * @param pointerId The unique pointer id. 1787 * @return The X coordinate where the pointer went down. 1788 */ 1789 public float getReceivedPointerDownX(int pointerId) { 1790 return mReceivedPointerDownX[pointerId]; 1791 } 1792 1793 /** 1794 * @param pointerId The unique pointer id. 1795 * @return The Y coordinate where the pointer went down. 1796 */ 1797 public float getReceivedPointerDownY(int pointerId) { 1798 return mReceivedPointerDownY[pointerId]; 1799 } 1800 1801 /** 1802 * @param pointerId The unique pointer id. 1803 * @return The time when the pointer went down. 1804 */ 1805 public long getReceivedPointerDownTime(int pointerId) { 1806 return mReceivedPointerDownTime[pointerId]; 1807 } 1808 1809 /** 1810 * @return The id of the primary pointer. 1811 */ 1812 public int getPrimaryPointerId() { 1813 if (mPrimaryPointerId == INVALID_POINTER_ID) { 1814 mPrimaryPointerId = findPrimaryPointerId(); 1815 } 1816 return mPrimaryPointerId; 1817 } 1818 1819 /** 1820 * @return The time when the last up received pointer went down. 1821 */ 1822 public long getLastReceivedUpPointerDownTime() { 1823 return mLastReceivedUpPointerDownTime; 1824 } 1825 1826 /** 1827 * @return The down X of the last received pointer that went up. 1828 */ 1829 public float getLastReceivedUpPointerDownX() { 1830 return mLastReceivedUpPointerDownX; 1831 } 1832 1833 /** 1834 * @return The down Y of the last received pointer that went up. 1835 */ 1836 public float getLastReceivedUpPointerDownY() { 1837 return mLastReceivedUpPointerDownY; 1838 } 1839 1840 /** 1841 * @return The edge flags of the last received down event. 1842 */ 1843 public int getLastReceivedDownEdgeFlags() { 1844 return mLastReceivedDownEdgeFlags; 1845 } 1846 1847 /** 1848 * Handles a received pointer down event. 1849 * 1850 * @param pointerIndex The index of the pointer that has changed. 1851 * @param event The event to be handled. 1852 */ 1853 private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) { 1854 final int pointerId = event.getPointerId(pointerIndex); 1855 final int pointerFlag = (1 << pointerId); 1856 1857 mLastReceivedUpPointerDownTime = 0; 1858 mLastReceivedUpPointerDownX = 0; 1859 mLastReceivedUpPointerDownX = 0; 1860 1861 mLastReceivedDownEdgeFlags = event.getEdgeFlags(); 1862 1863 mReceivedPointersDown |= pointerFlag; 1864 mReceivedPointerDownX[pointerId] = event.getX(pointerIndex); 1865 mReceivedPointerDownY[pointerId] = event.getY(pointerIndex); 1866 mReceivedPointerDownTime[pointerId] = event.getEventTime(); 1867 1868 mPrimaryPointerId = pointerId; 1869 } 1870 1871 /** 1872 * Handles a received pointer up event. 1873 * 1874 * @param pointerIndex The index of the pointer that has changed. 1875 * @param event The event to be handled. 1876 */ 1877 private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) { 1878 final int pointerId = event.getPointerId(pointerIndex); 1879 final int pointerFlag = (1 << pointerId); 1880 1881 mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId); 1882 mLastReceivedUpPointerDownX = mReceivedPointerDownX[pointerId]; 1883 mLastReceivedUpPointerDownY = mReceivedPointerDownY[pointerId]; 1884 1885 mReceivedPointersDown &= ~pointerFlag; 1886 mReceivedPointerDownX[pointerId] = 0; 1887 mReceivedPointerDownY[pointerId] = 0; 1888 mReceivedPointerDownTime[pointerId] = 0; 1889 1890 if (mPrimaryPointerId == pointerId) { 1891 mPrimaryPointerId = INVALID_POINTER_ID; 1892 } 1893 } 1894 1895 /** 1896 * @return The primary pointer id. 1897 */ 1898 private int findPrimaryPointerId() { 1899 int primaryPointerId = INVALID_POINTER_ID; 1900 long minDownTime = Long.MAX_VALUE; 1901 1902 // Find the pointer that went down first. 1903 int pointerIdBits = mReceivedPointersDown; 1904 while (pointerIdBits > 0) { 1905 final int pointerId = Integer.numberOfTrailingZeros(pointerIdBits); 1906 pointerIdBits &= ~(1 << pointerId); 1907 final long downPointerTime = mReceivedPointerDownTime[pointerId]; 1908 if (downPointerTime < minDownTime) { 1909 minDownTime = downPointerTime; 1910 primaryPointerId = pointerId; 1911 } 1912 } 1913 return primaryPointerId; 1914 } 1915 1916 @Override 1917 public String toString() { 1918 StringBuilder builder = new StringBuilder(); 1919 builder.append("========================="); 1920 builder.append("\nDown pointers #"); 1921 builder.append(getReceivedPointerDownCount()); 1922 builder.append(" [ "); 1923 for (int i = 0; i < MAX_POINTER_COUNT; i++) { 1924 if (isReceivedPointerDown(i)) { 1925 builder.append(i); 1926 builder.append(" "); 1927 } 1928 } 1929 builder.append("]"); 1930 builder.append("\nPrimary pointer id [ "); 1931 builder.append(getPrimaryPointerId()); 1932 builder.append(" ]"); 1933 builder.append("\n========================="); 1934 return builder.toString(); 1935 } 1936 } 1937 } 1938