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