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