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