From 9254b3e336a01ff79742882bd98860b48ebae379 Mon Sep 17 00:00:00 2001 From: Zachary Kuznia Date: Thu, 21 Jan 2016 13:24:47 -0800 Subject: [PATCH] Make AccessibilityGestureDetector handle gesture detection start and end. Change-Id: I2c1861d5d6c5c0dc921e62f03ee6283f1f7a62b6 --- .../AccessibilityGestureDetector.java | 98 ++++++++++-- .../server/accessibility/TouchExplorer.java | 151 ++++++------------ 2 files changed, 140 insertions(+), 109 deletions(-) diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java index e31080150280f..912e8e2eb2463 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java @@ -27,6 +27,8 @@ import android.gesture.Prediction; import android.util.Slog; import android.view.GestureDetector; import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.ViewConfiguration; import com.android.internal.R; @@ -46,7 +48,9 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen public interface Listener { public void onDoubleTapAndHold(MotionEvent event, int policyFlags); public boolean onDoubleTap(MotionEvent event, int policyFlags); - public boolean onGesture(int gestureId); + public boolean onGestureCompleted(int gestureId); + public void onGestureStarted(); + public void onGestureCancelled(MotionEvent event, int policyFlags); } private final Listener mListener; @@ -64,6 +68,10 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen // Indicates that motion events are being collected to match a gesture. private boolean mRecognizingGesture; + // Indicates that we've collected enough data to be sure it could be a + // gesture. + private boolean mGestureConfirmed; + // Indicates that motion events from the second pointer are being checked // for a double tap. private boolean mSecondFingerDoubleTap; @@ -81,18 +89,41 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen // The Y of the previous event. private float mPreviousY; + // The X of the down event. + private float mBaseX; + + // The Y of the down event. + private float mBaseY; + + // Slop between the first and second tap to be a double tap. + private final int mDoubleTapSlop; + + // The scaled velocity above which we detect gestures. + private final int mScaledGestureDetectionVelocity; + // Buffer for storing points for gesture detection. private final ArrayList mStrokeBuffer = new ArrayList(100); + // Helper to track gesture velocity. + private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); + // The minimal delta between moves to add a gesture point. private static final int TOUCH_TOLERANCE = 3; // The minimal score for accepting a predicted gesture. private static final float MIN_PREDICTION_SCORE = 2.0f; + // The velocity above which we detect gestures. Expressed in DIPs/Second. + private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000; + + // Constant used to calculate velocity in seconds. + private static final int VELOCITY_UNITS_SECONDS = 1000; + AccessibilityGestureDetector(Context context, Listener listener) { mListener = listener; + mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); + mGestureDetector = new GestureDetector(context, this); mGestureDetector.setOnDoubleTapListener(this); @@ -100,9 +131,14 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen mGestureLibrary.setOrientationStyle(8 /* GestureStore.ORIENTATION_SENSITIVE_8 */); mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE); mGestureLibrary.load(); + + final float density = context.getResources().getDisplayMetrics().density; + mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density); } public boolean onMotionEvent(MotionEvent event, int policyFlags) { + mVelocityTracker.addMovement(event); + final float x = event.getX(); final float y = event.getY(); @@ -112,14 +148,48 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen mDoubleTapDetected = false; mSecondFingerDoubleTap = false; mRecognizingGesture = true; + mGestureConfirmed = false; + mBaseX = x; + mBaseY = y; mPreviousX = x; mPreviousY = y; mStrokeBuffer.clear(); mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); + mVelocityTracker.clear(); + mVelocityTracker.addMovement(event); break; case MotionEvent.ACTION_MOVE: if (mRecognizingGesture) { + if (!mGestureConfirmed) { + mVelocityTracker.addMovement(event); + // It is *important* to use the distance traveled by the pointers + // on the screen which may or may not be magnified. + final float deltaX = mBaseX - event.getX(0); + final float deltaY = mBaseY - event.getY(0); + final double moveDelta = Math.hypot(deltaX, deltaY); + // The user has moved enough for us to decide. + if (moveDelta > mDoubleTapSlop) { + // Check whether the user is performing a gesture. We + // detect gestures if the pointer is moving above a + // given velocity. + mVelocityTracker.computeCurrentVelocity(VELOCITY_UNITS_SECONDS); + final float maxAbsVelocity = Math.max( + Math.abs(mVelocityTracker.getXVelocity(0)), + Math.abs(mVelocityTracker.getYVelocity(0))); + if (maxAbsVelocity > mScaledGestureDetectionVelocity) { + // We have to perform gesture detection, so + // notify the listener. + mGestureConfirmed = true; + mListener.onGestureStarted(); + } else { + // This won't match any gesture, so notify the + // listener. + cancelGesture(); + mListener.onGestureCancelled(event, policyFlags); + } + } + } final float dX = Math.abs(x - mPreviousX); final float dY = Math.abs(y - mPreviousY); if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) { @@ -134,12 +204,13 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen if (maybeFinishDoubleTap(event, policyFlags)) { return true; } - if (mRecognizingGesture) { + if (mGestureConfirmed) { mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime())); - if (recognizeGesture()) { - return true; + if (!recognizeGesture()) { + mListener.onGestureCancelled(event, policyFlags); } + return true; } break; @@ -167,6 +238,10 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen return true; } break; + + case MotionEvent.ACTION_CANCEL: + clear(); + break; } // If we're detecting taps on the second finger, map events from the @@ -194,18 +269,13 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen mDoubleTapDetected = false; mSecondFingerDoubleTap = false; cancelGesture(); - mStrokeBuffer.clear(); + mVelocityTracker.clear(); } public boolean firstTapDetected() { return mFirstTapDetected; } - public void cancelGesture() { - mRecognizingGesture = false; - mStrokeBuffer.clear(); - } - @Override public void onLongPress(MotionEvent e) { maybeSendLongPress(e, mPolicyFlags); @@ -251,6 +321,12 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen return mListener.onDoubleTap(event, policyFlags); } + private void cancelGesture() { + mRecognizingGesture = false; + mGestureConfirmed = false; + mStrokeBuffer.clear(); + } + private boolean recognizeGesture() { Gesture gesture = new Gesture(); gesture.addStroke(new GestureStroke(mStrokeBuffer)); @@ -265,7 +341,7 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen } try { final int gestureId = Integer.parseInt(bestPrediction.name); - if (mListener.onGesture(gestureId)) { + if (mListener.onGestureCompleted(gestureId)) { return true; } } catch (NumberFormatException nfe) { diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java index ca30349867dea..9e6cd002e8894 100644 --- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java @@ -25,7 +25,6 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; -import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.view.WindowManagerPolicy; import android.view.accessibility.AccessibilityEvent; @@ -87,9 +86,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe // Invalid pointer ID. private static final int INVALID_POINTER_ID = -1; - // The velocity above which we detect gestures. - private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000; - // The minimal distance before we take the middle of the distance between // the two dragging pointers as opposed to use the location of the primary one. private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200; @@ -134,15 +130,9 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe // the two dragging pointers as opposed to use the location of the primary one. private final int mScaledMinPointerDistanceToUseMiddleLocation; - // The scaled velocity above which we detect gestures. - private final int mScaledGestureDetectionVelocity; - // The handler to which to delegate events. private EventStreamTransformation mNext; - // Helper to track gesture velocity. - private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); - // Helper class to track received pointers. private final ReceivedPointerTracker mReceivedPointerTracker; @@ -200,7 +190,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe final float density = context.getResources().getDisplayMetrics().density; mScaledMinPointerDistanceToUseMiddleLocation = (int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density); - mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density); } @Override @@ -289,11 +278,19 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe mReceivedPointerTracker.onMotionEvent(rawEvent); - if (mGestureDetector.onMotionEvent(event, policyFlags)) { + // The motion detector is interested in the movements in physical space, + // so it uses the rawEvent to ignore magnification and other + // transformations. + if (mGestureDetector.onMotionEvent(rawEvent, policyFlags)) { // Event was handled by the gesture detector. return; } + if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) { + clear(event, policyFlags); + return; + } + switch(mCurrentState) { case STATE_TOUCH_EXPLORING: { handleMotionEventStateTouchExploring(event, rawEvent, policyFlags); @@ -305,7 +302,7 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe handleMotionEventStateDelegating(event, policyFlags); } break; case STATE_GESTURE_DETECTING: { - handleMotionEventGestureDetecting(rawEvent, policyFlags); + // Already handled. } break; default: throw new IllegalStateException("Illegal state: " + mCurrentState); @@ -440,26 +437,50 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe } @Override - public boolean onGesture(int gestureId) { + public boolean onGestureCompleted(int gestureId) { if (mCurrentState != STATE_GESTURE_DETECTING) { return false; } - mAms.onTouchInteractionEnd(); - - // Announce the end of the gesture recognition. - sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); - // Announce the end of a the touch interaction. - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); + endGestureDetection(); mAms.onGesture(gestureId); - mExitGestureDetectionModeDelayed.cancel(); - mCurrentState = STATE_TOUCH_EXPLORING; - return true; } + @Override + public void onGestureStarted() { + // We have to perform gesture detection, so + // clear the current state and try to detect. + mCurrentState = STATE_GESTURE_DETECTING; + mSendHoverEnterAndMoveDelayed.cancel(); + mSendHoverExitDelayed.cancel(); + mExitGestureDetectionModeDelayed.post(); + // Send accessibility event to announce the start + // of gesture recognition. + sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START); + } + + @Override + public void onGestureCancelled(MotionEvent event, int policyFlags) { + if (mCurrentState == STATE_GESTURE_DETECTING) { + endGestureDetection(); + } else if (mCurrentState == STATE_TOUCH_EXPLORING) { + final int pointerId = mReceivedPointerTracker.getPrimaryPointerId(); + final int pointerIdBits = (1 << pointerId); + + // Cache the event until we discern exploration from gesturing. + mSendHoverEnterAndMoveDelayed.addEvent(event); + + // We have just decided that the user is touch, + // exploring so start sending events. + mSendHoverEnterAndMoveDelayed.forceSendAndRemove(); + mSendHoverExitDelayed.cancel(); + sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags); + } + } + /** * Handles a motion event in touch exploring state. * @@ -471,8 +492,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe int policyFlags) { ReceivedPointerTracker receivedTracker = mReceivedPointerTracker; - mVelocityTracker.addMovement(rawEvent); - switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: { mAms.onTouchInteractionStart(); @@ -525,46 +544,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe if (mSendHoverEnterAndMoveDelayed.isPending()) { // Cache the event until we discern exploration from gesturing. mSendHoverEnterAndMoveDelayed.addEvent(event); - - // It is *important* to use the distance traveled by the pointers - // on the screen which may or may not be magnified. - final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId) - - rawEvent.getX(pointerIndex); - final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId) - - rawEvent.getY(pointerIndex); - final double moveDelta = Math.hypot(deltaX, deltaY); - // The user has moved enough for us to decide. - if (moveDelta > mDoubleTapSlop) { - // Check whether the user is performing a gesture. We - // detect gestures if the pointer is moving above a - // given velocity. - mVelocityTracker.computeCurrentVelocity(1000); - final float maxAbsVelocity = Math.max( - Math.abs(mVelocityTracker.getXVelocity(pointerId)), - Math.abs(mVelocityTracker.getYVelocity(pointerId))); - if (maxAbsVelocity > mScaledGestureDetectionVelocity) { - // We have to perform gesture detection, so - // clear the current state and try to detect. - mCurrentState = STATE_GESTURE_DETECTING; - mVelocityTracker.clear(); - mSendHoverEnterAndMoveDelayed.cancel(); - mSendHoverExitDelayed.cancel(); - mExitGestureDetectionModeDelayed.post(); - // Send accessibility event to announce the start - // of gesture recognition. - sendAccessibilityEvent( - AccessibilityEvent.TYPE_GESTURE_DETECTION_START); - } else { - // We have just decided that the user is touch, - // exploring so start sending events. - mGestureDetector.cancelGesture(); - mSendHoverEnterAndMoveDelayed.forceSendAndRemove(); - mSendHoverExitDelayed.cancel(); - sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, - pointerIdBits, policyFlags); - } - break; - } } else { if (mTouchExplorationInProgress) { sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); @@ -602,11 +581,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe } } - // We know that a new state transition is to happen and the - // new state will not be gesture recognition, so cancel - // the gesture. - mGestureDetector.cancelGesture(); - if (isDraggingGesture(event)) { // Two pointers moving in the same direction within // a given distance perform a drag. @@ -620,7 +594,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe mCurrentState = STATE_DELEGATING; sendDownForAllNotInjectedPointers(event, policyFlags); } - mVelocityTracker.clear(); } break; default: { // More than one pointer so the user is not touch exploring @@ -639,7 +612,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe // More than two pointers are delegated to the view hierarchy. mCurrentState = STATE_DELEGATING; sendDownForAllNotInjectedPointers(event, policyFlags); - mVelocityTracker.clear(); } } } break; @@ -648,8 +620,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe final int pointerId = event.getPointerId(event.getActionIndex()); final int pointerIdBits = (1 << pointerId); - mVelocityTracker.clear(); - if (mSendHoverEnterAndMoveDelayed.isPending()) { // If we have not delivered the enter schedule an exit. mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags); @@ -663,9 +633,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe } } break; - case MotionEvent.ACTION_CANCEL: { - clear(event, policyFlags); - } break; } } @@ -756,9 +723,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe } mCurrentState = STATE_TOUCH_EXPLORING; } break; - case MotionEvent.ACTION_CANCEL: { - clear(event, policyFlags); - } break; } } @@ -795,9 +759,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe mCurrentState = STATE_TOUCH_EXPLORING; } break; - case MotionEvent.ACTION_CANCEL: { - clear(event, policyFlags); - } break; default: { // Deliver the event. sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); @@ -805,22 +766,16 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe } } - private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) { - switch (event.getActionMasked()) { - case MotionEvent.ACTION_UP: { - mAms.onTouchInteractionEnd(); - // Announce the end of the gesture recognition. - sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); - // Announce the end of a the touch interaction. - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); + private void endGestureDetection() { + mAms.onTouchInteractionEnd(); - mExitGestureDetectionModeDelayed.cancel(); - mCurrentState = STATE_TOUCH_EXPLORING; - } break; - case MotionEvent.ACTION_CANCEL: { - clear(event, policyFlags); - } break; - } + // Announce the end of the gesture recognition. + sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); + // Announce the end of a the touch interaction. + sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); + + mExitGestureDetectionModeDelayed.cancel(); + mCurrentState = STATE_TOUCH_EXPLORING; } /**