Loading services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +29 −4 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_HOVER_EXIT import static com.android.server.accessibility.gestures.TouchState.ALL_POINTER_ID_BITS; import android.accessibilityservice.AccessibilityGestureEvent; import android.annotation.NonNull; import android.content.Context; import android.graphics.Point; import android.graphics.Region; Loading @@ -48,6 +49,7 @@ import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.BaseEventStreamTransformation; import com.android.server.accessibility.EventStreamTransformation; Loading Loading @@ -162,6 +164,12 @@ public class TouchExplorer extends BaseEventStreamTransformation */ public TouchExplorer( Context context, AccessibilityManagerService service, GestureManifold detector) { this(context, service, detector, new Handler(context.getMainLooper())); } @VisibleForTesting TouchExplorer(Context context, AccessibilityManagerService service, GestureManifold detector, @NonNull Handler mainHandler) { mContext = context; mAms = service; mState = new TouchState(); Loading @@ -169,7 +177,7 @@ public class TouchExplorer extends BaseEventStreamTransformation mDispatcher = new EventDispatcher(context, mAms, super.getNext(), mState); mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout(); mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); mHandler = new Handler(context.getMainLooper()); mHandler = mainHandler; mExitGestureDetectionModeDelayed = new ExitGestureDetectionModeDelayed(); mSendHoverEnterAndMoveDelayed = new SendHoverEnterAndMoveDelayed(); mSendHoverExitDelayed = new SendHoverExitDelayed(); Loading Loading @@ -290,20 +298,37 @@ public class TouchExplorer extends BaseEventStreamTransformation public void onAccessibilityEvent(AccessibilityEvent event) { final int eventType = event.getEventType(); if (eventType == TYPE_VIEW_HOVER_EXIT) { sendsPendingA11yEventsIfNeeded(); } super.onAccessibilityEvent(event); } /* * Sends pending {@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END} or {@{@link * AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END}} after receiving last hover exit * event. */ private void sendsPendingA11yEventsIfNeeded() { // The last hover exit A11y event should be sent by view after receiving hover exit motion // event. In some view hierarchy, the ViewGroup transforms hover move motion event to hover // exit motion event and than dispatch to itself. It causes unexpected A11y exit events. if (mSendHoverExitDelayed.isPending()) { return; } // The event for gesture end should be strictly after the // last hover exit event. if (mSendTouchExplorationEndDelayed.isPending() && eventType == TYPE_VIEW_HOVER_EXIT) { if (mSendTouchExplorationEndDelayed.isPending()) { mSendTouchExplorationEndDelayed.cancel(); mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END); } // The event for touch interaction end should be strictly after the // last hover exit and the touch exploration gesture end events. if (mSendTouchInteractionEndDelayed.isPending() && eventType == TYPE_VIEW_HOVER_EXIT) { if (mSendTouchInteractionEndDelayed.isPending()) { mSendTouchInteractionEndDelayed.cancel(); mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_INTERACTION_END); } super.onAccessibilityEvent(event); } @Override Loading services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java +128 −15 Original line number Diff line number Diff line Loading @@ -24,29 +24,32 @@ import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_POINTER_DOWN; import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; import static android.view.ViewConfiguration.getDoubleTapTimeout; import static com.android.server.accessibility.gestures.TouchState.STATE_CLEAR; import static com.android.server.accessibility.gestures.TouchState.STATE_DELEGATING; import static com.android.server.accessibility.gestures.TouchState.STATE_DRAGGING; import static com.android.server.accessibility.gestures.TouchState.STATE_GESTURE_DETECTING; import static com.android.server.accessibility.gestures.TouchState.STATE_TOUCH_EXPLORING; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import android.content.Context; import android.graphics.PointF; import android.os.Looper; import android.os.SystemClock; import android.testing.DexmakerShareClassLoaderRule; import android.view.InputDevice; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.EventStreamTransformation; import com.android.server.testutils.OffsettableClock; import org.junit.Before; import org.junit.Rule; Loading @@ -61,6 +64,8 @@ import java.util.List; public class TouchExplorerTest { private static final String LOG_TAG = "TouchExplorerTest"; // The constant of mDetermineUserIntentTimeout. private static final int USER_INTENT_TIMEOUT = getDoubleTapTimeout(); private static final int FLAG_1FINGER = 0x8000; private static final int FLAG_2FINGERS = 0x0100; private static final int FLAG_3FINGERS = 0x0200; Loading @@ -80,6 +85,7 @@ public class TouchExplorerTest { private EventStreamTransformation mCaptor; private MotionEvent mLastEvent; private TestHandler mHandler; private TouchExplorer mTouchExplorer; private long mLastDownTime = Integer.MIN_VALUE; Loading Loading @@ -112,26 +118,28 @@ public class TouchExplorerTest { @Before public void setUp() { if (Looper.myLooper() == null) { Looper.prepare(); } Context context = InstrumentationRegistry.getContext(); AccessibilityManagerService ams = new AccessibilityManagerService(context); GestureManifold detector = mock(GestureManifold.class); mCaptor = new EventCaptor(); mTouchExplorer = new TouchExplorer(context, ams, detector); mHandler = new TestHandler(); mTouchExplorer = new TouchExplorer(context, ams, detector, mHandler); mTouchExplorer.setNext(mCaptor); } @Test public void testOneFingerMove_shouldInjectHoverEvents() { goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER); try { Thread.sleep(2 * ViewConfiguration.getDoubleTapTimeout()); } catch (InterruptedException e) { fail("Interrupted while waiting for transition to touch exploring state."); } // Wait for transiting to touch exploring state. mHandler.fastForward(2 * USER_INTENT_TIMEOUT); moveEachPointers(mLastEvent, p(10, 10)); send(mLastEvent); goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER); assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT); assertState(STATE_TOUCH_EXPLORING); } /** Loading @@ -144,11 +152,8 @@ public class TouchExplorerTest { // Inject a set of move events that have the same coordinates as the down event. moveEachPointers(mLastEvent, p(0, 0)); send(mLastEvent); try { Thread.sleep(2 * ViewConfiguration.getDoubleTapTimeout()); } catch (InterruptedException e) { fail("Interrupted while waiting for transition to touch exploring state."); } // Wait for transition to touch exploring state. mHandler.fastForward(2 * USER_INTENT_TIMEOUT); // Now move for real. moveEachPointers(mLastEvent, p(10, 10)); send(mLastEvent); Loading Loading @@ -181,6 +186,66 @@ public class TouchExplorerTest { assertCapturedEvents(ACTION_DOWN, ACTION_MOVE, ACTION_MOVE, ACTION_UP); } @Test public void testUpEvent_OneFingerMove_clearStateAndInjectHoverEvents() { goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER); moveEachPointers(mLastEvent, p(10, 10)); send(mLastEvent); send(upEvent()); // Wait for sending hover exit event to transit to clear state. mHandler.fastForward(USER_INTENT_TIMEOUT); assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT); assertState(STATE_CLEAR); } /* * The gesture should be completed in USER_INTENT_TIMEOUT duration otherwise the A11y * touch-exploration end event runnable will be scheduled after receiving the up event. * The distance between start and end point is shorter than the minimum swipe distance. * Note that the delayed time of each runnable is USER_INTENT_TIMEOUT. */ @Test public void testFlickCrossViews_clearStateAndExpectedEvents() { final int oneThirdUserIntentTimeout = USER_INTENT_TIMEOUT / 3; // Touch the first view. send(downEvent()); // Wait for the finger moving to the second view. mHandler.fastForward(oneThirdUserIntentTimeout); moveEachPointers(mLastEvent, p(10, 10)); send(mLastEvent); // Wait for the finger lifting from the second view. mHandler.fastForward(oneThirdUserIntentTimeout); // Now there are three delayed Runnables, hover enter/move runnable, hover exit motion event // runnable and a11y interaction end event runnable. The last two runnables are scheduled // after sending the up event. send(upEvent()); // Wait for running hover enter/move runnable. The runnable is scheduled when sending // the down event. mHandler.fastForward(oneThirdUserIntentTimeout); // Wait for the views responding to hover enter/move events. mHandler.fastForward(oneThirdUserIntentTimeout); // Simulate receiving the a11y exit event sent by the first view. AccessibilityEvent a11yExitEvent = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); mTouchExplorer.onAccessibilityEvent(a11yExitEvent); // Wait for running the hover exit event runnable. After it, touch-exploration end event // runnable will be scheduled. mHandler.fastForward(oneThirdUserIntentTimeout); // Wait for the second views responding to hover exit events. mHandler.fastForward(oneThirdUserIntentTimeout); // Simulate receiving the a11y exit event sent by the second view. mTouchExplorer.onAccessibilityEvent(a11yExitEvent); assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT); assertState(STATE_CLEAR); } @Test public void testTwoFingersMove_shouldDelegatingAndInjectActionDownPointerDown() { goFromStateClearTo(STATE_MOVING_2FINGERS); Loading Loading @@ -307,7 +372,7 @@ public class TouchExplorerTest { } } catch (Throwable t) { throw new RuntimeException( "Failed to go to state " + TouchState.getStateSymbolicName(state), t); "Failed to go to state " + stateToString(state), t); } } Loading Loading @@ -337,7 +402,7 @@ public class TouchExplorerTest { } } catch (Throwable t) { throw new RuntimeException( "Failed to go to state " + TouchState.getStateSymbolicName(state), t); "Failed to return to clear from state " + stateToString(state), t); } } Loading Loading @@ -476,4 +541,52 @@ public class TouchExplorerTest { /* source */ InputDevice.SOURCE_TOUCHSCREEN, /* flags */ 0); } private static String stateToString(int state) { if (state <= STATE_GESTURE_DETECTING /* maximum value of touch state */) { return TouchState.getStateSymbolicName(state); } switch (state) { case STATE_TOUCH_EXPLORING_1FINGER: return "STATE_TOUCH_EXPLORING_1FINGER"; case STATE_TOUCH_EXPLORING_2FINGER: return "STATE_TOUCH_EXPLORING_2FINGER"; case STATE_TOUCH_EXPLORING_3FINGER: return "STATE_TOUCH_EXPLORING_3FINGER"; case STATE_MOVING_2FINGERS: return "STATE_MOVING_2FINGERS"; case STATE_MOVING_3FINGERS: return "STATE_MOVING_3FINGERS"; case STATE_DRAGGING_2FINGERS: return "STATE_DRAGGING_2FINGERS"; case STATE_PINCH_2FINGERS: return "STATE_PINCH_2FINGERS"; default: return "stateToString -- Unknown state: " + Integer.toHexString(state); } } /** * A {@link android.os.Handler} that doesn't process messages until {@link * #fastForward(int)} is invoked. * * @see com.android.server.testutils.TestHandler */ private class TestHandler extends com.android.server.testutils.TestHandler { private final OffsettableClock mClock; TestHandler() { this(null, new OffsettableClock.Stopped()); } TestHandler(Callback callback, OffsettableClock clock) { super(Looper.myLooper(), callback, clock); mClock = clock; } void fastForward(int ms) { mClock.fastForward(ms); timeAdvance(); } } } services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java +17 −7 Original line number Diff line number Diff line Loading @@ -55,18 +55,24 @@ public class TestHandler extends Handler { // Boxing is ok here - both msg ids and their pending counts tend to be well below 128 private final Map<Integer, Integer> mPendingMsgTypeCounts = new ArrayMap<>(); private final LongSupplier mClock; private int mMessageCount = 0; public TestHandler(Callback callback) { this(callback, DEFAULT_CLOCK); } public TestHandler(Callback callback, LongSupplier clock) { super(Looper.getMainLooper(), callback); this(Looper.getMainLooper(), callback, clock); } public TestHandler(Looper looper, Callback callback, LongSupplier clock) { super(looper, callback); mClock = clock; } @Override public boolean sendMessageAtTime(Message msg, long uptimeMillis) { ++mMessageCount; mPendingMsgTypeCounts.put(msg.what, mPendingMsgTypeCounts.getOrDefault(msg.what, 0) + 1); Loading @@ -78,7 +84,7 @@ public class TestHandler extends Handler { // post a dummy queue entry to keep track of message removal return super.sendMessageAtTime(msg, Long.MAX_VALUE) && mMessages.add(new MsgInfo(Message.obtain(msg), uptimeMillis)); && mMessages.add(new MsgInfo(Message.obtain(msg), uptimeMillis, mMessageCount)); } /** @see TestHandler */ Loading Loading @@ -142,25 +148,29 @@ public class TestHandler extends Handler { public class MsgInfo implements Comparable<MsgInfo> { public final Message message; public final long sendTime; public final int mMessageOrder; public final RuntimeException postPoint; private MsgInfo(Message message, long sendTime) { private MsgInfo(Message message, long sendTime, int messageOrder) { this.message = message; this.sendTime = sendTime; this.postPoint = new RuntimeException("Message originated from here:"); mMessageOrder = messageOrder; } @Override public int compareTo(MsgInfo o) { return Long.compare(sendTime, o.sendTime); final int result = Long.compare(sendTime, o.sendTime); return result != 0 ? result : Integer.compare(mMessageOrder, o.mMessageOrder); } @Override public String toString() { return "MsgInfo{" + "message=" + messageToString(message) + ", sendTime=" + sendTime + '}'; "message =" + messageToString(message) + ", sendTime =" + sendTime + ", mMessageOrder =" + mMessageOrder + '}'; } } } Loading
services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +29 −4 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_HOVER_EXIT import static com.android.server.accessibility.gestures.TouchState.ALL_POINTER_ID_BITS; import android.accessibilityservice.AccessibilityGestureEvent; import android.annotation.NonNull; import android.content.Context; import android.graphics.Point; import android.graphics.Region; Loading @@ -48,6 +49,7 @@ import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.BaseEventStreamTransformation; import com.android.server.accessibility.EventStreamTransformation; Loading Loading @@ -162,6 +164,12 @@ public class TouchExplorer extends BaseEventStreamTransformation */ public TouchExplorer( Context context, AccessibilityManagerService service, GestureManifold detector) { this(context, service, detector, new Handler(context.getMainLooper())); } @VisibleForTesting TouchExplorer(Context context, AccessibilityManagerService service, GestureManifold detector, @NonNull Handler mainHandler) { mContext = context; mAms = service; mState = new TouchState(); Loading @@ -169,7 +177,7 @@ public class TouchExplorer extends BaseEventStreamTransformation mDispatcher = new EventDispatcher(context, mAms, super.getNext(), mState); mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout(); mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); mHandler = new Handler(context.getMainLooper()); mHandler = mainHandler; mExitGestureDetectionModeDelayed = new ExitGestureDetectionModeDelayed(); mSendHoverEnterAndMoveDelayed = new SendHoverEnterAndMoveDelayed(); mSendHoverExitDelayed = new SendHoverExitDelayed(); Loading Loading @@ -290,20 +298,37 @@ public class TouchExplorer extends BaseEventStreamTransformation public void onAccessibilityEvent(AccessibilityEvent event) { final int eventType = event.getEventType(); if (eventType == TYPE_VIEW_HOVER_EXIT) { sendsPendingA11yEventsIfNeeded(); } super.onAccessibilityEvent(event); } /* * Sends pending {@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END} or {@{@link * AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END}} after receiving last hover exit * event. */ private void sendsPendingA11yEventsIfNeeded() { // The last hover exit A11y event should be sent by view after receiving hover exit motion // event. In some view hierarchy, the ViewGroup transforms hover move motion event to hover // exit motion event and than dispatch to itself. It causes unexpected A11y exit events. if (mSendHoverExitDelayed.isPending()) { return; } // The event for gesture end should be strictly after the // last hover exit event. if (mSendTouchExplorationEndDelayed.isPending() && eventType == TYPE_VIEW_HOVER_EXIT) { if (mSendTouchExplorationEndDelayed.isPending()) { mSendTouchExplorationEndDelayed.cancel(); mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_END); } // The event for touch interaction end should be strictly after the // last hover exit and the touch exploration gesture end events. if (mSendTouchInteractionEndDelayed.isPending() && eventType == TYPE_VIEW_HOVER_EXIT) { if (mSendTouchInteractionEndDelayed.isPending()) { mSendTouchInteractionEndDelayed.cancel(); mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_INTERACTION_END); } super.onAccessibilityEvent(event); } @Override Loading
services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java +128 −15 Original line number Diff line number Diff line Loading @@ -24,29 +24,32 @@ import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_POINTER_DOWN; import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; import static android.view.ViewConfiguration.getDoubleTapTimeout; import static com.android.server.accessibility.gestures.TouchState.STATE_CLEAR; import static com.android.server.accessibility.gestures.TouchState.STATE_DELEGATING; import static com.android.server.accessibility.gestures.TouchState.STATE_DRAGGING; import static com.android.server.accessibility.gestures.TouchState.STATE_GESTURE_DETECTING; import static com.android.server.accessibility.gestures.TouchState.STATE_TOUCH_EXPLORING; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import android.content.Context; import android.graphics.PointF; import android.os.Looper; import android.os.SystemClock; import android.testing.DexmakerShareClassLoaderRule; import android.view.InputDevice; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.EventStreamTransformation; import com.android.server.testutils.OffsettableClock; import org.junit.Before; import org.junit.Rule; Loading @@ -61,6 +64,8 @@ import java.util.List; public class TouchExplorerTest { private static final String LOG_TAG = "TouchExplorerTest"; // The constant of mDetermineUserIntentTimeout. private static final int USER_INTENT_TIMEOUT = getDoubleTapTimeout(); private static final int FLAG_1FINGER = 0x8000; private static final int FLAG_2FINGERS = 0x0100; private static final int FLAG_3FINGERS = 0x0200; Loading @@ -80,6 +85,7 @@ public class TouchExplorerTest { private EventStreamTransformation mCaptor; private MotionEvent mLastEvent; private TestHandler mHandler; private TouchExplorer mTouchExplorer; private long mLastDownTime = Integer.MIN_VALUE; Loading Loading @@ -112,26 +118,28 @@ public class TouchExplorerTest { @Before public void setUp() { if (Looper.myLooper() == null) { Looper.prepare(); } Context context = InstrumentationRegistry.getContext(); AccessibilityManagerService ams = new AccessibilityManagerService(context); GestureManifold detector = mock(GestureManifold.class); mCaptor = new EventCaptor(); mTouchExplorer = new TouchExplorer(context, ams, detector); mHandler = new TestHandler(); mTouchExplorer = new TouchExplorer(context, ams, detector, mHandler); mTouchExplorer.setNext(mCaptor); } @Test public void testOneFingerMove_shouldInjectHoverEvents() { goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER); try { Thread.sleep(2 * ViewConfiguration.getDoubleTapTimeout()); } catch (InterruptedException e) { fail("Interrupted while waiting for transition to touch exploring state."); } // Wait for transiting to touch exploring state. mHandler.fastForward(2 * USER_INTENT_TIMEOUT); moveEachPointers(mLastEvent, p(10, 10)); send(mLastEvent); goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER); assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT); assertState(STATE_TOUCH_EXPLORING); } /** Loading @@ -144,11 +152,8 @@ public class TouchExplorerTest { // Inject a set of move events that have the same coordinates as the down event. moveEachPointers(mLastEvent, p(0, 0)); send(mLastEvent); try { Thread.sleep(2 * ViewConfiguration.getDoubleTapTimeout()); } catch (InterruptedException e) { fail("Interrupted while waiting for transition to touch exploring state."); } // Wait for transition to touch exploring state. mHandler.fastForward(2 * USER_INTENT_TIMEOUT); // Now move for real. moveEachPointers(mLastEvent, p(10, 10)); send(mLastEvent); Loading Loading @@ -181,6 +186,66 @@ public class TouchExplorerTest { assertCapturedEvents(ACTION_DOWN, ACTION_MOVE, ACTION_MOVE, ACTION_UP); } @Test public void testUpEvent_OneFingerMove_clearStateAndInjectHoverEvents() { goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER); moveEachPointers(mLastEvent, p(10, 10)); send(mLastEvent); send(upEvent()); // Wait for sending hover exit event to transit to clear state. mHandler.fastForward(USER_INTENT_TIMEOUT); assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT); assertState(STATE_CLEAR); } /* * The gesture should be completed in USER_INTENT_TIMEOUT duration otherwise the A11y * touch-exploration end event runnable will be scheduled after receiving the up event. * The distance between start and end point is shorter than the minimum swipe distance. * Note that the delayed time of each runnable is USER_INTENT_TIMEOUT. */ @Test public void testFlickCrossViews_clearStateAndExpectedEvents() { final int oneThirdUserIntentTimeout = USER_INTENT_TIMEOUT / 3; // Touch the first view. send(downEvent()); // Wait for the finger moving to the second view. mHandler.fastForward(oneThirdUserIntentTimeout); moveEachPointers(mLastEvent, p(10, 10)); send(mLastEvent); // Wait for the finger lifting from the second view. mHandler.fastForward(oneThirdUserIntentTimeout); // Now there are three delayed Runnables, hover enter/move runnable, hover exit motion event // runnable and a11y interaction end event runnable. The last two runnables are scheduled // after sending the up event. send(upEvent()); // Wait for running hover enter/move runnable. The runnable is scheduled when sending // the down event. mHandler.fastForward(oneThirdUserIntentTimeout); // Wait for the views responding to hover enter/move events. mHandler.fastForward(oneThirdUserIntentTimeout); // Simulate receiving the a11y exit event sent by the first view. AccessibilityEvent a11yExitEvent = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); mTouchExplorer.onAccessibilityEvent(a11yExitEvent); // Wait for running the hover exit event runnable. After it, touch-exploration end event // runnable will be scheduled. mHandler.fastForward(oneThirdUserIntentTimeout); // Wait for the second views responding to hover exit events. mHandler.fastForward(oneThirdUserIntentTimeout); // Simulate receiving the a11y exit event sent by the second view. mTouchExplorer.onAccessibilityEvent(a11yExitEvent); assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT); assertState(STATE_CLEAR); } @Test public void testTwoFingersMove_shouldDelegatingAndInjectActionDownPointerDown() { goFromStateClearTo(STATE_MOVING_2FINGERS); Loading Loading @@ -307,7 +372,7 @@ public class TouchExplorerTest { } } catch (Throwable t) { throw new RuntimeException( "Failed to go to state " + TouchState.getStateSymbolicName(state), t); "Failed to go to state " + stateToString(state), t); } } Loading Loading @@ -337,7 +402,7 @@ public class TouchExplorerTest { } } catch (Throwable t) { throw new RuntimeException( "Failed to go to state " + TouchState.getStateSymbolicName(state), t); "Failed to return to clear from state " + stateToString(state), t); } } Loading Loading @@ -476,4 +541,52 @@ public class TouchExplorerTest { /* source */ InputDevice.SOURCE_TOUCHSCREEN, /* flags */ 0); } private static String stateToString(int state) { if (state <= STATE_GESTURE_DETECTING /* maximum value of touch state */) { return TouchState.getStateSymbolicName(state); } switch (state) { case STATE_TOUCH_EXPLORING_1FINGER: return "STATE_TOUCH_EXPLORING_1FINGER"; case STATE_TOUCH_EXPLORING_2FINGER: return "STATE_TOUCH_EXPLORING_2FINGER"; case STATE_TOUCH_EXPLORING_3FINGER: return "STATE_TOUCH_EXPLORING_3FINGER"; case STATE_MOVING_2FINGERS: return "STATE_MOVING_2FINGERS"; case STATE_MOVING_3FINGERS: return "STATE_MOVING_3FINGERS"; case STATE_DRAGGING_2FINGERS: return "STATE_DRAGGING_2FINGERS"; case STATE_PINCH_2FINGERS: return "STATE_PINCH_2FINGERS"; default: return "stateToString -- Unknown state: " + Integer.toHexString(state); } } /** * A {@link android.os.Handler} that doesn't process messages until {@link * #fastForward(int)} is invoked. * * @see com.android.server.testutils.TestHandler */ private class TestHandler extends com.android.server.testutils.TestHandler { private final OffsettableClock mClock; TestHandler() { this(null, new OffsettableClock.Stopped()); } TestHandler(Callback callback, OffsettableClock clock) { super(Looper.myLooper(), callback, clock); mClock = clock; } void fastForward(int ms) { mClock.fastForward(ms); timeAdvance(); } } }
services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java +17 −7 Original line number Diff line number Diff line Loading @@ -55,18 +55,24 @@ public class TestHandler extends Handler { // Boxing is ok here - both msg ids and their pending counts tend to be well below 128 private final Map<Integer, Integer> mPendingMsgTypeCounts = new ArrayMap<>(); private final LongSupplier mClock; private int mMessageCount = 0; public TestHandler(Callback callback) { this(callback, DEFAULT_CLOCK); } public TestHandler(Callback callback, LongSupplier clock) { super(Looper.getMainLooper(), callback); this(Looper.getMainLooper(), callback, clock); } public TestHandler(Looper looper, Callback callback, LongSupplier clock) { super(looper, callback); mClock = clock; } @Override public boolean sendMessageAtTime(Message msg, long uptimeMillis) { ++mMessageCount; mPendingMsgTypeCounts.put(msg.what, mPendingMsgTypeCounts.getOrDefault(msg.what, 0) + 1); Loading @@ -78,7 +84,7 @@ public class TestHandler extends Handler { // post a dummy queue entry to keep track of message removal return super.sendMessageAtTime(msg, Long.MAX_VALUE) && mMessages.add(new MsgInfo(Message.obtain(msg), uptimeMillis)); && mMessages.add(new MsgInfo(Message.obtain(msg), uptimeMillis, mMessageCount)); } /** @see TestHandler */ Loading Loading @@ -142,25 +148,29 @@ public class TestHandler extends Handler { public class MsgInfo implements Comparable<MsgInfo> { public final Message message; public final long sendTime; public final int mMessageOrder; public final RuntimeException postPoint; private MsgInfo(Message message, long sendTime) { private MsgInfo(Message message, long sendTime, int messageOrder) { this.message = message; this.sendTime = sendTime; this.postPoint = new RuntimeException("Message originated from here:"); mMessageOrder = messageOrder; } @Override public int compareTo(MsgInfo o) { return Long.compare(sendTime, o.sendTime); final int result = Long.compare(sendTime, o.sendTime); return result != 0 ? result : Integer.compare(mMessageOrder, o.mMessageOrder); } @Override public String toString() { return "MsgInfo{" + "message=" + messageToString(message) + ", sendTime=" + sendTime + '}'; "message =" + messageToString(message) + ", sendTime =" + sendTime + ", mMessageOrder =" + mMessageOrder + '}'; } } }