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