Skip to content

Commit f068fed

Browse files
committed
Accessibility HOVER_ENTER / EXIT without enclosing EXPLORATION_GESTURE_START / END
1. The initial implementation was not sending the gesture start and end events until the the user has moved more than a given slop and did not do it faster than a given velocity. However, there is the case where if the user did not move or just taped on the screen an exploration occurs. The system was not sending the exploration start and end events for the latter case. 2. The delaued command for long press was not canceled when the pointer moves more than the slop distance. bug:7282811 Change-Id: I7d98470cd4d9ea9b2519326e5e550ff68b040747
1 parent 26884df commit f068fed

File tree

1 file changed

+103
-78
lines changed

1 file changed

+103
-78
lines changed

services/java/com/android/server/accessibility/TouchExplorer.java

Lines changed: 103 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,6 @@ class TouchExplorer implements EventStreamTransformation {
102102
// The timeout after which we are no longer trying to detect a gesture.
103103
private static final int EXIT_GESTURE_DETECTION_TIMEOUT = 2000;
104104

105-
// The timeout to send interaction end events in case we did not
106-
// receive the expected hover exit event due to a misbehaving app.
107-
private static final int SEND_INTERACTION_END_EVENTS_TIMEOUT = 200;
108-
109105
// Temporary array for storing pointer IDs.
110106
private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT];
111107

@@ -139,8 +135,11 @@ class TouchExplorer implements EventStreamTransformation {
139135
// Command for delayed sending of a hover exit event.
140136
private final SendHoverDelayed mSendHoverExitDelayed;
141137

142-
// Command for delayed sending of interaction ending events.
143-
private final SendInteractionEndEventsDelayed mSendInteractionEndEventsDelayed;
138+
// Command for delayed sending of touch exploration end events.
139+
private final SendAccessibilityEventDelayed mSendTouchExplorationEndDelayed;
140+
141+
// Command for delayed sending of touch interaction end events.
142+
private final SendAccessibilityEventDelayed mSendTouchInteractionEndDelayed;
144143

145144
// Command for delayed sending of a long press.
146145
private final PerformLongPressDelayed mPerformLongPressDelayed;
@@ -209,11 +208,8 @@ class TouchExplorer implements EventStreamTransformation {
209208
// The id of the last touch explored window.
210209
private int mLastTouchedWindowId;
211210

212-
// Whether touch exploration gesture has ended.
213-
private boolean mTouchExplorationGestureEnded;
214-
215-
// Whether touch interaction has ended.
216-
private boolean mTouchInteractionEnded;
211+
// Whether touch exploration is in progress.
212+
private boolean mTouchExplorationInProgress;
217213

218214
/**
219215
* Creates a new instance.
@@ -240,7 +236,12 @@ public TouchExplorer(Context context, AccessibilityManagerService service) {
240236
mGestureLibrary.load();
241237
mSendHoverEnterDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_ENTER, true);
242238
mSendHoverExitDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_EXIT, false);
243-
mSendInteractionEndEventsDelayed = new SendInteractionEndEventsDelayed();
239+
mSendTouchExplorationEndDelayed = new SendAccessibilityEventDelayed(
240+
AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END,
241+
mDetermineUserIntentTimeout);
242+
mSendTouchInteractionEndDelayed = new SendAccessibilityEventDelayed(
243+
AccessibilityEvent.TYPE_TOUCH_INTERACTION_END,
244+
mDetermineUserIntentTimeout);
244245
mDoubleTapDetector = new DoubleTapDetector();
245246
final float density = context.getResources().getDisplayMetrics().density;
246247
mScaledMinPointerDistanceToUseMiddleLocation =
@@ -265,7 +266,7 @@ private void clear(MotionEvent event, int policyFlags) {
265266
switch (mCurrentState) {
266267
case STATE_TOUCH_EXPLORING: {
267268
// If a touch exploration gesture is in progress send events for its end.
268-
sendExitEventsIfNeeded(policyFlags);
269+
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
269270
} break;
270271
case STATE_DRAGGING: {
271272
mDraggingPointerId = INVALID_POINTER_ID;
@@ -286,7 +287,8 @@ private void clear(MotionEvent event, int policyFlags) {
286287
mSendHoverExitDelayed.remove();
287288
mPerformLongPressDelayed.remove();
288289
mExitGestureDetectionModeDelayed.remove();
289-
mSendInteractionEndEventsDelayed.remove();
290+
mSendTouchExplorationEndDelayed.remove();
291+
mSendTouchInteractionEndDelayed.remove();
290292
// Reset the pointer trackers.
291293
mReceivedPointerTracker.clear();
292294
mInjectedPointerTracker.clear();
@@ -301,6 +303,7 @@ private void clear(MotionEvent event, int policyFlags) {
301303
if (mNext != null) {
302304
mNext.clear();
303305
}
306+
mTouchExplorationInProgress = false;
304307
}
305308

306309
@Override
@@ -341,19 +344,17 @@ public void onAccessibilityEvent(AccessibilityEvent event) {
341344

342345
// The event for gesture end should be strictly after the
343346
// last hover exit event.
344-
if (mTouchExplorationGestureEnded
347+
if (mSendTouchExplorationEndDelayed.isPending()
345348
&& eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
346-
mSendInteractionEndEventsDelayed.remove();
347-
mTouchExplorationGestureEnded = false;
349+
mSendTouchExplorationEndDelayed.remove();
348350
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
349351
}
350352

351353
// The event for touch interaction end should be strictly after the
352354
// last hover exit and the touch exploration gesture end events.
353-
if (mTouchInteractionEnded
355+
if (mSendTouchInteractionEndDelayed.isPending()
354356
&& eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
355-
mSendInteractionEndEventsDelayed.remove();
356-
mTouchInteractionEnded = false;
357+
mSendTouchInteractionEndDelayed.remove();
357358
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
358359
}
359360

@@ -396,15 +397,6 @@ private void handleMotionEventStateTouchExploring(MotionEvent event, MotionEvent
396397

397398
switch (event.getActionMasked()) {
398399
case MotionEvent.ACTION_DOWN:
399-
// The delayed enter not delivered implies that we have delivered
400-
// TYPE_TOUCH_INTERACTION_START and not TYPE_TOUCH_INTERACTION_END,
401-
// therefore we need to deliver the interaction end event here.
402-
if (mSendHoverEnterDelayed.isPending()) {
403-
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
404-
}
405-
// Announce the start of a new touch interaction.
406-
sendAccessibilityEvent(
407-
AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
408400
// Pre-feed the motion events to the gesture detector since we
409401
// have a distance slop before getting into gesture detection
410402
// mode and not using the points within this slop significantly
@@ -426,8 +418,20 @@ private void handleMotionEventStateTouchExploring(MotionEvent event, MotionEvent
426418
mSendHoverExitDelayed.remove();
427419
}
428420

429-
if (mSendInteractionEndEventsDelayed.isPending()) {
430-
mSendInteractionEndEventsDelayed.forceSendAndRemove();
421+
if (mSendTouchExplorationEndDelayed.isPending()) {
422+
mSendTouchExplorationEndDelayed.forceSendAndRemove();
423+
}
424+
425+
if (mSendTouchInteractionEndDelayed.isPending()) {
426+
mSendTouchInteractionEndDelayed.forceSendAndRemove();
427+
}
428+
429+
// Every pointer that goes down is active until it moves or
430+
// another one goes down. Hence, having more than one pointer
431+
// down we have already send the interaction start event.
432+
if (event.getPointerCount() == 1) {
433+
sendAccessibilityEvent(
434+
AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
431435
}
432436

433437
mPerformLongPressDelayed.remove();
@@ -443,11 +447,13 @@ private void handleMotionEventStateTouchExploring(MotionEvent event, MotionEvent
443447
mPerformLongPressDelayed.post(event, policyFlags);
444448
break;
445449
}
446-
// Deliver hover enter with a delay to have a chance
447-
// to detect what the user is trying to do.
448-
final int pointerId = receivedTracker.getPrimaryActivePointerId();
449-
final int pointerIdBits = (1 << pointerId);
450-
mSendHoverEnterDelayed.post(event, true, pointerIdBits, policyFlags);
450+
if (!mTouchExplorationInProgress) {
451+
// Deliver hover enter with a delay to have a chance
452+
// to detect what the user is trying to do.
453+
final int pointerId = receivedTracker.getPrimaryActivePointerId();
454+
final int pointerIdBits = (1 << pointerId);
455+
mSendHoverEnterDelayed.post(event, true, pointerIdBits, policyFlags);
456+
}
451457
} break;
452458
default: {
453459
/* do nothing - let the code for ACTION_MOVE decide what to do */
@@ -512,12 +518,27 @@ private void handleMotionEventStateTouchExploring(MotionEvent event, MotionEvent
512518
break;
513519
}
514520
} else {
521+
// Cancel the long press if pending and the user
522+
// moved more than the slop.
523+
if (mPerformLongPressDelayed.isPending()) {
524+
final float deltaX =
525+
receivedTracker.getReceivedPointerDownX(pointerId)
526+
- rawEvent.getX(pointerIndex);
527+
final float deltaY =
528+
receivedTracker.getReceivedPointerDownY(pointerId)
529+
- rawEvent.getY(pointerIndex);
530+
final double moveDelta = Math.hypot(deltaX, deltaY);
531+
// The user has moved enough for us to decide.
532+
if (moveDelta > mTouchSlop) {
533+
mPerformLongPressDelayed.remove();
534+
}
535+
}
515536
// The user is wither double tapping or performing long
516537
// press so do not send move events yet.
517538
if (mDoubleTapDetector.firstTapDetected()) {
518539
break;
519540
}
520-
sendEnterEventsIfNeeded(policyFlags);
541+
sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
521542
sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
522543
policyFlags);
523544
}
@@ -548,7 +569,7 @@ private void handleMotionEventStateTouchExploring(MotionEvent event, MotionEvent
548569
}
549570
// We are sending events so send exit and gesture
550571
// end since we transition to another state.
551-
sendExitEventsIfNeeded(policyFlags);
572+
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
552573
}
553574

554575
// We know that a new state transition is to happen and the
@@ -583,7 +604,7 @@ private void handleMotionEventStateTouchExploring(MotionEvent event, MotionEvent
583604
mPerformLongPressDelayed.remove();
584605
// We are sending events so send exit and gesture
585606
// end since we transition to another state.
586-
sendExitEventsIfNeeded(policyFlags);
607+
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
587608
}
588609

589610
// More than two pointers are delegated to the view hierarchy.
@@ -612,11 +633,14 @@ private void handleMotionEventStateTouchExploring(MotionEvent event, MotionEvent
612633

613634
// If we have not delivered the enter schedule exit.
614635
if (mSendHoverEnterDelayed.isPending()) {
615-
mSendHoverEnterDelayed.mTouchExplorationInProgress = false;
616636
mSendHoverExitDelayed.post(event, false, pointerIdBits, policyFlags);
617637
} else {
618638
// The user is touch exploring so we send events for end.
619-
sendExitEventsIfNeeded(policyFlags);
639+
sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
640+
}
641+
642+
if (!mSendTouchInteractionEndDelayed.isPending()) {
643+
mSendTouchInteractionEndDelayed.post();
620644
}
621645
} break;
622646
}
@@ -844,6 +868,14 @@ private void sendAccessibilityEvent(int type) {
844868
if (accessibilityManager.isEnabled()) {
845869
AccessibilityEvent event = AccessibilityEvent.obtain(type);
846870
accessibilityManager.sendAccessibilityEvent(event);
871+
switch (type) {
872+
case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: {
873+
mTouchExplorationInProgress = true;
874+
} break;
875+
case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: {
876+
mTouchExplorationInProgress = false;
877+
} break;
878+
}
847879
}
848880
}
849881

@@ -891,14 +923,12 @@ private void sendDownForAllActiveNotInjectedPointers(MotionEvent prototype, int
891923
*
892924
* @param policyFlags The policy flags associated with the event.
893925
*/
894-
private void sendExitEventsIfNeeded(int policyFlags) {
926+
private void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) {
895927
MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
896928
if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
897929
final int pointerIdBits = event.getPointerIdBits();
898-
mTouchExplorationGestureEnded = true;
899-
mTouchInteractionEnded = true;
900-
if (!mSendInteractionEndEventsDelayed.isPending()) {
901-
mSendInteractionEndEventsDelayed.post();
930+
if (!mSendTouchExplorationEndDelayed.isPending()) {
931+
mSendTouchExplorationEndDelayed.post();
902932
}
903933
sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
904934
}
@@ -910,10 +940,11 @@ private void sendExitEventsIfNeeded(int policyFlags) {
910940
*
911941
* @param policyFlags The policy flags associated with the event.
912942
*/
913-
private void sendEnterEventsIfNeeded(int policyFlags) {
943+
private void sendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags) {
914944
MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
915945
if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
916946
final int pointerIdBits = event.getPointerIdBits();
947+
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
917948
sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
918949
}
919950
}
@@ -1179,8 +1210,12 @@ public void onDoubleTap(MotionEvent secondTapUp, int policyFlags) {
11791210
mSendHoverExitDelayed.remove();
11801211
mPerformLongPressDelayed.remove();
11811212

1182-
// The touch interaction has ended since we will send a click.
1183-
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
1213+
if (mSendTouchExplorationEndDelayed.isPending()) {
1214+
mSendTouchExplorationEndDelayed.forceSendAndRemove();
1215+
}
1216+
if (mSendTouchInteractionEndDelayed.isPending()) {
1217+
mSendTouchInteractionEndDelayed.forceSendAndRemove();
1218+
}
11841219

11851220
int clickLocationX;
11861221
int clickLocationY;
@@ -1414,7 +1449,7 @@ public void run() {
14141449
mLongPressingPointerDeltaX = (int) mEvent.getX(pointerIndex) - clickLocationX;
14151450
mLongPressingPointerDeltaY = (int) mEvent.getY(pointerIndex) - clickLocationY;
14161451

1417-
sendExitEventsIfNeeded(mPolicyFlags);
1452+
sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags);
14181453

14191454
mCurrentState = STATE_DELEGATING;
14201455
sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags);
@@ -1443,7 +1478,6 @@ class SendHoverDelayed implements Runnable {
14431478
private MotionEvent mPrototype;
14441479
private int mPointerIdBits;
14451480
private int mPolicyFlags;
1446-
private boolean mTouchExplorationInProgress;
14471481

14481482
public SendHoverDelayed(int hoverAction, boolean gestureStarted) {
14491483
mHoverAction = hoverAction;
@@ -1454,7 +1488,6 @@ public void post(MotionEvent prototype, boolean touchExplorationInProgress,
14541488
int pointerIdBits, int policyFlags) {
14551489
remove();
14561490
mPrototype = MotionEvent.obtain(prototype);
1457-
mTouchExplorationInProgress = touchExplorationInProgress;
14581491
mPointerIdBits = pointerIdBits;
14591492
mPolicyFlags = policyFlags;
14601493
mHandler.postDelayed(this, mDetermineUserIntentTimeout);
@@ -1491,7 +1524,6 @@ private void clear() {
14911524
mPrototype = null;
14921525
mPointerIdBits = -1;
14931526
mPolicyFlags = 0;
1494-
mTouchExplorationInProgress = false;
14951527
}
14961528

14971529
public void forceSendAndRemove() {
@@ -1508,37 +1540,37 @@ public void run() {
15081540
Slog.d(LOG_TAG_SEND_HOVER_DELAYED, mGestureStarted ?
15091541
"touchExplorationGestureStarted" : "touchExplorationGestureEnded");
15101542
}
1511-
if (mTouchExplorationInProgress) {
1512-
if (mGestureStarted) {
1513-
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
1514-
} else {
1515-
mTouchExplorationGestureEnded = true;
1516-
mTouchInteractionEnded = true;
1517-
if (!mSendInteractionEndEventsDelayed.isPending()) {
1518-
mSendInteractionEndEventsDelayed.post();
1519-
}
1520-
}
1543+
if (mGestureStarted) {
1544+
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
15211545
} else {
1522-
if (!mGestureStarted) {
1523-
mTouchInteractionEnded = true;
1524-
if (!mSendInteractionEndEventsDelayed.isPending()) {
1525-
mSendInteractionEndEventsDelayed.post();
1526-
}
1546+
if (!mSendTouchExplorationEndDelayed.isPending()) {
1547+
mSendTouchExplorationEndDelayed.post();
1548+
}
1549+
if (mSendTouchInteractionEndDelayed.isPending()) {
1550+
mSendTouchInteractionEndDelayed.remove();
1551+
mSendTouchInteractionEndDelayed.post();
15271552
}
15281553
}
15291554
sendMotionEvent(mPrototype, mHoverAction, mPointerIdBits, mPolicyFlags);
15301555
clear();
15311556
}
15321557
}
15331558

1534-
private class SendInteractionEndEventsDelayed implements Runnable {
1559+
private class SendAccessibilityEventDelayed implements Runnable {
1560+
private final int mEventType;
1561+
private final int mDelay;
1562+
1563+
public SendAccessibilityEventDelayed(int eventType, int delay) {
1564+
mEventType = eventType;
1565+
mDelay = delay;
1566+
}
15351567

15361568
public void remove() {
15371569
mHandler.removeCallbacks(this);
15381570
}
15391571

15401572
public void post() {
1541-
mHandler.postDelayed(this, SEND_INTERACTION_END_EVENTS_TIMEOUT);
1573+
mHandler.postDelayed(this, mDelay);
15421574
}
15431575

15441576
public boolean isPending() {
@@ -1554,14 +1586,7 @@ public void forceSendAndRemove() {
15541586

15551587
@Override
15561588
public void run() {
1557-
if (mTouchExplorationGestureEnded) {
1558-
mTouchExplorationGestureEnded = false;
1559-
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
1560-
}
1561-
if (mTouchInteractionEnded) {
1562-
mTouchInteractionEnded = false;
1563-
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
1564-
}
1589+
sendAccessibilityEvent(mEventType);
15651590
}
15661591
}
15671592

0 commit comments

Comments
 (0)