@@ -102,6 +102,10 @@ 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+
105109 // Temporary array for storing pointer IDs.
106110 private final int [] mTempPointerIds = new int [MAX_POINTER_COUNT ];
107111
@@ -135,6 +139,9 @@ class TouchExplorer implements EventStreamTransformation {
135139 // Command for delayed sending of a hover exit event.
136140 private final SendHoverDelayed mSendHoverExitDelayed ;
137141
142+ // Command for delayed sending of interaction ending events.
143+ private final SendInteractionEndEventsDelayed mSendInteractionEndEventsDelayed ;
144+
138145 // Command for delayed sending of a long press.
139146 private final PerformLongPressDelayed mPerformLongPressDelayed ;
140147
@@ -233,6 +240,7 @@ public TouchExplorer(Context context, AccessibilityManagerService service) {
233240 mGestureLibrary .load ();
234241 mSendHoverEnterDelayed = new SendHoverDelayed (MotionEvent .ACTION_HOVER_ENTER , true );
235242 mSendHoverExitDelayed = new SendHoverDelayed (MotionEvent .ACTION_HOVER_EXIT , false );
243+ mSendInteractionEndEventsDelayed = new SendInteractionEndEventsDelayed ();
236244 mDoubleTapDetector = new DoubleTapDetector ();
237245 final float density = context .getResources ().getDisplayMetrics ().density ;
238246 mScaledMinPointerDistanceToUseMiddleLocation =
@@ -278,6 +286,7 @@ private void clear(MotionEvent event, int policyFlags) {
278286 mSendHoverExitDelayed .remove ();
279287 mPerformLongPressDelayed .remove ();
280288 mExitGestureDetectionModeDelayed .remove ();
289+ mSendInteractionEndEventsDelayed .remove ();
281290 // Reset the pointer trackers.
282291 mReceivedPointerTracker .clear ();
283292 mInjectedPointerTracker .clear ();
@@ -334,6 +343,7 @@ public void onAccessibilityEvent(AccessibilityEvent event) {
334343 // last hover exit event.
335344 if (mTouchExplorationGestureEnded
336345 && eventType == AccessibilityEvent .TYPE_VIEW_HOVER_EXIT ) {
346+ mSendInteractionEndEventsDelayed .remove ();
337347 mTouchExplorationGestureEnded = false ;
338348 sendAccessibilityEvent (AccessibilityEvent .TYPE_TOUCH_EXPLORATION_GESTURE_END );
339349 }
@@ -342,6 +352,7 @@ public void onAccessibilityEvent(AccessibilityEvent event) {
342352 // last hover exit and the touch exploration gesture end events.
343353 if (mTouchInteractionEnded
344354 && eventType == AccessibilityEvent .TYPE_VIEW_HOVER_EXIT ) {
355+ mSendInteractionEndEventsDelayed .remove ();
345356 mTouchInteractionEnded = false ;
346357 sendAccessibilityEvent (AccessibilityEvent .TYPE_TOUCH_INTERACTION_END );
347358 }
@@ -416,6 +427,10 @@ private void handleMotionEventStateTouchExploring(MotionEvent event, int policyF
416427 mSendHoverExitDelayed .remove ();
417428 }
418429
430+ if (mSendInteractionEndEventsDelayed .isPending ()) {
431+ mSendInteractionEndEventsDelayed .forceSendAndRemove ();
432+ }
433+
419434 mPerformLongPressDelayed .remove ();
420435
421436 // If we have the first tap schedule a long press and break
@@ -873,6 +888,9 @@ private void sendExitEventsIfNeeded(int policyFlags) {
873888 final int pointerIdBits = event .getPointerIdBits ();
874889 mTouchExplorationGestureEnded = true ;
875890 mTouchInteractionEnded = true ;
891+ if (!mSendInteractionEndEventsDelayed .isPending ()) {
892+ mSendInteractionEndEventsDelayed .post ();
893+ }
876894 sendMotionEvent (event , MotionEvent .ACTION_HOVER_EXIT , pointerIdBits , policyFlags );
877895 }
878896 }
@@ -1484,17 +1502,57 @@ public void run() {
14841502 } else {
14851503 mTouchExplorationGestureEnded = true ;
14861504 mTouchInteractionEnded = true ;
1505+ if (!mSendInteractionEndEventsDelayed .isPending ()) {
1506+ mSendInteractionEndEventsDelayed .post ();
1507+ }
14871508 }
14881509 } else {
14891510 if (!mGestureStarted ) {
14901511 mTouchInteractionEnded = true ;
1512+ if (!mSendInteractionEndEventsDelayed .isPending ()) {
1513+ mSendInteractionEndEventsDelayed .post ();
1514+ }
14911515 }
14921516 }
14931517 sendMotionEvent (mPrototype , mHoverAction , mPointerIdBits , mPolicyFlags );
14941518 clear ();
14951519 }
14961520 }
14971521
1522+ private class SendInteractionEndEventsDelayed implements Runnable {
1523+
1524+ public void remove () {
1525+ mHandler .removeCallbacks (this );
1526+ }
1527+
1528+ public void post () {
1529+ mHandler .postDelayed (this , SEND_INTERACTION_END_EVENTS_TIMEOUT );
1530+ }
1531+
1532+ public boolean isPending () {
1533+ return mHandler .hasCallbacks (this );
1534+ }
1535+
1536+ public void forceSendAndRemove () {
1537+ if (isPending ()) {
1538+ run ();
1539+ remove ();
1540+ }
1541+ }
1542+
1543+ @ Override
1544+ public void run () {
1545+ if (mTouchExplorationGestureEnded ) {
1546+ mTouchExplorationGestureEnded = false ;
1547+ sendAccessibilityEvent (AccessibilityEvent .TYPE_TOUCH_EXPLORATION_GESTURE_END );
1548+ }
1549+ if (mTouchInteractionEnded ) {
1550+ mTouchInteractionEnded = false ;
1551+ sendAccessibilityEvent (AccessibilityEvent .TYPE_TOUCH_INTERACTION_END );
1552+ }
1553+ }
1554+ }
1555+
14981556 @ Override
14991557 public String toString () {
15001558 return LOG_TAG ;
0 commit comments