diff options
8 files changed, 166 insertions, 22 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index f4c8429619dd..f57540d087d2 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2,6 +2,7 @@ package android { public static final class Manifest.permission { + field @FlaggedApi("com.android.server.accessibility.motion_event_observing") public static final String ACCESSIBILITY_MOTION_EVENT_OBSERVING = "android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING"; field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS"; field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"; @@ -99,6 +100,8 @@ package android.accessibilityservice { public class AccessibilityServiceInfo implements android.os.Parcelable { method @NonNull public android.content.ComponentName getComponentName(); + method @FlaggedApi("android.view.accessibility.motion_event_observing") public int getObservedMotionEventSources(); + method @FlaggedApi("android.view.accessibility.motion_event_observing") public void setObservedMotionEventSources(int); } } diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index 8ad6ea207665..fc342fa3431a 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -20,6 +20,7 @@ import static android.accessibilityservice.util.AccessibilityUtils.getFilteredHt import static android.accessibilityservice.util.AccessibilityUtils.loadSafeAnimatedImage; import static android.content.pm.PackageManager.FEATURE_FINGERPRINT; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; @@ -53,6 +54,7 @@ import android.view.InputDevice; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.Flags; import com.android.internal.R; import com.android.internal.compat.IPlatformCompat; @@ -630,7 +632,8 @@ public class AccessibilityServiceInfo implements Parcelable { InputDevice.SOURCE_TOUCH_NAVIGATION, InputDevice.SOURCE_ROTARY_ENCODER, InputDevice.SOURCE_JOYSTICK, - InputDevice.SOURCE_SENSOR + InputDevice.SOURCE_SENSOR, + InputDevice.SOURCE_TOUCHSCREEN }) @Retention(RetentionPolicy.SOURCE) public @interface MotionEventSources {} @@ -642,6 +645,8 @@ public class AccessibilityServiceInfo implements Parcelable { @MotionEventSources private int mMotionEventSources = 0; + private int mObservedMotionEventSources = 0; + /** * Creates a new instance. */ @@ -817,6 +822,9 @@ public class AccessibilityServiceInfo implements Parcelable { mInteractiveUiTimeout = other.mInteractiveUiTimeout; flags = other.flags; mMotionEventSources = other.mMotionEventSources; + if (Flags.motionEventObserving()) { + setObservedMotionEventSources(other.mObservedMotionEventSources); + } // NOTE: Ensure that only properties that are safe to be modified by the service itself // are included here (regardless of hidden setters, etc.). } @@ -1024,16 +1032,75 @@ public class AccessibilityServiceInfo implements Parcelable { */ public void setMotionEventSources(@MotionEventSources int motionEventSources) { mMotionEventSources = motionEventSources; + mObservedMotionEventSources = 0; + } + + /** + * Sets the bit mask of {@link android.view.InputDevice} sources that the accessibility service + * wants to observe generic {@link android.view.MotionEvent}s from if it has already requested + * to listen to them using {@link #setMotionEventSources(int)}. Events from these sources will + * be sent to the rest of the input pipeline without being consumed by accessibility services. + * This service will still be able to see them. + * + * <p><strong>Note:</strong> you will need to call this function every time you call {@link + * #setMotionEventSources(int)}. Calling {@link #setMotionEventSources(int)} clears the list of + * observed motion event sources for this service. + * + * <p><strong>Note:</strong> {@link android.view.InputDevice} sources contain source class bits + * that complicate bitwise flag removal operations. To remove a specific source you should + * rebuild the entire value using bitwise OR operations on the individual source constants. + * + * <p>Including an {@link android.view.InputDevice} source that does not send {@link + * android.view.MotionEvent}s is effectively a no-op for that source, since you will not receive + * any events from that source. + * + * <p><strong>Note:</strong> Calling this function with a source that has not been listened to + * using {@link #setMotionEventSources(int)} will throw an exception. + * + * @see AccessibilityService#onMotionEvent + * @see #MotionEventSources + * @see #setMotionEventSources(int) + * @hide + */ + @FlaggedApi(Flags.FLAG_MOTION_EVENT_OBSERVING) + @TestApi + public void setObservedMotionEventSources(int observedMotionEventSources) { + // Confirm that any sources requested here have already been requested for listening. + if ((observedMotionEventSources & ~mMotionEventSources) != 0) { + String message = + String.format( + "Requested motion event sources for listening = 0x%x but requested" + + " motion event sources for observing = 0x%x.", + mMotionEventSources, observedMotionEventSources); + throw new IllegalArgumentException(message); + } + mObservedMotionEventSources = observedMotionEventSources; + } + + /** + * Returns the bit mask of {@link android.view.InputDevice} sources that the accessibility + * service wants to observe generic {@link android.view.MotionEvent}s from if it has already + * requested to listen to them using {@link #setMotionEventSources(int)}. Events from these + * sources will be sent to the rest of the input pipeline without being consumed by + * accessibility services. This service will still be able to see them. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_MOTION_EVENT_OBSERVING) + @MotionEventSources + @TestApi + public int getObservedMotionEventSources() { + return mObservedMotionEventSources; } /** * The localized summary of the accessibility service. - * <p> - * <strong>Statically set from - * {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong> - * </p> - * @return The localized summary if available, and {@code null} if a summary - * has not been provided. + * + * <p><strong>Statically set from {@link AccessibilityService#SERVICE_META_DATA + * meta-data}.</strong> + * + * @return The localized summary if available, and {@code null} if a summary has not been + * provided. */ public CharSequence loadSummary(PackageManager packageManager) { if (mSummaryResId == 0) { @@ -1260,6 +1327,7 @@ public class AccessibilityServiceInfo implements Parcelable { parcel.writeString(mTileServiceName); parcel.writeInt(mIntroResId); parcel.writeInt(mMotionEventSources); + parcel.writeInt(mObservedMotionEventSources); } private void initFromParcel(Parcel parcel) { @@ -1285,6 +1353,8 @@ public class AccessibilityServiceInfo implements Parcelable { mTileServiceName = parcel.readString(); mIntroResId = parcel.readInt(); mMotionEventSources = parcel.readInt(); + // use the setter here because it throws an exception for invalid values. + setObservedMotionEventSources(parcel.readInt()); } @Override diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index aa4275d62046..a80f150c40bd 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -45,6 +45,13 @@ flag { } flag { + name: "motion_event_observing" + namespace: "accessibility" + description: "Allows accessibility services to intercept but not consume motion events from specified sources." + bug: "297595990" +} + +flag { namespace: "accessibility" name: "granular_scrolling" description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen" diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index cf41a06a824e..c7025f845279 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4806,6 +4806,13 @@ <permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" android:protectionLevel="signature" /> + <!-- @FlaggedApi("com.android.server.accessibility.motion_event_observing") + @hide + @TestApi + Allows an accessibility service to observe motion events without consuming them. --> + <permission android:name="android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING" + android:protectionLevel="signature" /> + <!-- @hide Allows an application to collect frame statistics --> <permission android:name="android.permission.FRAME_STATS" android:protectionLevel="signature" /> diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 650319fd58f9..ad9ea22ef3dc 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -873,6 +873,9 @@ <uses-permission android:name="android.permission.GET_BINDING_UID_IMPORTANCE" /> + <!-- Permissions required for CTS test - CtsAccessibilityServiceTestCases--> + <uses-permission android:name="android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 71878954e9ec..d4123f6c2a42 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -209,6 +209,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final ComponentName mComponentName; int mGenericMotionEventSources; + int mObservedMotionEventSources; // the events pending events to be dispatched to this service final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<>(); @@ -397,6 +398,19 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mNotificationTimeout = info.notificationTimeout; mIsDefault = (info.flags & DEFAULT) != 0; mGenericMotionEventSources = info.getMotionEventSources(); + if (android.view.accessibility.Flags.motionEventObserving()) { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING) + == PackageManager.PERMISSION_GRANTED) { + mObservedMotionEventSources = info.getObservedMotionEventSources(); + } else { + Slog.e( + LOG_TAG, + "Observing motion events requires" + + " android.Manifest.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING."); + mObservedMotionEventSources = 0; + } + } if (supportsFlagForNotImportantViews(info)) { if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) { @@ -1599,7 +1613,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final int displayId = displays[i].getDisplayId(); onDisplayRemoved(displayId); } - if (Flags.cleanupA11yOverlays()) { + if (com.android.server.accessibility.Flags.cleanupA11yOverlays()) { detachAllOverlays(); } } @@ -1913,6 +1927,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return (mGenericMotionEventSources & eventSourceWithoutClass) != 0; } + /** * Called by the invocation handler to notify the service that the * state of magnification has changed. diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 6cac6a47c77b..9ddc35ae240b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -198,6 +198,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo // State tracking for generic MotionEvents is display-agnostic so we only need one. private GenericMotionEventStreamState mGenericMotionEventStreamState; private int mCombinedGenericMotionEventSources = 0; + private int mCombinedMotionEventObservedSources = 0; private EventStreamState mKeyboardStreamState; @@ -525,16 +526,33 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } if ((mEnabledFeatures & FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS) != 0) { - addFirstEventHandler(displayId, new BaseEventStreamTransformation() { - @Override - public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, - int policyFlags) { - if (!anyServiceWantsGenericMotionEvent(rawEvent) - || !mAms.sendMotionEventToListeningServices(rawEvent)) { - super.onMotionEvent(event, rawEvent, policyFlags); - } - } - }); + addFirstEventHandler( + displayId, + new BaseEventStreamTransformation() { + @Override + public void onMotionEvent( + MotionEvent event, MotionEvent rawEvent, int policyFlags) { + boolean passAlongEvent = true; + if (anyServiceWantsGenericMotionEvent(event)) { + // Some service wants this event, so try to deliver it to at least + // one service. + if (mAms.sendMotionEventToListeningServices(event)) { + // A service accepted this event, so prevent it from passing + // down the stream by default. + passAlongEvent = false; + } + // However, if a service is observing these events instead of + // consuming them then ensure + // it is always passed along to the next stage of the event stream. + if (anyServiceWantsToObserveMotionEvent(event)) { + passAlongEvent = true; + } + } + if (passAlongEvent) { + super.onMotionEvent(event, rawEvent, policyFlags); + } + } + }); } if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0 @@ -542,15 +560,14 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP) != 0) || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) { final MagnificationGestureHandler magnificationGestureHandler = - createMagnificationGestureHandler(displayId, - displayContext); + createMagnificationGestureHandler(displayId, displayContext); addFirstEventHandler(displayId, magnificationGestureHandler); mMagnificationGestureHandler.put(displayId, magnificationGestureHandler); } if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) { - MotionEventInjector injector = new MotionEventInjector( - mContext.getMainLooper(), mAms.getTraceManager()); + MotionEventInjector injector = + new MotionEventInjector(mContext.getMainLooper(), mAms.getTraceManager()); addFirstEventHandler(displayId, injector); mMotionEventInjectors.put(displayId, injector); } @@ -923,6 +940,20 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } } + private boolean anyServiceWantsToObserveMotionEvent(MotionEvent event) { + // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing + // touch exploration. + if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN) + && (mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) { + return false; + } + final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK; + return (mCombinedGenericMotionEventSources + & mCombinedMotionEventObservedSources + & eventSourceWithoutClass) + != 0; + } + private boolean anyServiceWantsGenericMotionEvent(MotionEvent event) { // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing // touch exploration. @@ -938,6 +969,10 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo mCombinedGenericMotionEventSources = sources; } + public void setCombinedMotionEventObservedSources(int sources) { + mCombinedMotionEventObservedSources = sources; + } + /** * Keeps state of streams of events from all keyboard devices. */ diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 2eecb4d3a86c..bf3f317fc88f 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2825,8 +2825,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub flags |= AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS; } int combinedGenericMotionEventSources = 0; + int combinedMotionEventObservedSources = 0; for (AccessibilityServiceConnection connection : userState.mBoundServices) { combinedGenericMotionEventSources |= connection.mGenericMotionEventSources; + combinedMotionEventObservedSources |= connection.mObservedMotionEventSources; } if (combinedGenericMotionEventSources != 0) { flags |= AccessibilityInputFilter.FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS; @@ -2845,6 +2847,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags); mInputFilter.setCombinedGenericMotionEventSources( combinedGenericMotionEventSources); + mInputFilter.setCombinedMotionEventObservedSources( + combinedMotionEventObservedSources); } else { if (mHasInputFilter) { mHasInputFilter = false; |