From abfaf269a9922cd338a4ec4134f29fc35ccfd346 Mon Sep 17 00:00:00 2001 From: Ameer Armaly Date: Tue, 28 Jul 2020 13:49:15 -0700 Subject: [DO NOT MERGE] Enable two-finger passthrough swipes. Bug: 162521649 Test: atest GestureManifoldTest TouchExplorerTest AccessibilityGestureDetectorTest FrameworksServicesTests:TouchExplorerTest Change-Id: I9e6b691295a0fa006578d7b8ad8468579c66939c --- .../AccessibilityServiceInfo.java | 15 +++ .../AbstractAccessibilityServiceConnection.java | 12 +- .../accessibility/AccessibilityInputFilter.java | 13 +- .../accessibility/AccessibilityManagerService.java | 6 + .../accessibility/AccessibilityUserState.java | 12 ++ .../accessibility/gestures/GestureManifold.java | 35 ++++- .../accessibility/gestures/TouchExplorer.java | 141 ++++++++++++++++----- .../accessibility/gestures/TouchExplorerTest.java | 14 +- 8 files changed, 206 insertions(+), 42 deletions(-) diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index ca22bf4a62dc..d334de60713a 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -364,6 +364,18 @@ public class AccessibilityServiceInfo implements Parcelable { */ public static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x0001000; + /** + * This flag requests that when when {@link #FLAG_REQUEST_MULTI_FINGER_GESTURES} is enabled, + * two-finger passthrough gestures are re-enabled. Two-finger swipe gestures are not detected, + * but instead passed through as one-finger gestures. In addition, three-finger swipes from the + * bottom of the screen are not detected, and instead are passed through unchanged. If {@link + * #FLAG_REQUEST_MULTI_FINGER_GESTURES} is disabled this flag has no effect. + * + * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE + * @hide + */ + public static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 0x0002000; + /** {@hide} */ public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000; @@ -624,6 +636,7 @@ public class AccessibilityServiceInfo implements Parcelable { 0); flags = asAttributes.getInt( com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0); + flags |= FLAG_REQUEST_2_FINGER_PASSTHROUGH; mSettingsActivityName = asAttributes.getString( com.android.internal.R.styleable.AccessibilityService_settingsActivity); if (asAttributes.getBoolean(com.android.internal.R.styleable @@ -1261,6 +1274,8 @@ public class AccessibilityServiceInfo implements Parcelable { return "FLAG_SERVICE_HANDLES_DOUBLE_TAP"; case FLAG_REQUEST_MULTI_FINGER_GESTURES: return "FLAG_REQUEST_MULTI_FINGER_GESTURES"; + case FLAG_REQUEST_2_FINGER_PASSTHROUGH: + return "FLAG_REQUEST_2_FINGER_PASSTHROUGH"; case FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY: return "FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY"; case FLAG_REPORT_VIEW_IDS: diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 40a2816ee7de..8822d55cc30f 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -148,6 +148,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ private boolean mRequestMultiFingerGestures; + private boolean mRequestTwoFingerPassthrough; + boolean mRequestFilterKeyEvents; boolean mRetrieveInteractiveWindows; @@ -323,8 +325,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ & AccessibilityServiceInfo.FLAG_SERVICE_HANDLES_DOUBLE_TAP) != 0; mRequestMultiFingerGestures = (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0; - mRequestFilterKeyEvents = (info.flags - & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0; + mRequestTwoFingerPassthrough = + (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0; + mRequestFilterKeyEvents = + (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0; mRetrieveInteractiveWindows = (info.flags & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0; mCaptureFingerprintGestures = (info.flags @@ -1773,6 +1777,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return mRequestMultiFingerGestures; } + public boolean isTwoFingerPassthroughEnabled() { + return mRequestTwoFingerPassthrough; + } + @Override public void setGestureDetectionPassthroughRegion(int displayId, Region region) { mSystemSupport.setGestureDetectionPassthroughRegion(displayId, region); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 020f2253743d..a04a7c570b5c 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -114,6 +114,13 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo */ static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x00000100; + /** + * Flag for enabling multi-finger gestures. + * + * @see #setUserAndEnabledFeatures(int, int) + */ + static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 0x00000200; + static final int FEATURES_AFFECTING_MOTION_EVENTS = FLAG_FEATURE_INJECT_MOTION_EVENTS | FLAG_FEATURE_AUTOCLICK @@ -121,7 +128,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo | FLAG_FEATURE_SCREEN_MAGNIFIER | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER | FLAG_SERVICE_HANDLES_DOUBLE_TAP - | FLAG_REQUEST_MULTI_FINGER_GESTURES; + | FLAG_REQUEST_MULTI_FINGER_GESTURES + | FLAG_REQUEST_2_FINGER_PASSTHROUGH; private final Context mContext; @@ -417,6 +425,9 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo if ((mEnabledFeatures & FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0) { explorer.setMultiFingerGesturesEnabled(true); } + if ((mEnabledFeatures & FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0) { + explorer.setTwoFingerPassthroughEnabled(true); + } addFirstEventHandler(displayId, explorer); mTouchExplorer.put(displayId, explorer); } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 499a2711d8e6..75fcc4c490ea 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1742,6 +1742,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (userState.isMultiFingerGesturesEnabledLocked()) { flags |= AccessibilityInputFilter.FLAG_REQUEST_MULTI_FINGER_GESTURES; } + if (userState.isTwoFingerPassthroughEnabledLocked()) { + flags |= AccessibilityInputFilter.FLAG_REQUEST_2_FINGER_PASSTHROUGH; + } } if (userState.isFilterKeyEventsEnabledLocked()) { flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS; @@ -2020,6 +2023,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub boolean touchExplorationEnabled = mUiAutomationManager.isTouchExplorationEnabledLocked(); boolean serviceHandlesDoubleTapEnabled = false; boolean requestMultiFingerGestures = false; + boolean requestTwoFingerPassthrough = false; final int serviceCount = userState.mBoundServices.size(); for (int i = 0; i < serviceCount; i++) { AccessibilityServiceConnection service = userState.mBoundServices.get(i); @@ -2027,6 +2031,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub touchExplorationEnabled = true; serviceHandlesDoubleTapEnabled = service.isServiceHandlesDoubleTapEnabled(); requestMultiFingerGestures = service.isMultiFingerGesturesEnabled(); + requestTwoFingerPassthrough = service.isTwoFingerPassthroughEnabled(); break; } } @@ -2043,6 +2048,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } userState.setServiceHandlesDoubleTapLocked(serviceHandlesDoubleTapEnabled); userState.setMultiFingerGesturesLocked(requestMultiFingerGestures); + userState.setTwoFingerPassthroughLocked(requestTwoFingerPassthrough); } private boolean readAccessibilityShortcutKeySettingLocked(AccessibilityUserState userState) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index 0845d019c060..299a776df1d0 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -109,6 +109,7 @@ class AccessibilityUserState { private boolean mIsTouchExplorationEnabled; private boolean mServiceHandlesDoubleTap; private boolean mRequestMultiFingerGestures; + private boolean mRequestTwoFingerPassthrough; private int mUserInteractiveUiTimeout; private int mUserNonInteractiveUiTimeout; private int mNonInteractiveUiTimeout = 0; @@ -160,6 +161,7 @@ class AccessibilityUserState { mIsTouchExplorationEnabled = false; mServiceHandlesDoubleTap = false; mRequestMultiFingerGestures = false; + mRequestTwoFingerPassthrough = false; mIsDisplayMagnificationEnabled = false; mIsAutoclickEnabled = false; mUserNonInteractiveUiTimeout = 0; @@ -446,6 +448,8 @@ class AccessibilityUserState { .append(String.valueOf(mServiceHandlesDoubleTap)); pw.append(", requestMultiFingerGestures=") .append(String.valueOf(mRequestMultiFingerGestures)); + pw.append(", requestTwoFingerPassthrough=") + .append(String.valueOf(mRequestTwoFingerPassthrough)); pw.append(", displayMagnificationEnabled=").append(String.valueOf( mIsDisplayMagnificationEnabled)); pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled)); @@ -733,6 +737,14 @@ class AccessibilityUserState { public void setMultiFingerGesturesLocked(boolean enabled) { mRequestMultiFingerGestures = enabled; } + public boolean isTwoFingerPassthroughEnabledLocked() { + return mRequestTwoFingerPassthrough; + } + + public void setTwoFingerPassthroughLocked(boolean enabled) { + mRequestTwoFingerPassthrough = enabled; + } + public int getUserInteractiveUiTimeoutLocked() { return mUserInteractiveUiTimeout; diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java index e9c70c60a322..8604fe7a7359 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java @@ -94,8 +94,13 @@ class GestureManifold implements GestureMatcher.StateChangeListener { private boolean mServiceHandlesDoubleTap = false; // Whether multi-finger gestures are enabled. boolean mMultiFingerGesturesEnabled; + // Whether the two-finger passthrough is enabled when multi-finger gestures are enabled. + private boolean mTwoFingerPassthroughEnabled; // A list of all the multi-finger gestures, for easy adding and removal. private final List mMultiFingerGestures = new ArrayList<>(); + // A list of two-finger swipes, for easy adding and removal when turning on or off two-finger + // passthrough. + private final List mTwoFingerSwipes = new ArrayList<>(); // Shared state information. private TouchState mState; @@ -105,6 +110,7 @@ class GestureManifold implements GestureMatcher.StateChangeListener { mListener = listener; mState = state; mMultiFingerGesturesEnabled = false; + mTwoFingerPassthroughEnabled = false; // Set up gestures. // Start with double tap. mGestures.add(new MultiTap(context, 2, GESTURE_DOUBLE_TAP, this)); @@ -161,14 +167,14 @@ class GestureManifold implements GestureMatcher.StateChangeListener { mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 4, 3, GESTURE_4_FINGER_TRIPLE_TAP, this)); // Two-finger swipes. - mMultiFingerGestures.add( + mTwoFingerSwipes.add( new MultiFingerSwipe(context, 2, DOWN, GESTURE_2_FINGER_SWIPE_DOWN, this)); - mMultiFingerGestures.add( + mTwoFingerSwipes.add( new MultiFingerSwipe(context, 2, LEFT, GESTURE_2_FINGER_SWIPE_LEFT, this)); - mMultiFingerGestures.add( + mTwoFingerSwipes.add( new MultiFingerSwipe(context, 2, RIGHT, GESTURE_2_FINGER_SWIPE_RIGHT, this)); - mMultiFingerGestures.add( - new MultiFingerSwipe(context, 2, UP, GESTURE_2_FINGER_SWIPE_UP, this)); + mTwoFingerSwipes.add(new MultiFingerSwipe(context, 2, UP, GESTURE_2_FINGER_SWIPE_UP, this)); + mMultiFingerGestures.addAll(mTwoFingerSwipes); // Three-finger swipes. mMultiFingerGestures.add( new MultiFingerSwipe(context, 3, DOWN, GESTURE_3_FINGER_SWIPE_DOWN, this)); @@ -360,6 +366,25 @@ class GestureManifold implements GestureMatcher.StateChangeListener { } } + public boolean isTwoFingerPassthroughEnabled() { + return mTwoFingerPassthroughEnabled; + } + + public void setTwoFingerPassthroughEnabled(boolean mode) { + if (mTwoFingerPassthroughEnabled != mode) { + mTwoFingerPassthroughEnabled = mode; + if (!mode) { + mMultiFingerGestures.addAll(mTwoFingerSwipes); + if (mMultiFingerGesturesEnabled) { + mGestures.addAll(mTwoFingerSwipes); + } + } else { + mMultiFingerGestures.removeAll(mTwoFingerSwipes); + mGestures.removeAll(mTwoFingerSwipes); + } + } + } + public void setServiceHandlesDoubleTap(boolean mode) { mServiceHandlesDoubleTap = mode; } diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index fbc986bdd730..b2b37ddbb31e 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -24,6 +24,7 @@ import android.accessibilityservice.AccessibilityGestureEvent; import android.content.Context; import android.graphics.Region; import android.os.Handler; +import android.util.DisplayMetrics; import android.util.Slog; import android.view.InputDevice; import android.view.MotionEvent; @@ -73,12 +74,21 @@ public class TouchExplorer extends BaseEventStreamTransformation // The timeout after which we are no longer trying to detect a gesture. private static final int EXIT_GESTURE_DETECTION_TIMEOUT = 2000; + // The height of the top and bottom edges for edge-swipes. + // For now this is only used to allow three-finger edge-swipes from the bottom. + private static final float EDGE_SWIPE_HEIGHT_CM = 0.25f; + + // The calculated edge height for the top and bottom edges. + private final float mEdgeSwipeHeightPixels; // Timeout before trying to decide what the user is trying to do. private final int mDetermineUserIntentTimeout; // Slop between the first and second tap to be a double tap. private final int mDoubleTapSlop; + // Slop to move before being considered a move rather than a tap. + private final int mTouchSlop; + // The current state of the touch explorer. private TouchState mState; @@ -151,6 +161,9 @@ public class TouchExplorer extends BaseEventStreamTransformation mDispatcher = new EventDispatcher(context, mAms, super.getNext(), mState); mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout(); mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); + mEdgeSwipeHeightPixels = metrics.ydpi / GestureUtils.CM_PER_INCH * EDGE_SWIPE_HEIGHT_CM; mHandler = new Handler(context.getMainLooper()); mExitGestureDetectionModeDelayed = new ExitGestureDetectionModeDelayed(); mSendHoverEnterAndMoveDelayed = new SendHoverEnterAndMoveDelayed(); @@ -196,16 +209,10 @@ public class TouchExplorer extends BaseEventStreamTransformation if (mState.isTouchExploring()) { // If a touch exploration gesture is in progress send events for its end. sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); - } else if (mState.isDragging()) { - mDraggingPointerId = INVALID_POINTER_ID; - // Send exit to all pointers that we have delivered. - mDispatcher.sendUpForInjectedDownPointers(event, policyFlags); - } else if (mState.isDelegating()) { - // Send exit to all pointers that we have delivered. - mDispatcher.sendUpForInjectedDownPointers(event, policyFlags); - } else if (mState.isGestureDetecting()) { - // No state specific cleanup required. } + mDraggingPointerId = INVALID_POINTER_ID; + // Send exit to any pointers that we have delivered as part of delegating or dragging. + mDispatcher.sendUpForInjectedDownPointers(event, policyFlags); // Remove all pending callbacks. mSendHoverEnterAndMoveDelayed.cancel(); mSendHoverExitDelayed.cancel(); @@ -532,7 +539,26 @@ public class TouchExplorer extends BaseEventStreamTransformation // stream consistent. sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } + if (mGestureDetector.isMultiFingerGesturesEnabled() + && mGestureDetector.isTwoFingerPassthroughEnabled()) { + if (event.getPointerCount() == 3) { + boolean isOnBottomEdge = false; + // If three fingers go down on the bottom edge of the screen, delegate immediately. + final long screenHeight = mContext.getResources().getDisplayMetrics().heightPixels; + for (int i = 0; i < TouchState.MAX_POINTER_COUNT; ++i) { + if (mReceivedPointerTracker.getReceivedPointerDownY(i) + > (screenHeight - mEdgeSwipeHeightPixels)) { + isOnBottomEdge = true; + } + } + if (isOnBottomEdge) { + mState.startDelegating(); + mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags); + } + } + } } + /** * Handles ACTION_MOVE while in the touch interacting state. This is where transitions to * delegating and dragging states are handled. @@ -541,7 +567,7 @@ public class TouchExplorer extends BaseEventStreamTransformation MotionEvent event, MotionEvent rawEvent, int policyFlags) { final int pointerId = mReceivedPointerTracker.getPrimaryPointerId(); final int pointerIndex = event.findPointerIndex(pointerId); - final int pointerIdBits = (1 << pointerId); + int pointerIdBits = (1 << pointerId); switch (event.getPointerCount()) { case 1: // We have not started sending events since we try to @@ -552,12 +578,26 @@ public class TouchExplorer extends BaseEventStreamTransformation } break; case 2: - if (mGestureDetector.isMultiFingerGesturesEnabled()) { + if (mGestureDetector.isMultiFingerGesturesEnabled() + && !mGestureDetector.isTwoFingerPassthroughEnabled()) { return; } // Make sure we don't have any pending transitions to touch exploration mSendHoverEnterAndMoveDelayed.cancel(); mSendHoverExitDelayed.cancel(); + if (mGestureDetector.isMultiFingerGesturesEnabled() + && mGestureDetector.isTwoFingerPassthroughEnabled()) { + 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 < mTouchSlop) { + return; + } + } // 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 @@ -566,8 +606,8 @@ public class TouchExplorer extends BaseEventStreamTransformation // Two pointers moving in the same direction within // a given distance perform a drag. mState.startDragging(); - mDraggingPointerId = pointerId; adjustEventLocationForDrag(event); + pointerIdBits = 1 << mDraggingPointerId; event.setEdgeFlags(mReceivedPointerTracker.getLastReceivedDownEdgeFlags()); mDispatcher.sendMotionEvent( event, MotionEvent.ACTION_DOWN, rawEvent, pointerIdBits, policyFlags); @@ -626,7 +666,8 @@ public class TouchExplorer extends BaseEventStreamTransformation event, MotionEvent.ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags); break; case 2: - if (mGestureDetector.isMultiFingerGesturesEnabled()) { + if (mGestureDetector.isMultiFingerGesturesEnabled() + && !mGestureDetector.isTwoFingerPassthroughEnabled()) { return; } if (mSendHoverEnterAndMoveDelayed.isPending()) { @@ -681,7 +722,8 @@ public class TouchExplorer extends BaseEventStreamTransformation */ private void handleMotionEventStateDragging( MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (mGestureDetector.isMultiFingerGesturesEnabled()) { + if (mGestureDetector.isMultiFingerGesturesEnabled() + && !mGestureDetector.isTwoFingerPassthroughEnabled()) { // Multi-finger gestures conflict with this functionality. return; } @@ -734,6 +776,7 @@ public class TouchExplorer extends BaseEventStreamTransformation // The two pointers are moving either in different directions or // no close enough => delegate the gesture to the view hierarchy. mState.startDelegating(); + mDraggingPointerId = INVALID_POINTER_ID; // Remove move history before send injected non-move events event = MotionEvent.obtainNoHistory(event); // Send an event to the end of the drag gesture. @@ -749,6 +792,7 @@ public class TouchExplorer extends BaseEventStreamTransformation } break; default: { mState.startDelegating(); + mDraggingPointerId = INVALID_POINTER_ID; event = MotionEvent.obtainNoHistory(event); // Send an event to the end of the drag gesture. mDispatcher.sendMotionEvent( @@ -772,17 +816,15 @@ public class TouchExplorer extends BaseEventStreamTransformation } } break; case MotionEvent.ACTION_UP: { - mAms.onTouchInteractionEnd(); - // Announce the end of a new touch interaction. - mDispatcher.sendAccessibilityEvent( - AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); final int pointerId = event.getPointerId(event.getActionIndex()); if (pointerId == mDraggingPointerId) { - mDraggingPointerId = INVALID_POINTER_ID; - // Send an event to the end of the drag gesture. mDispatcher.sendMotionEvent( event, MotionEvent.ACTION_UP, rawEvent, pointerIdBits, policyFlags); } + mAms.onTouchInteractionEnd(); + // Announce the end of a new touch interaction. + mDispatcher.sendAccessibilityEvent( + AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); } break; } } @@ -901,21 +943,55 @@ public class TouchExplorer extends BaseEventStreamTransformation } /** - * Adjust the location of an injected event when performing a drag The new location will be in - * between the two fingers touching the screen. + * Adjust the location of an injected event when performing a drag. The location will be the + * location of the finger closest to an edge of the screen. */ private void adjustEventLocationForDrag(MotionEvent event) { - final float firstPtrX = event.getX(0); final float firstPtrY = event.getY(0); + final int firstPtrId = event.getPointerId(0); final float secondPtrX = event.getX(1); final float secondPtrY = event.getY(1); - final int pointerIndex = event.findPointerIndex(mDraggingPointerId); - final float deltaX = - (pointerIndex == 0) ? (secondPtrX - firstPtrX) : (firstPtrX - secondPtrX); - final float deltaY = - (pointerIndex == 0) ? (secondPtrY - firstPtrY) : (firstPtrY - secondPtrY); - event.offsetLocation(deltaX / 2, deltaY / 2); + final int secondPtrId = event.getPointerId(1); + float draggingX; + float draggingY; + if (mDraggingPointerId == INVALID_POINTER_ID) { + // The goal is to use the coordinates of the finger that is closest to its closest edge. + if (getDistanceToClosestEdge(firstPtrX, firstPtrY) + < getDistanceToClosestEdge(secondPtrX, secondPtrY)) { + draggingX = firstPtrX; + draggingY = firstPtrY; + mDraggingPointerId = firstPtrId; + } else { + draggingX = secondPtrX; + draggingY = secondPtrY; + mDraggingPointerId = secondPtrId; + } + } else { + // Just use the coordinates of the dragging pointer. + int pointerIndex = event.findPointerIndex(mDraggingPointerId); + draggingX = event.getX(pointerIndex); + draggingY = event.getY(pointerIndex); + } + event.setLocation(draggingX, draggingY); + } + + private float getDistanceToClosestEdge(float x, float y) { + final long width = mContext.getResources().getDisplayMetrics().widthPixels; + final long height = mContext.getResources().getDisplayMetrics().heightPixels; + float distance = Float.MAX_VALUE; + if (x < (width - x)) { + distance = x; + } else { + distance = width - x; + } + if (distance > y) { + distance = y; + } + if (distance > (height - y)) { + distance = (height - y); + } + return distance; } public TouchState getState() { @@ -944,6 +1020,13 @@ public class TouchExplorer extends BaseEventStreamTransformation mGestureDetector.setMultiFingerGesturesEnabled(enabled); } + /** + * This function turns on and off two-finger passthrough gestures such as drag and pinch when + * multi-finger gestures are enabled. + */ + public void setTwoFingerPassthroughEnabled(boolean enabled) { + mGestureDetector.setTwoFingerPassthroughEnabled(enabled); + } public void setGestureDetectionPassthroughRegion(Region region) { mGestureDetectionPassthroughRegion = region; } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java index 9053234aa220..f929321a61be 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java @@ -31,6 +31,7 @@ import android.os.SystemClock; import android.testing.DexmakerShareClassLoaderRule; import android.view.InputDevice; import android.view.MotionEvent; +import android.view.ViewConfiguration; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -72,6 +73,8 @@ public class TouchExplorerTest { private EventStreamTransformation mCaptor; private MotionEvent mLastEvent; private TouchExplorer mTouchExplorer; + private Context mContext; + private int mTouchSlop; private long mLastDownTime = Integer.MIN_VALUE; // mock package-private GestureManifold class @@ -109,11 +112,12 @@ public class TouchExplorerTest { @Before public void setUp() { - Context context = InstrumentationRegistry.getContext(); - AccessibilityManagerService ams = new AccessibilityManagerService(context); + mContext = InstrumentationRegistry.getContext(); + mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); + AccessibilityManagerService ams = new AccessibilityManagerService(mContext); GestureManifold detector = mock(GestureManifold.class); mCaptor = new EventCaptor(); - mTouchExplorer = new TouchExplorer(context, ams, detector); + mTouchExplorer = new TouchExplorer(mContext, ams, detector); mTouchExplorer.setNext(mCaptor); } @@ -226,13 +230,13 @@ public class TouchExplorerTest { break; case STATE_DRAGGING_2FINGERS: { goFromStateClearTo(STATE_TOUCH_EXPLORING_2FINGER); - moveEachPointers(mLastEvent, p(10, 0), p(10, 0)); + moveEachPointers(mLastEvent, p(mTouchSlop, 0), p(mTouchSlop, 0)); send(mLastEvent); } break; case STATE_PINCH_2FINGERS: { goFromStateClearTo(STATE_DRAGGING_2FINGERS); - moveEachPointers(mLastEvent, p(10, 0), p(-10, 1)); + moveEachPointers(mLastEvent, p(mTouchSlop, 0), p(-mTouchSlop, 1)); send(mLastEvent); } break; -- cgit v1.2.3-59-g8ed1b From c39597b6343c19a08a95b195c06b4054678b6fe2 Mon Sep 17 00:00:00 2001 From: Phil Weaver Date: Fri, 7 Aug 2020 18:07:16 +0000 Subject: [DO NOT MERGE] Fix crash with multifinger touch exploration Range check index into motion event x and y before using it. Bug: 163107812 Test: Relying on treehugger. I can't reproduce the crash, so I'm just adding checks. Change-Id: I4ac5023ef9b5c101748b870a01a425a1365fb85c --- .../accessibility/gestures/TouchExplorer.java | 28 +++++++++++++++------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index b2b37ddbb31e..a4961178cc89 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -587,6 +587,9 @@ public class TouchExplorer extends BaseEventStreamTransformation mSendHoverExitDelayed.cancel(); if (mGestureDetector.isMultiFingerGesturesEnabled() && mGestureDetector.isTwoFingerPassthroughEnabled()) { + if (pointerIndex < 0) { + return; + } final float deltaX = mReceivedPointerTracker.getReceivedPointerDownX(pointerId) - rawEvent.getX(pointerIndex); @@ -953,25 +956,32 @@ public class TouchExplorer extends BaseEventStreamTransformation final float secondPtrX = event.getX(1); final float secondPtrY = event.getY(1); final int secondPtrId = event.getPointerId(1); - float draggingX; - float draggingY; + float draggingX = firstPtrX; + float draggingY = firstPtrY; + if (mDraggingPointerId != INVALID_POINTER_ID) { + // Just use the coordinates of the dragging pointer. + int pointerIndex = event.findPointerIndex(mDraggingPointerId); + if (pointerIndex >= 0) { + draggingX = event.getX(pointerIndex); + draggingY = event.getY(pointerIndex); + } else { + // We've lost track of the dragging pointer. Try to recover by invalidating it. + // We'll the drop into the code below to choose a new one. + mDraggingPointerId = INVALID_POINTER_ID; + } + } + // Not quite an else, since the above code can invalidate the pointer if (mDraggingPointerId == INVALID_POINTER_ID) { // The goal is to use the coordinates of the finger that is closest to its closest edge. if (getDistanceToClosestEdge(firstPtrX, firstPtrY) < getDistanceToClosestEdge(secondPtrX, secondPtrY)) { - draggingX = firstPtrX; - draggingY = firstPtrY; + // X and Y initialized to firstPtrX and Y was right mDraggingPointerId = firstPtrId; } else { draggingX = secondPtrX; draggingY = secondPtrY; mDraggingPointerId = secondPtrId; } - } else { - // Just use the coordinates of the dragging pointer. - int pointerIndex = event.findPointerIndex(mDraggingPointerId); - draggingX = event.getX(pointerIndex); - draggingY = event.getY(pointerIndex); } event.setLocation(draggingX, draggingY); } -- cgit v1.2.3-59-g8ed1b From 1f49d41a6a637f04e29a843bdef27709eb49557d Mon Sep 17 00:00:00 2001 From: Phil Weaver Date: Fri, 7 Aug 2020 22:25:17 +0000 Subject: [DO NOT MERGE] Address drag unreliability during touch exploration The code was trying to set the location of the drag, but MotionEvent doesn't behave consistently when setting a single location to a multi-touch event. All that's needed is to select the correct pointer, so removing messing with the location gives more consistent behavior. Bug: 163105030 Change-Id: I6c315cf0a1e48847cc778ec8c34b382d13ce3555 Test: Manually testing at this point of the passthrough, and relying on treehugger. --- .../accessibility/gestures/TouchExplorer.java | 49 +++++++--------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index a4961178cc89..b5417cc1504d 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -609,7 +609,7 @@ public class TouchExplorer extends BaseEventStreamTransformation // Two pointers moving in the same direction within // a given distance perform a drag. mState.startDragging(); - adjustEventLocationForDrag(event); + computeDraggingPointerIdIfNeeded(event); pointerIdBits = 1 << mDraggingPointerId; event.setEdgeFlags(mReceivedPointerTracker.getLastReceivedDownEdgeFlags()); mDispatcher.sendMotionEvent( @@ -768,7 +768,7 @@ public class TouchExplorer extends BaseEventStreamTransformation case 2: { if (isDraggingGesture(event)) { // If still dragging send a drag event. - adjustEventLocationForDrag(event); + computeDraggingPointerIdIfNeeded(event); mDispatcher.sendMotionEvent( event, MotionEvent.ACTION_MOVE, @@ -946,44 +946,27 @@ public class TouchExplorer extends BaseEventStreamTransformation } /** - * Adjust the location of an injected event when performing a drag. The location will be the - * location of the finger closest to an edge of the screen. + * Computes {@link #mDraggingPointerId} if it is invalid. The pointer will be the finger + * closet to an edge of the screen. */ - private void adjustEventLocationForDrag(MotionEvent event) { + private void computeDraggingPointerIdIfNeeded(MotionEvent event) { + if (mDraggingPointerId != INVALID_POINTER_ID) { + // If we have a valid pointer ID, we should be good + final int pointerIndex = event.findPointerIndex(mDraggingPointerId); + if (event.findPointerIndex(pointerIndex) >= 0) { + return; + } + } + // Use the pointer that is closest to its closest edge. final float firstPtrX = event.getX(0); final float firstPtrY = event.getY(0); final int firstPtrId = event.getPointerId(0); final float secondPtrX = event.getX(1); final float secondPtrY = event.getY(1); final int secondPtrId = event.getPointerId(1); - float draggingX = firstPtrX; - float draggingY = firstPtrY; - if (mDraggingPointerId != INVALID_POINTER_ID) { - // Just use the coordinates of the dragging pointer. - int pointerIndex = event.findPointerIndex(mDraggingPointerId); - if (pointerIndex >= 0) { - draggingX = event.getX(pointerIndex); - draggingY = event.getY(pointerIndex); - } else { - // We've lost track of the dragging pointer. Try to recover by invalidating it. - // We'll the drop into the code below to choose a new one. - mDraggingPointerId = INVALID_POINTER_ID; - } - } - // Not quite an else, since the above code can invalidate the pointer - if (mDraggingPointerId == INVALID_POINTER_ID) { - // The goal is to use the coordinates of the finger that is closest to its closest edge. - if (getDistanceToClosestEdge(firstPtrX, firstPtrY) - < getDistanceToClosestEdge(secondPtrX, secondPtrY)) { - // X and Y initialized to firstPtrX and Y was right - mDraggingPointerId = firstPtrId; - } else { - draggingX = secondPtrX; - draggingY = secondPtrY; - mDraggingPointerId = secondPtrId; - } - } - event.setLocation(draggingX, draggingY); + mDraggingPointerId = (getDistanceToClosestEdge(firstPtrX, firstPtrY) + < getDistanceToClosestEdge(secondPtrX, secondPtrY)) + ? firstPtrId : secondPtrId; } private float getDistanceToClosestEdge(float x, float y) { -- cgit v1.2.3-59-g8ed1b From aa25d2041fd1e53058f8a6342172231de3822f79 Mon Sep 17 00:00:00 2001 From: Ameer Armaly Date: Tue, 18 Aug 2020 13:49:44 -0700 Subject: [DO NOT MERGE] Start dragging where the fingers initially went down. This does not apply when transitioning from touch exploration to dragging. Bug: 162521649 Test: manual Change-Id: I92022ca0359040a6563e6a48c110face574dc9df --- .../accessibility/gestures/TouchExplorer.java | 57 ++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index b5417cc1504d..90806969816e 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -608,12 +608,20 @@ public class TouchExplorer extends BaseEventStreamTransformation if (isDraggingGesture(event)) { // Two pointers moving in the same direction within // a given distance perform a drag. - mState.startDragging(); computeDraggingPointerIdIfNeeded(event); pointerIdBits = 1 << mDraggingPointerId; event.setEdgeFlags(mReceivedPointerTracker.getLastReceivedDownEdgeFlags()); - mDispatcher.sendMotionEvent( - event, MotionEvent.ACTION_DOWN, rawEvent, pointerIdBits, policyFlags); + MotionEvent downEvent = computeDownEventForDrag(event); + if (downEvent != null) { + mDispatcher.sendMotionEvent(downEvent, MotionEvent.ACTION_DOWN, rawEvent, + pointerIdBits, policyFlags); + mDispatcher.sendMotionEvent(event, MotionEvent.ACTION_MOVE, rawEvent, + pointerIdBits, policyFlags); + } else { + mDispatcher.sendMotionEvent(event, MotionEvent.ACTION_DOWN, rawEvent, + pointerIdBits, policyFlags); + } + mState.startDragging(); } else { // Two pointers moving arbitrary are delegated to the view hierarchy. mState.startDelegating(); @@ -987,6 +995,49 @@ public class TouchExplorer extends BaseEventStreamTransformation return distance; } + /** + * Creates a down event using the down coordinates of the dragging pointer and other information + * from the supplied event. The supplied event's down time is adjusted to reflect the time when + * the dragging pointer initially went down. + */ + private MotionEvent computeDownEventForDrag(MotionEvent event) { + // Creating a down event only makes sense if we haven't started touch exploring yet. + if (mState.isTouchExploring() + || mDraggingPointerId == INVALID_POINTER_ID + || event == null) { + return null; + } + final float x = mReceivedPointerTracker.getReceivedPointerDownX(mDraggingPointerId); + final float y = mReceivedPointerTracker.getReceivedPointerDownY(mDraggingPointerId); + final long time = mReceivedPointerTracker.getReceivedPointerDownTime(mDraggingPointerId); + MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[1]; + coords[0] = new MotionEvent.PointerCoords(); + coords[0].x = x; + coords[0].y = y; + MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1]; + properties[0] = new MotionEvent.PointerProperties(); + properties[0].id = mDraggingPointerId; + properties[0].toolType = MotionEvent.TOOL_TYPE_FINGER; + MotionEvent downEvent = + MotionEvent.obtain( + time, + time, + MotionEvent.ACTION_DOWN, + 1, + properties, + coords, + event.getMetaState(), + event.getButtonState(), + event.getXPrecision(), + event.getYPrecision(), + event.getDeviceId(), + event.getEdgeFlags(), + event.getSource(), + event.getFlags()); + event.setDownTime(time); + return downEvent; + } + public TouchState getState() { return mState; } -- cgit v1.2.3-59-g8ed1b From a7da0f4f6d9a0c5e7959831867ddc5c3bd1baf43 Mon Sep 17 00:00:00 2001 From: Ameer Armaly Date: Tue, 18 Aug 2020 18:09:42 -0700 Subject: [DO NOT MERGE] Require both fingers to move before starting two-finger passthrough. Bug: 162521649 Test: manual Change-Id: Ia6b7bf84fd0c7777f21afbd0e0c09bf3f4a10fb2 --- .../accessibility/gestures/TouchExplorer.java | 26 ++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index 90806969816e..5509b3fdaa1b 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -590,15 +590,23 @@ public class TouchExplorer extends BaseEventStreamTransformation if (pointerIndex < 0) { return; } - 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 < mTouchSlop) { - return; + // Require both fingers to have moved a certain amount before starting a drag. + for (int index = 0; index < event.getPointerCount(); ++index) { + int id = event.getPointerId(index); + if (!mReceivedPointerTracker.isReceivedPointerDown(id)) { + // Something is wrong with the event stream. + Slog.e(LOG_TAG, "Invalid pointer id: " + id); + } + final float deltaX = + mReceivedPointerTracker.getReceivedPointerDownX(id) + - rawEvent.getX(index); + final float deltaY = + mReceivedPointerTracker.getReceivedPointerDownY(id) + - rawEvent.getY(index); + final double moveDelta = Math.hypot(deltaX, deltaY); + if (moveDelta < mTouchSlop) { + return; + } } } // More than one pointer so the user is not touch exploring -- cgit v1.2.3-59-g8ed1b From 217bb3b1d2182d83101337cff0930d2696254699 Mon Sep 17 00:00:00 2001 From: Ameer Armaly Date: Tue, 11 Aug 2020 17:27:02 -0700 Subject: [DO NOT MERGE] Fix edge swipe logic. 1) Only start delegating upon first move event, if all three fingers went down on the bottom edge. 2) The previous logic only required one finger to be in the edge boundary. It now requires all fingers to be in the edge boundary. Bug: 162521649 Test: manual Change-Id: I829c463c63b384642d129ff3b5ccdb0db816ddcd --- .../accessibility/gestures/TouchExplorer.java | 50 ++++++++++++---------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index 5509b3fdaa1b..a13f59c2e549 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -539,24 +539,6 @@ public class TouchExplorer extends BaseEventStreamTransformation // stream consistent. sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } - if (mGestureDetector.isMultiFingerGesturesEnabled() - && mGestureDetector.isTwoFingerPassthroughEnabled()) { - if (event.getPointerCount() == 3) { - boolean isOnBottomEdge = false; - // If three fingers go down on the bottom edge of the screen, delegate immediately. - final long screenHeight = mContext.getResources().getDisplayMetrics().heightPixels; - for (int i = 0; i < TouchState.MAX_POINTER_COUNT; ++i) { - if (mReceivedPointerTracker.getReceivedPointerDownY(i) - > (screenHeight - mEdgeSwipeHeightPixels)) { - isOnBottomEdge = true; - } - } - if (isOnBottomEdge) { - mState.startDelegating(); - mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags); - } - } - } } /** @@ -638,12 +620,34 @@ public class TouchExplorer extends BaseEventStreamTransformation break; default: if (mGestureDetector.isMultiFingerGesturesEnabled()) { - return; + if (mGestureDetector.isTwoFingerPassthroughEnabled()) { + if (event.getPointerCount() == 3) { + boolean isOnBottomEdge = true; + // If three fingers went down on the bottom edge of the screen, delegate + // immediately. + final long screenHeight = + mContext.getResources().getDisplayMetrics().heightPixels; + for (int i = 0; i < TouchState.MAX_POINTER_COUNT; ++i) { + if (mReceivedPointerTracker.getReceivedPointerDownY(i) + < (screenHeight - mEdgeSwipeHeightPixels)) { + isOnBottomEdge = false; + } + } + if (isOnBottomEdge) { + if (DEBUG) { + Slog.d(LOG_TAG, "Three-finger edge swipe detected."); + } + mState.startDelegating(); + mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags); + } + } + } + } else { + // More than two pointers are delegated to the view hierarchy. + mState.startDelegating(); + event = MotionEvent.obtainNoHistory(event); + mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags); } - // More than two pointers are delegated to the view hierarchy. - mState.startDelegating(); - event = MotionEvent.obtainNoHistory(event); - mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags); break; } } -- cgit v1.2.3-59-g8ed1b From dd8ef30adfc6e47b6d48c2aa558c1a793ce8eab9 Mon Sep 17 00:00:00 2001 From: ryanlwlin Date: Thu, 20 Aug 2020 09:53:58 +0800 Subject: [DO NOT MERGE] Fix hardly to perform 3-finger swipe from the bottom We accidentky checked invalid pointers down position. In stead of checking all pointers, we check the pointer ids from the motion event. Bug: 165576902 Test: manually test atest TouchExplorerTest Change-Id: Id530bf4b760351215726b2afd781433b590f57ce --- .../accessibility/gestures/TouchExplorer.java | 27 ++++++++++++++-------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index a13f59c2e549..abde43076c4e 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -622,18 +622,9 @@ public class TouchExplorer extends BaseEventStreamTransformation if (mGestureDetector.isMultiFingerGesturesEnabled()) { if (mGestureDetector.isTwoFingerPassthroughEnabled()) { if (event.getPointerCount() == 3) { - boolean isOnBottomEdge = true; // If three fingers went down on the bottom edge of the screen, delegate // immediately. - final long screenHeight = - mContext.getResources().getDisplayMetrics().heightPixels; - for (int i = 0; i < TouchState.MAX_POINTER_COUNT; ++i) { - if (mReceivedPointerTracker.getReceivedPointerDownY(i) - < (screenHeight - mEdgeSwipeHeightPixels)) { - isOnBottomEdge = false; - } - } - if (isOnBottomEdge) { + if (allPointersDownOnBottomEdge(event)) { if (DEBUG) { Slog.d(LOG_TAG, "Three-finger edge swipe detected."); } @@ -1050,6 +1041,22 @@ public class TouchExplorer extends BaseEventStreamTransformation return downEvent; } + private boolean allPointersDownOnBottomEdge(MotionEvent event) { + final long screenHeight = + mContext.getResources().getDisplayMetrics().heightPixels; + for (int i = 0; i < event.getPointerCount(); ++i) { + final int pointerId = event.getPointerId(i); + final float pointerDownY = mReceivedPointerTracker.getReceivedPointerDownY(pointerId); + if (pointerDownY < (screenHeight - mEdgeSwipeHeightPixels)) { + if (DEBUG) { + Slog.d(LOG_TAG, "The pointer is not on the bottom edge" + pointerDownY); + } + return false; + } + } + return true; + } + public TouchState getState() { return mState; } -- cgit v1.2.3-59-g8ed1b From 81570aa5ade3cbaba15ca0517e0c7a5218fe04d1 Mon Sep 17 00:00:00 2001 From: ryanlwlin Date: Tue, 25 Aug 2020 17:45:40 +0800 Subject: [DO NOT MERGE] Fix sometime couldn't trigger A11y button with 3-finger We inject the down events from where we detect the 3-finger swipe gesture. It may not trigger the A11y Button if the event is not inside the system gesture detection area. We inject the original down location to injection all down events. Bug: 166187595 Test: manually test Change-Id: I0c0f84047fc24e40724332d51d7e3dc414bd0186 --- .../accessibility/gestures/EventDispatcher.java | 69 ++++++++++++++++++++++ .../accessibility/gestures/TouchExplorer.java | 8 ++- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java index 070626be9f80..c70dfcc93e49 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java @@ -256,6 +256,7 @@ class EventDispatcher { return actionMasked; } } + /** * Sends down events to the view hierarchy for all pointers which are not already being * delivered i.e. pointers that are not yet injected. @@ -285,6 +286,74 @@ class EventDispatcher { } /** + * Sends down events to the view hierarchy for all pointers which are not already being + * delivered with original down location. i.e. pointers that are not yet injected. + * + * @param prototype The prototype from which to create the injected events. + * @param policyFlags The policy flags associated with the event. + */ + void sendDownForAllNotInjectedPointersWithOriginalDown(MotionEvent prototype, int policyFlags) { + // Inject the injected pointers. + int pointerIdBits = 0; + final int pointerCount = prototype.getPointerCount(); + final MotionEvent event = computeEventWithOriginalDown(prototype); + for (int i = 0; i < pointerCount; i++) { + final int pointerId = prototype.getPointerId(i); + // Do not send event for already delivered pointers. + if (!mState.isInjectedPointerDown(pointerId)) { + pointerIdBits |= (1 << pointerId); + final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i); + sendMotionEvent( + event, + action, + mState.getLastReceivedEvent(), + pointerIdBits, + policyFlags); + } + } + } + + private MotionEvent computeEventWithOriginalDown(MotionEvent prototype) { + final int pointerCount = prototype.getPointerCount(); + if (pointerCount != mState.getReceivedPointerTracker().getReceivedPointerDownCount()) { + Slog.w(LOG_TAG, "The pointer count doesn't match the received count."); + return MotionEvent.obtain(prototype); + } + MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointerCount]; + MotionEvent.PointerProperties[] properties = + new MotionEvent.PointerProperties[pointerCount]; + for (int i = 0; i < pointerCount; ++i) { + final int pointerId = prototype.getPointerId(i); + final float x = mState.getReceivedPointerTracker().getReceivedPointerDownX(pointerId); + final float y = mState.getReceivedPointerTracker().getReceivedPointerDownY(pointerId); + coords[i] = new MotionEvent.PointerCoords(); + coords[i].x = x; + coords[i].y = y; + properties[i] = new MotionEvent.PointerProperties(); + properties[i].id = pointerId; + properties[i].toolType = MotionEvent.TOOL_TYPE_FINGER; + } + MotionEvent event = + MotionEvent.obtain( + prototype.getDownTime(), + prototype.getEventTime(), + prototype.getAction(), + pointerCount, + properties, + coords, + prototype.getMetaState(), + prototype.getButtonState(), + prototype.getXPrecision(), + prototype.getYPrecision(), + prototype.getDeviceId(), + prototype.getEdgeFlags(), + prototype.getSource(), + prototype.getFlags()); + return event; + } + + /** + * * Sends up events to the view hierarchy for all pointers which are already being delivered i.e. * pointers that are injected. * diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index abde43076c4e..2b1e872420bc 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -629,7 +629,13 @@ public class TouchExplorer extends BaseEventStreamTransformation Slog.d(LOG_TAG, "Three-finger edge swipe detected."); } mState.startDelegating(); - mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags); + if (mState.isTouchExploring()) { + mDispatcher.sendDownForAllNotInjectedPointers(event, + policyFlags); + } else { + mDispatcher.sendDownForAllNotInjectedPointersWithOriginalDown( + event, policyFlags); + } } } } -- cgit v1.2.3-59-g8ed1b From be8a943e46f53b1bceaa112349e03dfd4bb4c2e1 Mon Sep 17 00:00:00 2001 From: Ameer Armaly Date: Tue, 25 Aug 2020 19:00:17 -0700 Subject: [DO NOT MERGE] Use correct touch slop value for two-finger passthrough. Throughout the gesture system we scale the touch slop radius to the number of fingers that are down at any given moment. Therefore the correct value to trigger two-finger passthrough should be 2 * mTouchSlop. This should make three-finger taps less problematic. Bug: 163438921 Bug: 162521649 Test: manual Change-Id: I4b36b5575bf11ad767d83effd6292dc84eadb27d Change-Id: I9fb9d58ef679151c9fc595bcb375f6209ea3a5af Change-Id: Ibcae2a5a3fde37b97859165efabf2b0df13ded9b --- .../java/com/android/server/accessibility/gestures/TouchExplorer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index 2b1e872420bc..bfb59f43aa1d 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -586,7 +586,7 @@ public class TouchExplorer extends BaseEventStreamTransformation mReceivedPointerTracker.getReceivedPointerDownY(id) - rawEvent.getY(index); final double moveDelta = Math.hypot(deltaX, deltaY); - if (moveDelta < mTouchSlop) { + if (moveDelta < (2 * mTouchSlop)) { return; } } -- cgit v1.2.3-59-g8ed1b From a070cca5e207a6f475e467adf123ecf1285f8bbf Mon Sep 17 00:00:00 2001 From: Ameer Armaly Date: Tue, 25 Aug 2020 19:05:55 -0700 Subject: [DO NOT MERGE] MultiFingerSwipe: scale touch slop to number of fingers. Bug: 162521649 Test: manual Change-Id: I990693ac07f4653f7d8ff9c097b5720593235afb --- .../com/android/server/accessibility/gestures/MultiFingerSwipe.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java index 4b89731b75b6..2a305397924f 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiFingerSwipe.java @@ -294,7 +294,7 @@ class MultiFingerSwipe extends GestureMatcher { + Float.toString(mGestureDetectionThresholdPixels)); } if (getState() == STATE_CLEAR) { - if (moveDelta < mTouchSlop) { + if (moveDelta < (mTargetFingerCount * mTouchSlop)) { // This still counts as a touch not a swipe. continue; } else if (mStrokeBuffers[pointerIndex].size() == 0) { -- cgit v1.2.3-59-g8ed1b From bf402bd69882538acacc42baec09d8f95c53a2f7 Mon Sep 17 00:00:00 2001 From: ryanlwlin Date: Thu, 27 Aug 2020 18:58:24 +0800 Subject: [DO NOT MERGE] Fix Accidently trigger A11y button long pressed We used the original down location while injecting all down events. It accidently increases the velocity in the beginning and cause Launcher detect the gesture into unexpected state. Test: manually Bug: 162521649 Change-Id: I527f3e785ee1082f86ef1a60e57ccd825bdfeae9 --- .../android/server/accessibility/gestures/EventDispatcher.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java index c70dfcc93e49..a4fec82bcf56 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/EventDispatcher.java @@ -287,7 +287,9 @@ class EventDispatcher { /** * Sends down events to the view hierarchy for all pointers which are not already being - * delivered with original down location. i.e. pointers that are not yet injected. + * delivered with original down location. i.e. pointers that are not yet injected. The down time + * is also replaced by the original one. + * * * @param prototype The prototype from which to create the injected events. * @param policyFlags The policy flags associated with the event. @@ -336,7 +338,10 @@ class EventDispatcher { MotionEvent event = MotionEvent.obtain( prototype.getDownTime(), - prototype.getEventTime(), + // The event time is used for downTime while sending ACTION_DOWN. We adjust + // it to avoid the motion velocity is too fast in the beginning after + // Delegating. + prototype.getDownTime(), prototype.getAction(), pointerCount, properties, -- cgit v1.2.3-59-g8ed1b From 0295f94356b40d4582a12dd061160d97f216b473 Mon Sep 17 00:00:00 2001 From: Ameer Armaly Date: Thu, 8 Oct 2020 08:25:00 -0700 Subject: [DO NOT MERGE ] Stop setting flag to enable two-finger passthrough swipes. Bug: 162521649 Test: atest GestureManifoldTest TouchExplorerTest AccessibilityGestureDetectorTest FrameworksServicesTests:TouchExplorerTest Change-Id: I7718ac2ae86d46aa05d45a1ac3abc556e000e39d --- core/java/android/accessibilityservice/AccessibilityServiceInfo.java | 1 - .../com/android/server/accessibility/gestures/TouchExplorerTest.java | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index d334de60713a..a41fa6431d4d 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -636,7 +636,6 @@ public class AccessibilityServiceInfo implements Parcelable { 0); flags = asAttributes.getInt( com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0); - flags |= FLAG_REQUEST_2_FINGER_PASSTHROUGH; mSettingsActivityName = asAttributes.getString( com.android.internal.R.styleable.AccessibilityService_settingsActivity); if (asAttributes.getBoolean(com.android.internal.R.styleable diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java index f929321a61be..c18cad42b494 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java @@ -162,7 +162,7 @@ public class TouchExplorerTest { goFromStateClearTo(STATE_DRAGGING_2FINGERS); assertState(STATE_DRAGGING); - assertCapturedEvents(MotionEvent.ACTION_DOWN); + assertCapturedEvents(MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE); assertCapturedEventsNoHistory(); } @@ -174,6 +174,7 @@ public class TouchExplorerTest { assertState(STATE_DELEGATING); assertCapturedEvents( /* goto dragging state */ MotionEvent.ACTION_DOWN, + MotionEvent.ACTION_MOVE, /* leave dragging state */ MotionEvent.ACTION_UP, MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN); -- cgit v1.2.3-59-g8ed1b