TouchExplorer.java revision 736c2756bf3c14ae9fef7255c119057f7a2be1ed
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 static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END; 20import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START; 21 22import com.android.server.accessibility.AccessibilityInputFilter.Explorer; 23import com.android.server.wm.InputFilter; 24 25import android.content.Context; 26import android.os.Handler; 27import android.os.SystemClock; 28import android.util.Slog; 29import android.util.SparseArray; 30import android.view.MotionEvent; 31import android.view.ViewConfiguration; 32import android.view.WindowManagerPolicy; 33import android.view.MotionEvent.PointerCoords; 34import android.view.accessibility.AccessibilityEvent; 35import android.view.accessibility.AccessibilityManager; 36 37import java.util.Arrays; 38 39/** 40 * This class is a strategy for performing touch exploration. It 41 * transforms the motion event stream by modifying, adding, replacing, 42 * and consuming certain events. The interaction model is: 43 * 44 * <ol> 45 * <li>1. One finger moving around performs touch exploration.</li> 46 * <li>2. Two close fingers moving in the same direction perform a drag.</li> 47 * <li>3. Multi-finger gestures are delivered to view hierarchy.</li> 48 * <li>4. Pointers that have not moved more than a specified distance after they 49 * went down are considered inactive.</li> 50 * <li>5. Two fingers moving too far from each other or in different directions 51 * are considered a multi-finger gesture.</li> 52 * <li>6. Tapping on the last touch explored location within given time and 53 * distance slop performs a click.</li> 54 * <li>7. Tapping and holding for a while on the last touch explored location within 55 * given time and distance slop performs a long press.</li> 56 * <ol> 57 * 58 * @hide 59 */ 60public class TouchExplorer implements Explorer { 61 private static final boolean DEBUG = false; 62 63 // Tag for logging received events. 64 private static final String LOG_TAG_RECEIVED = "TouchExplorer-RECEIVED"; 65 // Tag for logging injected events. 66 private static final String LOG_TAG_INJECTED = "TouchExplorer-INJECTED"; 67 // Tag for logging the current state. 68 private static final String LOG_TAG_STATE = "TouchExplorer-STATE"; 69 70 // States this explorer can be in. 71 private static final int STATE_TOUCH_EXPLORING = 0x00000001; 72 private static final int STATE_DRAGGING = 0x00000002; 73 private static final int STATE_DELEGATING = 0x00000004; 74 75 // Human readable symbolic names for the states of the explorer. 76 private static final SparseArray<String> sStateSymbolicNames = new SparseArray<String>(); 77 static { 78 SparseArray<String> symbolicNames = sStateSymbolicNames; 79 symbolicNames.append(STATE_TOUCH_EXPLORING, "STATE_TOUCH_EXPLORING"); 80 symbolicNames.append(STATE_DRAGGING, "STATE_DRAGING"); 81 symbolicNames.append(STATE_DELEGATING, "STATE_DELEGATING"); 82 } 83 84 // Invalid pointer ID. 85 private static final int INVALID_POINTER_ID = -1; 86 87 // The coefficient by which to multiply 88 // ViewConfiguration.#getScaledTouchExplorationTapSlop() 89 // to compute #mDraggingDistance. 90 private static final int COEFFICIENT_DRAGGING_DISTANCE = 2; 91 92 // The time slop in milliseconds for activating an item after it has 93 // been touch explored. Tapping on an item within this slop will perform 94 // a click and tapping and holding down a long press. 95 private static final long ACTIVATION_TIME_SLOP = 2000; 96 97 // This constant captures the current implementation detail that 98 // pointer IDs are between 0 and 31 inclusive (subject to change). 99 // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h) 100 private static final int MAX_POINTER_COUNT = 32; 101 102 // The minimum of the cosine between the vectors of two moving 103 // pointers so they can be considered moving in the same direction. 104 private static final float MIN_ANGLE_COS = 0.866025404f; // cos(pi/6) 105 106 // The delay for sending a hover enter event. 107 private static final long DELAY_SEND_HOVER_MOVE = 200; 108 109 // Temporary array for storing pointer IDs. 110 private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT]; 111 112 // Temporary array for mapping new to old pointer IDs while filtering inactive pointers. 113 private final int [] mTempNewToOldPointerIndexMap = new int[MAX_POINTER_COUNT]; 114 115 // Temporary array for storing PointerCoords 116 private final PointerCoords[] mTempPointerCoords= new PointerCoords[MAX_POINTER_COUNT]; 117 118 // The maximal distance between two pointers so they are 119 // considered to be performing a drag operation. 120 private final float mDraggingDistance; 121 122 // The distance from the last touch explored location tapping within 123 // which would perform a click and tapping and holding a long press. 124 private final int mTouchExplorationTapSlop; 125 126 // Context handle for accessing resources. 127 private final Context mContext; 128 129 // The InputFilter this tracker is associated with i.e. the filter 130 // which delegates event processing to this touch explorer. 131 private final InputFilter mInputFilter; 132 133 // Helper class for tracking pointers on the screen, for example which 134 // pointers are down, which are active, etc. 135 private final PointerTracker mPointerTracker; 136 137 // Handle to the accessibility manager for firing accessibility events 138 // announcing touch exploration gesture start and end. 139 private final AccessibilityManager mAccessibilityManager; 140 141 // The last event that was received while performing touch exploration. 142 private MotionEvent mLastTouchExploreEvent; 143 144 // The current state of the touch explorer. 145 private int mCurrentState = STATE_TOUCH_EXPLORING; 146 147 // Flag whether a touch exploration gesture is in progress. 148 private boolean mTouchExploreGestureInProgress; 149 150 // The ID of the pointer used for dragging. 151 private int mDraggingPointerId; 152 153 // Handler for performing asynchronous operations. 154 private final Handler mHandler; 155 156 // Command for delayed sending of a hover event. 157 private final SendHoverDelayed mSendHoverDelayed; 158 159 /** 160 * Creates a new instance. 161 * 162 * @param inputFilter The input filter associated with this explorer. 163 * @param context A context handle for accessing resources. 164 */ 165 public TouchExplorer(InputFilter inputFilter, Context context) { 166 mInputFilter = inputFilter; 167 mTouchExplorationTapSlop = 168 ViewConfiguration.get(context).getScaledTouchExplorationTapSlop(); 169 mDraggingDistance = mTouchExplorationTapSlop * COEFFICIENT_DRAGGING_DISTANCE; 170 mPointerTracker = new PointerTracker(context); 171 mContext = context; 172 mHandler = new Handler(context.getMainLooper()); 173 mSendHoverDelayed = new SendHoverDelayed(); 174 mAccessibilityManager = AccessibilityManager.getInstance(context); 175 176 // Populate the temporary array with PointerCorrds to be reused. 177 for (int i = 0, count = mTempPointerCoords.length; i < count; i++) { 178 mTempPointerCoords[i] = new PointerCoords(); 179 } 180 } 181 182 public void clear(MotionEvent event, int policyFlags) { 183 sendUpForInjectedDownPointers(event, policyFlags); 184 clear(); 185 } 186 187 /** 188 * {@inheritDoc} 189 */ 190 public void onMotionEvent(MotionEvent event, int policyFlags) { 191 if (DEBUG) { 192 Slog.d(LOG_TAG_RECEIVED, "Received event: " + event + ", policyFlags=0x" 193 + Integer.toHexString(policyFlags)); 194 Slog.d(LOG_TAG_STATE, sStateSymbolicNames.get(mCurrentState)); 195 } 196 197 // Keep track of the pointers's state. 198 mPointerTracker.onReceivedMotionEvent(event); 199 200 switch(mCurrentState) { 201 case STATE_TOUCH_EXPLORING: { 202 handleMotionEventStateTouchExploring(event, policyFlags); 203 } break; 204 case STATE_DRAGGING: { 205 handleMotionEventStateDragging(event, policyFlags); 206 } break; 207 case STATE_DELEGATING: { 208 handleMotionEventStateDelegating(event, policyFlags); 209 } break; 210 default: { 211 throw new IllegalStateException("Illegal state: " + mCurrentState); 212 } 213 } 214 } 215 216 /** 217 * Handles a motion event in touch exploring state. 218 * 219 * @param event The event to be handled. 220 * @param policyFlags The policy flags associated with the event. 221 */ 222 private void handleMotionEventStateTouchExploring(MotionEvent event, int policyFlags) { 223 PointerTracker pointerTracker = mPointerTracker; 224 final int activePointerCount = pointerTracker.getActivePointerCount(); 225 226 switch (event.getActionMasked()) { 227 case MotionEvent.ACTION_DOWN: { 228 // Send a hover for every finger down so the user gets feedback 229 // where she is currently touching. 230 mSendHoverDelayed.forceSendAndRemove(); 231 mSendHoverDelayed.post(event, MotionEvent.ACTION_HOVER_ENTER, 0, policyFlags, 232 DELAY_SEND_HOVER_MOVE); 233 } break; 234 case MotionEvent.ACTION_POINTER_DOWN: { 235 switch (activePointerCount) { 236 case 0: { 237 throw new IllegalStateException("The must always be one active pointer in" 238 + "touch exploring state!"); 239 } 240 case 1: { 241 // Schedule a hover event which will lead to firing an 242 // accessibility event from the hovered view. 243 mSendHoverDelayed.remove(); 244 final int pointerId = pointerTracker.getPrimaryActivePointerId(); 245 final int pointerIndex = event.findPointerIndex(pointerId); 246 final int lastAction = pointerTracker.getLastInjectedHoverAction(); 247 // If a schedules hover enter for another pointer is delivered we send move. 248 final int action = (lastAction == MotionEvent.ACTION_HOVER_ENTER) 249 ? MotionEvent.ACTION_HOVER_MOVE 250 : MotionEvent.ACTION_HOVER_ENTER; 251 mSendHoverDelayed.post(event, action, pointerIndex, policyFlags, 252 DELAY_SEND_HOVER_MOVE); 253 254 if (mLastTouchExploreEvent == null) { 255 break; 256 } 257 258 // If more pointers down on the screen since the last touch 259 // exploration we discard the last cached touch explore event. 260 if (event.getPointerCount() != mLastTouchExploreEvent.getPointerCount()) { 261 mLastTouchExploreEvent = null; 262 } 263 } break; 264 default: { 265 /* do nothing - let the code for ACTION_MOVE decide what to do */ 266 } break; 267 } 268 } break; 269 case MotionEvent.ACTION_MOVE: { 270 switch (activePointerCount) { 271 case 0: { 272 /* do nothing - no active pointers so we swallow the event */ 273 } break; 274 case 1: { 275 final int pointerId = pointerTracker.getPrimaryActivePointerId(); 276 final int pointerIndex = event.findPointerIndex(pointerId); 277 278 // Detect touch exploration gesture start by having one active pointer 279 // that moved more than a given distance. 280 if (!mTouchExploreGestureInProgress) { 281 final float deltaX = pointerTracker.getReceivedPointerDownX(pointerId) 282 - event.getX(pointerIndex); 283 final float deltaY = pointerTracker.getReceivedPointerDownY(pointerId) 284 - event.getY(pointerIndex); 285 final double moveDelta = Math.hypot(deltaX, deltaY); 286 287 if (moveDelta > mTouchExplorationTapSlop) { 288 mTouchExploreGestureInProgress = true; 289 sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START); 290 // Make sure the scheduled down/move event is sent. 291 mSendHoverDelayed.forceSendAndRemove(); 292 sendHoverEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIndex, 293 policyFlags); 294 } 295 } else { 296 // Touch exploration gesture in progress so send a hover event. 297 sendHoverEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIndex, 298 policyFlags); 299 } 300 301 // Detect long press on the last touch explored position. 302 if (!mTouchExploreGestureInProgress && mLastTouchExploreEvent != null) { 303 304 // If the down was not in the time slop => nothing else to do. 305 final long pointerDownTime = 306 pointerTracker.getReceivedPointerDownTime(pointerId); 307 final long lastExploreTime = mLastTouchExploreEvent.getEventTime(); 308 final long deltaTimeExplore = pointerDownTime - lastExploreTime; 309 if (deltaTimeExplore > ACTIVATION_TIME_SLOP) { 310 mLastTouchExploreEvent = null; 311 break; 312 } 313 314 // If the pointer moved more than the tap slop => nothing else to do. 315 final float deltaX = mLastTouchExploreEvent.getX(pointerIndex) 316 - event.getX(pointerIndex); 317 final float deltaY = mLastTouchExploreEvent.getY(pointerIndex) 318 - event.getY(pointerIndex); 319 final float moveDelta = (float) Math.hypot(deltaX, deltaY); 320 if (moveDelta > mTouchExplorationTapSlop) { 321 mLastTouchExploreEvent = null; 322 break; 323 } 324 325 // If down for long enough we get a long press. 326 final long deltaTimeMove = event.getEventTime() - pointerDownTime; 327 if (deltaTimeMove > ViewConfiguration.getLongPressTimeout()) { 328 mCurrentState = STATE_DELEGATING; 329 // Make sure the scheduled hover exit is delivered. 330 mSendHoverDelayed.forceSendAndRemove(); 331 sendDownForAllActiveNotInjectedPointers(event, policyFlags); 332 sendMotionEvent(event, policyFlags); 333 mTouchExploreGestureInProgress = false; 334 mLastTouchExploreEvent = null; 335 } 336 } 337 } break; 338 case 2: { 339 // Make sure the scheduled hover enter is delivered. 340 mSendHoverDelayed.forceSendAndRemove(); 341 // We want to no longer hover over the location so subsequent 342 // touch at the same spot will generate a hover enter. 343 final int pointerId = pointerTracker.getPrimaryActivePointerId(); 344 final int pointerIndex = event.findPointerIndex(pointerId); 345 sendHoverEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIndex, 346 policyFlags); 347 348 if (isDraggingGesture(event)) { 349 // Two pointers moving in the same direction within 350 // a given distance perform a drag. 351 mCurrentState = STATE_DRAGGING; 352 if (mTouchExploreGestureInProgress) { 353 sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END); 354 mTouchExploreGestureInProgress = false; 355 } 356 mDraggingPointerId = pointerTracker.getPrimaryActivePointerId(); 357 sendDragEvent(event, MotionEvent.ACTION_DOWN, policyFlags); 358 } else { 359 // Two pointers moving arbitrary are delegated to the view hierarchy. 360 mCurrentState = STATE_DELEGATING; 361 if (mTouchExploreGestureInProgress) { 362 sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END); 363 mTouchExploreGestureInProgress = false; 364 } 365 sendDownForAllActiveNotInjectedPointers(event, policyFlags); 366 } 367 } break; 368 default: { 369 // Make sure the scheduled hover enter is delivered. 370 mSendHoverDelayed.forceSendAndRemove(); 371 // We want to no longer hover over the location so subsequent 372 // touch at the same spot will generate a hover enter. 373 final int pointerId = pointerTracker.getPrimaryActivePointerId(); 374 final int pointerIndex = event.findPointerIndex(pointerId); 375 sendHoverEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIndex, 376 policyFlags); 377 378 // More than two pointers are delegated to the view hierarchy. 379 mCurrentState = STATE_DELEGATING; 380 mSendHoverDelayed.remove(); 381 if (mTouchExploreGestureInProgress) { 382 sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END); 383 mTouchExploreGestureInProgress = false; 384 } 385 sendDownForAllActiveNotInjectedPointers(event, policyFlags); 386 } 387 } 388 } break; 389 case MotionEvent.ACTION_UP: 390 case MotionEvent.ACTION_POINTER_UP: { 391 switch (activePointerCount) { 392 case 0: { 393 // If the pointer that went up was not active we have nothing to do. 394 if (!pointerTracker.wasLastReceivedUpPointerActive()) { 395 break; 396 } 397 398 // If touch exploring announce the end of the gesture. 399 if (mTouchExploreGestureInProgress) { 400 sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END); 401 mTouchExploreGestureInProgress = false; 402 } 403 404 // Detect whether to activate i.e. click on the last explored location. 405 if (mLastTouchExploreEvent != null) { 406 final int pointerId = pointerTracker.getLastReceivedUpPointerId(); 407 408 // If the down was not in the time slop => nothing else to do. 409 final long eventTime = 410 pointerTracker.getLastReceivedUpPointerDownTime(); 411 final long exploreTime = mLastTouchExploreEvent.getEventTime(); 412 final long deltaTime = eventTime - exploreTime; 413 if (deltaTime > ACTIVATION_TIME_SLOP) { 414 mSendHoverDelayed.forceSendAndRemove(); 415 scheduleHoverExit(event, policyFlags); 416 mLastTouchExploreEvent = MotionEvent.obtain(event); 417 break; 418 } 419 420 // If the pointer moved more than the tap slop => nothing else to do. 421 final int pointerIndex = event.findPointerIndex(pointerId); 422 final float deltaX = pointerTracker.getLastReceivedUpPointerDownX() 423 - event.getX(pointerIndex); 424 final float deltaY = pointerTracker.getLastReceivedUpPointerDownY() 425 - event.getY(pointerIndex); 426 final float deltaMove = (float) Math.hypot(deltaX, deltaY); 427 if (deltaMove > mTouchExplorationTapSlop) { 428 mSendHoverDelayed.forceSendAndRemove(); 429 scheduleHoverExit(event, policyFlags); 430 mLastTouchExploreEvent = MotionEvent.obtain(event); 431 break; 432 } 433 434 // All preconditions are met, so click the last explored location. 435 mSendHoverDelayed.forceSendAndRemove(); 436 sendActionDownAndUp(mLastTouchExploreEvent, policyFlags); 437 mLastTouchExploreEvent = null; 438 } else { 439 mSendHoverDelayed.forceSendAndRemove(); 440 scheduleHoverExit(event, policyFlags); 441 mLastTouchExploreEvent = MotionEvent.obtain(event); 442 } 443 } break; 444 } 445 } break; 446 case MotionEvent.ACTION_CANCEL: { 447 final int lastAction = pointerTracker.getLastInjectedHoverAction(); 448 if (lastAction != MotionEvent.ACTION_HOVER_EXIT) { 449 final int pointerId = pointerTracker.getPrimaryActivePointerId(); 450 final int pointerIndex = event.findPointerIndex(pointerId); 451 sendHoverEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIndex, 452 policyFlags); 453 } 454 clear(); 455 } break; 456 } 457 } 458 459 /** 460 * Handles a motion event in dragging state. 461 * 462 * @param event The event to be handled. 463 * @param policyFlags The policy flags associated with the event. 464 */ 465 private void handleMotionEventStateDragging(MotionEvent event, int policyFlags) { 466 switch (event.getActionMasked()) { 467 case MotionEvent.ACTION_DOWN: { 468 throw new IllegalStateException("Dragging state can be reached only if two " 469 + "pointers are already down"); 470 } 471 case MotionEvent.ACTION_POINTER_DOWN: { 472 // We are in dragging state so we have two pointers and another one 473 // goes down => delegate the three pointers to the view hierarchy 474 mCurrentState = STATE_DELEGATING; 475 sendDragEvent(event, MotionEvent.ACTION_UP, policyFlags); 476 sendDownForAllActiveNotInjectedPointers(event, policyFlags); 477 } break; 478 case MotionEvent.ACTION_MOVE: { 479 final int activePointerCount = mPointerTracker.getActivePointerCount(); 480 switch (activePointerCount) { 481 case 2: { 482 if (isDraggingGesture(event)) { 483 // If still dragging send a drag event. 484 sendDragEvent(event, MotionEvent.ACTION_MOVE, policyFlags); 485 } else { 486 // The two pointers are moving either in different directions or 487 // no close enough => delegate the gesture to the view hierarchy. 488 mCurrentState = STATE_DELEGATING; 489 // Send an event to the end of the drag gesture. 490 sendDragEvent(event, MotionEvent.ACTION_UP, policyFlags); 491 // Deliver all active pointers to the view hierarchy. 492 sendDownForAllActiveNotInjectedPointers(event, policyFlags); 493 } 494 } break; 495 default: { 496 mCurrentState = STATE_DELEGATING; 497 // Send an event to the end of the drag gesture. 498 sendDragEvent(event, MotionEvent.ACTION_UP, policyFlags); 499 // Deliver all active pointers to the view hierarchy. 500 sendDownForAllActiveNotInjectedPointers(event, policyFlags); 501 } 502 } 503 } break; 504 case MotionEvent.ACTION_POINTER_UP: { 505 mCurrentState = STATE_TOUCH_EXPLORING; 506 // Send an event to the end of the drag gesture. 507 sendDragEvent(event, MotionEvent.ACTION_UP, policyFlags); 508 } break; 509 case MotionEvent.ACTION_CANCEL: { 510 clear(); 511 } break; 512 } 513 } 514 515 /** 516 * Handles a motion event in delegating state. 517 * 518 * @param event The event to be handled. 519 * @param policyFlags The policy flags associated with the event. 520 */ 521 public void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) { 522 switch (event.getActionMasked()) { 523 case MotionEvent.ACTION_DOWN: { 524 throw new IllegalStateException("Delegating state can only be reached if " 525 + "there is at least one pointer down!"); 526 } 527 case MotionEvent.ACTION_UP: { 528 mCurrentState = STATE_TOUCH_EXPLORING; 529 } break; 530 case MotionEvent.ACTION_MOVE: { 531 // Check whether some other pointer became active because they have moved 532 // a given distance and if such exist send them to the view hierarchy 533 final int notInjectedCount = mPointerTracker.getNotInjectedActivePointerCount(); 534 if (notInjectedCount > 0) { 535 sendDownForAllActiveNotInjectedPointers(event, policyFlags); 536 } 537 } break; 538 case MotionEvent.ACTION_POINTER_UP: { 539 // No active pointers => go to initial state. 540 if (mPointerTracker.getActivePointerCount() == 0) { 541 mCurrentState = STATE_TOUCH_EXPLORING; 542 } 543 } break; 544 case MotionEvent.ACTION_CANCEL: { 545 clear(); 546 } break; 547 } 548 // Deliver the event striping out inactive pointers. 549 sendMotionEventStripInactivePointers(event, policyFlags); 550 } 551 552 /** 553 * Schedules a hover up event so subsequent poking on the same location after 554 * the scheduled delay will perform exploration. 555 * 556 * @param prototype The prototype from which to create the injected events. 557 * @param policyFlags The policy flags associated with the event. 558 */ 559 private void scheduleHoverExit(MotionEvent prototype, 560 int policyFlags) { 561 final int pointerId = mPointerTracker.getLastReceivedUpPointerId(); 562 final int pointerIndex = prototype.findPointerIndex(pointerId); 563 // We want to no longer hover over the location so subsequent 564 // touch at the same spot will generate a hover enter. 565 mSendHoverDelayed.post(prototype, MotionEvent.ACTION_HOVER_EXIT, 566 pointerIndex, policyFlags, ACTIVATION_TIME_SLOP); 567 } 568 569 /** 570 * Sends down events to the view hierarchy for all active pointers which are 571 * not already being delivered i.e. pointers that are not yet injected. 572 * 573 * @param prototype The prototype from which to create the injected events. 574 * @param policyFlags The policy flags associated with the event. 575 */ 576 private void sendDownForAllActiveNotInjectedPointers(MotionEvent prototype, int policyFlags) { 577 PointerCoords[] pointerCoords = mTempPointerCoords; 578 PointerTracker pointerTracker = mPointerTracker; 579 int[] pointerIds = mTempPointerIds; 580 int pointerDataIndex = 0; 581 582 final int pinterCount = prototype.getPointerCount(); 583 for (int i = 0; i < pinterCount; i++) { 584 final int pointerId = prototype.getPointerId(i); 585 586 // Skip inactive pointers. 587 if (!pointerTracker.isActivePointer(pointerId)) { 588 continue; 589 } 590 // Skip already delivered pointers. 591 if (pointerTracker.isInjectedPointerDown(pointerId)) { 592 continue; 593 } 594 595 // Populate and inject an event for the current pointer. 596 pointerIds[pointerDataIndex] = pointerId; 597 prototype.getPointerCoords(i, pointerCoords[pointerDataIndex]); 598 599 final long downTime = pointerTracker.getLastInjectedDownEventTime(); 600 final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, pointerDataIndex); 601 final int pointerCount = pointerDataIndex + 1; 602 final long pointerDownTime = SystemClock.uptimeMillis(); 603 604 MotionEvent event = MotionEvent.obtain(downTime, pointerDownTime, 605 action, pointerCount, pointerIds, pointerCoords, prototype.getMetaState(), 606 prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(), 607 prototype.getEdgeFlags(), prototype.getSource(), prototype.getFlags()); 608 sendMotionEvent(event, policyFlags); 609 event.recycle(); 610 611 pointerDataIndex++; 612 } 613 } 614 615 /** 616 * Sends up events to the view hierarchy for all active pointers which are 617 * already being delivered i.e. pointers that are injected. 618 * 619 * @param prototype The prototype from which to create the injected events. 620 * @param policyFlags The policy flags associated with the event. 621 */ 622 private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) { 623 PointerTracker pointerTracker = mPointerTracker; 624 PointerCoords[] pointerCoords = mTempPointerCoords; 625 int[] pointerIds = mTempPointerIds; 626 int pointerDataIndex = 0; 627 628 final int pointerCount = prototype.getPointerCount(); 629 for (int i = 0; i < pointerCount; i++) { 630 final int pointerId = prototype.getPointerId(i); 631 632 // Skip non injected down pointers. 633 if (!pointerTracker.isInjectedPointerDown(pointerId)) { 634 continue; 635 } 636 637 // Populate and inject event. 638 pointerIds[pointerDataIndex] = pointerId; 639 prototype.getPointerCoords(i, pointerCoords[pointerDataIndex]); 640 641 final long downTime = pointerTracker.getLastInjectedDownEventTime(); 642 final int action = computeInjectionAction(MotionEvent.ACTION_UP, pointerDataIndex); 643 final int newPointerCount = pointerDataIndex + 1; 644 final long eventTime = SystemClock.uptimeMillis(); 645 646 MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, 647 newPointerCount, pointerIds, pointerCoords, prototype.getMetaState(), 648 prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(), 649 prototype.getEdgeFlags(), prototype.getSource(), prototype.getFlags()); 650 651 sendMotionEvent(event, policyFlags); 652 event.recycle(); 653 654 pointerDataIndex++; 655 } 656 } 657 658 /** 659 * Sends a motion event by first stripping the inactive pointers. 660 * 661 * @param prototype The prototype from which to create the injected event. 662 * @param policyFlags The policy flags associated with the event. 663 */ 664 private void sendMotionEventStripInactivePointers(MotionEvent prototype, int policyFlags) { 665 PointerTracker pointerTracker = mPointerTracker; 666 667 // All pointers active therefore we just inject the event as is. 668 if (prototype.getPointerCount() == pointerTracker.getActivePointerCount()) { 669 sendMotionEvent(prototype, policyFlags); 670 return; 671 } 672 673 // No active pointers and the one that just went up was not 674 // active, therefore we have nothing to do. 675 if (pointerTracker.getActivePointerCount() == 0 676 && !pointerTracker.wasLastReceivedUpPointerActive()) { 677 return; 678 } 679 680 // Filter out inactive pointers from the event and inject it. 681 PointerCoords[] pointerCoords = mTempPointerCoords; 682 int[] pointerIds = mTempPointerIds; 683 int [] newToOldPointerIndexMap = mTempNewToOldPointerIndexMap; 684 int newPointerIndex = 0; 685 int actionIndex = prototype.getActionIndex(); 686 687 final int oldPointerCount = prototype.getPointerCount(); 688 for (int oldPointerIndex = 0; oldPointerIndex < oldPointerCount; oldPointerIndex++) { 689 final int pointerId = prototype.getPointerId(oldPointerIndex); 690 691 // If the pointer is inactive or the pointer that just went up 692 // was inactive we strip the pointer data from the event. 693 if (!pointerTracker.isActiveOrWasLastActiveUpPointer(pointerId)) { 694 if (oldPointerIndex <= prototype.getActionIndex()) { 695 actionIndex--; 696 } 697 continue; 698 } 699 700 newToOldPointerIndexMap[newPointerIndex] = oldPointerIndex; 701 pointerIds[newPointerIndex] = pointerId; 702 prototype.getPointerCoords(oldPointerIndex, pointerCoords[newPointerIndex]); 703 704 newPointerIndex++; 705 } 706 707 // If we skipped all pointers => nothing to do. 708 if (newPointerIndex == 0) { 709 return; 710 } 711 712 // Populate and inject the event. 713 final long downTime = pointerTracker.getLastInjectedDownEventTime(); 714 final int action = computeInjectionAction(prototype.getActionMasked(), actionIndex); 715 final int newPointerCount = newPointerIndex; 716 MotionEvent prunedEvent = MotionEvent.obtain(downTime, prototype.getEventTime(), action, 717 newPointerCount, pointerIds, pointerCoords, prototype.getMetaState(), 718 prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(), 719 prototype.getEdgeFlags(), prototype.getSource(),prototype.getFlags()); 720 721 // Add the filtered history. 722 final int historySize = prototype.getHistorySize(); 723 for (int historyIndex = 0; historyIndex < historySize; historyIndex++) { 724 for (int pointerIndex = 0; pointerIndex < newPointerCount; pointerIndex++) { 725 final int oldPointerIndex = newToOldPointerIndexMap[pointerIndex]; 726 prototype.getPointerCoords(oldPointerIndex, pointerCoords[pointerIndex]); 727 } 728 final long historicalTime = prototype.getHistoricalEventTime(historyIndex); 729 prunedEvent.addBatch(historicalTime, pointerCoords, 0); 730 } 731 732 sendMotionEvent(prunedEvent, policyFlags); 733 prunedEvent.recycle(); 734 } 735 736 /** 737 * Sends a dragging event from a two pointer event. The two pointers are 738 * merged into one and delivered to the view hierarchy. Through the entire 739 * drag gesture the pointer id delivered to the view hierarchy is the same. 740 * 741 * @param prototype The prototype from which to create the injected event. 742 * @param action The dragging action that is to be injected. 743 * @param policyFlags The policy flags associated with the event. 744 */ 745 private void sendDragEvent(MotionEvent prototype, int action, int policyFlags) { 746 PointerCoords[] pointerCoords = mTempPointerCoords; 747 int[] pointerIds = mTempPointerIds; 748 final int pointerId = mDraggingPointerId; 749 final int pointerIndex = prototype.findPointerIndex(pointerId); 750 751 // Populate the event with the date of the dragging pointer and inject it. 752 pointerIds[0] = pointerId; 753 prototype.getPointerCoords(pointerIndex, pointerCoords[0]); 754 755 MotionEvent event = MotionEvent.obtain(prototype.getDownTime(), 756 prototype.getEventTime(), action, 1, pointerIds, pointerCoords, 757 prototype.getMetaState(), prototype.getXPrecision(), prototype.getYPrecision(), 758 prototype.getDeviceId(), prototype.getEdgeFlags(), prototype.getSource(), 759 prototype.getFlags()); 760 761 sendMotionEvent(event, policyFlags); 762 event.recycle(); 763 } 764 765 /** 766 * Sends an up and down events. 767 * 768 * @param prototype The prototype from which to create the injected events. 769 * @param policyFlags The policy flags associated with the event. 770 */ 771 private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) { 772 PointerCoords[] pointerCoords = mTempPointerCoords; 773 int[] pointerIds = mTempPointerIds; 774 final int pointerId = mPointerTracker.getLastReceivedUpPointerId(); 775 final int pointerIndex = prototype.findPointerIndex(pointerId); 776 777 // Send down. 778 pointerIds[0] = pointerId; 779 prototype.getPointerCoords(pointerIndex, pointerCoords[0]); 780 781 final long downTime = SystemClock.uptimeMillis(); 782 783 MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, 784 1, mTempPointerIds, mTempPointerCoords, prototype.getMetaState(), 785 prototype.getXPrecision(), prototype.getYPrecision(), prototype.getDeviceId(), 786 prototype.getEdgeFlags(), prototype.getSource(), prototype.getFlags()); 787 788 // Clone the down event before recycling it. 789 MotionEvent upEvent = MotionEvent.obtain(downEvent); 790 791 sendMotionEvent(downEvent, policyFlags); 792 downEvent.recycle(); 793 794 // Send up. 795 upEvent.setAction(MotionEvent.ACTION_UP); 796 sendMotionEvent(upEvent, policyFlags); 797 upEvent.recycle(); 798 } 799 800 /** 801 * Sends a hover event. 802 * 803 * @param prototype The prototype from which to create the injected event. 804 * @param action The hover action. 805 * @param pointerIndex The action pointer index. 806 * @param policyFlags The policy flags associated with the event. 807 */ 808 private void sendHoverEvent(MotionEvent prototype, int action, int pointerIndex, int 809 policyFlags) { 810 PointerCoords[] pointerCoords = mTempPointerCoords; 811 int[] pointerIds = mTempPointerIds; 812 813 // Keep only data relevant to a hover event. 814 pointerIds[0] = prototype.getPointerId(pointerIndex); 815 pointerCoords[0].clear(); 816 pointerCoords[0].x = prototype.getX(pointerIndex); 817 pointerCoords[0].y = prototype.getY(pointerIndex); 818 819 final long downTime = mPointerTracker.getLastInjectedDownEventTime(); 820 821 // Populate and inject a hover event. 822 MotionEvent hoverEvent = MotionEvent.obtain(downTime, prototype.getEventTime(), action, 823 1, pointerIds, pointerCoords, 0, 0, 0, prototype.getDeviceId(), 0, 824 prototype.getSource(), 0); 825 826 sendMotionEvent(hoverEvent, policyFlags); 827 hoverEvent.recycle(); 828 } 829 830 /** 831 * Computes the action for an injected event based on a masked action 832 * and a pointer index. 833 * 834 * @param actionMasked The masked action. 835 * @param pointerIndex The index of the pointer which has changed. 836 * @return The action to be used for injection. 837 */ 838 private int computeInjectionAction(int actionMasked, int pointerIndex) { 839 switch (actionMasked) { 840 case MotionEvent.ACTION_DOWN: 841 case MotionEvent.ACTION_POINTER_DOWN: { 842 PointerTracker pointerTracker = mPointerTracker; 843 // Compute the action based on how many down pointers are injected. 844 if (pointerTracker.getInjectedPointerDownCount() == 0) { 845 return MotionEvent.ACTION_DOWN; 846 } else { 847 return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) 848 | MotionEvent.ACTION_POINTER_DOWN; 849 } 850 } 851 case MotionEvent.ACTION_POINTER_UP: { 852 PointerTracker pointerTracker = mPointerTracker; 853 // Compute the action based on how many down pointers are injected. 854 if (pointerTracker.getInjectedPointerDownCount() == 1) { 855 return MotionEvent.ACTION_UP; 856 } else { 857 return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) 858 | MotionEvent.ACTION_POINTER_UP; 859 } 860 } 861 default: 862 return actionMasked; 863 } 864 } 865 866 /** 867 * Determines whether a two pointer gesture is a dragging one. 868 * 869 * @param event The event with the pointer data. 870 * @return True if the gesture is a dragging one. 871 */ 872 private boolean isDraggingGesture(MotionEvent event) { 873 PointerTracker pointerTracker = mPointerTracker; 874 int[] pointerIds = mTempPointerIds; 875 pointerTracker.populateActivePointerIds(pointerIds); 876 877 final int firstPtrIndex = event.findPointerIndex(pointerIds[0]); 878 final int secondPtrIndex = event.findPointerIndex(pointerIds[1]); 879 880 final float firstPtrX = event.getX(firstPtrIndex); 881 final float firstPtrY = event.getY(firstPtrIndex); 882 final float secondPtrX = event.getX(secondPtrIndex); 883 final float secondPtrY = event.getY(secondPtrIndex); 884 885 // Check if the pointers are close enough. 886 final float deltaX = firstPtrX - secondPtrX; 887 final float deltaY = firstPtrY - secondPtrY; 888 final float deltaMove = (float) Math.hypot(deltaX, deltaY); 889 if (deltaMove > mDraggingDistance) { 890 return false; 891 } 892 893 // Check if the pointers are moving in the same direction. 894 final float firstDeltaX = 895 firstPtrX - pointerTracker.getReceivedPointerDownX(firstPtrIndex); 896 final float firstDeltaY = 897 firstPtrY - pointerTracker.getReceivedPointerDownY(firstPtrIndex); 898 final float firstMagnitude = 899 (float) Math.sqrt(firstDeltaX * firstDeltaX + firstDeltaY * firstDeltaY); 900 final float firstXNormalized = 901 (firstMagnitude > 0) ? firstDeltaX / firstMagnitude : firstDeltaX; 902 final float firstYNormalized = 903 (firstMagnitude > 0) ? firstDeltaY / firstMagnitude : firstDeltaY; 904 905 final float secondDeltaX = 906 secondPtrX - pointerTracker.getReceivedPointerDownX(secondPtrIndex); 907 final float secondDeltaY = 908 secondPtrY - pointerTracker.getReceivedPointerDownY(secondPtrIndex); 909 final float secondMagnitude = 910 (float) Math.sqrt(secondDeltaX * secondDeltaX + secondDeltaY * secondDeltaY); 911 final float secondXNormalized = 912 (secondMagnitude > 0) ? secondDeltaX / secondMagnitude : secondDeltaX; 913 final float secondYNormalized = 914 (secondMagnitude > 0) ? secondDeltaY / secondMagnitude : secondDeltaY; 915 916 final float angleCos = 917 firstXNormalized * secondXNormalized + firstYNormalized * secondYNormalized; 918 919 if (angleCos < MIN_ANGLE_COS) { 920 return false; 921 } 922 923 return true; 924 } 925 926 /** 927 * Sends an event announcing the start/end of a touch exploration gesture. 928 * 929 * @param eventType The type of the event to send. 930 */ 931 private void sendAccessibilityEvent(int eventType) { 932 AccessibilityEvent event = AccessibilityEvent.obtain(eventType); 933 event.setPackageName(mContext.getPackageName()); 934 event.setClassName(getClass().getName()); 935 mAccessibilityManager.sendAccessibilityEvent(event); 936 } 937 938 /** 939 * Sends a motion event to the input filter for injection. 940 * 941 * @param event The event to send. 942 * @param policyFlags The policy flags associated with the event. 943 */ 944 private void sendMotionEvent(MotionEvent event, int policyFlags) { 945 if (DEBUG) { 946 Slog.d(LOG_TAG_INJECTED, "Injecting event: " + event + ", policyFlags=0x" 947 + Integer.toHexString(policyFlags)); 948 } 949 // Make sure that the user will see the event. 950 policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER; 951 mPointerTracker.onInjectedMotionEvent(event); 952 mInputFilter.sendInputEvent(event, policyFlags); 953 } 954 955 /** 956 * Clears the internal state of this explorer. 957 */ 958 private void clear() { 959 mSendHoverDelayed.remove(); 960 mPointerTracker.clear(); 961 mLastTouchExploreEvent = null; 962 mCurrentState = STATE_TOUCH_EXPLORING; 963 mTouchExploreGestureInProgress = false; 964 mDraggingPointerId = INVALID_POINTER_ID; 965 } 966 967 /** 968 * Helper class for tracking pointers and more specifically which of 969 * them are currently down, which are active, and which are delivered 970 * to the view hierarchy. The enclosing {@link TouchExplorer} uses the 971 * pointer state reported by this class to perform touch exploration. 972 * <p> 973 * The main purpose of this class is to allow the touch explorer to 974 * disregard pointers put down by accident by the user and not being 975 * involved in the interaction. For example, a blind user grabs the 976 * device with her left hand such that she touches the screen and she 977 * uses her right hand's index finger to explore the screen content. 978 * In this scenario the touches generated by the left hand are to be 979 * ignored. 980 */ 981 class PointerTracker { 982 private static final String LOG_TAG = "PointerTracker"; 983 984 // The coefficient by which to multiply 985 // ViewConfiguration.#getScaledTouchSlop() 986 // to compute #mThresholdActivePointer. 987 private static final int COEFFICIENT_ACTIVE_POINTER = 2; 988 989 // Pointers that moved less than mThresholdActivePointer 990 // are considered active i.e. are ignored. 991 private final double mThresholdActivePointer; 992 993 // Keep track of where and when a pointer went down. 994 private final float[] mReceivedPointerDownX = new float[MAX_POINTER_COUNT]; 995 private final float[] mReceivedPointerDownY = new float[MAX_POINTER_COUNT]; 996 private final long[] mReceivedPointerDownTime = new long[MAX_POINTER_COUNT]; 997 998 // Which pointers are down. 999 private int mReceivedPointersDown; 1000 1001 // Which down pointers are active. 1002 private int mActivePointers; 1003 1004 // Primary active pointer which is either the first that went down 1005 // or if it goes up the next active that most recently went down. 1006 private int mPrimaryActivePointerId; 1007 1008 // Flag indicating that there is at least one active pointer moving. 1009 private boolean mHasMovingActivePointer; 1010 1011 // Keep track of which pointers sent to the system are down. 1012 private int mInjectedPointersDown; 1013 1014 // Keep track of the last up pointer data. 1015 private float mLastReceivedUpPointerDownX; 1016 private float mLastReveivedUpPointerDownY; 1017 private long mLastReceivedUpPointerDownTime; 1018 private int mLastReceivedUpPointerId; 1019 private boolean mLastReceivedUpPointerActive; 1020 1021 // The time of the last injected down. 1022 private long mLastInjectedDownEventTime; 1023 1024 // The action of the last injected hover event. 1025 private int mLastInjectedHoverEventAction = MotionEvent.ACTION_HOVER_EXIT; 1026 1027 /** 1028 * Creates a new instance. 1029 * 1030 * @param context Context for looking up resources. 1031 */ 1032 public PointerTracker(Context context) { 1033 mThresholdActivePointer = 1034 ViewConfiguration.get(context).getScaledTouchSlop() * COEFFICIENT_ACTIVE_POINTER; 1035 } 1036 1037 /** 1038 * Clears the internals state. 1039 */ 1040 public void clear() { 1041 Arrays.fill(mReceivedPointerDownX, 0); 1042 Arrays.fill(mReceivedPointerDownY, 0); 1043 Arrays.fill(mReceivedPointerDownTime, 0); 1044 mReceivedPointersDown = 0; 1045 mActivePointers = 0; 1046 mPrimaryActivePointerId = 0; 1047 mHasMovingActivePointer = false; 1048 mInjectedPointersDown = 0; 1049 mLastReceivedUpPointerDownX = 0; 1050 mLastReveivedUpPointerDownY = 0; 1051 mLastReceivedUpPointerDownTime = 0; 1052 mLastReceivedUpPointerId = 0; 1053 mLastReceivedUpPointerActive = false; 1054 } 1055 1056 /** 1057 * Processes a received {@link MotionEvent} event. 1058 * 1059 * @param event The event to process. 1060 */ 1061 public void onReceivedMotionEvent(MotionEvent event) { 1062 final int action = event.getActionMasked(); 1063 switch (action) { 1064 case MotionEvent.ACTION_DOWN: { 1065 // New gesture so restart tracking injected down pointers. 1066 mInjectedPointersDown = 0; 1067 handleReceivedPointerDown(0, event); 1068 } break; 1069 case MotionEvent.ACTION_POINTER_DOWN: { 1070 handleReceivedPointerDown(event.getActionIndex(), event); 1071 } break; 1072 case MotionEvent.ACTION_MOVE: { 1073 handleReceivedPointerMove(event); 1074 } break; 1075 case MotionEvent.ACTION_UP: { 1076 handleReceivedPointerUp(0, event); 1077 } break; 1078 case MotionEvent.ACTION_POINTER_UP: { 1079 handleReceivedPointerUp(event.getActionIndex(), event); 1080 } break; 1081 } 1082 if (DEBUG) { 1083 Slog.i(LOG_TAG, "Received pointer: " + toString()); 1084 } 1085 } 1086 1087 /** 1088 * Processes an injected {@link MotionEvent} event. 1089 * 1090 * @param event The event to process. 1091 */ 1092 public void onInjectedMotionEvent(MotionEvent event) { 1093 final int action = event.getActionMasked(); 1094 switch (action) { 1095 case MotionEvent.ACTION_DOWN: { 1096 handleInjectedPointerDown(0, event); 1097 } break; 1098 case MotionEvent.ACTION_POINTER_DOWN: { 1099 handleInjectedPointerDown(event.getActionIndex(), event); 1100 } break; 1101 case MotionEvent.ACTION_UP: { 1102 handleInjectedPointerUp(0, event); 1103 } break; 1104 case MotionEvent.ACTION_POINTER_UP: { 1105 handleInjectedPointerUp(event.getActionIndex(), event); 1106 } break; 1107 case MotionEvent.ACTION_HOVER_ENTER: 1108 case MotionEvent.ACTION_HOVER_MOVE: 1109 case MotionEvent.ACTION_HOVER_EXIT: { 1110 mLastInjectedHoverEventAction = event.getActionMasked(); 1111 } break; 1112 } 1113 if (DEBUG) { 1114 Slog.i(LOG_TAG, "Injected pointer: " + toString()); 1115 } 1116 } 1117 1118 /** 1119 * @return The number of received pointers that are down. 1120 */ 1121 public int getReceivedPointerDownCount() { 1122 return Integer.bitCount(mReceivedPointersDown); 1123 } 1124 1125 /** 1126 * @return The number of down input pointers that are active. 1127 */ 1128 public int getActivePointerCount() { 1129 return Integer.bitCount(mActivePointers); 1130 } 1131 1132 /** 1133 * Whether an received pointer is down. 1134 * 1135 * @param pointerId The unique pointer id. 1136 * @return True if the pointer is down. 1137 */ 1138 public boolean isReceivedPointerDown(int pointerId) { 1139 final int pointerFlag = (1 << pointerId); 1140 return (mReceivedPointersDown & pointerFlag) != 0; 1141 } 1142 1143 /** 1144 * Whether an injected pointer is down. 1145 * 1146 * @param pointerId The unique pointer id. 1147 * @return True if the pointer is down. 1148 */ 1149 public boolean isInjectedPointerDown(int pointerId) { 1150 final int pointerFlag = (1 << pointerId); 1151 return (mInjectedPointersDown & pointerFlag) != 0; 1152 } 1153 1154 /** 1155 * @return The number of down pointers injected to the view hierarchy. 1156 */ 1157 public int getInjectedPointerDownCount() { 1158 return Integer.bitCount(mInjectedPointersDown); 1159 } 1160 1161 /** 1162 * Whether an input pointer is active. 1163 * 1164 * @param pointerId The unique pointer id. 1165 * @return True if the pointer is active. 1166 */ 1167 public boolean isActivePointer(int pointerId) { 1168 final int pointerFlag = (1 << pointerId); 1169 return (mActivePointers & pointerFlag) != 0; 1170 } 1171 1172 /** 1173 * @param pointerId The unique pointer id. 1174 * @return The X coordinate where the pointer went down. 1175 */ 1176 public float getReceivedPointerDownX(int pointerId) { 1177 return mReceivedPointerDownX[pointerId]; 1178 } 1179 1180 /** 1181 * @param pointerId The unique pointer id. 1182 * @return The Y coordinate where the pointer went down. 1183 */ 1184 public float getReceivedPointerDownY(int pointerId) { 1185 return mReceivedPointerDownY[pointerId]; 1186 } 1187 1188 /** 1189 * @param pointerId The unique pointer id. 1190 * @return The time when the pointer went down. 1191 */ 1192 public long getReceivedPointerDownTime(int pointerId) { 1193 return mReceivedPointerDownTime[pointerId]; 1194 } 1195 1196 /** 1197 * @return The id of the primary pointer. 1198 */ 1199 public int getPrimaryActivePointerId() { 1200 if (mPrimaryActivePointerId == INVALID_POINTER_ID) { 1201 mPrimaryActivePointerId = findPrimaryActivePointer(); 1202 } 1203 return mPrimaryActivePointerId; 1204 } 1205 1206 /** 1207 * @return The X coordinate where the last up received pointer went down. 1208 */ 1209 public float getLastReceivedUpPointerDownX() { 1210 return mLastReceivedUpPointerDownX; 1211 } 1212 1213 /** 1214 * @return The Y coordinate where the last up received pointer went down. 1215 */ 1216 public float getLastReceivedUpPointerDownY() { 1217 return mLastReveivedUpPointerDownY; 1218 } 1219 1220 /** 1221 * @return The time when the last up received pointer went down. 1222 */ 1223 public long getLastReceivedUpPointerDownTime() { 1224 return mLastReceivedUpPointerDownTime; 1225 } 1226 1227 /** 1228 * @return The id of the last received pointer that went up. 1229 */ 1230 public int getLastReceivedUpPointerId() { 1231 return mLastReceivedUpPointerId; 1232 } 1233 1234 /** 1235 * @return Whether the last received pointer that went up was active. 1236 */ 1237 public boolean wasLastReceivedUpPointerActive() { 1238 return mLastReceivedUpPointerActive; 1239 } 1240 1241 /** 1242 * @return The time of the last injected down event. 1243 */ 1244 public long getLastInjectedDownEventTime() { 1245 return mLastInjectedDownEventTime; 1246 } 1247 1248 /** 1249 * @return The action of the last injected hover event. 1250 */ 1251 public int getLastInjectedHoverAction() { 1252 return mLastInjectedHoverEventAction; 1253 } 1254 1255 /** 1256 * Populates the active pointer IDs to the given array. 1257 * <p> 1258 * Note: The client is responsible for providing large enough array. 1259 * 1260 * @param outPointerIds The array to which to write the active pointers. 1261 */ 1262 public void populateActivePointerIds(int[] outPointerIds) { 1263 int index = 0; 1264 for (int idBits = mActivePointers; idBits != 0; ) { 1265 final int id = Integer.numberOfTrailingZeros(idBits); 1266 idBits &= ~(1 << id); 1267 outPointerIds[index] = id; 1268 index++; 1269 } 1270 } 1271 1272 /** 1273 * @return The number of non injected active pointers. 1274 */ 1275 public int getNotInjectedActivePointerCount() { 1276 final int pointerState = mActivePointers & ~mInjectedPointersDown; 1277 return Integer.bitCount(pointerState); 1278 } 1279 1280 /** 1281 * @param pointerId The unique pointer id. 1282 * @return Whether the pointer is active or was the last active than went up. 1283 */ 1284 private boolean isActiveOrWasLastActiveUpPointer(int pointerId) { 1285 return (isActivePointer(pointerId) 1286 || (mLastReceivedUpPointerId == pointerId 1287 && mLastReceivedUpPointerActive)); 1288 } 1289 1290 /** 1291 * Handles a received pointer down event. 1292 * 1293 * @param pointerIndex The index of the pointer that has changed. 1294 * @param event The event to be handled. 1295 */ 1296 private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) { 1297 final int pointerId = event.getPointerId(pointerIndex); 1298 final int pointerFlag = (1 << pointerId); 1299 1300 mLastReceivedUpPointerId = 0; 1301 mLastReceivedUpPointerDownX = 0; 1302 mLastReveivedUpPointerDownY = 0; 1303 mLastReceivedUpPointerDownTime = 0; 1304 mLastReceivedUpPointerActive = false; 1305 1306 mReceivedPointersDown |= pointerFlag; 1307 mReceivedPointerDownX[pointerId] = event.getX(pointerIndex); 1308 mReceivedPointerDownY[pointerId] = event.getY(pointerIndex); 1309 mReceivedPointerDownTime[pointerId] = event.getEventTime(); 1310 1311 if (!mHasMovingActivePointer) { 1312 // If still no moving active pointers every 1313 // down pointer is the only active one. 1314 mActivePointers = pointerFlag; 1315 mPrimaryActivePointerId = pointerId; 1316 } else { 1317 // If at least one moving active pointer every 1318 // subsequent down pointer is active. 1319 mActivePointers |= pointerFlag; 1320 } 1321 } 1322 1323 /** 1324 * Handles a received pointer move event. 1325 * 1326 * @param event The event to be handled. 1327 */ 1328 private void handleReceivedPointerMove(MotionEvent event) { 1329 detectActivePointers(event); 1330 } 1331 1332 /** 1333 * Handles a received pointer up event. 1334 * 1335 * @param pointerIndex The index of the pointer that has changed. 1336 * @param event The event to be handled. 1337 */ 1338 private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) { 1339 final int pointerId = event.getPointerId(pointerIndex); 1340 final int pointerFlag = (1 << pointerId); 1341 1342 mLastReceivedUpPointerId = pointerId; 1343 mLastReceivedUpPointerDownX = getReceivedPointerDownX(pointerId); 1344 mLastReveivedUpPointerDownY = getReceivedPointerDownY(pointerId); 1345 mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId); 1346 mLastReceivedUpPointerActive = isActivePointer(pointerId); 1347 1348 mReceivedPointersDown &= ~pointerFlag; 1349 mActivePointers &= ~pointerFlag; 1350 mReceivedPointerDownX[pointerId] = 0; 1351 mReceivedPointerDownY[pointerId] = 0; 1352 mReceivedPointerDownTime[pointerId] = 0; 1353 1354 if (mActivePointers == 0) { 1355 mHasMovingActivePointer = false; 1356 } 1357 if (mPrimaryActivePointerId == pointerId) { 1358 mPrimaryActivePointerId = INVALID_POINTER_ID; 1359 } 1360 } 1361 1362 /** 1363 * Handles a injected pointer down event. 1364 * 1365 * @param pointerIndex The index of the pointer that has changed. 1366 * @param event The event to be handled. 1367 */ 1368 private void handleInjectedPointerDown(int pointerIndex, MotionEvent event) { 1369 final int pointerId = event.getPointerId(pointerIndex); 1370 final int pointerFlag = (1 << pointerId); 1371 mInjectedPointersDown |= pointerFlag; 1372 mLastInjectedDownEventTime = event.getEventTime(); 1373 } 1374 1375 /** 1376 * Handles a injected pointer up event. 1377 * 1378 * @param pointerIndex The index of the pointer that has changed. 1379 * @param event The event to be handled. 1380 */ 1381 private void handleInjectedPointerUp(int pointerIndex, MotionEvent event) { 1382 final int pointerId = event.getPointerId(pointerIndex); 1383 final int pointerFlag = (1 << pointerId); 1384 mInjectedPointersDown &= ~pointerFlag; 1385 } 1386 1387 /** 1388 * Detects the active pointers in an event. 1389 * 1390 * @param event The event to examine. 1391 */ 1392 private void detectActivePointers(MotionEvent event) { 1393 for (int i = 0, count = event.getPointerCount(); i < count; i++) { 1394 final int pointerId = event.getPointerId(i); 1395 if (mHasMovingActivePointer) { 1396 // If already active => nothing to do. 1397 if (isActivePointer(pointerId)) { 1398 continue; 1399 } 1400 } 1401 // Active pointers are ones that moved more than a given threshold. 1402 final float pointerDeltaMove = computePointerDeltaMove(i, event); 1403 if (pointerDeltaMove > mThresholdActivePointer) { 1404 final int pointerFlag = (1 << pointerId); 1405 mActivePointers |= pointerFlag; 1406 mHasMovingActivePointer = true; 1407 } 1408 } 1409 } 1410 1411 /** 1412 * @return The primary active pointer. 1413 */ 1414 private int findPrimaryActivePointer() { 1415 int primaryActivePointerId = INVALID_POINTER_ID; 1416 long minDownTime = Long.MAX_VALUE; 1417 // Find the active pointer that went down first. 1418 for (int i = 0, count = mReceivedPointerDownTime.length; i < count; i++) { 1419 if (isActivePointer(i)) { 1420 final long downPointerTime = mReceivedPointerDownTime[i]; 1421 if (downPointerTime < minDownTime) { 1422 minDownTime = downPointerTime; 1423 primaryActivePointerId = i; 1424 } 1425 } 1426 } 1427 return primaryActivePointerId; 1428 } 1429 1430 /** 1431 * Computes the move for a given action pointer index since the 1432 * corresponding pointer went down. 1433 * 1434 * @param pointerIndex The action pointer index. 1435 * @param event The event to examine. 1436 * @return The distance the pointer has moved. 1437 */ 1438 private float computePointerDeltaMove(int pointerIndex, MotionEvent event) { 1439 final int pointerId = event.getPointerId(pointerIndex); 1440 final float deltaX = event.getX(pointerIndex) - mReceivedPointerDownX[pointerId]; 1441 final float deltaY = event.getY(pointerIndex) - mReceivedPointerDownY[pointerId]; 1442 return (float) Math.hypot(deltaX, deltaY); 1443 } 1444 1445 @Override 1446 public String toString() { 1447 StringBuilder builder = new StringBuilder(); 1448 builder.append("========================="); 1449 builder.append("\nDown pointers #"); 1450 builder.append(getReceivedPointerDownCount()); 1451 builder.append(" [ "); 1452 for (int i = 0; i < MAX_POINTER_COUNT; i++) { 1453 if (isReceivedPointerDown(i)) { 1454 builder.append(i); 1455 builder.append(" "); 1456 } 1457 } 1458 builder.append("]"); 1459 builder.append("\nActive pointers #"); 1460 builder.append(getActivePointerCount()); 1461 builder.append(" [ "); 1462 for (int i = 0; i < MAX_POINTER_COUNT; i++) { 1463 if (isActivePointer(i)) { 1464 builder.append(i); 1465 builder.append(" "); 1466 } 1467 } 1468 builder.append("]"); 1469 builder.append("\nPrimary active pointer id [ "); 1470 builder.append(getPrimaryActivePointerId()); 1471 builder.append(" ]"); 1472 builder.append("\n========================="); 1473 return builder.toString(); 1474 } 1475 } 1476 1477 /** 1478 * Class for delayed sending of hover events. 1479 */ 1480 private final class SendHoverDelayed implements Runnable { 1481 private static final String LOG_TAG = "SendHoverEnterOrExitDelayed"; 1482 1483 private MotionEvent mEvent; 1484 private int mAction; 1485 private int mPointerIndex; 1486 private int mPolicyFlags; 1487 1488 public void post(MotionEvent prototype, int action, int pointerIndex, int policyFlags, 1489 long delay) { 1490 remove(); 1491 mEvent = MotionEvent.obtain(prototype); 1492 mAction = action; 1493 mPointerIndex = pointerIndex; 1494 mPolicyFlags = policyFlags; 1495 mHandler.postDelayed(this, delay); 1496 } 1497 1498 public void remove() { 1499 mHandler.removeCallbacks(this); 1500 clear(); 1501 } 1502 1503 private boolean isPenidng() { 1504 return (mEvent != null); 1505 } 1506 1507 private void clear() { 1508 if (!isPenidng()) { 1509 return; 1510 } 1511 mEvent.recycle(); 1512 mEvent = null; 1513 mAction = 0; 1514 mPointerIndex = -1; 1515 mPolicyFlags = 0; 1516 } 1517 1518 public void forceSendAndRemove() { 1519 if (isPenidng()) { 1520 run(); 1521 remove(); 1522 } 1523 } 1524 1525 public void run() { 1526 if (DEBUG) { 1527 if (mAction == MotionEvent.ACTION_HOVER_ENTER) { 1528 Slog.d(LOG_TAG, "Injecting: " + MotionEvent.ACTION_HOVER_ENTER); 1529 } else if (mAction == MotionEvent.ACTION_HOVER_MOVE) { 1530 Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_MOVE"); 1531 } else if (mAction == MotionEvent.ACTION_HOVER_EXIT) { 1532 Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_EXIT"); 1533 } 1534 } 1535 1536 sendHoverEvent(mEvent, mAction, mPointerIndex, mPolicyFlags); 1537 clear(); 1538 } 1539 } 1540} 1541