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