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