diff options
12 files changed, 250 insertions, 13 deletions
diff --git a/api/current.txt b/api/current.txt index 498d4704928f..ce82b5ec8cba 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2851,6 +2851,7 @@ package android.accessibilityservice { method public int describeContents(); method public int getDisplayId(); method public int getGestureId(); + method @NonNull public java.util.List<android.view.MotionEvent> getMotionEvents(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityGestureEvent> CREATOR; } @@ -2932,6 +2933,7 @@ package android.accessibilityservice { field public static final int GESTURE_SWIPE_UP_AND_DOWN = 7; // 0x7 field public static final int GESTURE_SWIPE_UP_AND_LEFT = 13; // 0xd field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe + field public static final int GESTURE_UNKNOWN = 0; // 0x0 field public static final int GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS = 14; // 0xe field public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON = 11; // 0xb field public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON_CHOOSER = 12; // 0xc @@ -3047,6 +3049,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_SEND_MOTION_EVENTS = 16384; // 0x4000 field public static final int FLAG_SERVICE_HANDLES_DOUBLE_TAP = 2048; // 0x800 field public int eventTypes; field public int feedbackType; diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java index e3139eb798f7..d4713cb611c3 100644 --- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java +++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java @@ -46,6 +46,7 @@ import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_TRIPLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD; +import static android.accessibilityservice.AccessibilityService.GESTURE_PASSTHROUGH; import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN; import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT; import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT; @@ -62,15 +63,21 @@ import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN; import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT; import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT; +import static android.accessibilityservice.AccessibilityService.GESTURE_TOUCH_EXPLORATION; +import static android.accessibilityservice.AccessibilityService.GESTURE_UNKNOWN; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.TestApi; +import android.content.pm.ParceledListSlice; import android.os.Parcel; import android.os.Parcelable; +import android.view.MotionEvent; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; /** * This class describes the gesture event including gesture id and which display it happens @@ -87,6 +94,8 @@ public final class AccessibilityGestureEvent implements Parcelable { /** @hide */ @IntDef(prefix = { "GESTURE_" }, value = { + GESTURE_UNKNOWN, + GESTURE_TOUCH_EXPLORATION, GESTURE_2_FINGER_SINGLE_TAP, GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD, GESTURE_2_FINGER_DOUBLE_TAP, @@ -139,17 +148,27 @@ public final class AccessibilityGestureEvent implements Parcelable { @GestureId private final int mGestureId; private final int mDisplayId; + private List<MotionEvent> mMotionEvents = new ArrayList<>(); /** @hide */ - @TestApi - public AccessibilityGestureEvent(int gestureId, int displayId) { + public AccessibilityGestureEvent( + int gestureId, int displayId, @NonNull List<MotionEvent> motionEvents) { mGestureId = gestureId; mDisplayId = displayId; + mMotionEvents.addAll(motionEvents); + } + + /** @hide */ + @TestApi + public AccessibilityGestureEvent(int gestureId, int displayId) { + this(gestureId, displayId, new ArrayList<MotionEvent>()); } private AccessibilityGestureEvent(@NonNull Parcel parcel) { mGestureId = parcel.readInt(); mDisplayId = parcel.readInt(); + ParceledListSlice<MotionEvent> slice = parcel.readParcelable(getClass().getClassLoader()); + mMotionEvents = slice.getList(); } /** @@ -172,6 +191,15 @@ public final class AccessibilityGestureEvent implements Parcelable { return mGestureId; } + /** + * Returns the motion events that lead to this gesture. + * + */ + @NonNull + public List<MotionEvent> getMotionEvents() { + return mMotionEvents; + } + @NonNull @Override public String toString() { @@ -179,12 +207,26 @@ public final class AccessibilityGestureEvent implements Parcelable { stringBuilder.append("gestureId: ").append(eventTypeToString(mGestureId)); stringBuilder.append(", "); stringBuilder.append("displayId: ").append(mDisplayId); + stringBuilder.append(", "); + stringBuilder.append("Motion Events: ["); + for (int i = 0; i < mMotionEvents.size(); ++i) { + String action = MotionEvent.actionToString(mMotionEvents.get(i).getActionMasked()); + stringBuilder.append(action); + if (i < (mMotionEvents.size() - 1)) { + stringBuilder.append(", "); + } else { + stringBuilder.append("]"); + } + } stringBuilder.append(']'); return stringBuilder.toString(); } private static String eventTypeToString(int eventType) { switch (eventType) { + case GESTURE_UNKNOWN: return "GESTURE_UNKNOWN"; + case GESTURE_PASSTHROUGH: return "GESTURE_PASSTHROUGH"; + case GESTURE_TOUCH_EXPLORATION: return "GESTURE_TOUCH_EXPLORATION"; case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP"; case GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD: return "GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD"; @@ -252,6 +294,7 @@ public final class AccessibilityGestureEvent implements Parcelable { public void writeToParcel(@NonNull Parcel parcel, int flags) { parcel.writeInt(mGestureId); parcel.writeInt(mDisplayId); + parcel.writeParcelable(new ParceledListSlice<MotionEvent>(mMotionEvents), 0); } /** diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 7c6d4484ee12..0ad9e446dfc7 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -235,6 +235,29 @@ import java.util.function.Consumer; public abstract class AccessibilityService extends Service { /** + * The user has performed a touch-exploration gesture on the touch screen without ever + * triggering gesture detection. This gesture is only dispatched when {@link + * AccessibilityServiceInfo#FLAG_SEND_MOTION_EVENTS} is set. + * + * @hide + */ + public static final int GESTURE_TOUCH_EXPLORATION = -2; + + /** + * The user has performed a passthrough gesture on the touch screen without ever triggering + * gesture detection. This gesture is only dispatched when {@link + * AccessibilityServiceInfo#FLAG_SEND_MOTION_EVENTS} is set. + * @hide + */ + public static final int GESTURE_PASSTHROUGH = -1; + + /** + * The user has performed an unrecognized gesture on the touch screen. This gesture is only + * dispatched when {@link AccessibilityServiceInfo#FLAG_SEND_MOTION_EVENTS} is set. + */ + public static final int GESTURE_UNKNOWN = 0; + + /** * The user has performed a swipe up gesture on the touch screen. */ public static final int GESTURE_SWIPE_UP = 1; diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 769d006cb2b6..f953da48c217 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -376,6 +376,20 @@ public class AccessibilityServiceInfo implements Parcelable { */ public static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 0x0002000; + /** + * This flag requests that when when {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled, a + * service will receive the motion events for each successfully-detected gesture. The service + * will also receive an AccessibilityGestureEvent of type GESTURE_INVALID for each cancelled + * gesture along with its motion events. A service will receive a gesture of type + * GESTURE_PASSTHROUGH and accompanying motion events for every passthrough gesture that does + * not start gesture detection. This information can be used to collect instances of improper + * gesture detection behavior and relay that information to framework developers. 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_SEND_MOTION_EVENTS = 0x0004000; + /** {@hide} */ public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000; @@ -1276,6 +1290,8 @@ public class AccessibilityServiceInfo implements Parcelable { return "FLAG_REQUEST_MULTI_FINGER_GESTURES"; case FLAG_REQUEST_2_FINGER_PASSTHROUGH: return "FLAG_REQUEST_2_FINGER_PASSTHROUGH"; + case FLAG_SEND_MOTION_EVENTS: + return "FLAG_SEND_MOTION_EVENTS"; case FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY: return "FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY"; case FLAG_REPORT_VIEW_IDS: diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 64de32c9ad09..b74f96de33b1 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3748,6 +3748,7 @@ <flag name="flagServiceHandlesDoubleTap" value="0x00000800" /> <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_MULTI_FINGER_GESTURES}. --> <flag name="flagRequestMultiFingerGestures" value="0x00001000" /> + <flag name="flagSendMotionEvents" value="0x0004000" /> </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/non-updatable-api/current.txt b/non-updatable-api/current.txt index babd54d723b8..225a2e70e998 100644 --- a/non-updatable-api/current.txt +++ b/non-updatable-api/current.txt @@ -2851,6 +2851,7 @@ package android.accessibilityservice { method public int describeContents(); method public int getDisplayId(); method public int getGestureId(); + method @NonNull public java.util.List<android.view.MotionEvent> getMotionEvents(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityGestureEvent> CREATOR; } @@ -2932,6 +2933,7 @@ package android.accessibilityservice { field public static final int GESTURE_SWIPE_UP_AND_DOWN = 7; // 0x7 field public static final int GESTURE_SWIPE_UP_AND_LEFT = 13; // 0xd field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe + field public static final int GESTURE_UNKNOWN = 0; // 0x0 field public static final int GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS = 14; // 0xe field public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON = 11; // 0xb field public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON_CHOOSER = 12; // 0xc @@ -3047,6 +3049,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_SEND_MOTION_EVENTS = 16384; // 0x4000 field public static final int FLAG_SERVICE_HANDLES_DOUBLE_TAP = 2048; // 0x800 field public int eventTypes; field public int feedbackType; diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index a167ab16f944..d6d4e4f6c746 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -150,6 +150,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ private boolean mRequestTwoFingerPassthrough; + private boolean mSendMotionEvents; + boolean mRequestFilterKeyEvents; boolean mRetrieveInteractiveWindows; @@ -329,6 +331,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ & AccessibilityServiceInfo.FLAG_REQUEST_MULTI_FINGER_GESTURES) != 0; mRequestTwoFingerPassthrough = (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0; + mSendMotionEvents = + (info.flags & AccessibilityServiceInfo.FLAG_SEND_MOTION_EVENTS) != 0; mRequestFilterKeyEvents = (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0; mRetrieveInteractiveWindows = (info.flags @@ -1780,6 +1784,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return mRequestTwoFingerPassthrough; } + public boolean isSendMotionEventsEnabled() { + return mSendMotionEvents; + } + @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 cd9ab8db0854..857ac6ae62a9 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -119,12 +119,19 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x00000100; /** - * Flag for enabling multi-finger gestures. + * Flag for enabling two-finger passthrough when multi-finger gestures are enabled. * * @see #setUserAndEnabledFeatures(int, int) */ static final int FLAG_REQUEST_2_FINGER_PASSTHROUGH = 0x00000200; + /** + * Flag for including motion events when dispatching a gesture. + * + * @see #setUserAndEnabledFeatures(int, int) + */ + static final int FLAG_SEND_MOTION_EVENTS = 0x00000400; + static final int FEATURES_AFFECTING_MOTION_EVENTS = FLAG_FEATURE_INJECT_MOTION_EVENTS | FLAG_FEATURE_AUTOCLICK @@ -432,6 +439,9 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo if ((mEnabledFeatures & FLAG_REQUEST_2_FINGER_PASSTHROUGH) != 0) { explorer.setTwoFingerPassthroughEnabled(true); } + if ((mEnabledFeatures & FLAG_SEND_MOTION_EVENTS) != 0) { + explorer.setSendMotionEventsEnabled(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 e1c499389ec5..35481a282701 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1867,6 +1867,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (userState.isFilterKeyEventsEnabledLocked()) { flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS; } + if (userState.isSendMotionEventsEnabled()) { + flags |= AccessibilityInputFilter.FLAG_SEND_MOTION_EVENTS; + } + if (userState.isAutoclickEnabledLocked()) { flags |= AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK; } @@ -2147,6 +2151,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub boolean serviceHandlesDoubleTapEnabled = false; boolean requestMultiFingerGestures = false; boolean requestTwoFingerPassthrough = false; + boolean sendMotionEvents = false; final int serviceCount = userState.mBoundServices.size(); for (int i = 0; i < serviceCount; i++) { AccessibilityServiceConnection service = userState.mBoundServices.get(i); @@ -2155,6 +2160,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub serviceHandlesDoubleTapEnabled = service.isServiceHandlesDoubleTapEnabled(); requestMultiFingerGestures = service.isMultiFingerGesturesEnabled(); requestTwoFingerPassthrough = service.isTwoFingerPassthroughEnabled(); + sendMotionEvents = service.isSendMotionEventsEnabled(); break; } } @@ -2172,6 +2178,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState.setServiceHandlesDoubleTapLocked(serviceHandlesDoubleTapEnabled); userState.setMultiFingerGesturesLocked(requestMultiFingerGestures); userState.setTwoFingerPassthroughLocked(requestTwoFingerPassthrough); + userState.setSendMotionEventsEnabled(sendMotionEvents); } 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 4c9e44403026..240c7ff061c8 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -111,6 +111,7 @@ class AccessibilityUserState { private boolean mServiceHandlesDoubleTap; private boolean mRequestMultiFingerGestures; private boolean mRequestTwoFingerPassthrough; + private boolean mSendMotionEventsEnabled; private int mUserInteractiveUiTimeout; private int mUserNonInteractiveUiTimeout; private int mNonInteractiveUiTimeout = 0; @@ -171,6 +172,7 @@ class AccessibilityUserState { mServiceHandlesDoubleTap = false; mRequestMultiFingerGestures = false; mRequestTwoFingerPassthrough = false; + mSendMotionEventsEnabled = false; mIsDisplayMagnificationEnabled = false; mIsAutoclickEnabled = false; mUserNonInteractiveUiTimeout = 0; @@ -460,6 +462,7 @@ class AccessibilityUserState { .append(String.valueOf(mRequestMultiFingerGestures)); pw.append(", requestTwoFingerPassthrough=") .append(String.valueOf(mRequestTwoFingerPassthrough)); + pw.append(", sendMotionEventsEnabled").append(String.valueOf(mSendMotionEventsEnabled)); pw.append(", displayMagnificationEnabled=").append(String.valueOf( mIsDisplayMagnificationEnabled)); pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled)); @@ -802,6 +805,13 @@ class AccessibilityUserState { mRequestTwoFingerPassthrough = enabled; } + public boolean isSendMotionEventsEnabled() { + return mSendMotionEventsEnabled; + } + + public void setSendMotionEventsEnabled(boolean mode) { + mSendMotionEventsEnabled = mode; + } 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 14af8c63073e..2c38dc330a62 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java @@ -99,11 +99,15 @@ class GestureManifold implements GestureMatcher.StateChangeListener { boolean mMultiFingerGesturesEnabled; // Whether the two-finger passthrough is enabled when multi-finger gestures are enabled. private boolean mTwoFingerPassthroughEnabled; + // Whether to send the motion events during gesture dispatch. + private boolean mSendMotionEventsEnabled = false; // A list of all the multi-finger gestures, for easy adding and removal. private final List<GestureMatcher> 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<GestureMatcher> mTwoFingerSwipes = new ArrayList<>(); + // The list of motion events for the current gesture. + private List<MotionEvent> mEvents = new ArrayList<>(); // Shared state information. private TouchState mState; @@ -230,6 +234,9 @@ class GestureManifold implements GestureMatcher.StateChangeListener { return false; } } + if (mSendMotionEventsEnabled) { + mEvents.add(MotionEvent.obtainNoHistory(rawEvent)); + } for (GestureMatcher matcher : mGestures) { if (matcher.getState() != GestureMatcher.STATE_GESTURE_CANCELED) { if (DEBUG) { @@ -240,9 +247,8 @@ class GestureManifold implements GestureMatcher.StateChangeListener { Slog.d(LOG_TAG, matcher.toString()); } if (matcher.getState() == GestureMatcher.STATE_GESTURE_COMPLETED) { - // Here we just clear and return. The actual gesture dispatch is done in + // Here we just return. The actual gesture dispatch is done in // onStateChanged(). - clear(); // No need to process this event any further. return true; } @@ -255,6 +261,11 @@ class GestureManifold implements GestureMatcher.StateChangeListener { for (GestureMatcher matcher : mGestures) { matcher.clear(); } + if (mEvents != null) { + while (mEvents.size() > 0) { + mEvents.remove(0).recycle(); + } + } } /** @@ -340,29 +351,28 @@ class GestureManifold implements GestureMatcher.StateChangeListener { case GESTURE_DOUBLE_TAP: if (mServiceHandlesDoubleTap) { AccessibilityGestureEvent gestureEvent = - new AccessibilityGestureEvent(gestureId, event.getDisplayId()); + new AccessibilityGestureEvent(gestureId, event.getDisplayId(), mEvents); mListener.onGestureCompleted(gestureEvent); } else { mListener.onDoubleTap(event, rawEvent, policyFlags); } - clear(); break; case GESTURE_DOUBLE_TAP_AND_HOLD: if (mServiceHandlesDoubleTap) { AccessibilityGestureEvent gestureEvent = - new AccessibilityGestureEvent(gestureId, event.getDisplayId()); + new AccessibilityGestureEvent(gestureId, event.getDisplayId(), mEvents); mListener.onGestureCompleted(gestureEvent); } else { mListener.onDoubleTapAndHold(event, rawEvent, policyFlags); } - clear(); break; default: AccessibilityGestureEvent gestureEvent = - new AccessibilityGestureEvent(gestureId, event.getDisplayId()); + new AccessibilityGestureEvent(gestureId, event.getDisplayId(), mEvents); mListener.onGestureCompleted(gestureEvent); break; } + clear(); } public boolean isMultiFingerGesturesEnabled() { @@ -406,4 +416,25 @@ class GestureManifold implements GestureMatcher.StateChangeListener { public boolean isServiceHandlesDoubleTapEnabled() { return mServiceHandlesDoubleTap; } + + public void setSendMotionEventsEnabled(boolean mode) { + mSendMotionEventsEnabled = mode; + if (!mode) { + while (mEvents.size() > 0) { + mEvents.remove(0).recycle(); + } + } + } + + public boolean isSendMotionEventsEnabled() { + return mSendMotionEventsEnabled; + } + + /** + * Returns the current list of motion events. It is the caller's responsibility to copy the list + * if they want it to persist after a call to clear(). + */ + public List<MotionEvent> getMotionEvents() { + return mEvents; + } } 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 d8c692b88a0f..5460e80a2802 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -37,6 +37,7 @@ import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_HOVER_EXIT import static com.android.server.accessibility.gestures.TouchState.ALL_POINTER_ID_BITS; import android.accessibilityservice.AccessibilityGestureEvent; +import android.accessibilityservice.AccessibilityService; import android.annotation.NonNull; import android.content.Context; import android.graphics.Region; @@ -340,6 +341,14 @@ public class TouchExplorer extends BaseEventStreamTransformation public void onDoubleTapAndHold(MotionEvent event, MotionEvent rawEvent, int policyFlags) { if (mDispatcher.longPressWithTouchEvents(event, policyFlags)) { sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); + if (isSendMotionEventsEnabled()) { + AccessibilityGestureEvent gestureEvent = + new AccessibilityGestureEvent( + AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD, + event.getDisplayId(), + mGestureDetector.getMotionEvents()); + mAms.onGesture(gestureEvent); + } mState.startDelegating(); } } @@ -350,7 +359,14 @@ public class TouchExplorer extends BaseEventStreamTransformation // Remove pending event deliveries. mSendHoverEnterAndMoveDelayed.cancel(); mSendHoverExitDelayed.cancel(); - + if (isSendMotionEventsEnabled()) { + AccessibilityGestureEvent gestureEvent = + new AccessibilityGestureEvent( + AccessibilityService.GESTURE_DOUBLE_TAP, + event.getDisplayId(), + mGestureDetector.getMotionEvents()); + mAms.onGesture(gestureEvent); + } if (mSendTouchExplorationEndDelayed.isPending()) { mSendTouchExplorationEndDelayed.forceSendAndRemove(); } @@ -385,6 +401,9 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public boolean onGestureCompleted(AccessibilityGestureEvent gestureEvent) { + if (DEBUG) { + Slog.d(LOG_TAG, "Dispatching gesture event:" + gestureEvent.toString()); + } endGestureDetection(true); mSendTouchInteractionEndDelayed.cancel(); mAms.onGesture(gestureEvent); @@ -411,12 +430,24 @@ public class TouchExplorer extends BaseEventStreamTransformation mDispatcher.sendMotionEvent( event, ACTION_HOVER_MOVE, - mState.getLastReceivedEvent(), + event, pointerIdBits, policyFlags); return true; } } + if (isSendMotionEventsEnabled()) { + // Send a gesture with motion events to represent the cancelled gesture. + AccessibilityGestureEvent gestureEvent = + new AccessibilityGestureEvent( + AccessibilityService.GESTURE_UNKNOWN, + event.getDisplayId(), + mGestureDetector.getMotionEvents()); + if (DEBUG) { + Slog.d(LOG_TAG, "Dispatching gesture event:" + gestureEvent.toString()); + } + mAms.onGesture(gestureEvent); + } return false; } @@ -620,6 +651,14 @@ public class TouchExplorer extends BaseEventStreamTransformation if (isDraggingGesture(event)) { // Two pointers moving in the same direction within // a given distance perform a drag. + if (isSendMotionEventsEnabled()) { + AccessibilityGestureEvent gestureEvent = + new AccessibilityGestureEvent( + AccessibilityService.GESTURE_PASSTHROUGH, + event.getDisplayId(), + mGestureDetector.getMotionEvents()); + mAms.onGesture(gestureEvent); + } computeDraggingPointerIdIfNeeded(event); pointerIdBits = 1 << mDraggingPointerId; event.setEdgeFlags(mReceivedPointerTracker.getLastReceivedDownEdgeFlags()); @@ -636,6 +675,14 @@ public class TouchExplorer extends BaseEventStreamTransformation mState.startDragging(); } else { // Two pointers moving arbitrary are delegated to the view hierarchy. + if (isSendMotionEventsEnabled()) { + AccessibilityGestureEvent gestureEvent = + new AccessibilityGestureEvent( + AccessibilityService.GESTURE_PASSTHROUGH, + event.getDisplayId(), + mGestureDetector.getMotionEvents()); + mAms.onGesture(gestureEvent); + } mState.startDelegating(); mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags); } @@ -650,6 +697,14 @@ public class TouchExplorer extends BaseEventStreamTransformation if (DEBUG) { Slog.d(LOG_TAG, "Three-finger edge swipe detected."); } + if (isSendMotionEventsEnabled()) { + AccessibilityGestureEvent gestureEvent = + new AccessibilityGestureEvent( + AccessibilityService.GESTURE_PASSTHROUGH, + event.getDisplayId(), + mGestureDetector.getMotionEvents()); + mAms.onGesture(gestureEvent); + } mState.startDelegating(); if (mState.isTouchExploring()) { mDispatcher.sendDownForAllNotInjectedPointers(event, @@ -663,6 +718,14 @@ public class TouchExplorer extends BaseEventStreamTransformation } } else { // More than two pointers are delegated to the view hierarchy. + if (isSendMotionEventsEnabled()) { + AccessibilityGestureEvent gestureEvent = + new AccessibilityGestureEvent( + AccessibilityService.GESTURE_PASSTHROUGH, + event.getDisplayId(), + mGestureDetector.getMotionEvents()); + mAms.onGesture(gestureEvent); + } mState.startDelegating(); event = MotionEvent.obtainNoHistory(event); mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags); @@ -1109,6 +1172,7 @@ public class TouchExplorer extends BaseEventStreamTransformation public void setTwoFingerPassthroughEnabled(boolean enabled) { mGestureDetector.setTwoFingerPassthroughEnabled(enabled); } + public void setGestureDetectionPassthroughRegion(Region region) { mGestureDetectionPassthroughRegion = region; } @@ -1117,6 +1181,17 @@ public class TouchExplorer extends BaseEventStreamTransformation mTouchExplorationPassthroughRegion = region; } + /** + * Whether to send the motion events that make up each gesture to the accessibility service. + */ + public void setSendMotionEventsEnabled(boolean mode) { + mGestureDetector.setSendMotionEventsEnabled(mode); + } + + public boolean isSendMotionEventsEnabled() { + return mGestureDetector.isSendMotionEventsEnabled(); + } + private boolean shouldPerformGestureDetection(MotionEvent event) { if (mState.isDelegating()) { return false; @@ -1213,7 +1288,14 @@ public class TouchExplorer extends BaseEventStreamTransformation public void run() { // Send an accessibility event to announce the touch exploration start. mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START); - + if (isSendMotionEventsEnabled()) { + AccessibilityGestureEvent gestureEvent = + new AccessibilityGestureEvent( + AccessibilityService.GESTURE_TOUCH_EXPLORATION, + mState.getLastReceivedEvent().getDisplayId(), + mGestureDetector.getMotionEvents()); + mAms.onGesture(gestureEvent); + } if (!mEvents.isEmpty() && !mRawEvents.isEmpty()) { // Deliver a down event. mDispatcher.sendMotionEvent(mEvents.get(0), ACTION_HOVER_ENTER, |