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