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