1 /* 2 * Copyright (C) 2019 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.gestures; 18 19 import static android.view.MotionEvent.INVALID_POINTER_ID; 20 21 import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG; 22 23 import android.annotation.IntDef; 24 import android.util.Slog; 25 import android.view.MotionEvent; 26 import android.view.accessibility.AccessibilityEvent; 27 28 /** 29 * This class describes the state of the touch explorer as well as the state of received and 30 * injected pointers. This data is accessed both for purposes of touch exploration and gesture 31 * dispatch. 32 */ 33 public class TouchState { 34 private static final String LOG_TAG = "TouchState"; 35 // Pointer-related constants 36 // This constant captures the current implementation detail that 37 // pointer IDs are between 0 and 31 inclusive (subject to change). 38 // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h) 39 static final int MAX_POINTER_COUNT = 32; 40 // Constant referring to the ids bits of all pointers. 41 public static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF; 42 43 // States that the touch explorer can be in. 44 // In the clear state the user is not touching the screen. 45 public static final int STATE_CLEAR = 0; 46 // The user is touching the screen and we are trying to figure out their intent. 47 // This state gets its name from the TYPE_TOUCH_INTERACTION start and end accessibility events. 48 public static final int STATE_TOUCH_INTERACTING = 1; 49 // The user is explicitly exploring the screen. 50 public static final int STATE_TOUCH_EXPLORING = 2; 51 // the user is dragging with two fingers. 52 public static final int STATE_DRAGGING = 3; 53 // The user is performing some other two finger gesture which we pass through to the view 54 // hierarchy as a one-finger gesture e.g. two-finger scrolling. 55 public static final int STATE_DELEGATING = 4; 56 // The user is performing something that might be a gesture. 57 public static final int STATE_GESTURE_DETECTING = 5; 58 59 @IntDef({ 60 STATE_CLEAR, 61 STATE_TOUCH_INTERACTING, 62 STATE_TOUCH_EXPLORING, 63 STATE_DRAGGING, 64 STATE_DELEGATING, 65 STATE_GESTURE_DETECTING 66 }) 67 public @interface State {} 68 69 // The current state of the touch explorer. 70 private int mState = STATE_CLEAR; 71 // Helper class to track received pointers. 72 // Todo: collapse or hide this class so multiple classes don't modify it. 73 private final ReceivedPointerTracker mReceivedPointerTracker; 74 // The most recently received motion event. 75 private MotionEvent mLastReceivedEvent; 76 // The accompanying raw event without any transformations. 77 private MotionEvent mLastReceivedRawEvent; 78 // The id of the last touch explored window. 79 private int mLastTouchedWindowId; 80 // The last injected hover event. 81 private MotionEvent mLastInjectedHoverEvent; 82 // The last injected hover event used for performing clicks. 83 private MotionEvent mLastInjectedHoverEventForClick; 84 // The time of the last injected down. 85 private long mLastInjectedDownEventTime; 86 // Keep track of which pointers sent to the system are down. 87 private int mInjectedPointersDown; 88 TouchState()89 public TouchState() { 90 mReceivedPointerTracker = new ReceivedPointerTracker(); 91 } 92 93 /** Clears the internal shared state. */ clear()94 public void clear() { 95 setState(STATE_CLEAR); 96 // Reset the pointer trackers. 97 if (mLastReceivedEvent != null) { 98 mLastReceivedEvent.recycle(); 99 mLastReceivedEvent = null; 100 } 101 mLastTouchedWindowId = -1; 102 mReceivedPointerTracker.clear(); 103 mInjectedPointersDown = 0; 104 } 105 106 /** 107 * Updates the state in response to a touch event received by TouchExplorer. 108 * 109 * @param rawEvent The raw touch event. 110 */ onReceivedMotionEvent(MotionEvent rawEvent)111 public void onReceivedMotionEvent(MotionEvent rawEvent) { 112 if (mLastReceivedEvent != null) { 113 mLastReceivedEvent.recycle(); 114 } 115 if (mLastReceivedRawEvent != null) { 116 mLastReceivedRawEvent.recycle(); 117 } 118 mLastReceivedEvent = MotionEvent.obtain(rawEvent); 119 mReceivedPointerTracker.onMotionEvent(rawEvent); 120 } 121 122 /** 123 * Processes an injected {@link MotionEvent} event. 124 * 125 * @param event The event to process. 126 */ onInjectedMotionEvent(MotionEvent event)127 void onInjectedMotionEvent(MotionEvent event) { 128 final int action = event.getActionMasked(); 129 final int pointerId = event.getPointerId(event.getActionIndex()); 130 final int pointerFlag = (1 << pointerId); 131 switch (action) { 132 case MotionEvent.ACTION_DOWN: 133 case MotionEvent.ACTION_POINTER_DOWN: 134 mInjectedPointersDown |= pointerFlag; 135 mLastInjectedDownEventTime = event.getDownTime(); 136 break; 137 case MotionEvent.ACTION_UP: 138 case MotionEvent.ACTION_POINTER_UP: 139 mInjectedPointersDown &= ~pointerFlag; 140 if (mInjectedPointersDown == 0) { 141 mLastInjectedDownEventTime = 0; 142 } 143 break; 144 case MotionEvent.ACTION_HOVER_ENTER: 145 case MotionEvent.ACTION_HOVER_MOVE: 146 if (mLastInjectedHoverEvent != null) { 147 mLastInjectedHoverEvent.recycle(); 148 } 149 mLastInjectedHoverEvent = MotionEvent.obtain(event); 150 break; 151 case MotionEvent.ACTION_HOVER_EXIT: 152 if (mLastInjectedHoverEvent != null) { 153 mLastInjectedHoverEvent.recycle(); 154 } 155 mLastInjectedHoverEvent = MotionEvent.obtain(event); 156 if (mLastInjectedHoverEventForClick != null) { 157 mLastInjectedHoverEventForClick.recycle(); 158 } 159 mLastInjectedHoverEventForClick = MotionEvent.obtain(event); 160 break; 161 } 162 if (DEBUG) { 163 Slog.i(LOG_TAG, "Injected pointer:\n" + toString()); 164 } 165 } 166 167 /** Updates state in response to an accessibility event received from the outside. */ onReceivedAccessibilityEvent(AccessibilityEvent event)168 public void onReceivedAccessibilityEvent(AccessibilityEvent event) { 169 // If a new window opens or the accessibility focus moves we no longer 170 // want to click/long press on the last touch explored location. 171 switch (event.getEventType()) { 172 case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: 173 case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: 174 if (mLastInjectedHoverEventForClick != null) { 175 mLastInjectedHoverEventForClick.recycle(); 176 mLastInjectedHoverEventForClick = null; 177 } 178 mLastTouchedWindowId = -1; 179 break; 180 case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: 181 case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: 182 mLastTouchedWindowId = event.getWindowId(); 183 break; 184 } 185 } 186 onInjectedAccessibilityEvent(int type)187 public void onInjectedAccessibilityEvent(int type) { 188 // The below state transitions go here because the related events are often sent on a 189 // delay. 190 // This allows state to accurately reflect the state in the moment. 191 // TODO: replaced the delayed event senders with delayed state transitions 192 // so that state transitions trigger events rather than events triggering state 193 // transitions. 194 switch (type) { 195 case AccessibilityEvent.TYPE_TOUCH_INTERACTION_START: 196 startTouchInteracting(); 197 break; 198 case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END: 199 clear(); 200 break; 201 case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: 202 startTouchExploring(); 203 break; 204 case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: 205 startTouchInteracting(); 206 break; 207 case AccessibilityEvent.TYPE_GESTURE_DETECTION_START: 208 startGestureDetecting(); 209 break; 210 case AccessibilityEvent.TYPE_GESTURE_DETECTION_END: 211 // Clear to make sure that we don't accidentally execute passthrough, and that we 212 // are ready for the next interaction. 213 clear(); 214 break; 215 default: 216 break; 217 } 218 } 219 220 @State getState()221 public int getState() { 222 return mState; 223 } 224 225 /** Transitions to a new state. */ setState(@tate int state)226 public void setState(@State int state) { 227 if (mState == state) return; 228 if (DEBUG) { 229 Slog.i(LOG_TAG, getStateSymbolicName(mState) + "->" + getStateSymbolicName(state)); 230 } 231 mState = state; 232 } 233 isTouchExploring()234 public boolean isTouchExploring() { 235 return mState == STATE_TOUCH_EXPLORING; 236 } 237 238 /** Starts touch exploration. */ startTouchExploring()239 public void startTouchExploring() { 240 setState(STATE_TOUCH_EXPLORING); 241 } 242 isDelegating()243 public boolean isDelegating() { 244 return mState == STATE_DELEGATING; 245 } 246 247 /** Starts delegating gestures to the view hierarchy. */ startDelegating()248 public void startDelegating() { 249 setState(STATE_DELEGATING); 250 } 251 isGestureDetecting()252 public boolean isGestureDetecting() { 253 return mState == STATE_GESTURE_DETECTING; 254 } 255 256 /** Initiates gesture detection. */ startGestureDetecting()257 public void startGestureDetecting() { 258 setState(STATE_GESTURE_DETECTING); 259 } 260 isDragging()261 public boolean isDragging() { 262 return mState == STATE_DRAGGING; 263 } 264 265 /** Starts a dragging gesture. */ startDragging()266 public void startDragging() { 267 setState(STATE_DRAGGING); 268 } 269 isTouchInteracting()270 public boolean isTouchInteracting() { 271 return mState == STATE_TOUCH_INTERACTING; 272 } 273 274 /** 275 * Transitions to the touch interacting state, where we attempt to figure out what the user is 276 * doing. 277 */ startTouchInteracting()278 public void startTouchInteracting() { 279 setState(STATE_TOUCH_INTERACTING); 280 } 281 isClear()282 public boolean isClear() { 283 return mState == STATE_CLEAR; 284 } 285 /** Returns a string representation of the current state. */ toString()286 public String toString() { 287 return "TouchState { " + "mState: " + getStateSymbolicName(mState) + " }"; 288 } 289 /** Returns a string representation of the specified state. */ getStateSymbolicName(int state)290 public static String getStateSymbolicName(int state) { 291 switch (state) { 292 case STATE_CLEAR: 293 return "STATE_CLEAR"; 294 case STATE_TOUCH_INTERACTING: 295 return "STATE_TOUCH_INTERACTING"; 296 case STATE_TOUCH_EXPLORING: 297 return "STATE_TOUCH_EXPLORING"; 298 case STATE_DRAGGING: 299 return "STATE_DRAGGING"; 300 case STATE_DELEGATING: 301 return "STATE_DELEGATING"; 302 case STATE_GESTURE_DETECTING: 303 return "STATE_GESTURE_DETECTING"; 304 default: 305 return "Unknown state: " + state; 306 } 307 } 308 getReceivedPointerTracker()309 public ReceivedPointerTracker getReceivedPointerTracker() { 310 return mReceivedPointerTracker; 311 } 312 313 /** @return The last received event. */ getLastReceivedEvent()314 public MotionEvent getLastReceivedEvent() { 315 return mLastReceivedEvent; 316 } 317 318 /** @return The the last injected hover event. */ getLastInjectedHoverEvent()319 public MotionEvent getLastInjectedHoverEvent() { 320 return mLastInjectedHoverEvent; 321 } 322 323 /** @return The time of the last injected down event. */ getLastInjectedDownEventTime()324 public long getLastInjectedDownEventTime() { 325 return mLastInjectedDownEventTime; 326 } 327 getLastTouchedWindowId()328 public int getLastTouchedWindowId() { 329 return mLastTouchedWindowId; 330 } 331 332 /** @return The number of down pointers injected to the view hierarchy. */ getInjectedPointerDownCount()333 public int getInjectedPointerDownCount() { 334 return Integer.bitCount(mInjectedPointersDown); 335 } 336 337 /** @return The bits of the injected pointers that are down. */ getInjectedPointersDown()338 public int getInjectedPointersDown() { 339 return mInjectedPointersDown; 340 } 341 342 /** 343 * Whether an injected pointer is down. 344 * 345 * @param pointerId The unique pointer id. 346 * @return True if the pointer is down. 347 */ isInjectedPointerDown(int pointerId)348 public boolean isInjectedPointerDown(int pointerId) { 349 final int pointerFlag = (1 << pointerId); 350 return (mInjectedPointersDown & pointerFlag) != 0; 351 } 352 353 /** @return The the last injected hover event used for a click. */ getLastInjectedHoverEventForClick()354 public MotionEvent getLastInjectedHoverEventForClick() { 355 return mLastInjectedHoverEventForClick; 356 } 357 358 /** This class tracks where and when a pointer went down. It does not track its movement. */ 359 class ReceivedPointerTracker { 360 private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker"; 361 362 private final PointerDownInfo[] mReceivedPointers = new PointerDownInfo[MAX_POINTER_COUNT]; 363 364 // Which pointers are down. 365 private int mReceivedPointersDown; 366 367 // The edge flags of the last received down event. 368 private int mLastReceivedDownEdgeFlags; 369 370 // Primary pointer which is either the first that went down 371 // or if it goes up the next one that most recently went down. 372 private int mPrimaryPointerId; 373 ReceivedPointerTracker()374 ReceivedPointerTracker() { 375 clear(); 376 } 377 378 /** Clears the internals state. */ clear()379 public void clear() { 380 mReceivedPointersDown = 0; 381 mPrimaryPointerId = 0; 382 for (int i = 0; i < MAX_POINTER_COUNT; ++i) { 383 mReceivedPointers[i] = new PointerDownInfo(); 384 } 385 } 386 387 /** 388 * Processes a received {@link MotionEvent} event. 389 * 390 * @param event The event to process. 391 */ onMotionEvent(MotionEvent event)392 public void onMotionEvent(MotionEvent event) { 393 final int action = event.getActionMasked(); 394 switch (action) { 395 case MotionEvent.ACTION_DOWN: 396 handleReceivedPointerDown(event.getActionIndex(), event); 397 break; 398 case MotionEvent.ACTION_POINTER_DOWN: 399 handleReceivedPointerDown(event.getActionIndex(), event); 400 break; 401 case MotionEvent.ACTION_UP: 402 handleReceivedPointerUp(event.getActionIndex(), event); 403 break; 404 case MotionEvent.ACTION_POINTER_UP: 405 handleReceivedPointerUp(event.getActionIndex(), event); 406 break; 407 } 408 if (DEBUG) { 409 Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer:\n" + toString()); 410 } 411 } 412 413 /** @return The number of received pointers that are down. */ getReceivedPointerDownCount()414 public int getReceivedPointerDownCount() { 415 return Integer.bitCount(mReceivedPointersDown); 416 } 417 418 /** 419 * Whether an received pointer is down. 420 * 421 * @param pointerId The unique pointer id. 422 * @return True if the pointer is down. 423 */ isReceivedPointerDown(int pointerId)424 public boolean isReceivedPointerDown(int pointerId) { 425 final int pointerFlag = (1 << pointerId); 426 return (mReceivedPointersDown & pointerFlag) != 0; 427 } 428 429 /** 430 * @param pointerId The unique pointer id. 431 * @return The X coordinate where the pointer went down. 432 */ getReceivedPointerDownX(int pointerId)433 public float getReceivedPointerDownX(int pointerId) { 434 return mReceivedPointers[pointerId].mX; 435 } 436 437 /** 438 * @param pointerId The unique pointer id. 439 * @return The Y coordinate where the pointer went down. 440 */ getReceivedPointerDownY(int pointerId)441 public float getReceivedPointerDownY(int pointerId) { 442 return mReceivedPointers[pointerId].mY; 443 } 444 445 /** 446 * @param pointerId The unique pointer id. 447 * @return The time when the pointer went down. 448 */ getReceivedPointerDownTime(int pointerId)449 public long getReceivedPointerDownTime(int pointerId) { 450 return mReceivedPointers[pointerId].mTime; 451 } 452 453 /** @return The id of the primary pointer. */ getPrimaryPointerId()454 public int getPrimaryPointerId() { 455 if (mPrimaryPointerId == INVALID_POINTER_ID) { 456 mPrimaryPointerId = findPrimaryPointerId(); 457 } 458 return mPrimaryPointerId; 459 } 460 461 /** @return The edge flags of the last received down event. */ getLastReceivedDownEdgeFlags()462 public int getLastReceivedDownEdgeFlags() { 463 return mLastReceivedDownEdgeFlags; 464 } 465 466 /** 467 * Handles a received pointer down event. 468 * 469 * @param pointerIndex The index of the pointer that has changed. 470 * @param event The event to be handled. 471 */ handleReceivedPointerDown(int pointerIndex, MotionEvent event)472 private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) { 473 final int pointerId = event.getPointerId(pointerIndex); 474 final int pointerFlag = (1 << pointerId); 475 mLastReceivedDownEdgeFlags = event.getEdgeFlags(); 476 477 mReceivedPointersDown |= pointerFlag; 478 mReceivedPointers[pointerId].set( 479 event.getX(pointerIndex), event.getY(pointerIndex), event.getEventTime()); 480 481 mPrimaryPointerId = pointerId; 482 } 483 484 /** 485 * Handles a received pointer up event. 486 * 487 * @param pointerIndex The index of the pointer that has changed. 488 * @param event The event to be handled. 489 */ handleReceivedPointerUp(int pointerIndex, MotionEvent event)490 private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) { 491 final int pointerId = event.getPointerId(pointerIndex); 492 final int pointerFlag = (1 << pointerId); 493 mReceivedPointersDown &= ~pointerFlag; 494 mReceivedPointers[pointerId].clear(); 495 if (mPrimaryPointerId == pointerId) { 496 mPrimaryPointerId = INVALID_POINTER_ID; 497 } 498 } 499 500 /** @return The primary pointer id. */ findPrimaryPointerId()501 private int findPrimaryPointerId() { 502 int primaryPointerId = INVALID_POINTER_ID; 503 long minDownTime = Long.MAX_VALUE; 504 505 // Find the pointer that went down first. 506 int pointerIdBits = mReceivedPointersDown; 507 while (pointerIdBits > 0) { 508 final int pointerId = Integer.numberOfTrailingZeros(pointerIdBits); 509 pointerIdBits &= ~(1 << pointerId); 510 final long downPointerTime = mReceivedPointers[pointerId].mTime; 511 if (downPointerTime < minDownTime) { 512 minDownTime = downPointerTime; 513 primaryPointerId = pointerId; 514 } 515 } 516 return primaryPointerId; 517 } 518 519 @Override toString()520 public String toString() { 521 StringBuilder builder = new StringBuilder(); 522 builder.append("========================="); 523 builder.append("\nDown pointers #"); 524 builder.append(getReceivedPointerDownCount()); 525 builder.append(" [ "); 526 for (int i = 0; i < MAX_POINTER_COUNT; i++) { 527 if (isReceivedPointerDown(i)) { 528 builder.append(i); 529 builder.append(" "); 530 } 531 } 532 builder.append("]"); 533 builder.append("\nPrimary pointer id [ "); 534 builder.append(getPrimaryPointerId()); 535 builder.append(" ]"); 536 builder.append("\n========================="); 537 return builder.toString(); 538 } 539 } 540 541 /** 542 * This class tracks where and when an individual pointer went down. Note that it does not track 543 * when it went up. 544 */ 545 class PointerDownInfo { 546 private float mX; 547 private float mY; 548 private long mTime; 549 set(float x, float y, long time)550 public void set(float x, float y, long time) { 551 mX = x; 552 mY = y; 553 mTime = time; 554 } 555 clear()556 public void clear() { 557 mX = 0; 558 mY = 0; 559 mTime = 0; 560 } 561 } 562 } 563