Loading services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +157 −91 Original line number Diff line number Diff line Loading @@ -66,12 +66,6 @@ public class TouchExplorer extends BaseEventStreamTransformation // Tag for logging received events. private static final String LOG_TAG = "TouchExplorer"; // States this explorer can be in. private static final int STATE_TOUCH_EXPLORING = 0x00000001; private static final int STATE_DRAGGING = 0x00000002; private static final int STATE_DELEGATING = 0x00000004; private static final int STATE_GESTURE_DETECTING = 0x00000005; // The maximum of the cosine between the vectors of two moving // pointers so they can be considered moving in the same direction. private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4) Loading Loading @@ -248,7 +242,13 @@ public class TouchExplorer extends BaseEventStreamTransformation return; } if (mState.isTouchExploring()) { // TODO: extract the below functions into separate handlers for each state. // Right now the number of functions and number of states make the code messy. if (mState.isClear()) { handleMotionEventStateClear(event, rawEvent, policyFlags); } else if (mState.isTouchInteracting()) { handleMotionEventStateTouchInteracting(event, rawEvent, policyFlags); } else if (mState.isTouchExploring()) { handleMotionEventStateTouchExploring(event, rawEvent, policyFlags); } else if (mState.isDragging()) { handleMotionEventStateDragging(event, policyFlags); Loading Loading @@ -286,8 +286,8 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public void onDoubleTapAndHold(MotionEvent event, int policyFlags) { // Ignore the event if we aren't touch exploring. if (!mState.isTouchExploring()) { // Ignore the event if we aren't touch interacting. if (!mState.isTouchInteracting()) { return; } Loading @@ -304,8 +304,7 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public boolean onDoubleTap(MotionEvent event, int policyFlags) { // Ignore the event if we aren't touch exploring. if (!mState.isTouchExploring()) { if (!mState.isTouchInteracting()) { return false; } Loading Loading @@ -380,35 +379,26 @@ public class TouchExplorer extends BaseEventStreamTransformation } /** * Handles a motion event in touch exploring state. * * @param event The event to be handled. * @param rawEvent The raw (unmodified) motion event. * @param policyFlags The policy flags associated with the event. * Handles a motion event in the clear state i.e. no fingers are touching the screen. */ private void handleMotionEventStateTouchExploring( private void handleMotionEventStateClear( MotionEvent event, MotionEvent rawEvent, int policyFlags) { switch (event.getActionMasked()) { // The only way to leave the clear state is for a pointer to go down. case MotionEvent.ACTION_DOWN: handleActionDownStateTouchExploring(event, policyFlags); break; case MotionEvent.ACTION_POINTER_DOWN: handleActionPointerDownStateTouchExploring(); handleActionDown(event, policyFlags); break; case MotionEvent.ACTION_MOVE: handleActionMoveStateTouchExploring(event, rawEvent, policyFlags); break; case MotionEvent.ACTION_UP: handleActionUpStateTouchExploring(event, policyFlags); default: // Some other nonsensical event. break; } } /** * Handles ACTION_DOWN while in the default touch exploring state. This event represents the * Handles ACTION_DOWN while in the clear or touch interacting states. This event represents the * first finger touching the screen. */ private void handleActionDownStateTouchExploring(MotionEvent event, int policyFlags) { private void handleActionDown(MotionEvent event, int policyFlags) { mAms.onTouchInteractionStart(); // If we still have not notified the user for the last Loading @@ -418,13 +408,13 @@ public class TouchExplorer extends BaseEventStreamTransformation mSendHoverExitDelayed.cancel(); // If a touch exploration gesture is in progress send events for its end. if (mState.isTouchExplorationInProgress()) { if (mState.isTouchExploring()) { sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } // Avoid duplicated TYPE_TOUCH_INTERACTION_START event when 2nd tap of double // tap. if (!mGestureDetector.firstTapDetected()) { if (!mGestureDetector.firstTapDetected() && mState.isClear()) { mSendTouchExplorationEndDelayed.forceSendAndRemove(); mSendTouchInteractionEndDelayed.forceSendAndRemove(); sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START); Loading @@ -433,13 +423,15 @@ public class TouchExplorer extends BaseEventStreamTransformation mSendTouchInteractionEndDelayed.cancel(); } if (!mGestureDetector.firstTapDetected() && !mState.isTouchExplorationInProgress()) { if (!mGestureDetector.firstTapDetected() && !mState.isTouchExploring()) { if (!mSendHoverEnterAndMoveDelayed.isPending()) { // Deliver hover enter with a delay to have a chance // to detect what the user is trying to do. // Queue a delayed transition to STATE_TOUCH_EXPLORING. // If we do not detect that this is a gesture, delegation or drag the transition // will fire by default. // The idea is to avoid getting stuck in STATE_TOUCH_INTERACTING final int pointerId = mReceivedPointerTracker.getPrimaryPointerId(); final int pointerIdBits = (1 << pointerId); mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits, policyFlags); mSendHoverEnterAndMoveDelayed.post(event, pointerIdBits, policyFlags); } else { // Cache the event until we discern exploration from gesturing. mSendHoverEnterAndMoveDelayed.addEvent(event); Loading @@ -447,11 +439,65 @@ public class TouchExplorer extends BaseEventStreamTransformation } } /** * Handles a motion event in touch interacting state. * * @param event The event to be handled. * @param rawEvent The raw (unmodified) motion event. * @param policyFlags The policy flags associated with the event. */ private void handleMotionEventStateTouchInteracting( MotionEvent event, MotionEvent rawEvent, int policyFlags) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: // Continue the previous interaction. mSendTouchInteractionEndDelayed.cancel(); handleActionDown(event, policyFlags); break; case MotionEvent.ACTION_POINTER_DOWN: handleActionPointerDown(); break; case MotionEvent.ACTION_MOVE: handleActionMoveStateTouchInteracting(event, rawEvent, policyFlags); break; case MotionEvent.ACTION_UP: handleActionUp(event, policyFlags); break; } } /** * Handles a motion event in touch exploring state. * * @param event The event to be handled. * @param rawEvent The raw (unmodified) motion event. * @param policyFlags The policy flags associated with the event. */ private void handleMotionEventStateTouchExploring( MotionEvent event, MotionEvent rawEvent, int policyFlags) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: // We should have already received ACTION_DOWN. Ignore. break; case MotionEvent.ACTION_POINTER_DOWN: handleActionPointerDown(); break; case MotionEvent.ACTION_MOVE: handleActionMoveStateTouchExploring(event, rawEvent, policyFlags); break; case MotionEvent.ACTION_UP: handleActionUp(event, policyFlags); break; default: break; } } /** * Handles ACTION_POINTER_DOWN when in the touch exploring state. This event represents an * additional finger touching the screen. */ private void handleActionPointerDownStateTouchExploring() { private void handleActionPointerDown() { // Another finger down means that if we have not started to deliver // hover events, we will not have to. The code for ACTION_MOVE will // decide what we will actually do next. Loading @@ -459,10 +505,10 @@ public class TouchExplorer extends BaseEventStreamTransformation mSendHoverExitDelayed.cancel(); } /** * Handles ACTION_MOVE while in the initial touch exploring state. This is where transitions to * Handles ACTION_MOVE while in the touch interacting state. This is where transitions to * delegating and dragging states are handled. */ private void handleActionMoveStateTouchExploring( private void handleActionMoveStateTouchInteracting( MotionEvent event, MotionEvent rawEvent, int policyFlags) { final int pointerId = mReceivedPointerTracker.getPrimaryPointerId(); final int pointerIndex = event.findPointerIndex(pointerId); Loading @@ -474,41 +520,14 @@ public class TouchExplorer extends BaseEventStreamTransformation if (mSendHoverEnterAndMoveDelayed.isPending()) { // Cache the event until we discern exploration from gesturing. mSendHoverEnterAndMoveDelayed.addEvent(event); } else if (mState.isTouchExplorationInProgress()) { sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); sendMotionEvent( event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags); } break; case 2: // More than one pointer so the user is not touch exploring // and now we have to decide whether to delegate or drag. if (mSendHoverEnterAndMoveDelayed.isPending()) { // We have not started sending events so cancel // scheduled sending events. // Make sure we don't have any pending transitions to touch exploration mSendHoverEnterAndMoveDelayed.cancel(); mSendHoverExitDelayed.cancel(); } else if (mState.isTouchExplorationInProgress()) { // If the user is touch exploring the second pointer may be // performing a double tap to activate an item without need // for the user to lift his exploring finger. // It is *important* to use the distance traveled by the pointers // on the screen which may or may not be magnified. final float deltaX = mReceivedPointerTracker.getReceivedPointerDownX(pointerId) - rawEvent.getX(pointerIndex); final float deltaY = mReceivedPointerTracker.getReceivedPointerDownY(pointerId) - rawEvent.getY(pointerIndex); final double moveDelta = Math.hypot(deltaX, deltaY); if (moveDelta < mDoubleTapSlop) { break; } // We are sending events so send exit and gesture // end since we transition to another state. sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } // More than one pointer so the user is not touch exploring // and now we have to decide whether to delegate or drag. // Remove move history before send injected non-move events event = MotionEvent.obtainNoHistory(event); if (isDraggingGesture(event)) { Loading @@ -525,19 +544,6 @@ public class TouchExplorer extends BaseEventStreamTransformation } break; default: // More than one pointer so the user is not touch exploring // and now we have to decide whether to delegate or drag. if (mSendHoverEnterAndMoveDelayed.isPending()) { // We have not started sending events so cancel // scheduled sending events. mSendHoverEnterAndMoveDelayed.cancel(); mSendHoverExitDelayed.cancel(); } else { // We are sending events so send exit and gesture // end since we transition to another state. sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } // More than two pointers are delegated to the view hierarchy. mState.startDelegating(); event = MotionEvent.obtainNoHistory(event); Loading @@ -547,14 +553,13 @@ public class TouchExplorer extends BaseEventStreamTransformation } /** * Handles ACTION_UP while in the initial touch exploring state. This event represents all * fingers being lifted from the screen. * Handles ACTION_UP while in the touch interacting state. This event represents all fingers * being lifted from the screen. */ private void handleActionUpStateTouchExploring(MotionEvent event, int policyFlags) { private void handleActionUp(MotionEvent event, int policyFlags) { mAms.onTouchInteractionEnd(); final int pointerId = event.getPointerId(event.getActionIndex()); final int pointerIdBits = (1 << pointerId); if (mSendHoverEnterAndMoveDelayed.isPending()) { // If we have not delivered the enter schedule an exit. mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags); Loading @@ -562,12 +567,68 @@ public class TouchExplorer extends BaseEventStreamTransformation // The user is touch exploring so we send events for end. sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } if (!mSendTouchInteractionEndDelayed.isPending()) { mSendTouchInteractionEndDelayed.post(); } } /** * Handles move events while touch exploring. this is also where we drag or delegate based on * the number of fingers moving on the screen. */ private void handleActionMoveStateTouchExploring( MotionEvent event, MotionEvent rawEvent, int policyFlags) { final int pointerId = mReceivedPointerTracker.getPrimaryPointerId(); final int pointerIdBits = (1 << pointerId); final int pointerIndex = event.findPointerIndex(pointerId); switch (event.getPointerCount()) { case 1: // Touch exploration. sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags); break; case 2: if (mSendHoverEnterAndMoveDelayed.isPending()) { // We have not started sending events so cancel // scheduled sending events. mSendHoverEnterAndMoveDelayed.cancel(); mSendHoverExitDelayed.cancel(); } // If the user is touch exploring the second pointer may be // performing a double tap to activate an item without need // for the user to lift his exploring finger. // It is *important* to use the distance traveled by the pointers // on the screen which may or may not be magnified. final float deltaX = mReceivedPointerTracker.getReceivedPointerDownX(pointerId) - rawEvent.getX(pointerIndex); final float deltaY = mReceivedPointerTracker.getReceivedPointerDownY(pointerId) - rawEvent.getY(pointerIndex); final double moveDelta = Math.hypot(deltaX, deltaY); if (moveDelta > mDoubleTapSlop) { // The user is trying to either delegate or drag. handleActionMoveStateTouchInteracting(event, rawEvent, policyFlags); } else { // Otherwise the double tap will be handled by the gesture detector. sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } break; default: // Three or more fingers is something other than touch exploration. if (mSendHoverEnterAndMoveDelayed.isPending()) { // We have not started sending events so cancel // scheduled sending events. mSendHoverEnterAndMoveDelayed.cancel(); mSendHoverExitDelayed.cancel(); } else { sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } handleActionMoveStateTouchInteracting(event, rawEvent, policyFlags); break; } } /** * Handles a motion event in dragging state. * Loading Loading @@ -670,7 +731,6 @@ public class TouchExplorer extends BaseEventStreamTransformation // Send an event to the end of the drag gesture. sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); } mState.startTouchExploring(); } break; } } Loading @@ -697,7 +757,6 @@ public class TouchExplorer extends BaseEventStreamTransformation mAms.onTouchInteractionEnd(); sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); mState.startTouchExploring(); } break; default: { // Deliver the event. Loading @@ -717,7 +776,6 @@ public class TouchExplorer extends BaseEventStreamTransformation } mExitGestureDetectionModeDelayed.cancel(); mState.startTouchExploring(); } /** Loading @@ -731,9 +789,14 @@ public class TouchExplorer extends BaseEventStreamTransformation AccessibilityEvent event = AccessibilityEvent.obtain(type); event.setWindowId(mAms.getActiveWindowId()); accessibilityManager.sendAccessibilityEvent(event); mState.onInjectedAccessibilityEvent(type); if (DEBUG) { Slog.d( LOG_TAG, "Sending accessibility event" + AccessibilityEvent.eventTypeToString(type)); } } mState.onInjectedAccessibilityEvent(type); } /** * Sends down events to the view hierarchy for all pointers which are Loading Loading @@ -915,6 +978,10 @@ public class TouchExplorer extends BaseEventStreamTransformation MAX_DRAGGING_ANGLE_COS); } public TouchState getState() { return mState; } /** * Class for delayed exiting from gesture detecting mode. */ Loading Loading @@ -947,8 +1014,7 @@ public class TouchExplorer extends BaseEventStreamTransformation private int mPointerIdBits; private int mPolicyFlags; public void post(MotionEvent event, boolean touchExplorationInProgress, int pointerIdBits, int policyFlags) { public void post(MotionEvent event, int pointerIdBits, int policyFlags) { cancel(); addEvent(event); mPointerIdBits = pointerIdBits; Loading services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java +64 −30 Original line number Diff line number Diff line Loading @@ -41,20 +41,33 @@ public class TouchState { public static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF; // States that the touch explorer can be in. public static final int STATE_TOUCH_EXPLORING = 0x00000001; public static final int STATE_DRAGGING = 0x00000002; public static final int STATE_DELEGATING = 0x00000003; public static final int STATE_GESTURE_DETECTING = 0x00000004; @IntDef({STATE_TOUCH_EXPLORING, STATE_DRAGGING, STATE_DELEGATING, STATE_GESTURE_DETECTING}) // In the clear state the user is not touching the screen. public static final int STATE_CLEAR = 0; // The user is touching the screen and we are trying to figure out their intent. // This state gets its name from the TYPE_TOUCH_INTERACTION start and end accessibility events. public static final int STATE_TOUCH_INTERACTING = 1; // The user is explicitly exploring the screen. public static final int STATE_TOUCH_EXPLORING = 2; // the user is dragging with two fingers. public static final int STATE_DRAGGING = 3; // The user is performing some other two finger gesture which we pass through to the view // hierarchy as a one-finger gesture e.g. two-finger scrolling. public static final int STATE_DELEGATING = 4; // The user is performing something that might be a gesture. public static final int STATE_GESTURE_DETECTING = 5; @IntDef({ STATE_CLEAR, STATE_TOUCH_INTERACTING, STATE_TOUCH_EXPLORING, STATE_DRAGGING, STATE_DELEGATING, STATE_GESTURE_DETECTING }) public @interface State {} // The current state of the touch explorer. private int mState = STATE_TOUCH_EXPLORING; // Whether touch exploration is in progress. // TODO: Add separate states to represent intend detection and actual touch exploration so that // only one variable describes the state. private boolean mTouchExplorationInProgress; private int mState = STATE_CLEAR; // Helper class to track received pointers. // Todo: collapse or hide this class so multiple classes don't modify it. private final ReceivedPointerTracker mReceivedPointerTracker; Loading @@ -69,8 +82,7 @@ public class TouchState { /** Clears the internal shared state. */ public void clear() { mState = STATE_TOUCH_EXPLORING; mTouchExplorationInProgress = false; setState(STATE_CLEAR); // Reset the pointer trackers. mReceivedPointerTracker.clear(); mInjectedPointerTracker.clear(); Loading @@ -94,18 +106,33 @@ public class TouchState { mReceivedPointerTracker.onMotionEvent(rawEvent); } /** * Updates the state in response to an accessibility event being sent from TouchExplorer. * * @param type The event type. */ public void onInjectedAccessibilityEvent(int type) { // The below state transitions go here because the related events are often sent on a // delay. // This allows state to accurately reflect the state in the moment. // TODO: replaced the delayed event senders with delayed state transitions // so that state transitions trigger events rather than events triggering state // transitions. switch (type) { case AccessibilityEvent.TYPE_TOUCH_INTERACTION_START: startTouchInteracting(); break; case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END: clear(); break; case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: mTouchExplorationInProgress = true; startTouchExploring(); break; case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: mTouchExplorationInProgress = false; startTouchInteracting(); break; case AccessibilityEvent.TYPE_GESTURE_DETECTION_START: startGestureDetecting(); break; case AccessibilityEvent.TYPE_GESTURE_DETECTION_END: startTouchInteracting(); break; default: break; } } Loading @@ -117,6 +144,7 @@ public class TouchState { /** Transitions to a new state. */ public void setState(@State int state) { if (mState == state) return; if (DEBUG) { Slog.i(LOG_TAG, getStateSymbolicName(mState) + "->" + getStateSymbolicName(state)); } Loading Loading @@ -159,26 +187,32 @@ public class TouchState { setState(STATE_DRAGGING); } public boolean isTouchExplorationInProgress() { return mTouchExplorationInProgress; public boolean isTouchInteracting() { return mState == STATE_TOUCH_INTERACTING; } public void setTouchExplorationInProgress(boolean touchExplorationInProgress) { mTouchExplorationInProgress = touchExplorationInProgress; /** * Transitions to the touch interacting state, where we attempt to figure out what the user is * doing. */ public void startTouchInteracting() { setState(STATE_TOUCH_INTERACTING); } public boolean isClear() { return mState == STATE_CLEAR; } /** Returns a string representation of the current state. */ public String toString() { return "TouchState { " + "mState: " + getStateSymbolicName(mState) + ", mTouchExplorationInProgress" + mTouchExplorationInProgress + " }"; return "TouchState { " + "mState: " + getStateSymbolicName(mState) + " }"; } /** Returns a string representation of the specified state. */ public static String getStateSymbolicName(int state) { switch (state) { case STATE_CLEAR: return "STATE_CLEAR"; case STATE_TOUCH_INTERACTING: return "STATE_TOUCH_INTERACTING"; case STATE_TOUCH_EXPLORING: return "STATE_TOUCH_EXPLORING"; case STATE_DRAGGING: Loading services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java +22 −27 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +157 −91 Original line number Diff line number Diff line Loading @@ -66,12 +66,6 @@ public class TouchExplorer extends BaseEventStreamTransformation // Tag for logging received events. private static final String LOG_TAG = "TouchExplorer"; // States this explorer can be in. private static final int STATE_TOUCH_EXPLORING = 0x00000001; private static final int STATE_DRAGGING = 0x00000002; private static final int STATE_DELEGATING = 0x00000004; private static final int STATE_GESTURE_DETECTING = 0x00000005; // The maximum of the cosine between the vectors of two moving // pointers so they can be considered moving in the same direction. private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4) Loading Loading @@ -248,7 +242,13 @@ public class TouchExplorer extends BaseEventStreamTransformation return; } if (mState.isTouchExploring()) { // TODO: extract the below functions into separate handlers for each state. // Right now the number of functions and number of states make the code messy. if (mState.isClear()) { handleMotionEventStateClear(event, rawEvent, policyFlags); } else if (mState.isTouchInteracting()) { handleMotionEventStateTouchInteracting(event, rawEvent, policyFlags); } else if (mState.isTouchExploring()) { handleMotionEventStateTouchExploring(event, rawEvent, policyFlags); } else if (mState.isDragging()) { handleMotionEventStateDragging(event, policyFlags); Loading Loading @@ -286,8 +286,8 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public void onDoubleTapAndHold(MotionEvent event, int policyFlags) { // Ignore the event if we aren't touch exploring. if (!mState.isTouchExploring()) { // Ignore the event if we aren't touch interacting. if (!mState.isTouchInteracting()) { return; } Loading @@ -304,8 +304,7 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public boolean onDoubleTap(MotionEvent event, int policyFlags) { // Ignore the event if we aren't touch exploring. if (!mState.isTouchExploring()) { if (!mState.isTouchInteracting()) { return false; } Loading Loading @@ -380,35 +379,26 @@ public class TouchExplorer extends BaseEventStreamTransformation } /** * Handles a motion event in touch exploring state. * * @param event The event to be handled. * @param rawEvent The raw (unmodified) motion event. * @param policyFlags The policy flags associated with the event. * Handles a motion event in the clear state i.e. no fingers are touching the screen. */ private void handleMotionEventStateTouchExploring( private void handleMotionEventStateClear( MotionEvent event, MotionEvent rawEvent, int policyFlags) { switch (event.getActionMasked()) { // The only way to leave the clear state is for a pointer to go down. case MotionEvent.ACTION_DOWN: handleActionDownStateTouchExploring(event, policyFlags); break; case MotionEvent.ACTION_POINTER_DOWN: handleActionPointerDownStateTouchExploring(); handleActionDown(event, policyFlags); break; case MotionEvent.ACTION_MOVE: handleActionMoveStateTouchExploring(event, rawEvent, policyFlags); break; case MotionEvent.ACTION_UP: handleActionUpStateTouchExploring(event, policyFlags); default: // Some other nonsensical event. break; } } /** * Handles ACTION_DOWN while in the default touch exploring state. This event represents the * Handles ACTION_DOWN while in the clear or touch interacting states. This event represents the * first finger touching the screen. */ private void handleActionDownStateTouchExploring(MotionEvent event, int policyFlags) { private void handleActionDown(MotionEvent event, int policyFlags) { mAms.onTouchInteractionStart(); // If we still have not notified the user for the last Loading @@ -418,13 +408,13 @@ public class TouchExplorer extends BaseEventStreamTransformation mSendHoverExitDelayed.cancel(); // If a touch exploration gesture is in progress send events for its end. if (mState.isTouchExplorationInProgress()) { if (mState.isTouchExploring()) { sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } // Avoid duplicated TYPE_TOUCH_INTERACTION_START event when 2nd tap of double // tap. if (!mGestureDetector.firstTapDetected()) { if (!mGestureDetector.firstTapDetected() && mState.isClear()) { mSendTouchExplorationEndDelayed.forceSendAndRemove(); mSendTouchInteractionEndDelayed.forceSendAndRemove(); sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START); Loading @@ -433,13 +423,15 @@ public class TouchExplorer extends BaseEventStreamTransformation mSendTouchInteractionEndDelayed.cancel(); } if (!mGestureDetector.firstTapDetected() && !mState.isTouchExplorationInProgress()) { if (!mGestureDetector.firstTapDetected() && !mState.isTouchExploring()) { if (!mSendHoverEnterAndMoveDelayed.isPending()) { // Deliver hover enter with a delay to have a chance // to detect what the user is trying to do. // Queue a delayed transition to STATE_TOUCH_EXPLORING. // If we do not detect that this is a gesture, delegation or drag the transition // will fire by default. // The idea is to avoid getting stuck in STATE_TOUCH_INTERACTING final int pointerId = mReceivedPointerTracker.getPrimaryPointerId(); final int pointerIdBits = (1 << pointerId); mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits, policyFlags); mSendHoverEnterAndMoveDelayed.post(event, pointerIdBits, policyFlags); } else { // Cache the event until we discern exploration from gesturing. mSendHoverEnterAndMoveDelayed.addEvent(event); Loading @@ -447,11 +439,65 @@ public class TouchExplorer extends BaseEventStreamTransformation } } /** * Handles a motion event in touch interacting state. * * @param event The event to be handled. * @param rawEvent The raw (unmodified) motion event. * @param policyFlags The policy flags associated with the event. */ private void handleMotionEventStateTouchInteracting( MotionEvent event, MotionEvent rawEvent, int policyFlags) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: // Continue the previous interaction. mSendTouchInteractionEndDelayed.cancel(); handleActionDown(event, policyFlags); break; case MotionEvent.ACTION_POINTER_DOWN: handleActionPointerDown(); break; case MotionEvent.ACTION_MOVE: handleActionMoveStateTouchInteracting(event, rawEvent, policyFlags); break; case MotionEvent.ACTION_UP: handleActionUp(event, policyFlags); break; } } /** * Handles a motion event in touch exploring state. * * @param event The event to be handled. * @param rawEvent The raw (unmodified) motion event. * @param policyFlags The policy flags associated with the event. */ private void handleMotionEventStateTouchExploring( MotionEvent event, MotionEvent rawEvent, int policyFlags) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: // We should have already received ACTION_DOWN. Ignore. break; case MotionEvent.ACTION_POINTER_DOWN: handleActionPointerDown(); break; case MotionEvent.ACTION_MOVE: handleActionMoveStateTouchExploring(event, rawEvent, policyFlags); break; case MotionEvent.ACTION_UP: handleActionUp(event, policyFlags); break; default: break; } } /** * Handles ACTION_POINTER_DOWN when in the touch exploring state. This event represents an * additional finger touching the screen. */ private void handleActionPointerDownStateTouchExploring() { private void handleActionPointerDown() { // Another finger down means that if we have not started to deliver // hover events, we will not have to. The code for ACTION_MOVE will // decide what we will actually do next. Loading @@ -459,10 +505,10 @@ public class TouchExplorer extends BaseEventStreamTransformation mSendHoverExitDelayed.cancel(); } /** * Handles ACTION_MOVE while in the initial touch exploring state. This is where transitions to * Handles ACTION_MOVE while in the touch interacting state. This is where transitions to * delegating and dragging states are handled. */ private void handleActionMoveStateTouchExploring( private void handleActionMoveStateTouchInteracting( MotionEvent event, MotionEvent rawEvent, int policyFlags) { final int pointerId = mReceivedPointerTracker.getPrimaryPointerId(); final int pointerIndex = event.findPointerIndex(pointerId); Loading @@ -474,41 +520,14 @@ public class TouchExplorer extends BaseEventStreamTransformation if (mSendHoverEnterAndMoveDelayed.isPending()) { // Cache the event until we discern exploration from gesturing. mSendHoverEnterAndMoveDelayed.addEvent(event); } else if (mState.isTouchExplorationInProgress()) { sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); sendMotionEvent( event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags); } break; case 2: // More than one pointer so the user is not touch exploring // and now we have to decide whether to delegate or drag. if (mSendHoverEnterAndMoveDelayed.isPending()) { // We have not started sending events so cancel // scheduled sending events. // Make sure we don't have any pending transitions to touch exploration mSendHoverEnterAndMoveDelayed.cancel(); mSendHoverExitDelayed.cancel(); } else if (mState.isTouchExplorationInProgress()) { // If the user is touch exploring the second pointer may be // performing a double tap to activate an item without need // for the user to lift his exploring finger. // It is *important* to use the distance traveled by the pointers // on the screen which may or may not be magnified. final float deltaX = mReceivedPointerTracker.getReceivedPointerDownX(pointerId) - rawEvent.getX(pointerIndex); final float deltaY = mReceivedPointerTracker.getReceivedPointerDownY(pointerId) - rawEvent.getY(pointerIndex); final double moveDelta = Math.hypot(deltaX, deltaY); if (moveDelta < mDoubleTapSlop) { break; } // We are sending events so send exit and gesture // end since we transition to another state. sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } // More than one pointer so the user is not touch exploring // and now we have to decide whether to delegate or drag. // Remove move history before send injected non-move events event = MotionEvent.obtainNoHistory(event); if (isDraggingGesture(event)) { Loading @@ -525,19 +544,6 @@ public class TouchExplorer extends BaseEventStreamTransformation } break; default: // More than one pointer so the user is not touch exploring // and now we have to decide whether to delegate or drag. if (mSendHoverEnterAndMoveDelayed.isPending()) { // We have not started sending events so cancel // scheduled sending events. mSendHoverEnterAndMoveDelayed.cancel(); mSendHoverExitDelayed.cancel(); } else { // We are sending events so send exit and gesture // end since we transition to another state. sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } // More than two pointers are delegated to the view hierarchy. mState.startDelegating(); event = MotionEvent.obtainNoHistory(event); Loading @@ -547,14 +553,13 @@ public class TouchExplorer extends BaseEventStreamTransformation } /** * Handles ACTION_UP while in the initial touch exploring state. This event represents all * fingers being lifted from the screen. * Handles ACTION_UP while in the touch interacting state. This event represents all fingers * being lifted from the screen. */ private void handleActionUpStateTouchExploring(MotionEvent event, int policyFlags) { private void handleActionUp(MotionEvent event, int policyFlags) { mAms.onTouchInteractionEnd(); final int pointerId = event.getPointerId(event.getActionIndex()); final int pointerIdBits = (1 << pointerId); if (mSendHoverEnterAndMoveDelayed.isPending()) { // If we have not delivered the enter schedule an exit. mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags); Loading @@ -562,12 +567,68 @@ public class TouchExplorer extends BaseEventStreamTransformation // The user is touch exploring so we send events for end. sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } if (!mSendTouchInteractionEndDelayed.isPending()) { mSendTouchInteractionEndDelayed.post(); } } /** * Handles move events while touch exploring. this is also where we drag or delegate based on * the number of fingers moving on the screen. */ private void handleActionMoveStateTouchExploring( MotionEvent event, MotionEvent rawEvent, int policyFlags) { final int pointerId = mReceivedPointerTracker.getPrimaryPointerId(); final int pointerIdBits = (1 << pointerId); final int pointerIndex = event.findPointerIndex(pointerId); switch (event.getPointerCount()) { case 1: // Touch exploration. sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags); break; case 2: if (mSendHoverEnterAndMoveDelayed.isPending()) { // We have not started sending events so cancel // scheduled sending events. mSendHoverEnterAndMoveDelayed.cancel(); mSendHoverExitDelayed.cancel(); } // If the user is touch exploring the second pointer may be // performing a double tap to activate an item without need // for the user to lift his exploring finger. // It is *important* to use the distance traveled by the pointers // on the screen which may or may not be magnified. final float deltaX = mReceivedPointerTracker.getReceivedPointerDownX(pointerId) - rawEvent.getX(pointerIndex); final float deltaY = mReceivedPointerTracker.getReceivedPointerDownY(pointerId) - rawEvent.getY(pointerIndex); final double moveDelta = Math.hypot(deltaX, deltaY); if (moveDelta > mDoubleTapSlop) { // The user is trying to either delegate or drag. handleActionMoveStateTouchInteracting(event, rawEvent, policyFlags); } else { // Otherwise the double tap will be handled by the gesture detector. sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } break; default: // Three or more fingers is something other than touch exploration. if (mSendHoverEnterAndMoveDelayed.isPending()) { // We have not started sending events so cancel // scheduled sending events. mSendHoverEnterAndMoveDelayed.cancel(); mSendHoverExitDelayed.cancel(); } else { sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } handleActionMoveStateTouchInteracting(event, rawEvent, policyFlags); break; } } /** * Handles a motion event in dragging state. * Loading Loading @@ -670,7 +731,6 @@ public class TouchExplorer extends BaseEventStreamTransformation // Send an event to the end of the drag gesture. sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); } mState.startTouchExploring(); } break; } } Loading @@ -697,7 +757,6 @@ public class TouchExplorer extends BaseEventStreamTransformation mAms.onTouchInteractionEnd(); sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); mState.startTouchExploring(); } break; default: { // Deliver the event. Loading @@ -717,7 +776,6 @@ public class TouchExplorer extends BaseEventStreamTransformation } mExitGestureDetectionModeDelayed.cancel(); mState.startTouchExploring(); } /** Loading @@ -731,9 +789,14 @@ public class TouchExplorer extends BaseEventStreamTransformation AccessibilityEvent event = AccessibilityEvent.obtain(type); event.setWindowId(mAms.getActiveWindowId()); accessibilityManager.sendAccessibilityEvent(event); mState.onInjectedAccessibilityEvent(type); if (DEBUG) { Slog.d( LOG_TAG, "Sending accessibility event" + AccessibilityEvent.eventTypeToString(type)); } } mState.onInjectedAccessibilityEvent(type); } /** * Sends down events to the view hierarchy for all pointers which are Loading Loading @@ -915,6 +978,10 @@ public class TouchExplorer extends BaseEventStreamTransformation MAX_DRAGGING_ANGLE_COS); } public TouchState getState() { return mState; } /** * Class for delayed exiting from gesture detecting mode. */ Loading Loading @@ -947,8 +1014,7 @@ public class TouchExplorer extends BaseEventStreamTransformation private int mPointerIdBits; private int mPolicyFlags; public void post(MotionEvent event, boolean touchExplorationInProgress, int pointerIdBits, int policyFlags) { public void post(MotionEvent event, int pointerIdBits, int policyFlags) { cancel(); addEvent(event); mPointerIdBits = pointerIdBits; Loading
services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java +64 −30 Original line number Diff line number Diff line Loading @@ -41,20 +41,33 @@ public class TouchState { public static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF; // States that the touch explorer can be in. public static final int STATE_TOUCH_EXPLORING = 0x00000001; public static final int STATE_DRAGGING = 0x00000002; public static final int STATE_DELEGATING = 0x00000003; public static final int STATE_GESTURE_DETECTING = 0x00000004; @IntDef({STATE_TOUCH_EXPLORING, STATE_DRAGGING, STATE_DELEGATING, STATE_GESTURE_DETECTING}) // In the clear state the user is not touching the screen. public static final int STATE_CLEAR = 0; // The user is touching the screen and we are trying to figure out their intent. // This state gets its name from the TYPE_TOUCH_INTERACTION start and end accessibility events. public static final int STATE_TOUCH_INTERACTING = 1; // The user is explicitly exploring the screen. public static final int STATE_TOUCH_EXPLORING = 2; // the user is dragging with two fingers. public static final int STATE_DRAGGING = 3; // The user is performing some other two finger gesture which we pass through to the view // hierarchy as a one-finger gesture e.g. two-finger scrolling. public static final int STATE_DELEGATING = 4; // The user is performing something that might be a gesture. public static final int STATE_GESTURE_DETECTING = 5; @IntDef({ STATE_CLEAR, STATE_TOUCH_INTERACTING, STATE_TOUCH_EXPLORING, STATE_DRAGGING, STATE_DELEGATING, STATE_GESTURE_DETECTING }) public @interface State {} // The current state of the touch explorer. private int mState = STATE_TOUCH_EXPLORING; // Whether touch exploration is in progress. // TODO: Add separate states to represent intend detection and actual touch exploration so that // only one variable describes the state. private boolean mTouchExplorationInProgress; private int mState = STATE_CLEAR; // Helper class to track received pointers. // Todo: collapse or hide this class so multiple classes don't modify it. private final ReceivedPointerTracker mReceivedPointerTracker; Loading @@ -69,8 +82,7 @@ public class TouchState { /** Clears the internal shared state. */ public void clear() { mState = STATE_TOUCH_EXPLORING; mTouchExplorationInProgress = false; setState(STATE_CLEAR); // Reset the pointer trackers. mReceivedPointerTracker.clear(); mInjectedPointerTracker.clear(); Loading @@ -94,18 +106,33 @@ public class TouchState { mReceivedPointerTracker.onMotionEvent(rawEvent); } /** * Updates the state in response to an accessibility event being sent from TouchExplorer. * * @param type The event type. */ public void onInjectedAccessibilityEvent(int type) { // The below state transitions go here because the related events are often sent on a // delay. // This allows state to accurately reflect the state in the moment. // TODO: replaced the delayed event senders with delayed state transitions // so that state transitions trigger events rather than events triggering state // transitions. switch (type) { case AccessibilityEvent.TYPE_TOUCH_INTERACTION_START: startTouchInteracting(); break; case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END: clear(); break; case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: mTouchExplorationInProgress = true; startTouchExploring(); break; case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: mTouchExplorationInProgress = false; startTouchInteracting(); break; case AccessibilityEvent.TYPE_GESTURE_DETECTION_START: startGestureDetecting(); break; case AccessibilityEvent.TYPE_GESTURE_DETECTION_END: startTouchInteracting(); break; default: break; } } Loading @@ -117,6 +144,7 @@ public class TouchState { /** Transitions to a new state. */ public void setState(@State int state) { if (mState == state) return; if (DEBUG) { Slog.i(LOG_TAG, getStateSymbolicName(mState) + "->" + getStateSymbolicName(state)); } Loading Loading @@ -159,26 +187,32 @@ public class TouchState { setState(STATE_DRAGGING); } public boolean isTouchExplorationInProgress() { return mTouchExplorationInProgress; public boolean isTouchInteracting() { return mState == STATE_TOUCH_INTERACTING; } public void setTouchExplorationInProgress(boolean touchExplorationInProgress) { mTouchExplorationInProgress = touchExplorationInProgress; /** * Transitions to the touch interacting state, where we attempt to figure out what the user is * doing. */ public void startTouchInteracting() { setState(STATE_TOUCH_INTERACTING); } public boolean isClear() { return mState == STATE_CLEAR; } /** Returns a string representation of the current state. */ public String toString() { return "TouchState { " + "mState: " + getStateSymbolicName(mState) + ", mTouchExplorationInProgress" + mTouchExplorationInProgress + " }"; return "TouchState { " + "mState: " + getStateSymbolicName(mState) + " }"; } /** Returns a string representation of the specified state. */ public static String getStateSymbolicName(int state) { switch (state) { case STATE_CLEAR: return "STATE_CLEAR"; case STATE_TOUCH_INTERACTING: return "STATE_TOUCH_INTERACTING"; case STATE_TOUCH_EXPLORING: return "STATE_TOUCH_EXPLORING"; case STATE_DRAGGING: Loading
services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java +22 −27 File changed.Preview size limit exceeded, changes collapsed. Show changes