Merge "Make AccessibilityGestureDetector handle gesture detection start and end."

This commit is contained in:
Zach Kuznia
2016-02-02 18:31:35 +00:00
committed by Android (Google) Code Review
2 changed files with 140 additions and 109 deletions

View File

@@ -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<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(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) {

View File

@@ -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;
}
/**