diff --git a/api/current.txt b/api/current.txt index 76dad983daa0f..34d0218bfae47 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2871,7 +2871,9 @@ package android.accessibilityservice { method protected void onServiceConnected(); method public void onSystemActionsChanged(); method public final boolean performGlobalAction(int); + method public void setGestureDetectionPassthroughRegion(int, @NonNull android.graphics.Region); method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); + method public void setTouchExplorationPassthroughRegion(int, @NonNull android.graphics.Region); method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14 field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28 diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index b7a35f7548c76..83d609cd56480 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -2414,4 +2414,55 @@ public abstract class AccessibilityService extends Service { return mTimestamp; }; } + + /** + * When {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled, this + * function requests that touch interactions starting in the specified region of the screen + * bypass the gesture detector. There can only be one gesture detection passthrough region per + * display. Requesting a new gesture detection passthrough region clears the existing one. To + * disable this passthrough and return to the original behavior, pass in an empty region. When + * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this + * function has no effect. + * + * @param displayId The display on which to set this region. + * @param region the region of the screen. + */ + public void setGestureDetectionPassthroughRegion(int displayId, @NonNull Region region) { + Preconditions.checkNotNull(region, "region cannot be null"); + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); + if (connection != null) { + try { + connection.setGestureDetectionPassthroughRegion(displayId, region); + } catch (RemoteException re) { + throw new RuntimeException(re); + } + } + } + + /** + * When {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled, this + * function requests that touch interactions starting in the specified region of the screen + * bypass the touch explorer and go straight to the view hierarchy. There can only be one touch + * exploration passthrough region per display. Requesting a new touch explorationpassthrough + * region clears the existing one. To disable this passthrough and return to the original + * behavior, pass in an empty region. When {@link + * AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this function has + * no effect. + * + * @param displayId The display on which to set this region. + * @param region the region of the screen . + */ + public void setTouchExplorationPassthroughRegion(int displayId, @NonNull Region region) { + Preconditions.checkNotNull(region, "region cannot be null"); + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); + if (connection != null) { + try { + connection.setTouchExplorationPassthroughRegion(displayId, region); + } catch (RemoteException re) { + throw new RuntimeException(re); + } + } + } } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 9177d4d274917..0b3b9b2ecae19 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -111,4 +111,8 @@ interface IAccessibilityServiceConnection { int getWindowIdForLeashToken(IBinder token); void takeScreenshot(int displayId, in RemoteCallback callback); + + void setGestureDetectionPassthroughRegion(int displayId, in Region region); + + void setTouchExplorationPassthroughRegion(int displayId, in Region region); } diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index b71c5800161e9..75a7504cac2fc 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -157,4 +157,8 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon } public void takeScreenshot(int displayId, RemoteCallback callback) {} + + public void setTouchExplorationPassthroughRegion(int displayId, Region region) {} + + public void setGestureDetectionPassthroughRegion(int displayId, Region region) {} } diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 6c8aaf465c446..0a527d420c151 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -230,6 +230,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ /* This is exactly PendingIntent.getActivity, separated out for testability */ PendingIntent getPendingIntentActivity(Context context, int requestCode, Intent intent, int flags); + + void setGestureDetectionPassthroughRegion(int displayId, Region region); + + void setTouchExplorationPassthroughRegion(int displayId, Region region); } public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName, @@ -1736,4 +1740,14 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ public boolean isMultiFingerGesturesEnabled() { return mRequestMultiFingerGestures; } + + @Override + public void setGestureDetectionPassthroughRegion(int displayId, Region region) { + mSystemSupport.setGestureDetectionPassthroughRegion(displayId, region); + } + + @Override + public void setTouchExplorationPassthroughRegion(int displayId, Region region) { + mSystemSupport.setTouchExplorationPassthroughRegion(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 efddd8698f1b9..020f2253743d3 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -17,6 +17,7 @@ package com.android.server.accessibility; import android.content.Context; +import android.graphics.Region; import android.os.PowerManager; import android.util.Slog; import android.util.SparseArray; @@ -726,4 +727,16 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo return shouldProcess; } } + + public void setGestureDetectionPassthroughRegion(int displayId, Region region) { + if (region != null && mTouchExplorer.contains(displayId)) { + mTouchExplorer.get(displayId).setGestureDetectionPassthroughRegion(region); + } + } + + public void setTouchExplorationPassthroughRegion(int displayId, Region region) { + if (region != null && mTouchExplorer.contains(displayId)) { + mTouchExplorer.get(displayId).setTouchExplorationPassthroughRegion(region); + } + } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 055bb4c53b850..1a0ca260eb584 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -3058,4 +3058,40 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } } + + @Override + public void setGestureDetectionPassthroughRegion(int displayId, Region region) { + mMainHandler.sendMessage( + obtainMessage( + AccessibilityManagerService::setGestureDetectionPassthroughRegionInternal, + this, + displayId, + region)); + } + + @Override + public void setTouchExplorationPassthroughRegion(int displayId, Region region) { + mMainHandler.sendMessage( + obtainMessage( + AccessibilityManagerService::setTouchExplorationPassthroughRegionInternal, + this, + displayId, + region)); + } + + private void setTouchExplorationPassthroughRegionInternal(int displayId, Region region) { + synchronized (mLock) { + if (mHasInputFilter && mInputFilter != null) { + mInputFilter.setTouchExplorationPassthroughRegion(displayId, region); + } + } + } + + private void setGestureDetectionPassthroughRegionInternal(int displayId, Region region) { + synchronized (mLock) { + if (mHasInputFilter && mInputFilter != null) { + mInputFilter.setGestureDetectionPassthroughRegion(displayId, region); + } + } + } } 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 2cc11c580d940..61c6ef5cfda9e 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -23,6 +23,7 @@ import static com.android.server.accessibility.gestures.TouchState.ALL_POINTER_I import android.accessibilityservice.AccessibilityGestureEvent; import android.content.Context; import android.graphics.Point; +import android.graphics.Region; import android.os.Handler; import android.util.Slog; import android.view.InputDevice; @@ -120,6 +121,8 @@ public class TouchExplorer extends BaseEventStreamTransformation // Context in which this explorer operates. private final Context mContext; + private Region mGestureDetectionPassthroughRegion; + private Region mTouchExplorationPassthroughRegion; /** * Creates a new instance. @@ -165,6 +168,8 @@ public class TouchExplorer extends BaseEventStreamTransformation } else { mGestureDetector = detector; } + mGestureDetectionPassthroughRegion = new Region(); + mTouchExplorationPassthroughRegion = new Region(); } @Override @@ -230,10 +235,11 @@ public class TouchExplorer extends BaseEventStreamTransformation } mState.onReceivedMotionEvent(rawEvent); - - if (mGestureDetector.onMotionEvent(event, rawEvent, policyFlags)) { - // Event was handled by the gesture detector. - return; + if (shouldPerformGestureDetection(event)) { + if (mGestureDetector.onMotionEvent(event, rawEvent, policyFlags)) { + // Event was handled by the gesture detector. + return; + } } if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) { @@ -418,6 +424,21 @@ public class TouchExplorer extends BaseEventStreamTransformation mSendTouchExplorationEndDelayed.forceSendAndRemove(); mSendTouchInteractionEndDelayed.forceSendAndRemove(); mDispatcher.sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START); + if (mTouchExplorationPassthroughRegion.contains( + (int) event.getX(), (int) event.getY())) { + // The touch exploration passthrough overrides the gesture detection passthrough in + // the event they overlap. + // Pass this entire gesture through to the system as-is. + mState.startDelegating(); + event = MotionEvent.obtainNoHistory(event); + mDispatcher.sendMotionEvent( + event, event.getAction(), rawEvent, ALL_POINTER_ID_BITS, policyFlags); + mSendHoverEnterAndMoveDelayed.cancel(); + } else if (mGestureDetectionPassthroughRegion.contains( + (int) event.getX(), (int) event.getY())) { + // Jump straight to touch exploration. + mSendHoverEnterAndMoveDelayed.forceSendAndRemove(); + } } else { // Avoid duplicated TYPE_TOUCH_INTERACTION_START event when 2nd tap of double tap. mSendTouchInteractionEndDelayed.cancel(); @@ -909,6 +930,29 @@ public class TouchExplorer extends BaseEventStreamTransformation mGestureDetector.setMultiFingerGesturesEnabled(enabled); } + public void setGestureDetectionPassthroughRegion(Region region) { + mGestureDetectionPassthroughRegion = region; + } + + public void setTouchExplorationPassthroughRegion(Region region) { + mTouchExplorationPassthroughRegion = region; + } + + private boolean shouldPerformGestureDetection(MotionEvent event) { + if (mState.isDelegating()) { + return false; + } + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + final int x = (int) event.getX(); + final int y = (int) event.getY(); + if (mTouchExplorationPassthroughRegion.contains(x, y) + || mGestureDetectionPassthroughRegion.contains(x, y)) { + return false; + } + } + return true; + } + /** * Class for delayed exiting from gesture detecting mode. */ @@ -1135,5 +1179,4 @@ public class TouchExplorer extends BaseEventStreamTransformation + ", mTempPoint: " + mTempPoint + " }"; } - }