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