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