diff options
12 files changed, 128 insertions, 22 deletions
diff --git a/api/current.txt b/api/current.txt index 1cfc99acca32..b2c8061b9778 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2873,6 +2873,8 @@ package android.accessibilityservice { method public final boolean performGlobalAction(int); method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.graphics.Bitmap>); + field public static final int GESTURE_DOUBLE_TAP = 17; // 0x11 + field public static final int GESTURE_DOUBLE_TAP_AND_HOLD = 18; // 0x12 field public static final int GESTURE_SWIPE_DOWN = 2; // 0x2 field public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15; // 0xf field public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16; // 0x10 @@ -2989,6 +2991,7 @@ package android.accessibilityservice { field public static final int FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK = 1024; // 0x400 field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4 field public static final int FLAG_RETRIEVE_INTERACTIVE_WINDOWS = 64; // 0x40 + field public static final int FLAG_SERVICE_HANDLES_DOUBLE_TAP = 2048; // 0x800 field public int eventTypes; field public int feedbackType; field public int flags; diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index ee6ccc2f5cd7..7722dc318fd7 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -310,13 +310,11 @@ public abstract class AccessibilityService extends Service { /** * The user has performed a double tap gesture on the touch screen. - * @hide */ public static final int GESTURE_DOUBLE_TAP = 17; /** * The user has performed a double tap and hold gesture on the touch screen. - * @hide */ public static final int GESTURE_DOUBLE_TAP_AND_HOLD = 18; diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 12f2c3b17c96..342b6a5ba7e0 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -341,6 +341,16 @@ public class AccessibilityServiceInfo implements Parcelable { */ public static final int FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK = 0x00000400; + /** + * This flag requests that when {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled, + * double tap and double tap and hold gestures are dispatched to the service rather than being + * handled by the framework. If {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this + * flag has no effect. + * + * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE + */ + public static final int FLAG_SERVICE_HANDLES_DOUBLE_TAP = 0x0000800; + /** {@hide} */ public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000; @@ -1221,6 +1231,8 @@ public class AccessibilityServiceInfo implements Parcelable { return "FLAG_INCLUDE_NOT_IMPORTANT_VIEWS"; case FLAG_REQUEST_TOUCH_EXPLORATION_MODE: return "FLAG_REQUEST_TOUCH_EXPLORATION_MODE"; + case FLAG_SERVICE_HANDLES_DOUBLE_TAP: + return "FLAG_SERVICE_HANDLES_DOUBLE_TAP"; case FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY: return "FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY"; case FLAG_REPORT_VIEW_IDS: diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 9cbba87e6856..4b49d67fe44d 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -102,6 +102,9 @@ public final class AccessibilityManager { public static final int STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED = 0x00000004; /** @hide */ + public static final int STATE_FLAG_DISPATCH_DOUBLE_TAP = 0x00000008; + + /** @hide */ public static final int DALTONIZER_DISABLED = -1; /** @hide */ diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index c9c47b92f782..643a6ff98c1c 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3714,6 +3714,8 @@ <flag name="flagRequestFingerprintGestures" value="0x00000200" /> <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK}. --> <flag name="flagRequestShortcutWarningDialogSpokenFeedback" value="0x00000400" /> + <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_SERVICE_HANDLES_DOUBLE_TAP}. --> + <flag name="flagServiceHandlesDoubleTap" value="0x00000800" /> </attr> <!-- Component name of an activity that allows the user to modify the settings for this service. This setting cannot be changed at runtime. --> diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 3e74b7a92f5d..a79ddce73004 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -134,6 +134,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ boolean mRequestTouchExplorationMode; + private boolean mServiceHandlesDoubleTap; + boolean mRequestFilterKeyEvents; boolean mRetrieveInteractiveWindows; @@ -298,6 +300,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mRequestTouchExplorationMode = (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0; + mServiceHandlesDoubleTap = (info.flags + & AccessibilityServiceInfo.FLAG_SERVICE_HANDLES_DOUBLE_TAP) != 0; mRequestFilterKeyEvents = (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0; mRetrieveInteractiveWindows = (info.flags @@ -1689,4 +1693,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ msg.sendToTarget(); } } + + public boolean isServiceHandlesDoubleTapEnabled() { + return mServiceHandlesDoubleTap; + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 49582a97b890..ee976785be20 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -99,9 +99,20 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo */ static final int FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER = 0x00000040; - static final int FEATURES_AFFECTING_MOTION_EVENTS = FLAG_FEATURE_INJECT_MOTION_EVENTS - | FLAG_FEATURE_AUTOCLICK | FLAG_FEATURE_TOUCH_EXPLORATION - | FLAG_FEATURE_SCREEN_MAGNIFIER | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER; + /** + * Flag for dispatching double tap and double tap and hold to the service. + * + * @see #setUserAndEnabledFeatures(int, int) + */ + static final int FLAG_SERVICE_HANDLES_DOUBLE_TAP = 0x00000080; + + static final int FEATURES_AFFECTING_MOTION_EVENTS = + FLAG_FEATURE_INJECT_MOTION_EVENTS + | FLAG_FEATURE_AUTOCLICK + | FLAG_FEATURE_TOUCH_EXPLORATION + | FLAG_FEATURE_SCREEN_MAGNIFIER + | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER + | FLAG_SERVICE_HANDLES_DOUBLE_TAP; private final Context mContext; @@ -391,6 +402,9 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) { TouchExplorer explorer = new TouchExplorer(displayContext, mAms); + if ((mEnabledFeatures & FLAG_SERVICE_HANDLES_DOUBLE_TAP) != 0) { + explorer.setServiceHandlesDoubleTap(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 bcaecea4c388..560a8ef75c33 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1610,6 +1610,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (userState.isHandlingAccessibilityEventsLocked() && userState.isTouchExplorationEnabledLocked()) { flags |= AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION; + if (userState.isServiceHandlesDoubleTapEnabledLocked()) { + flags |= AccessibilityInputFilter.FLAG_SERVICE_HANDLES_DOUBLE_TAP; + } } if (userState.isFilterKeyEventsEnabledLocked()) { flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS; @@ -1882,21 +1885,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } private void updateTouchExplorationLocked(AccessibilityUserState userState) { - boolean enabled = mUiAutomationManager.isTouchExplorationEnabledLocked(); + boolean touchExplorationEnabled = mUiAutomationManager.isTouchExplorationEnabledLocked(); + boolean serviceHandlesDoubleTapEnabled = false; final int serviceCount = userState.mBoundServices.size(); for (int i = 0; i < serviceCount; i++) { AccessibilityServiceConnection service = userState.mBoundServices.get(i); if (canRequestAndRequestsTouchExplorationLocked(service, userState)) { - enabled = true; + touchExplorationEnabled = true; + serviceHandlesDoubleTapEnabled = service.isServiceHandlesDoubleTapEnabled(); break; } } - if (enabled != userState.isTouchExplorationEnabledLocked()) { - userState.setTouchExplorationEnabledLocked(enabled); + if (touchExplorationEnabled != userState.isTouchExplorationEnabledLocked()) { + userState.setTouchExplorationEnabledLocked(touchExplorationEnabled); + userState.setServiceHandlesDoubleTapLocked(serviceHandlesDoubleTapEnabled); final long identity = Binder.clearCallingIdentity(); try { Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.TOUCH_EXPLORATION_ENABLED, enabled ? 1 : 0, + Settings.Secure.TOUCH_EXPLORATION_ENABLED, touchExplorationEnabled ? 1 : 0, userState.mUserId); } finally { Binder.restoreCallingIdentity(identity); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index ebe2af62b5db..49b11583c4ab 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -103,6 +103,7 @@ class AccessibilityUserState { private boolean mIsPerformGesturesEnabled; private boolean mIsTextHighContrastEnabled; private boolean mIsTouchExplorationEnabled; + private boolean mServiceHandlesDoubleTap; private int mUserInteractiveUiTimeout; private int mUserNonInteractiveUiTimeout; private int mNonInteractiveUiTimeout = 0; @@ -151,6 +152,7 @@ class AccessibilityUserState { mAccessibilityShortcutKeyTargets.clear(); mAccessibilityButtonTargets.clear(); mIsTouchExplorationEnabled = false; + mServiceHandlesDoubleTap = false; mIsDisplayMagnificationEnabled = false; mIsAutoclickEnabled = false; mUserNonInteractiveUiTimeout = 0; @@ -351,6 +353,7 @@ class AccessibilityUserState { // Touch exploration relies on enabled accessibility. if (a11yEnabled && mIsTouchExplorationEnabled) { clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED; + clientState |= AccessibilityManager.STATE_FLAG_DISPATCH_DOUBLE_TAP; } if (mIsTextHighContrastEnabled) { clientState |= AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED; @@ -431,6 +434,8 @@ class AccessibilityUserState { pw.println(); pw.append(" attributes:{id=").append(String.valueOf(mUserId)); pw.append(", touchExplorationEnabled=").append(String.valueOf(mIsTouchExplorationEnabled)); + pw.append(", serviceHandlesDoubleTap=") + .append(String.valueOf(mServiceHandlesDoubleTap)); pw.append(", displayMagnificationEnabled=").append(String.valueOf( mIsDisplayMagnificationEnabled)); pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled)); @@ -675,6 +680,14 @@ class AccessibilityUserState { mIsTouchExplorationEnabled = enabled; } + public boolean isServiceHandlesDoubleTapEnabledLocked() { + return mServiceHandlesDoubleTap; + } + + public void setServiceHandlesDoubleTapLocked(boolean enabled) { + mServiceHandlesDoubleTap = 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 50d21ba59996..9afcf8dda437 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java @@ -65,6 +65,8 @@ class GestureManifold implements GestureMatcher.StateChangeListener { private final Handler mHandler; // Listener to be notified of gesture start and end. private Listener mListener; + // Whether enhanced touch exploration mode is enabled. + private boolean mServiceHandlesDoubleTap = false; // Shared state information. private TouchState mState; @@ -154,18 +156,23 @@ class GestureManifold implements GestureMatcher.StateChangeListener { */ public interface Listener { /** - * Called when the user has performed a double tap and then held down the second tap. + * When FLAG_SERVICE_HANDLES_DOUBLE_TAP is enabled, this method is not called; double-tap + * and hold is dispatched via onGestureCompleted. Otherwise, this method is called when the + * user has performed a double tap and then held down the second tap. */ void onDoubleTapAndHold(); /** - * Called when the user lifts their finger on the second tap of a double tap. + * When FLAG_SERVICE_HANDLES_DOUBLE_TAP is enabled, this method is not called; double-tap is + * dispatched via onGestureCompleted. Otherwise, this method is called when the user lifts + * their finger on the second tap of a double tap. + * * @return true if the event is consumed, else false */ boolean onDoubleTap(); /** - * Called when the system has decided the event stream is a gesture. + * Called when the system has decided the event stream is a potential gesture. * * @return true if the event is consumed, else false */ @@ -193,7 +200,13 @@ class GestureManifold implements GestureMatcher.StateChangeListener { public void onStateChanged( int gestureId, int state, MotionEvent event, MotionEvent rawEvent, int policyFlags) { if (state == GestureMatcher.STATE_GESTURE_STARTED && !mState.isGestureDetecting()) { - mListener.onGestureStarted(); + if (gestureId == GESTURE_DOUBLE_TAP || gestureId == GESTURE_DOUBLE_TAP_AND_HOLD) { + if (mServiceHandlesDoubleTap) { + mListener.onGestureStarted(); + } + } else { + mListener.onGestureStarted(); + } } else if (state == GestureMatcher.STATE_GESTURE_COMPLETED) { onGestureCompleted(gestureId); } else if (state == GestureMatcher.STATE_GESTURE_CANCELED && mState.isGestureDetecting()) { @@ -217,11 +230,23 @@ class GestureManifold implements GestureMatcher.StateChangeListener { // Gestures that complete on a delay call clear() here. switch (gestureId) { case GESTURE_DOUBLE_TAP: - mListener.onDoubleTap(); + if (mServiceHandlesDoubleTap) { + AccessibilityGestureEvent gestureEvent = + new AccessibilityGestureEvent(gestureId, event.getDisplayId()); + mListener.onGestureCompleted(gestureEvent); + } else { + mListener.onDoubleTap(); + } clear(); break; case GESTURE_DOUBLE_TAP_AND_HOLD: - mListener.onDoubleTapAndHold(); + if (mServiceHandlesDoubleTap) { + AccessibilityGestureEvent gestureEvent = + new AccessibilityGestureEvent(gestureId, event.getDisplayId()); + mListener.onGestureCompleted(gestureEvent); + } else { + mListener.onDoubleTapAndHold(); + } clear(); break; default: @@ -231,4 +256,12 @@ class GestureManifold implements GestureMatcher.StateChangeListener { break; } } + + public boolean isServiceHandlesDoubleTapEnabled() { + return mServiceHandlesDoubleTap; + } + + public void setServiceHandlesDoubleTap(boolean mode) { + mServiceHandlesDoubleTap = mode; + } } diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java index 4c9e590592fe..386cb0636cc2 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java @@ -70,6 +70,13 @@ class MultiTap extends GestureMatcher { } mBaseX = event.getX(); mBaseY = event.getY(); + if (mCurrentTaps + 1 == mTargetTaps) { + // Start gesture detecting on down of final tap. + // Note that if this instance is matching double tap, + // and the service is not requesting to handle double tap, GestureManifold will + // ignore this. + startGesture(event, rawEvent, policyFlags); + } } @Override 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 ba890c5ee41d..caf142420dd6 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -254,7 +254,10 @@ public class TouchExplorer extends BaseEventStreamTransformation } else if (mState.isDelegating()) { handleMotionEventStateDelegating(event, rawEvent, policyFlags); } else if (mState.isGestureDetecting()) { - // Already handled. + // Make sure we don't prematurely get TOUCH_INTERACTION_END + // It will be delivered on gesture completion or cancelation. + // Note that the delay for sending GESTURE_DETECTION_END remains in place. + mSendTouchInteractionEndDelayed.cancel(); } else { Slog.e(LOG_TAG, "Illegal state: " + mState); clear(event, policyFlags); @@ -331,12 +334,8 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent) { - if (!mState.isGestureDetecting()) { - return false; - } - endGestureDetection(true); - + mSendTouchInteractionEndDelayed.cancel(); mAms.onGesture(gestureEvent); return true; @@ -875,6 +874,14 @@ public class TouchExplorer extends BaseEventStreamTransformation } /** + * Whether to dispatch double tap and double tap and hold to the service rather than handle them + * in the framework. + */ + public void setServiceHandlesDoubleTap(boolean mode) { + mGestureDetector.setServiceHandlesDoubleTap(mode); + } + + /** * Class for delayed exiting from gesture detecting mode. */ private final class ExitGestureDetectionModeDelayed implements Runnable { |