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