diff options
Diffstat (limited to 'services/accessibility')
18 files changed, 910 insertions, 695 deletions
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index ee3bbcaf711d..ad87ceaf6f38 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -45,10 +45,10 @@ flag { } flag { - name: "compute_window_changes_on_a11y_v2" + name: "clear_shortcuts_when_activity_updates_to_service" namespace: "accessibility" - description: "Computes accessibility window changes in accessibility instead of wm package." - bug: "322444245" + description: "When an a11y activity is updated to an a11y service, clears the associated shortcuts so that we don't skip the AccessibilityServiceWarning." + bug: "358092445" metadata { purpose: PURPOSE_BUGFIX } @@ -86,10 +86,17 @@ flag { } flag { - name: "enable_hardware_shortcut_disables_warning" - namespace: "accessibility" - description: "When the user purposely enables the hardware shortcut, preemptively disables the first-time warning message." - bug: "287065325" + name: "enable_hardware_shortcut_disables_warning" + namespace: "accessibility" + description: "When the user purposely enables the hardware shortcut, preemptively disables the first-time warning message." + bug: "287065325" +} + +flag { + name: "enable_low_vision_hats" + namespace: "accessibility" + description: "Use HaTS for low vision feedback." + bug: "380346799" } flag { @@ -114,10 +121,13 @@ flag { } flag { - name: "enable_magnification_follows_mouse" + name: "enable_magnification_follows_mouse_bugfix" namespace: "accessibility" description: "Whether to enable mouse following for fullscreen magnification" bug: "354696546" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { @@ -162,6 +172,16 @@ flag { } flag { + name: "magnification_enlarge_pointer_bugfix" + namespace: "accessibility" + description: "When fullscreen magnification is enabled, pointer icon is enlarged" + bug: "355734856" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "manager_avoid_receiver_timeout" namespace: "accessibility" description: "Register receivers on background handler so they have more time to complete" @@ -172,6 +192,16 @@ flag { } flag { + name: "package_monitor_dedicated_thread" + namespace: "accessibility" + description: "Runs the A11yManagerService PackageMonitor on a dedicated thread" + bug: "348138695" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "manager_package_monitor_logic_fix" namespace: "accessibility" description: "Corrects the return values of the HandleForceStop function" diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index c234ee479a57..9ceca5d1dbfe 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -35,7 +35,6 @@ import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK; import static com.android.server.pm.UserManagerService.enforceCurrentUserIfVisibleBackgroundEnabled; -import static com.android.window.flags.Flags.deleteCaptureDisplay; import android.accessibilityservice.AccessibilityGestureEvent; import android.accessibilityservice.AccessibilityService; @@ -62,7 +61,6 @@ import android.graphics.ParcelableColorSpace; import android.graphics.Region; import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManager; -import android.hardware.display.DisplayManagerInternal; import android.hardware.usb.UsbDevice; import android.os.Binder; import android.os.Build; @@ -104,7 +102,6 @@ import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.os.SomeArgs; import com.android.internal.util.DumpUtils; import com.android.internal.util.function.pooled.PooledLambda; -import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection; import com.android.server.accessibility.magnification.MagnificationProcessor; import com.android.server.wm.WindowManagerInternal; @@ -165,16 +162,27 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ protected final AccessibilitySecurityPolicy mSecurityPolicy; protected final AccessibilityTrace mTrace; - // The attribution tag set by the service that is bound to this instance + /** The attribution tag set by the client that is bound to this instance */ protected String mAttributionTag; protected int mDisplayTypes = DISPLAY_TYPE_DEFAULT; - // The service that's bound to this instance. Whenever this value is non-null, this - // object is registered as a death recipient - IBinder mService; + /** + * Binder of the {@link #mClient}. + * + * <p>Whenever this value is non-null, it should be registered as a {@link + * IBinder.DeathRecipient} + */ + @Nullable IBinder mClientBinder; - IAccessibilityServiceClient mServiceInterface; + /** + * The accessibility client this class represents. + * + * <p>The client is in the application process, i.e., it's a client of system_server. Depending + * on the use case, the client can be an {@link AccessibilityService}, a {@code UiAutomation}, + * etc. + */ + @Nullable IAccessibilityServiceClient mClient; int mEventTypes; @@ -218,10 +226,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ int mGenericMotionEventSources; int mObservedMotionEventSources; - // the events pending events to be dispatched to this service + /** Pending events to be dispatched to the client */ final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<>(); - /** Whether this service relies on its {@link AccessibilityCache} being up to date */ + /** Whether the client relies on its {@link AccessibilityCache} being up to date */ boolean mUsesAccessibilityCache = false; // Handler only for dispatching accessibility events since we use event @@ -230,7 +238,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final SparseArray<IBinder> mOverlayWindowTokens = new SparseArray(); - // All the embedded accessibility overlays that have been added by this service. + /** All the embedded accessibility overlays that have been added by the client. */ private List<SurfaceControl> mOverlays = new ArrayList<>(); /** The timestamp of requesting to take screenshot in milliseconds */ @@ -274,7 +282,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ /** * Called back to notify system that the client has changed - * @param serviceInfoChanged True if the service's AccessibilityServiceInfo changed. + * + * @param serviceInfoChanged True if the client's AccessibilityServiceInfo changed. */ void onClientChangeLocked(boolean serviceInfoChanged); @@ -360,21 +369,22 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mIPlatformCompat = IPlatformCompat.Stub.asInterface( ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); - mEventDispatchHandler = new Handler(mainHandler.getLooper()) { - @Override - public void handleMessage(Message message) { - final int eventType = message.what; - AccessibilityEvent event = (AccessibilityEvent) message.obj; - boolean serviceWantsEvent = message.arg1 != 0; - notifyAccessibilityEventInternal(eventType, event, serviceWantsEvent); - } - }; + mEventDispatchHandler = + new Handler(mainHandler.getLooper()) { + @Override + public void handleMessage(Message message) { + final int eventType = message.what; + AccessibilityEvent event = (AccessibilityEvent) message.obj; + boolean clientWantsEvent = message.arg1 != 0; + notifyAccessibilityEventInternal(eventType, event, clientWantsEvent); + } + }; setDynamicallyConfigurableProperties(accessibilityServiceInfo); } @Override public boolean onKeyEvent(KeyEvent keyEvent, int sequenceNumber) { - if (!mRequestFilterKeyEvents || (mServiceInterface == null)) { + if (!mRequestFilterKeyEvents || (mClient == null)) { return false; } if((mAccessibilityServiceInfo.getCapabilities() @@ -388,7 +398,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ if (svcClientTracingEnabled()) { logTraceSvcClient("onKeyEvent", keyEvent + ", " + sequenceNumber); } - mServiceInterface.onKeyEvent(keyEvent, sequenceNumber); + mClient.onKeyEvent(keyEvent, sequenceNumber); } catch (RemoteException e) { return false; } @@ -470,7 +480,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } public boolean canReceiveEventsLocked() { - return (mEventTypes != 0 && mService != null); + return (mEventTypes != 0 && mClientBinder != null); } @RequiresNoPermission @@ -520,7 +530,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { - // If the XML manifest had data to configure the service its info + // If the XML manifest had data to configure the AccessibilityService, its info // should be already set. In such a case update only the dynamically // configurable properties. boolean oldRequestIme = mRequestImeApis; @@ -1500,68 +1510,31 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return; } final long identity = Binder.clearCallingIdentity(); - if (deleteCaptureDisplay()) { - try { - ScreenCapture.ScreenCaptureListener screenCaptureListener = new - ScreenCapture.ScreenCaptureListener( - (screenshotBuffer, result) -> { - if (screenshotBuffer != null && result == 0) { - sendScreenshotSuccess(screenshotBuffer, callback); - } else { - sendScreenshotFailure( - AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, - callback); - } + try { + ScreenCapture.ScreenCaptureListener screenCaptureListener = new + ScreenCapture.ScreenCaptureListener( + (screenshotBuffer, result) -> { + if (screenshotBuffer != null && result == 0) { + sendScreenshotSuccess(screenshotBuffer, callback); + } else { + sendScreenshotFailure( + AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, + callback); } - ); - mWindowManagerService.captureDisplay(displayId, null, screenCaptureListener); - } catch (Exception e) { - sendScreenshotFailure(AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, - callback); - } finally { - Binder.restoreCallingIdentity(identity); - } - } else { - try { - mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> { - final ScreenshotHardwareBuffer screenshotBuffer = LocalServices - .getService(DisplayManagerInternal.class).userScreenshot(displayId); - if (screenshotBuffer != null) { - sendScreenshotSuccess(screenshotBuffer, callback); - } else { - sendScreenshotFailure( - AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, - callback); } - }, null).recycleOnUse()); - } finally { - Binder.restoreCallingIdentity(identity); - } + ); + mWindowManagerService.captureDisplay(displayId, null, screenCaptureListener); + } catch (Exception e) { + sendScreenshotFailure(AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, + callback); + } finally { + Binder.restoreCallingIdentity(identity); } } private void sendScreenshotSuccess(ScreenshotHardwareBuffer screenshotBuffer, RemoteCallback callback) { - if (deleteCaptureDisplay()) { - mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> { - final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer(); - final ParcelableColorSpace colorSpace = - new ParcelableColorSpace(screenshotBuffer.getColorSpace()); - - final Bundle payload = new Bundle(); - payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS, - AccessibilityService.TAKE_SCREENSHOT_SUCCESS); - payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER, - hardwareBuffer); - payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace); - payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP, - SystemClock.uptimeMillis()); - - // Send back the result. - callback.sendResult(payload); - hardwareBuffer.close(); - }, null).recycleOnUse()); - } else { + mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> { final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer(); final ParcelableColorSpace colorSpace = new ParcelableColorSpace(screenshotBuffer.getColorSpace()); @@ -1578,7 +1551,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ // Send back the result. callback.sendResult(payload); hardwareBuffer.close(); - } + }, null).recycleOnUse()); } private void sendScreenshotFailure(@AccessibilityService.ScreenshotErrorCode int errorCode, @@ -1745,40 +1718,40 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ try { // Clear the proxy in the other process so this // IAccessibilityServiceConnection can be garbage collected. - if (mServiceInterface != null) { + if (mClient != null) { if (svcClientTracingEnabled()) { logTraceSvcClient("init", "null, " + mId + ", null"); } - mServiceInterface.init(null, mId, null); + mClient.init(null, mId, null); } } catch (RemoteException re) { /* ignore */ } - if (mService != null) { + if (mClientBinder != null) { try { - mService.unlinkToDeath(this, 0); + mClientBinder.unlinkToDeath(this, 0); } catch (NoSuchElementException e) { Slog.e(LOG_TAG, "Failed unregistering death link"); } - mService = null; + mClientBinder = null; } - mServiceInterface = null; + mClient = null; mReceivedAccessibilityButtonCallbackSinceBind = false; } public boolean isConnectedLocked() { - return (mService != null); + return (mClientBinder != null); } public void notifyAccessibilityEvent(AccessibilityEvent event) { synchronized (mLock) { final int eventType = event.getEventType(); - final boolean serviceWantsEvent = wantsEventLocked(event); + final boolean clientWantsEvent = clientWantsEventLocked(event); final boolean requiredForCacheConsistency = mUsesAccessibilityCache && ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & eventType) != 0); - if (!serviceWantsEvent && !requiredForCacheConsistency) { + if (!clientWantsEvent && !requiredForCacheConsistency) { return; } @@ -1786,7 +1759,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return; } // Make a copy since during dispatch it is possible the event to - // be modified to remove its source if the receiving service does + // be modified to remove its source if the receiving client does // not have permission to access the window content. AccessibilityEvent newEvent = AccessibilityEvent.obtain(event); Message message; @@ -1804,22 +1777,20 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ // Send all messages, bypassing mPendingEvents message = mEventDispatchHandler.obtainMessage(eventType, newEvent); } - message.arg1 = serviceWantsEvent ? 1 : 0; + message.arg1 = clientWantsEvent ? 1 : 0; mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout); } } /** - * Determines if given event can be dispatched to a service based on the package of the - * event source. Specifically, a service is notified if it is interested in events from the - * package. + * Determines if given event can be dispatched to a client based on the package of the event + * source. Specifically, a client is notified if it is interested in events from the package. * * @param event The event. - * @return True if the listener should be notified, false otherwise. + * @return True if the client should be notified, false otherwise. */ - private boolean wantsEventLocked(AccessibilityEvent event) { - + private boolean clientWantsEventLocked(AccessibilityEvent event) { if (!canReceiveEventsLocked()) { return false; } @@ -1850,22 +1821,20 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } /** - * Notifies an accessibility service client for a scheduled event given the event type. + * Notifies a client for a scheduled event given the event type. * * @param eventType The type of the event to dispatch. */ private void notifyAccessibilityEventInternal( - int eventType, - AccessibilityEvent event, - boolean serviceWantsEvent) { - IAccessibilityServiceClient listener; + int eventType, AccessibilityEvent event, boolean clientWantsEvent) { + IAccessibilityServiceClient client; synchronized (mLock) { - listener = mServiceInterface; + client = mClient; - // If the service died/was disabled while the message for dispatching - // the accessibility event was propagating the listener may be null. - if (listener == null) { + // If the client (in the application process) died/was disabled while the message for + // dispatching the accessibility event was propagating, "client" may be null. + if (client == null) { return; } @@ -1880,7 +1849,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ // 1) A binder thread calls notifyAccessibilityServiceDelayedLocked // which posts a message for dispatching an event and stores the event // in mPendingEvents. - // 2) The message is pulled from the queue by the handler on the service + // 2) The message is pulled from the queue by the handler on the client // thread and this method is just about to acquire the lock. // 3) Another binder thread acquires the lock in notifyAccessibilityEvent // 4) notifyAccessibilityEvent recycles the event that this method was about @@ -1888,7 +1857,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ // 5) This method grabs the new event, processes it, and removes it from // mPendingEvents // 6) The second message dispatched in (4) arrives, but the event has been - // remvoved in (5). + // removed in (5). event = mPendingEvents.get(eventType); if (event == null) { return; @@ -1905,14 +1874,14 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ try { if (svcClientTracingEnabled()) { - logTraceSvcClient("onAccessibilityEvent", event + ";" + serviceWantsEvent); + logTraceSvcClient("onAccessibilityEvent", event + ";" + clientWantsEvent); } - listener.onAccessibilityEvent(event, serviceWantsEvent); + client.onAccessibilityEvent(event, clientWantsEvent); if (DEBUG) { - Slog.i(LOG_TAG, "Event " + event + " sent to " + listener); + Slog.i(LOG_TAG, "Event " + event + " sent to " + client); } } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re); + Slog.e(LOG_TAG, "Error during sending " + event + " to " + client, re); } finally { event.recycle(); } @@ -1990,122 +1959,126 @@ 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. + * Called by the invocation handler to notify the client that the state of magnification has + * changed. */ - private void notifyMagnificationChangedInternal(int displayId, @NonNull Region region, - @NonNull MagnificationConfig config) { - final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); - if (listener != null) { + private void notifyMagnificationChangedInternal( + int displayId, @NonNull Region region, @NonNull MagnificationConfig config) { + final IAccessibilityServiceClient client = getClientSafely(); + if (client != null) { try { if (svcClientTracingEnabled()) { logTraceSvcClient("onMagnificationChanged", displayId + ", " + region + ", " + config.toString()); } - listener.onMagnificationChanged(displayId, region, config); + client.onMagnificationChanged(displayId, region, config); } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re); + Slog.e(LOG_TAG, "Error sending magnification changes to " + mClientBinder, re); } } } /** - * Called by the invocation handler to notify the service that the state of the soft - * keyboard show mode has changed. + * Called by the invocation handler to notify the client that the state of the soft keyboard + * show mode has changed. */ private void notifySoftKeyboardShowModeChangedInternal(int showState) { - final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); - if (listener != null) { + final IAccessibilityServiceClient client = getClientSafely(); + if (client != null) { try { if (svcClientTracingEnabled()) { logTraceSvcClient("onSoftKeyboardShowModeChanged", String.valueOf(showState)); } - listener.onSoftKeyboardShowModeChanged(showState); + client.onSoftKeyboardShowModeChanged(showState); } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error sending soft keyboard show mode changes to " + mService, + Slog.e( + LOG_TAG, + "Error sending soft keyboard show mode changes to " + mClientBinder, re); } } } private void notifyAccessibilityButtonClickedInternal(int displayId) { - final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); - if (listener != null) { + final IAccessibilityServiceClient client = getClientSafely(); + if (client != null) { try { if (svcClientTracingEnabled()) { logTraceSvcClient("onAccessibilityButtonClicked", String.valueOf(displayId)); } - listener.onAccessibilityButtonClicked(displayId); + client.onAccessibilityButtonClicked(displayId); } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error sending accessibility button click to " + mService, re); + Slog.e(LOG_TAG, "Error sending accessibility button click to " + mClientBinder, re); } } } private void notifyAccessibilityButtonAvailabilityChangedInternal(boolean available) { - // Only notify the service if it's not been notified or the state has changed + // Only notify the client if it's not been notified or the state has changed if (mReceivedAccessibilityButtonCallbackSinceBind && (mLastAccessibilityButtonCallbackState == available)) { return; } mReceivedAccessibilityButtonCallbackSinceBind = true; mLastAccessibilityButtonCallbackState = available; - final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); - if (listener != null) { + final IAccessibilityServiceClient client = getClientSafely(); + if (client != null) { try { if (svcClientTracingEnabled()) { logTraceSvcClient("onAccessibilityButtonAvailabilityChanged", String.valueOf(available)); } - listener.onAccessibilityButtonAvailabilityChanged(available); + client.onAccessibilityButtonAvailabilityChanged(available); } catch (RemoteException re) { - Slog.e(LOG_TAG, - "Error sending accessibility button availability change to " + mService, + Slog.e( + LOG_TAG, + "Error sending accessibility button availability change to " + + mClientBinder, re); } } } private void notifyGestureInternal(AccessibilityGestureEvent gestureInfo) { - final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); - if (listener != null) { + final IAccessibilityServiceClient client = getClientSafely(); + if (client != null) { try { if (svcClientTracingEnabled()) { logTraceSvcClient("onGesture", gestureInfo.toString()); } - listener.onGesture(gestureInfo); + client.onGesture(gestureInfo); } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error during sending gesture " + gestureInfo - + " to " + mService, re); + Slog.e( + LOG_TAG, + "Error during sending gesture " + gestureInfo + " to " + mClientBinder, + re); } } } private void notifySystemActionsChangedInternal() { - final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); - if (listener != null) { + final IAccessibilityServiceClient client = getClientSafely(); + if (client != null) { try { if (svcClientTracingEnabled()) { logTraceSvcClient("onSystemActionsChanged", ""); } - listener.onSystemActionsChanged(); + client.onSystemActionsChanged(); } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error sending system actions change to " + mService, - re); + Slog.e(LOG_TAG, "Error sending system actions change to " + mClientBinder, re); } } } private void notifyClearAccessibilityCacheInternal() { - final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); - if (listener != null) { + final IAccessibilityServiceClient client = getClientSafely(); + if (client != null) { try { if (svcClientTracingEnabled()) { logTraceSvcClient("clearAccessibilityCache", ""); } - listener.clearAccessibilityCache(); + client.clearAccessibilityCache(); } catch (RemoteException re) { Slog.e(LOG_TAG, "Error during requesting accessibility info cache" + " to be cleared.", re); @@ -2118,70 +2091,66 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ private void setImeSessionEnabledInternal(IAccessibilityInputMethodSession session, boolean enabled) { - final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); - if (listener != null && session != null) { + final IAccessibilityServiceClient client = getClientSafely(); + if (client != null && session != null) { try { if (svcClientTracingEnabled()) { logTraceSvcClient("createImeSession", ""); } - listener.setImeSessionEnabled(session, enabled); + client.setImeSessionEnabled(session, enabled); } catch (RemoteException re) { - Slog.e(LOG_TAG, - "Error requesting IME session from " + mService, re); + Slog.e(LOG_TAG, "Error requesting IME session from " + mClientBinder, re); } } } private void bindInputInternal() { - final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); - if (listener != null) { + final IAccessibilityServiceClient client = getClientSafely(); + if (client != null) { try { if (svcClientTracingEnabled()) { logTraceSvcClient("bindInput", ""); } - listener.bindInput(); + client.bindInput(); } catch (RemoteException re) { - Slog.e(LOG_TAG, - "Error binding input to " + mService, re); + Slog.e(LOG_TAG, "Error binding input to " + mClientBinder, re); } } } private void unbindInputInternal() { - final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); - if (listener != null) { + final IAccessibilityServiceClient client = getClientSafely(); + if (client != null) { try { if (svcClientTracingEnabled()) { logTraceSvcClient("unbindInput", ""); } - listener.unbindInput(); + client.unbindInput(); } catch (RemoteException re) { - Slog.e(LOG_TAG, - "Error unbinding input to " + mService, re); + Slog.e(LOG_TAG, "Error unbinding input to " + mClientBinder, re); } } } private void startInputInternal(IRemoteAccessibilityInputConnection connection, EditorInfo editorInfo, boolean restarting) { - final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); - if (listener != null) { + final IAccessibilityServiceClient client = getClientSafely(); + if (client != null) { try { if (svcClientTracingEnabled()) { logTraceSvcClient("startInput", "editorInfo=" + editorInfo + " restarting=" + restarting); } - listener.startInput(connection, editorInfo, restarting); + client.startInput(connection, editorInfo, restarting); } catch (RemoteException re) { - Slog.e(LOG_TAG, - "Error starting input to " + mService, re); + Slog.e(LOG_TAG, "Error starting input to " + mClientBinder, re); } } } - protected IAccessibilityServiceClient getServiceInterfaceSafely() { + protected IAccessibilityServiceClient getClientSafely() { synchronized (mLock) { - return mServiceInterface; + return mClient; } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 617cca9d3075..e1b6c9c5aa42 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -27,6 +27,8 @@ import android.annotation.NonNull; import android.content.Context; import android.graphics.Region; import android.hardware.input.InputManager; +import android.hardware.input.KeyGestureEvent; +import android.os.IBinder; import android.os.Looper; import android.os.PowerManager; import android.os.SystemClock; @@ -44,11 +46,14 @@ import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import android.view.accessibility.AccessibilityEvent; +import androidx.annotation.Nullable; + import com.android.server.LocalServices; import com.android.server.accessibility.gestures.TouchExplorer; import com.android.server.accessibility.magnification.FullScreenMagnificationController; import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler; import com.android.server.accessibility.magnification.FullScreenMagnificationVibrationHelper; +import com.android.server.accessibility.magnification.MagnificationController; import com.android.server.accessibility.magnification.MagnificationGestureHandler; import com.android.server.accessibility.magnification.MouseEventHandler; import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler; @@ -187,6 +192,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo private final AccessibilityManagerService mAms; + private final InputManager mInputManager; + private final SparseArray<EventStreamTransformation> mEventHandler; private final SparseArray<TouchExplorer> mTouchExplorer = new SparseArray<>(0); @@ -228,6 +235,47 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo */ private MotionEvent mLastActiveDeviceMotionEvent = null; + private boolean mKeyGestureEventHandlerInstalled = false; + private InputManager.KeyGestureEventHandler mKeyGestureEventHandler = + new InputManager.KeyGestureEventHandler() { + @Override + public boolean handleKeyGestureEvent( + @NonNull KeyGestureEvent event, + @Nullable IBinder focusedToken) { + final boolean complete = + event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE + && !event.isCancelled(); + final int gestureType = event.getKeyGestureType(); + final int displayId = isDisplayIdValid(event.getDisplayId()) + ? event.getDisplayId() : Display.DEFAULT_DISPLAY; + + switch (gestureType) { + case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN: + if (complete) { + mAms.getMagnificationController().scaleMagnificationByStep( + displayId, MagnificationController.ZOOM_DIRECTION_IN); + } + return true; + case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT: + if (complete) { + mAms.getMagnificationController().scaleMagnificationByStep( + displayId, MagnificationController.ZOOM_DIRECTION_OUT); + } + return true; + } + return false; + } + + @Override + public boolean isKeyGestureSupported(int gestureType) { + return switch (gestureType) { + case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN, + KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT -> true; + default -> false; + }; + } + }; + private static MotionEvent cancelMotion(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT @@ -287,6 +335,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo mContext = context; mAms = service; mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + mInputManager = context.getSystemService(InputManager.class); mEventHandler = eventHandler; } @@ -360,10 +409,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo final int eventSource = event.getSource(); final int displayId = event.getDisplayId(); if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) { - if (!Flags.doNotResetKeyEventState()) { - state.reset(); - clearEventStreamHandler(displayId, eventSource); - } if (DEBUG) { Slog.d(TAG, "Not processing event " + event); } @@ -723,6 +768,12 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo createMagnificationGestureHandler(displayId, displayContext); addFirstEventHandler(displayId, magnificationGestureHandler); mMagnificationGestureHandler.put(displayId, magnificationGestureHandler); + + if (com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures() + && !mKeyGestureEventHandlerInstalled) { + mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler); + mKeyGestureEventHandlerInstalled = true; + } } if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) { @@ -842,6 +893,11 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo mMouseKeysInterceptor.onDestroy(); mMouseKeysInterceptor = null; } + + if (mKeyGestureEventHandlerInstalled) { + mInputManager.unregisterKeyGestureEventHandler(mKeyGestureEventHandler); + mKeyGestureEventHandlerInstalled = false; + } } private MagnificationGestureHandler createMagnificationGestureHandler( @@ -1120,18 +1176,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } private boolean anyServiceWantsGenericMotionEvent(MotionEvent event) { - if (Flags.alwaysAllowObservingTouchEvents()) { - final boolean isTouchEvent = event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN); - if (isTouchEvent && !canShareGenericTouchEvent()) { - return false; - } - final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK; - return (mCombinedGenericMotionEventSources & eventSourceWithoutClass) != 0; - } - // 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) { + final boolean isTouchEvent = event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN); + if (isTouchEvent && !canShareGenericTouchEvent()) { return false; } final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK; @@ -1139,21 +1185,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } private boolean anyServiceWantsToObserveMotionEvent(MotionEvent event) { - if (Flags.alwaysAllowObservingTouchEvents()) { - final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK; - return (mCombinedMotionEventObservedSources & eventSourceWithoutClass) != 0; - } - // 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; + return (mCombinedMotionEventObservedSources & eventSourceWithoutClass) != 0; } private boolean canShareGenericTouchEvent() { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 49f15e46894d..5c1ad74fac93 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -42,9 +42,11 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATIN import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED; +import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import static android.view.accessibility.AccessibilityManager.FlashNotificationReason; +import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures; import static com.android.hardware.input.Flags.keyboardA11yMouseKeys; import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME; @@ -52,8 +54,10 @@ import static com.android.internal.accessibility.AccessibilityShortcutController import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME; import static com.android.internal.accessibility.common.ShortcutConstants.USER_SHORTCUT_TYPES; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.ALL; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP; @@ -110,12 +114,15 @@ import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.DisplayManager; import android.hardware.fingerprint.IFingerprintService; +import android.hardware.input.InputManager; +import android.hardware.input.KeyGestureEvent; import android.media.AudioManagerInternal; import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; @@ -336,6 +343,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private AlertDialog mEnableTouchExplorationDialog; + private final InputManager mInputManager; + private AccessibilityInputFilter mInputFilter; private boolean mHasInputFilter; @@ -501,6 +510,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + private InputManager.KeyGestureEventHandler mKeyGestureEventHandler = + new InputManager.KeyGestureEventHandler() { + @Override + public boolean handleKeyGestureEvent( + @NonNull KeyGestureEvent event, + @Nullable IBinder focusedToken) { + return AccessibilityManagerService.this.handleKeyGestureEvent(event); + } + + @Override + public boolean isKeyGestureSupported(int gestureType) { + return switch (gestureType) { + case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION, + KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK -> true; + default -> false; + }; + } + }; + @VisibleForTesting AccessibilityManagerService( Context context, @@ -540,6 +568,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mUmi = LocalServices.getService(UserManagerInternal.class); // TODO(b/255426725): not used on tests mVisibleBgUserIds = null; + mInputManager = context.getSystemService(InputManager.class); init(); } @@ -581,6 +610,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mUiAutomationManager, this); mFlashNotificationsController = new FlashNotificationsController(mContext); mUmi = LocalServices.getService(UserManagerInternal.class); + mInputManager = context.getSystemService(InputManager.class); if (UserManager.isVisibleBackgroundUsersEnabled()) { mVisibleBgUserIds = new SparseBooleanArray(); @@ -597,6 +627,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub registerBroadcastReceivers(); new AccessibilityContentObserver(mMainHandler).register( mContext.getContentResolver()); + if (enableTalkbackAndMagnifierKeyGestures()) { + mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler); + } disableAccessibilityMenuToMigrateIfNeeded(); } @@ -638,6 +671,79 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return mIsAccessibilityButtonShown; } + @VisibleForTesting + boolean handleKeyGestureEvent(KeyGestureEvent event) { + final boolean complete = + event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE + && !event.isCancelled(); + final int gestureType = event.getKeyGestureType(); + if (!complete) { + return false; + } + + String targetName; + switch (gestureType) { + case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION: + targetName = MAGNIFICATION_CONTROLLER_NAME; + break; + case KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK: + targetName = mContext.getString(R.string.config_defaultSelectToSpeakService); + if (targetName.isEmpty()) { + return false; + } + + final ComponentName targetServiceComponent = TextUtils.isEmpty(targetName) + ? null : ComponentName.unflattenFromString(targetName); + AccessibilityServiceInfo accessibilityServiceInfo; + synchronized (mLock) { + AccessibilityUserState userState = getCurrentUserStateLocked(); + accessibilityServiceInfo = + userState.getInstalledServiceInfoLocked(targetServiceComponent); + } + if (accessibilityServiceInfo == null) { + return false; + } + + // Skip enabling if a warning dialog is required for the feature. + // TODO(b/377752960): Explore better options to instead show the warning dialog + // in this scenario. + if (isAccessibilityServiceWarningRequired(accessibilityServiceInfo)) { + Slog.w(LOG_TAG, + "Accessibility warning is required before this service can be " + + "activated automatically via KEY_GESTURE shortcut."); + return false; + } + break; + default: + return false; + } + + List<String> shortcutTargets = getAccessibilityShortcutTargets( + KEY_GESTURE); + if (!shortcutTargets.contains(targetName)) { + int userId; + synchronized (mLock) { + userId = mCurrentUserId; + } + // TODO(b/377752960): Add dialog to confirm enabling the service and to + // activate the first time. + enableShortcutForTargets(true, UserShortcutType.KEY_GESTURE, + List.of(targetName), userId); + + // Do not perform action on first press since it was just registered. Eventually, + // this will be a separate dialog that appears that requires the user to confirm + // which will resolve this race condition. For now, just require two presses the + // first time it is activated. + return true; + } + + final int displayId = event.getDisplayId() != INVALID_DISPLAY + ? event.getDisplayId() : getLastNonProxyTopFocusedDisplayId(); + performAccessibilityShortcutInternal(displayId, KEY_GESTURE, targetName); + + return true; + } + @Override public Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec( int windowId) { @@ -901,7 +1007,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private void registerBroadcastReceivers() { // package changes mPackageMonitor = new ManagerPackageMonitor(this); - mPackageMonitor.register(mContext, null, UserHandle.ALL, true); + final Looper packageMonitorLooper; + if (Flags.packageMonitorDedicatedThread()) { + // Use a dedicated thread because the default BackgroundThread used by PackageMonitor + // is shared by other components and can get busy, causing a delay and eventual ANR when + // responding to broadcasts sent to this PackageMonitor. + HandlerThread packageMonitorThread = new HandlerThread(LOG_TAG + " PackageMonitor", + Process.THREAD_PRIORITY_BACKGROUND); + packageMonitorThread.start(); + packageMonitorLooper = packageMonitorThread.getLooper(); + } else { + packageMonitorLooper = null; + } + mPackageMonitor.register(mContext, packageMonitorLooper, UserHandle.ALL, true); // user change and unlock IntentFilter intentFilter = new IntentFilter(); @@ -910,8 +1028,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub intentFilter.addAction(Intent.ACTION_USER_REMOVED); intentFilter.addAction(Intent.ACTION_SETTING_RESTORED); - Handler receiverHandler = - Flags.managerAvoidReceiverTimeout() ? BackgroundThread.getHandler() : null; + Handler receiverHandler = BackgroundThread.getHandler(); mContext.registerReceiverAsUser(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -953,8 +1070,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub newValue, restoredFromSdk); } } + // Currently in SUW, the user can't see gesture shortcut option as the + // navigation system is set to button navigation. We'll rely on the + // SettingsBackupAgent to restore the settings since we don't + // need to merge an empty gesture target. case Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, - Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS, Settings.Secure.ACCESSIBILITY_QS_TARGETS, Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE -> restoreShortcutTargets(newValue, @@ -1210,14 +1330,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub int displayId = event.getDisplayId(); final int windowId = event.getWindowId(); if (windowId != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID - && displayId == Display.INVALID_DISPLAY) { + && displayId == INVALID_DISPLAY) { displayId = mA11yWindowManager.getDisplayIdByUserIdAndWindowId( resolvedUserId, windowId); event.setDisplayId(displayId); } synchronized (mLock) { if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED - && displayId != Display.INVALID_DISPLAY + && displayId != INVALID_DISPLAY && mA11yWindowManager.isTrackingWindowsLocked(displayId)) { shouldComputeWindows = true; } @@ -1435,8 +1555,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub interfacesToInterrupt = new ArrayList<>(services.size()); for (int i = 0; i < services.size(); i++) { AccessibilityServiceConnection service = services.get(i); - IBinder a11yServiceBinder = service.mService; - IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface; + IBinder a11yServiceBinder = service.mClientBinder; + IAccessibilityServiceClient a11yServiceInterface = service.mClient; if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) { interfacesToInterrupt.add(a11yServiceInterface); } @@ -2139,10 +2259,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (shortcutType == QUICK_SETTINGS && !android.view.accessibility.Flags.a11yQsShortcut()) { return; } - if (shortcutType == HARDWARE - && !android.view.accessibility.Flags.restoreA11yShortcutTargetService()) { - return; - } synchronized (mLock) { final AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM); @@ -2151,8 +2267,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mContext, shortcutType, userState.mUserId)) : userState.getShortcutTargetsLocked(shortcutType); - if (Flags.clearDefaultFromA11yShortcutTargetServiceRestore() - && shortcutType == HARDWARE) { + if (shortcutType == HARDWARE) { final String defaultService = mContext.getString(R.string.config_defaultAccessibilityService); final ComponentName defaultServiceComponent = TextUtils.isEmpty(defaultService) @@ -2513,6 +2628,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState, List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) { if (!parsedAccessibilityShortcutInfos.equals(userState.mInstalledShortcuts)) { + if (Flags.clearShortcutsWhenActivityUpdatesToService()) { + List<String> componentNames = userState.mInstalledShortcuts.stream() + .filter(a11yActivity -> + !parsedAccessibilityShortcutInfos.contains(a11yActivity)) + .map(a11yActivity -> a11yActivity.getComponentName().flattenToString()) + .toList(); + if (!componentNames.isEmpty()) { + enableShortcutsForTargets( + /* enable= */ false, UserShortcutType.ALL, + componentNames, userState.mUserId); + } + } + userState.mInstalledShortcuts.clear(); userState.mInstalledShortcuts.addAll(parsedAccessibilityShortcutInfos); userState.updateTileServiceMapForAccessibilityActivityLocked(); @@ -2799,27 +2927,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub final String builderValue = builder.toString(); final String settingValue = TextUtils.isEmpty(builderValue) ? defaultEmptyString : builderValue; - if (android.view.accessibility.Flags.restoreA11yShortcutTargetService()) { - final String currentValue = Settings.Secure.getStringForUser( - mContext.getContentResolver(), settingName, userId); - if (Objects.equals(settingValue, currentValue)) { - // This logic exists to fix a bug where AccessibilityManagerService was writing - // `null` to the ACCESSIBILITY_SHORTCUT_TARGET_SERVICE setting during early boot - // during setup, due to a race condition in package scanning making A11yMS think - // that the default service was not installed. - // - // Writing `null` was implicitly causing that Setting to have the default - // `DEFAULT_OVERRIDEABLE_BY_RESTORE` property, which was preventing B&R for that - // Setting altogether. - // - // The "quick fix" here is to not write `null` if the existing value is already - // `null`. The ideal fix would be use the Settings.Secure#putStringForUser overload - // that allows override-by-restore, but the full repercussions of using that here - // have not yet been evaluated. - // TODO: b/333457719 - Evaluate and fix AccessibilityManagerService's usage of - // "overridable by restore" when writing secure settings. - return; - } + final String currentValue = Settings.Secure.getStringForUser( + mContext.getContentResolver(), settingName, userId); + if (Objects.equals(settingValue, currentValue)) { + // This logic exists to fix a bug where AccessibilityManagerService was writing + // `null` to the ACCESSIBILITY_SHORTCUT_TARGET_SERVICE setting during early boot + // during setup, due to a race condition in package scanning making A11yMS think + // that the default service was not installed. + // + // Writing `null` was implicitly causing that Setting to have the default + // `DEFAULT_OVERRIDEABLE_BY_RESTORE` property, which was preventing B&R for that + // Setting altogether. + // + // The "quick fix" here is to not write `null` if the existing value is already + // `null`. The ideal fix would be use the Settings.Secure#putStringForUser overload + // that allows override-by-restore, but the full repercussions of using that here + // have not yet been evaluated. + // TODO: b/333457719 - Evaluate and fix AccessibilityManagerService's usage of + // "overridable by restore" when writing secure settings. + return; } final long identity = Binder.clearCallingIdentity(); try { @@ -3230,6 +3356,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub updateAccessibilityShortcutTargetsLocked(userState, SOFTWARE); updateAccessibilityShortcutTargetsLocked(userState, GESTURE); updateAccessibilityShortcutTargetsLocked(userState, QUICK_SETTINGS); + updateAccessibilityShortcutTargetsLocked(userState, KEY_GESTURE); // Update the capabilities before the mode because we will check the current mode is // invalid or not.. updateMagnificationCapabilitiesSettingsChangeLocked(userState); @@ -3360,6 +3487,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, QUICK_SETTINGS); somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, SOFTWARE); somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, GESTURE); + somethingChanged |= readAccessibilityShortcutTargetsLocked(userState, KEY_GESTURE); somethingChanged |= readAccessibilityButtonTargetComponentLocked(userState); somethingChanged |= readUserRecommendedUiTimeoutSettingsLocked(userState); somethingChanged |= readMagnificationModeForDefaultDisplayLocked(userState); @@ -3664,13 +3792,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub || (Flags.enableMagnificationMultipleFingerMultipleTapGesture() && userState.isMagnificationTwoFingerTripleTapEnabledLocked())); - final boolean createConnectionForCurrentCapability = - com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder() - || (userState.getMagnificationCapabilitiesLocked() - != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); - - final boolean connect = (shortcutEnabled && createConnectionForCurrentCapability) - || userHasMagnificationServicesLocked(userState); + final boolean connect = shortcutEnabled || userHasMagnificationServicesLocked(userState); getMagnificationConnectionManager().requestConnection(connect); } @@ -3871,6 +3993,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState.getShortcutTargetsLocked(HARDWARE); final Set<String> qsShortcutTargets = userState.getShortcutTargetsLocked(QUICK_SETTINGS); + final Set<String> shortcutTargets = userState.getShortcutTargetsLocked(ALL); userState.mEnabledServices.forEach(componentName -> { if (packageName != null && componentName != null && !packageName.equals(componentName.getPackageName())) { @@ -3891,7 +4014,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (TextUtils.isEmpty(serviceName)) { return; } - if (doesShortcutTargetsStringContain(buttonTargets, serviceName) + if (android.provider.Flags.a11yStandaloneGestureEnabled()) { + if (doesShortcutTargetsStringContain(shortcutTargets, serviceName)) { + return; + } + } else if (doesShortcutTargetsStringContain(buttonTargets, serviceName) || doesShortcutTargetsStringContain(shortcutKeyTargets, serviceName) || doesShortcutTargetsStringContain(qsShortcutTargets, serviceName)) { return; @@ -3936,6 +4063,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (android.provider.Flags.a11yStandaloneGestureEnabled()) { shortcutTypes.add(GESTURE); } + shortcutTypes.add(KEY_GESTURE); final ComponentName serviceName = service.getComponentName(); for (Integer shortcutType: shortcutTypes) { @@ -4046,13 +4174,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub */ private void performAccessibilityShortcutInternal(int displayId, @UserShortcutType int shortcutType, @Nullable String targetName) { - final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType); + final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal( + shortcutType); if (shortcutTargets.isEmpty()) { Slog.d(LOG_TAG, "No target to perform shortcut, shortcutType=" + shortcutType); return; } // In case the caller specified a target name - if (targetName != null && !doesShortcutTargetsStringContain(shortcutTargets, targetName)) { + if (targetName != null && !doesShortcutTargetsStringContain(shortcutTargets, + targetName)) { Slog.v(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName); targetName = null; } @@ -4274,6 +4404,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return; } + if (shortcutType == UserShortcutType.KEY_GESTURE + && !enableTalkbackAndMagnifierKeyGestures()) { + Slog.w(LOG_TAG, + "KEY_GESTURE type shortcuts are disabled by feature flag"); + return; + } + final String shortcutTypeSettingKey = ShortcutUtils.convertToKey(shortcutType); if (shortcutType == UserShortcutType.TRIPLETAP || shortcutType == UserShortcutType.TWOFINGER_DOUBLETAP) { @@ -4355,13 +4492,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } if (shortcutType == HARDWARE) { skipVolumeShortcutDialogTimeoutRestriction(userId); - if (com.android.server.accessibility.Flags.enableHardwareShortcutDisablesWarning()) { - persistIntToSetting( - userId, - Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, - AccessibilityShortcutController.DialogStatus.SHOWN - ); - } + persistIntToSetting( + userId, + Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, + AccessibilityShortcutController.DialogStatus.SHOWN + ); } else if (shortcutType == SOFTWARE) { // Update the A11y FAB size to large when the Magnification shortcut is // enabled and the user hasn't changed the floating button size @@ -4706,8 +4841,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub getMagnificationConnectionManager().setConnection(connection); - if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder() - && connection == null + if (connection == null && mMagnificationController.isFullScreenMagnificationControllerInitialized()) { // Since the connection does not exist, the system ui cannot provide the border // implementation for fullscreen magnification. So we call reset to deactivate the @@ -4968,9 +5102,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() && android.security.Flags.extendEcmToAllSettings()) { try { - return !mContext.getSystemService(EnhancedConfirmationManager.class) - .isRestricted(packageName, - AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE); + final EnhancedConfirmationManager userContextEcm = + mContext.createContextAsUser(UserHandle.of(userId), /* flags = */ 0) + .getSystemService(EnhancedConfirmationManager.class); + if (userContextEcm != null) { + return !userContextEcm.isRestricted(packageName, + AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE); + } + return false; } catch (PackageManager.NameNotFoundException e) { Log.e(LOG_TAG, "Exception when retrieving package:" + packageName, e); return false; @@ -5034,6 +5173,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @EnforcePermission(MANAGE_ACCESSIBILITY) public boolean isAccessibilityServiceWarningRequired(AccessibilityServiceInfo info) { isAccessibilityServiceWarningRequired_enforcePermission(); + if (info == null) { + Log.e(LOG_TAG, "Called isAccessibilityServiceWarningRequired with null service info"); + return true; + } + final ComponentName componentName = info.getComponentName(); // Warning is not required if the service is already enabled. @@ -5641,6 +5785,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final Uri mAccessibilityGestureTargetsUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS); + private final Uri mAccessibilityKeyGestureTargetsUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_KEY_GESTURE_TARGETS); + private final Uri mUserNonInteractiveUiTimeoutUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS); @@ -5705,6 +5852,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub contentResolver.registerContentObserver( mAccessibilityGestureTargetsUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver( + mAccessibilityKeyGestureTargetsUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver( mUserNonInteractiveUiTimeoutUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver( mUserInteractiveUiTimeoutUri, false, this, UserHandle.USER_ALL); @@ -5786,6 +5935,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (readAccessibilityShortcutTargetsLocked(userState, GESTURE)) { onUserStateChangedLocked(userState); } + } else if (mAccessibilityKeyGestureTargetsUri.equals(uri)) { + if (readAccessibilityShortcutTargetsLocked(userState, KEY_GESTURE)) { + onUserStateChangedLocked(userState); + } } else if (mUserNonInteractiveUiTimeoutUri.equals(uri) || mUserInteractiveUiTimeoutUri.equals(uri)) { readUserRecommendedUiTimeoutSettingsLocked(userState); @@ -6383,8 +6536,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // Only continue setting up the packages if the service has been initialized. // See: b/340927041 - if (Flags.skipPackageChangeBeforeUserSwitch() - && !mManagerService.isServiceInitializedLocked()) { + if (!mManagerService.isServiceInitializedLocked()) { Slog.w(LOG_TAG, "onSomePackagesChanged: service not initialized, skip the callback."); return; @@ -6492,28 +6644,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } final AccessibilityUserState userState = mManagerService.getUserStateLocked(userId); - if (Flags.managerPackageMonitorLogicFix()) { - if (!doit) { - // if we're not handling the stop here, then we only need to know - // if any of the force-stopped packages are currently enabled. - return userState.mEnabledServices.stream().anyMatch( - (comp) -> Arrays.stream(packages).anyMatch( - (pkg) -> pkg.equals(comp.getPackageName())) - ); - } else if (mManagerService.onPackagesForceStoppedLocked(packages, userState)) { - mManagerService.onUserStateChangedLocked(userState); - } - return false; - } else { - // this old logic did not properly indicate when base packageMonitor's routine - // should handle stopping the package. - if (doit && mManagerService.onPackagesForceStoppedLocked(packages, userState)) { - mManagerService.onUserStateChangedLocked(userState); - return false; - } else { - return true; - } + if (!doit) { + // if we're not handling the stop here, then we only need to know + // if any of the force-stopped packages are currently enabled. + return userState.mEnabledServices.stream().anyMatch( + (comp) -> Arrays.stream(packages).anyMatch( + (pkg) -> pkg.equals(comp.getPackageName())) + ); + } else if (mManagerService.onPackagesForceStoppedLocked(packages, userState)) { + mManagerService.onUserStateChangedLocked(userState); } + return false; } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java index f45fa921c4a2..5ae077363c88 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java @@ -405,10 +405,9 @@ public class AccessibilitySecurityPolicy { * @throws SecurityException if the input method is not in the same package as the service. */ @AccessibilityService.SoftKeyboardController.EnableImeResult - int canEnableDisableInputMethod(String imeId, AbstractAccessibilityServiceConnection service) - throws SecurityException { + int canEnableDisableInputMethod(String imeId, AbstractAccessibilityServiceConnection service, + int callingUserId) throws SecurityException { final String servicePackageName = service.getComponentName().getPackageName(); - final int callingUserId = UserHandle.getCallingUserId(); InputMethodInfo inputMethodInfo = null; List<InputMethodInfo> inputMethodInfoList = diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 786d167af5de..a3fe9ec5ea22 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -166,8 +166,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect if (userState.getBindInstantServiceAllowedLocked()) { flags |= Context.BIND_ALLOW_INSTANT; } - if (mService == null && mContext.bindServiceAsUser( - mIntent, this, flags, new UserHandle(userState.mUserId))) { + if (mClientBinder == null + && mContext.bindServiceAsUser( + mIntent, this, flags, new UserHandle(userState.mUserId))) { userState.getBindingServicesLocked().add(mComponentName); } } finally { @@ -227,20 +228,20 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect addWindowTokensForAllDisplays(); } synchronized (mLock) { - if (mService != service) { - if (mService != null) { - mService.unlinkToDeath(this, 0); + if (mClientBinder != service) { + if (mClientBinder != null) { + mClientBinder.unlinkToDeath(this, 0); } - mService = service; + mClientBinder = service; try { - mService.linkToDeath(this, 0); + mClientBinder.linkToDeath(this, 0); } catch (RemoteException re) { Slog.e(LOG_TAG, "Failed registering death link"); binderDied(); return; } } - mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service); + mClient = IAccessibilityServiceClient.Stub.asInterface(service); if (userState == null) return; userState.addServiceLocked(this); mSystemSupport.onClientChangeLocked(false); @@ -261,7 +262,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } private void initializeService() { - IAccessibilityServiceClient serviceInterface = null; + IAccessibilityServiceClient client = null; synchronized (mLock) { AccessibilityUserState userState = mUserStateWeakReference.get(); if (userState == null) return; @@ -272,18 +273,17 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect bindingServices.remove(mComponentName); crashedServices.remove(mComponentName); mAccessibilityServiceInfo.crashed = false; - serviceInterface = mServiceInterface; + client = mClient; } // There's a chance that service is removed from enabled_accessibility_services setting // key, but skip unbinding because of it's in binding state. Unbinds it if it's // not in enabled service list. - if (serviceInterface != null - && !userState.getEnabledServicesLocked().contains(mComponentName)) { + if (client != null && !userState.getEnabledServicesLocked().contains(mComponentName)) { mSystemSupport.onClientChangeLocked(false); return; } } - if (serviceInterface == null) { + if (client == null) { binderDied(); return; } @@ -292,10 +292,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect logTraceSvcClient("init", this + "," + mId + "," + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY)); } - serviceInterface.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY)); + client.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY)); } catch (RemoteException re) { - Slog.w(LOG_TAG, "Error while setting connection for service: " - + serviceInterface, re); + Slog.w(LOG_TAG, "Error while setting connection for service: " + client, re); binderDied(); } } @@ -411,9 +410,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect final @AccessibilityService.SoftKeyboardController.EnableImeResult int checkResult; final long identity = Binder.clearCallingIdentity(); try { - synchronized (mLock) { - checkResult = mSecurityPolicy.canEnableDisableInputMethod(imeId, this); - } + checkResult = mSecurityPolicy.canEnableDisableInputMethod(imeId, this, callingUserId); if (checkResult != ENABLE_IME_SUCCESS) { return checkResult; } @@ -496,7 +493,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public boolean isCapturingFingerprintGestures() { - return (mServiceInterface != null) + return (mClient != null) && mSecurityPolicy.canCaptureFingerprintGestures(this) && mCaptureFingerprintGestures; } @@ -506,17 +503,17 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect if (!isCapturingFingerprintGestures()) { return; } - IAccessibilityServiceClient serviceInterface; + IAccessibilityServiceClient client; synchronized (mLock) { - serviceInterface = mServiceInterface; + client = mClient; } - if (serviceInterface != null) { + if (client != null) { try { if (svcClientTracingEnabled()) { logTraceSvcClient( "onFingerprintCapturingGesturesChanged", String.valueOf(active)); } - mServiceInterface.onFingerprintCapturingGesturesChanged(active); + mClient.onFingerprintCapturingGesturesChanged(active); } catch (RemoteException e) { } } @@ -527,16 +524,16 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect if (!isCapturingFingerprintGestures()) { return; } - IAccessibilityServiceClient serviceInterface; + IAccessibilityServiceClient client; synchronized (mLock) { - serviceInterface = mServiceInterface; + client = mClient; } - if (serviceInterface != null) { + if (client != null) { try { if (svcClientTracingEnabled()) { logTraceSvcClient("onFingerprintGesture", String.valueOf(gesture)); } - mServiceInterface.onFingerprintGesture(gesture); + mClient.onFingerprintGesture(gesture); } catch (RemoteException e) { } } @@ -546,7 +543,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) { synchronized (mLock) { - if (mServiceInterface != null && mSecurityPolicy.canPerformGestures(this)) { + if (mClient != null && mSecurityPolicy.canPerformGestures(this)) { final long identity = Binder.clearCallingIdentity(); try { MotionEventInjector motionEventInjector = @@ -557,16 +554,18 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect if (motionEventInjector != null && mWindowManagerService.isTouchOrFaketouchDevice()) { motionEventInjector.injectEvents( - gestureSteps.getList(), mServiceInterface, sequence, displayId); + gestureSteps.getList(), mClient, sequence, displayId); } else { try { if (svcClientTracingEnabled()) { logTraceSvcClient("onPerformGestureResult", sequence + ", false"); } - mServiceInterface.onPerformGestureResult(sequence, false); + mClient.onPerformGestureResult(sequence, false); } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error sending motion event injection failure to " - + mServiceInterface, re); + Slog.e( + LOG_TAG, + "Error sending motion event injection failure to " + mClient, + re); } } } finally { @@ -631,48 +630,47 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override protected void createImeSessionInternal() { - final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); - if (listener != null) { + final IAccessibilityServiceClient client = getClientSafely(); + if (client != null) { try { if (svcClientTracingEnabled()) { logTraceSvcClient("createImeSession", ""); } AccessibilityInputMethodSessionCallback callback = new AccessibilityInputMethodSessionCallback(mUserId); - listener.createImeSession(callback); + client.createImeSession(callback); } catch (RemoteException re) { - Slog.e(LOG_TAG, - "Error requesting IME session from " + mService, re); + Slog.e(LOG_TAG, "Error requesting IME session from " + mClientBinder, re); } } } private void notifyMotionEventInternal(MotionEvent event) { - final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); - if (listener != null) { + final IAccessibilityServiceClient client = getClientSafely(); + if (client != null) { try { if (mTrace.isA11yTracingEnabled()) { logTraceSvcClient(".onMotionEvent ", event.toString()); } - listener.onMotionEvent(event); + client.onMotionEvent(event); } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error sending motion event to" + mService, re); + Slog.e(LOG_TAG, "Error sending motion event to" + mClientBinder, re); } } } private void notifyTouchStateInternal(int displayId, int state) { - final IAccessibilityServiceClient listener = getServiceInterfaceSafely(); - if (listener != null) { + final IAccessibilityServiceClient client = getClientSafely(); + if (client != null) { try { if (mTrace.isA11yTracingEnabled()) { logTraceSvcClient(".onTouchStateChanged ", TouchInteractionController.stateToString(state)); } - listener.onTouchStateChanged(displayId, state); + client.onTouchStateChanged(displayId, state); } catch (RemoteException re) { - Slog.e(LOG_TAG, "Error sending motion event to" + mService, re); + Slog.e(LOG_TAG, "Error sending motion event to" + mClientBinder, re); } } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index 0bf7ec001d4d..8b3e63d0dc5e 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -29,6 +29,7 @@ import static com.android.internal.accessibility.AccessibilityShortcutController import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.GESTURE; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.KEY_GESTURE; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.TRIPLETAP; @@ -106,21 +107,17 @@ class AccessibilityUserState { final Set<ComponentName> mTouchExplorationGrantedServices = new HashSet<>(); - private final ArraySet<String> mAccessibilityShortcutKeyTargets = new ArraySet<>(); - - private final ArraySet<String> mAccessibilityButtonTargets = new ArraySet<>(); - private final ArraySet<String> mAccessibilityGestureTargets = new ArraySet<>(); - private final ArraySet<String> mAccessibilityQsTargets = new ArraySet<>(); + private final HashMap<Integer, ArraySet<String>> mShortcutTargets = new HashMap<>(); /** - * The QuickSettings tiles in the QS Panel. This can be different from - * {@link #mAccessibilityQsTargets} in that {@link #mA11yTilesInQsPanel} stores the + * The QuickSettings tiles in the QS Panel. This can be different from the QS targets in + * {@link #mShortcutTargets} in that {@link #mA11yTilesInQsPanel} stores the * TileService's or the a11y framework tile component names (e.g. * {@link AccessibilityShortcutController#COLOR_INVERSION_TILE_COMPONENT_NAME}) instead of the * A11y Feature's component names. * <p/> * In addition, {@link #mA11yTilesInQsPanel} stores what's on the QS Panel, whereas - * {@link #mAccessibilityQsTargets} stores the targets that configured qs as their shortcut and + * {@link #mShortcutTargets} stores the targets that configured qs as their shortcut and * also grant full device control permission. */ private final ArraySet<ComponentName> mA11yTilesInQsPanel = new ArraySet<>(); @@ -208,6 +205,12 @@ class AccessibilityUserState { mSupportWindowMagnification = mContext.getResources().getBoolean( R.bool.config_magnification_area) && mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_WINDOW_MAGNIFICATION); + + mShortcutTargets.put(HARDWARE, new ArraySet<>()); + mShortcutTargets.put(SOFTWARE, new ArraySet<>()); + mShortcutTargets.put(GESTURE, new ArraySet<>()); + mShortcutTargets.put(QUICK_SETTINGS, new ArraySet<>()); + mShortcutTargets.put(KEY_GESTURE, new ArraySet<>()); } boolean isHandlingAccessibilityEventsLocked() { @@ -233,10 +236,7 @@ class AccessibilityUserState { // Clear state persisted in settings. mEnabledServices.clear(); mTouchExplorationGrantedServices.clear(); - mAccessibilityShortcutKeyTargets.clear(); - mAccessibilityButtonTargets.clear(); - mAccessibilityGestureTargets.clear(); - mAccessibilityQsTargets.clear(); + mShortcutTargets.forEach((type, targets) -> targets.clear()); mA11yTilesInQsPanel.clear(); mTargetAssignedToAccessibilityButton = null; mIsTouchExplorationEnabled = false; @@ -541,7 +541,7 @@ class AccessibilityUserState { private void dumpShortcutTargets( PrintWriter pw, @UserShortcutType int shortcutType, String name) { pw.append(" ").append(name).append(":{"); - ArraySet<String> targets = getShortcutTargetsInternalLocked(shortcutType); + ArraySet<String> targets = getShortcutTargetsLocked(shortcutType); int size = targets.size(); for (int i = 0; i < size; i++) { if (i > 0) { @@ -712,7 +712,7 @@ class AccessibilityUserState { */ public boolean isShortcutMagnificationEnabledLocked() { for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) { - if (getShortcutTargetsInternalLocked(shortcutType) + if (getShortcutTargetsLocked(shortcutType) .contains(MAGNIFICATION_CONTROLLER_NAME)) { return true; } @@ -788,43 +788,29 @@ class AccessibilityUserState { } /** - * Disable both shortcuts' magnification function. - */ - public void disableShortcutMagnificationLocked() { - mAccessibilityShortcutKeyTargets.remove(MAGNIFICATION_CONTROLLER_NAME); - mAccessibilityButtonTargets.remove(MAGNIFICATION_CONTROLLER_NAME); - } - - /** * Returns a set which contains the flattened component names and the system class names - * assigned to the given shortcut. The set is a defensive copy. To apply any changes to the set, - * use {@link #updateShortcutTargetsLocked(Set, int)} + * assigned to the given shortcut. <strong>The set is a defensive copy.</strong> + * To apply any changes to the set, use {@link #updateShortcutTargetsLocked(Set, int)} * - * @param shortcutType The shortcut type. + * @param shortcutTypes The shortcut type or types (in bitmask format). * @return The array set of the strings */ - public ArraySet<String> getShortcutTargetsLocked(@UserShortcutType int shortcutType) { - return new ArraySet<>(getShortcutTargetsInternalLocked(shortcutType)); - } - - private ArraySet<String> getShortcutTargetsInternalLocked(@UserShortcutType int shortcutType) { - if (shortcutType == HARDWARE) { - return mAccessibilityShortcutKeyTargets; - } else if (shortcutType == SOFTWARE) { - return mAccessibilityButtonTargets; - } else if (shortcutType == GESTURE) { - return mAccessibilityGestureTargets; - } else if (shortcutType == QUICK_SETTINGS) { - return mAccessibilityQsTargets; - } else if ((shortcutType == TRIPLETAP - && isMagnificationSingleFingerTripleTapEnabledLocked()) || ( - shortcutType == TWOFINGER_DOUBLETAP - && isMagnificationTwoFingerTripleTapEnabledLocked())) { - ArraySet<String> targets = new ArraySet<>(); - targets.add(MAGNIFICATION_CONTROLLER_NAME); - return targets; + public ArraySet<String> getShortcutTargetsLocked(int shortcutTypes) { + ArraySet<String> targets = new ArraySet<>(); + for (int shortcutType : ShortcutConstants.USER_SHORTCUT_TYPES) { + if ((shortcutTypes & shortcutType) != shortcutType) { + continue; + } + if ((shortcutType == TRIPLETAP + && isMagnificationSingleFingerTripleTapEnabledLocked()) || ( + shortcutType == TWOFINGER_DOUBLETAP + && isMagnificationTwoFingerTripleTapEnabledLocked())) { + targets.add(MAGNIFICATION_CONTROLLER_NAME); + } else if (mShortcutTargets.containsKey(shortcutType)) { + targets.addAll(mShortcutTargets.get(shortcutType)); + } } - return new ArraySet<>(); + return targets; } /** @@ -843,8 +829,10 @@ class AccessibilityUserState { if ((shortcutType & mask) != 0) { throw new IllegalArgumentException("Tap shortcuts cannot be updated with target sets."); } - - final Set<String> currentTargets = getShortcutTargetsInternalLocked(shortcutType); + if (!mShortcutTargets.containsKey(shortcutType)) { + mShortcutTargets.put(shortcutType, new ArraySet<>()); + } + ArraySet<String> currentTargets = mShortcutTargets.get(shortcutType); if (newTargets.equals(currentTargets)) { return false; } @@ -904,7 +892,7 @@ class AccessibilityUserState { } // getting internal set lets us directly modify targets, as it's not a copy. - Set<String> targets = getShortcutTargetsInternalLocked(shortcutType); + Set<String> targets = mShortcutTargets.get(shortcutType); return targets.removeIf(name -> { ComponentName componentName; if (name == null @@ -1169,13 +1157,6 @@ class AccessibilityUserState { ); } - /** - * Returns a copy of the targets which has qs shortcut turned on - */ - public ArraySet<String> getA11yQsTargets() { - return new ArraySet<>(mAccessibilityQsTargets); - } - public void updateA11yTilesInQsPanelLocked(Set<ComponentName> componentNames) { mA11yTilesInQsPanel.clear(); mA11yTilesInQsPanel.addAll(componentNames); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index 9a81aa6cc506..b7fd09f7b594 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -434,21 +434,23 @@ public class AccessibilityWindowManager { } /** - * Callbacks from window manager when there's an accessibility change in windows. + * Called when the windows for accessibility changed. * - * @param forceSend Send the windows for accessibility even if they haven't changed. - * @param topFocusedDisplayId The display Id which has the top focused window. + * @param forceSend Send the windows for accessibility even if they haven't + * changed. + * @param topFocusedDisplayId The display Id which has the top focused window. * @param topFocusedWindowToken The window token of top focused window. - * @param windows The windows for accessibility. + * @param screenSize The size of the display that the change happened. + * @param accessibilityWindows The windows for accessibility. */ @Override - public void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId, - IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows) { + public void onAccessibilityWindowsChanged(boolean forceSend, int topFocusedDisplayId, + @NonNull IBinder topFocusedWindowToken, @NonNull Point screenSize, + @NonNull List<AccessibilityWindow> accessibilityWindows) { synchronized (mLock) { - if (!Flags.computeWindowChangesOnA11yV2()) { - // If the flag is enabled, it's already done in #createWindowInfoListLocked. - updateWindowsByWindowAttributesLocked(windows); - } + final List<WindowInfo> windows = + createWindowInfoListLocked(screenSize, accessibilityWindows); + if (DEBUG) { Slogf.i(LOG_TAG, "mDisplayId=%d, topFocusedDisplayId=%d, currentUserId=%d, " + "visibleBgUsers=%s", mDisplayId, topFocusedDisplayId, @@ -463,14 +465,15 @@ public class AccessibilityWindowManager { Slogf.i(LOG_TAG, "%d windows changed: %s", windows.size(), windowsInfo); } } - if (shouldUpdateWindowsLocked(forceSend, windows)) { + + if (forceSend || shouldUpdateWindowsLocked(windows)) { mTopFocusedDisplayId = topFocusedDisplayId; if (!isProxyed(topFocusedDisplayId)) { mLastNonProxyTopFocusedDisplayId = topFocusedDisplayId; } mTopFocusedWindowToken = topFocusedWindowToken; if (DEBUG) { - Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): updating windows for " + Slogf.d(LOG_TAG, "onAccessibilityWindowsChanged(): updating windows for " + "display %d and token %s", topFocusedDisplayId, topFocusedWindowToken); } @@ -480,39 +483,14 @@ public class AccessibilityWindowManager { windows); // Someone may be waiting for the windows - advertise it. mLock.notifyAll(); - } - else if (DEBUG) { - Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): NOT updating windows for " + } else if (DEBUG) { + Slogf.d(LOG_TAG, "onAccessibilityWindowsChanged(): NOT updating windows for " + "display %d and token %s", topFocusedDisplayId, topFocusedWindowToken); } } } - /** - * Called when the windows for accessibility changed. This is called if - * {@link com.android.server.accessibility.Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y_V2} is - * true. - * - * @param forceSend Send the windows for accessibility even if they haven't - * changed. - * @param topFocusedDisplayId The display Id which has the top focused window. - * @param topFocusedWindowToken The window token of top focused window. - * @param screenSize The size of the display that the change happened. - * @param windows The windows for accessibility. - */ - @Override - public void onAccessibilityWindowsChanged(boolean forceSend, int topFocusedDisplayId, - @NonNull IBinder topFocusedWindowToken, @NonNull Point screenSize, - @NonNull List<AccessibilityWindow> windows) { - synchronized (mLock) { - final List<WindowInfo> windowInfoList = - createWindowInfoListLocked(screenSize, windows); - onWindowsForAccessibilityChanged(forceSend, topFocusedDisplayId, - topFocusedWindowToken, windowInfoList); - } - } - private List<WindowInfo> createWindowInfoListLocked(@NonNull Point screenSize, @NonNull List<AccessibilityWindow> visibleWindows) { final Set<IBinder> addedWindows = new ArraySet<>(); @@ -655,16 +633,6 @@ public class AccessibilityWindowManager { return true; } - private void updateWindowsByWindowAttributesLocked(List<WindowInfo> windows) { - for (int i = windows.size() - 1; i >= 0; i--) { - final WindowInfo windowInfo = windows.get(i); - final IBinder token = windowInfo.token; - final int windowId = findWindowIdLocked( - mAccessibilityUserManager.getCurrentUserIdLocked(), token); - updateWindowWithWindowAttributes(windowInfo, mWindowAttributes.get(windowId)); - } - } - private void updateWindowWithWindowAttributes(@NonNull WindowInfo windowInfo, @Nullable AccessibilityWindowAttributes attributes) { if (attributes == null) { @@ -674,12 +642,7 @@ public class AccessibilityWindowManager { windowInfo.locales = attributes.getLocales(); } - private boolean shouldUpdateWindowsLocked(boolean forceSend, - @NonNull List<WindowInfo> windows) { - if (forceSend) { - return true; - } - + private boolean shouldUpdateWindowsLocked(@NonNull List<WindowInfo> windows) { final int windowCount = windows.size(); if (VERBOSE) { Slogf.v(LOG_TAG, @@ -869,20 +832,12 @@ public class AccessibilityWindowManager { != AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; } - boolean hasWindowIgnore = false; if (windowCount > 0) { - for (int i = 0; i < windowCount; i++) { - final WindowInfo windowInfo = windows.get(i); - final AccessibilityWindowInfo window; - if (mTrackingWindows) { - window = populateReportedWindowLocked(userId, windowInfo, oldWindowsById); - if (window == null) { - hasWindowIgnore = true; - } - } else { - window = null; - } - if (window != null) { + if (mTrackingWindows) { + for (int i = 0; i < windowCount; i++) { + final WindowInfo windowInfo = windows.get(i); + final AccessibilityWindowInfo window = + populateReportedWindowLocked(userId, windowInfo, oldWindowsById); // Flip layers in list to be consistent with AccessibilityService#getWindows window.setLayer(windowCount - 1 - window.getLayer()); @@ -907,13 +862,6 @@ public class AccessibilityWindowManager { } } final int accessibilityWindowCount = mWindows.size(); - // Re-order the window layer of all windows in the windows list because there's - // window not been added into the windows list. - if (hasWindowIgnore) { - for (int i = 0; i < accessibilityWindowCount; i++) { - mWindows.get(i).setLayer(accessibilityWindowCount - 1 - i); - } - } if (isTopFocusedDisplay) { if (mTouchInteractionInProgress && activeWindowGone) { mActiveWindowId = mTopFocusedWindowId; @@ -990,19 +938,6 @@ public class AccessibilityWindowManager { private AccessibilityWindowInfo populateReportedWindowLocked(int userId, WindowInfo window, SparseArray<AccessibilityWindowInfo> oldWindowsById) { final int windowId = findWindowIdLocked(userId, window.token); - - // With the flag enabled, createWindowInfoListLocked() already removes invalid windows. - if (!Flags.computeWindowChangesOnA11yV2()) { - if (windowId < 0) { - return null; - } - - // Don't need to add the embedded hierarchy windows into the a11y windows list. - if (isEmbeddedHierarchyWindowsLocked(windowId)) { - return null; - } - } - final AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain(); reportedWindow.setId(windowId); diff --git a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java index 54368ca9c03e..1212c757f1c2 100644 --- a/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java +++ b/services/accessibility/java/com/android/server/accessibility/MouseKeysInterceptor.java @@ -73,12 +73,16 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation private static final int MESSAGE_MOVE_MOUSE_POINTER = 1; private static final int MESSAGE_SCROLL_MOUSE_POINTER = 2; - private static final float MOUSE_POINTER_MOVEMENT_STEP = 1.8f; private static final int KEY_NOT_SET = -1; /** Time interval after which mouse action will be repeated */ private static final int INTERVAL_MILLIS = 10; + @VisibleForTesting + public static final float MOUSE_POINTER_MOVEMENT_STEP = 1.8f; + @VisibleForTesting + public static final float MOUSE_SCROLL_STEP = 0.2f; + private final AccessibilityManagerService mAms; private final Handler mHandler; private final InputManager mInputManager; @@ -134,8 +138,8 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation DIAGONAL_UP_LEFT_MOVE(KeyEvent.KEYCODE_7), UP_MOVE_OR_SCROLL(KeyEvent.KEYCODE_8), DIAGONAL_UP_RIGHT_MOVE(KeyEvent.KEYCODE_9), - LEFT_MOVE(KeyEvent.KEYCODE_U), - RIGHT_MOVE(KeyEvent.KEYCODE_O), + LEFT_MOVE_OR_SCROLL(KeyEvent.KEYCODE_U), + RIGHT_MOVE_OR_SCROLL(KeyEvent.KEYCODE_O), DIAGONAL_DOWN_LEFT_MOVE(KeyEvent.KEYCODE_J), DOWN_MOVE_OR_SCROLL(KeyEvent.KEYCODE_K), DIAGONAL_DOWN_RIGHT_MOVE(KeyEvent.KEYCODE_L), @@ -263,6 +267,16 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation ); } + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + private void sendVirtualMouseScrollEvent(float x, float y) { + waitForVirtualMouseCreation(); + mVirtualMouse.sendScrollEvent(new VirtualMouseScrollEvent.Builder() + .setXAxisMovement(x) + .setYAxisMovement(y) + .build() + ); + } + /** * Performs a mouse scroll action based on the provided key code. * The scroll action will only be performed if the scroll toggle is on. @@ -280,19 +294,31 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation private void performMouseScrollAction(int keyCode) { MouseKeyEvent mouseKeyEvent = MouseKeyEvent.from( keyCode, mActiveInputDeviceId, mDeviceKeyCodeMap); - float y = switch (mouseKeyEvent) { - case UP_MOVE_OR_SCROLL -> 1.0f; - case DOWN_MOVE_OR_SCROLL -> -1.0f; - default -> 0.0f; - }; - waitForVirtualMouseCreation(); - mVirtualMouse.sendScrollEvent(new VirtualMouseScrollEvent.Builder() - .setYAxisMovement(y) - .build() - ); + float x = 0f; + float y = 0f; + + switch (mouseKeyEvent) { + case UP_MOVE_OR_SCROLL -> { + y = MOUSE_SCROLL_STEP; + } + case DOWN_MOVE_OR_SCROLL -> { + y = -MOUSE_SCROLL_STEP; + } + case LEFT_MOVE_OR_SCROLL -> { + x = MOUSE_SCROLL_STEP; + } + case RIGHT_MOVE_OR_SCROLL -> { + x = -MOUSE_SCROLL_STEP; + } + default -> { + x = 0.0f; + y = 0.0f; + } + } + sendVirtualMouseScrollEvent(x, y); if (DEBUG) { Slog.d(LOG_TAG, "Performed mouse key event: " + mouseKeyEvent.name() - + " for scroll action with axis movement (y=" + y + ")"); + + " for scroll action with axis movement (x=" + x + ", y=" + y + ")"); } } @@ -340,8 +366,8 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation * The method calculates the relative movement of the mouse pointer * and sends the corresponding event to the virtual mouse. * - * The UP and DOWN pointer actions will only take place for their respective keys - * if the scroll toggle is off. + * The UP, DOWN, LEFT, RIGHT pointer actions will only take place for their + * respective keys if the scroll toggle is off. * * @param keyCode The key code representing the direction or button press. * Supported keys are: @@ -349,8 +375,8 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_DOWN_LEFT_MOVE} * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DOWN_MOVE_OR_SCROLL} * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_DOWN_RIGHT_MOVE} - * <li>{@link MouseKeysInterceptor.MouseKeyEvent#LEFT_MOVE} - * <li>{@link MouseKeysInterceptor.MouseKeyEvent#RIGHT_MOVE} + * <li>{@link MouseKeysInterceptor.MouseKeyEvent#LEFT_MOVE_OR_SCROLL} + * <li>{@link MouseKeysInterceptor.MouseKeyEvent#RIGHT_MOVE_OR_SCROLL} * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_UP_LEFT_MOVE} * <li>{@link MouseKeysInterceptor.MouseKeyEvent#UP_MOVE_OR_SCROLL} * <li>{@link MouseKeysInterceptor.MouseKeyEvent#DIAGONAL_UP_RIGHT_MOVE} @@ -377,10 +403,10 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation x = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2); y = MOUSE_POINTER_MOVEMENT_STEP / sqrt(2); } - case LEFT_MOVE -> { + case LEFT_MOVE_OR_SCROLL -> { x = -MOUSE_POINTER_MOVEMENT_STEP; } - case RIGHT_MOVE -> { + case RIGHT_MOVE_OR_SCROLL -> { x = MOUSE_POINTER_MOVEMENT_STEP; } case DIAGONAL_UP_LEFT_MOVE -> { @@ -420,7 +446,9 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation private boolean isMouseScrollKey(int keyCode, InputDevice inputDevice) { return keyCode == MouseKeyEvent.UP_MOVE_OR_SCROLL.getKeyCode(inputDevice) - || keyCode == MouseKeyEvent.DOWN_MOVE_OR_SCROLL.getKeyCode(inputDevice); + || keyCode == MouseKeyEvent.DOWN_MOVE_OR_SCROLL.getKeyCode(inputDevice) + || keyCode == MouseKeyEvent.LEFT_MOVE_OR_SCROLL.getKeyCode(inputDevice) + || keyCode == MouseKeyEvent.RIGHT_MOVE_OR_SCROLL.getKeyCode(inputDevice); } /** @@ -597,7 +625,9 @@ public class MouseKeysInterceptor extends BaseEventStreamTransformation }); mHandler.removeCallbacksAndMessages(null); - mVirtualDevice.close(); + if (mVirtualDevice != null) { + mVirtualDevice.close(); + } } @Override diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java index 4cb3d247edb0..cd97d838e3a0 100644 --- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java @@ -109,14 +109,11 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon return mDeviceId; } - /** - * Called when the proxy is registered. - */ - void initializeServiceInterface(IAccessibilityServiceClient serviceInterface) - throws RemoteException { - mServiceInterface = serviceInterface; - mService = serviceInterface.asBinder(); - mServiceInterface.init(this, mId, this.mOverlayWindowTokens.get(mDisplayId)); + /** Called when the proxy is registered. */ + void initializeClient(IAccessibilityServiceClient client) throws RemoteException { + mClient = client; + mClientBinder = client.asBinder(); + mClient.init(this, mId, this.mOverlayWindowTokens.get(mDisplayId)); } /** diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java index b4deeb0a6872..f8551457d04d 100644 --- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java +++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java @@ -183,14 +183,12 @@ public class ProxyManager { synchronized (mLock) { mProxyA11yServiceConnections.put(displayId, connection); - if (Flags.proxyUseAppsOnVirtualDeviceListener()) { - if (mAppsOnVirtualDeviceListener == null) { - mAppsOnVirtualDeviceListener = allRunningUids -> - notifyProxyOfRunningAppsChange(allRunningUids); - final VirtualDeviceManagerInternal localVdm = getLocalVdm(); - if (localVdm != null) { - localVdm.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener); - } + if (mAppsOnVirtualDeviceListener == null) { + mAppsOnVirtualDeviceListener = allRunningUids -> + notifyProxyOfRunningAppsChange(allRunningUids); + final VirtualDeviceManagerInternal localVdm = getLocalVdm(); + if (localVdm != null) { + localVdm.registerAppsOnVirtualDeviceListener(mAppsOnVirtualDeviceListener); } } if (mProxyA11yServiceConnections.size() == 1) { @@ -214,7 +212,7 @@ public class ProxyManager { mA11yInputFilter.disableFeaturesForDisplayIfInstalled(displayId); } }); - connection.initializeServiceInterface(client); + connection.initializeClient(client); } private void registerVirtualDeviceListener() { @@ -331,14 +329,12 @@ public class ProxyManager { // device. if (!isProxyedDeviceId(deviceId)) { synchronized (mLock) { - if (Flags.proxyUseAppsOnVirtualDeviceListener()) { - if (mProxyA11yServiceConnections.size() == 0) { - final VirtualDeviceManagerInternal localVdm = getLocalVdm(); - if (localVdm != null && mAppsOnVirtualDeviceListener != null) { - localVdm.unregisterAppsOnVirtualDeviceListener( - mAppsOnVirtualDeviceListener); - mAppsOnVirtualDeviceListener = null; - } + if (mProxyA11yServiceConnections.size() == 0) { + final VirtualDeviceManagerInternal localVdm = getLocalVdm(); + if (localVdm != null && mAppsOnVirtualDeviceListener != null) { + localVdm.unregisterAppsOnVirtualDeviceListener( + mAppsOnVirtualDeviceListener); + mAppsOnVirtualDeviceListener = null; } } mSystemSupport.removeDeviceIdLocked(deviceId); @@ -561,8 +557,8 @@ public class ProxyManager { final ProxyAccessibilityServiceConnection proxy = mProxyA11yServiceConnections.valueAt(i); if (proxy != null && proxy.getDeviceId() == deviceId) { - final IBinder proxyBinder = proxy.mService; - final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface; + final IBinder proxyBinder = proxy.mClientBinder; + final IAccessibilityServiceClient proxyInterface = proxy.mClient; if ((proxyBinder != null) && (proxyInterface != null)) { interfaces.add(proxyInterface); } @@ -671,8 +667,7 @@ public class ProxyManager { + getLastSentStateLocked(deviceId)); Slog.v(LOG_TAG, "force update: " + forceUpdate); } - if ((getLastSentStateLocked(deviceId)) != proxyState - || (Flags.proxyUseAppsOnVirtualDeviceListener() && forceUpdate)) { + if ((getLastSentStateLocked(deviceId)) != proxyState || forceUpdate) { setLastStateLocked(deviceId, proxyState); mMainHandler.post(() -> { synchronized (mLock) { @@ -873,33 +868,22 @@ public class ProxyManager { for (int i = 0; i < clients.getRegisteredCallbackCount(); i++) { final AccessibilityManagerService.Client client = ((AccessibilityManagerService.Client) clients.getRegisteredCallbackCookie(i)); - if (Flags.proxyUseAppsOnVirtualDeviceListener()) { - if (deviceId == DEVICE_ID_DEFAULT || deviceId == DEVICE_ID_INVALID) { - continue; - } - boolean uidBelongsToDevice = - localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId); - if (client.mDeviceId != deviceId && uidBelongsToDevice) { - if (DEBUG) { - Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are " - + Arrays.toString(client.mPackageNames)); - } - client.mDeviceId = deviceId; - } else if (client.mDeviceId == deviceId && !uidBelongsToDevice) { - client.mDeviceId = DEVICE_ID_DEFAULT; - if (DEBUG) { - Slog.v(LOG_TAG, "Packages moved to the default device from device id " - + deviceId + " are " + Arrays.toString(client.mPackageNames)); - } + if (deviceId == DEVICE_ID_DEFAULT || deviceId == DEVICE_ID_INVALID) { + continue; + } + boolean uidBelongsToDevice = + localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId); + if (client.mDeviceId != deviceId && uidBelongsToDevice) { + if (DEBUG) { + Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are " + + Arrays.toString(client.mPackageNames)); } - } else { - if (deviceId != DEVICE_ID_DEFAULT && deviceId != DEVICE_ID_INVALID - && localVdm.getDeviceIdsForUid(client.mUid).contains(deviceId)) { - if (DEBUG) { - Slog.v(LOG_TAG, "Packages moved to device id " + deviceId + " are " - + Arrays.toString(client.mPackageNames)); - } - client.mDeviceId = deviceId; + client.mDeviceId = deviceId; + } else if (client.mDeviceId == deviceId && !uidBelongsToDevice) { + client.mDeviceId = DEVICE_ID_DEFAULT; + if (DEBUG) { + Slog.v(LOG_TAG, "Packages moved to the default device from device id " + + deviceId + " are " + Arrays.toString(client.mPackageNames)); } } } diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index f85d786f89c5..ed4eeb534412 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -107,8 +107,7 @@ class UiAutomationManager { Binder.getCallingUserHandle().getIdentifier()); if (mUiAutomationService != null) { throw new IllegalStateException( - "UiAutomationService " + mUiAutomationService.mServiceInterface - + "already registered!"); + "UiAutomationService " + mUiAutomationService.mClient + "already registered!"); } try { @@ -130,10 +129,9 @@ class UiAutomationManager { mainHandler, mLock, securityPolicy, systemSupport, trace, windowManagerInternal, systemActionPerformer, awm); mUiAutomationServiceOwner = owner; - mUiAutomationService.mServiceInterface = serviceClient; + mUiAutomationService.mClient = serviceClient; try { - mUiAutomationService.mServiceInterface.asBinder().linkToDeath(mUiAutomationService, - 0); + mUiAutomationService.mClient.asBinder().linkToDeath(mUiAutomationService, 0); } catch (RemoteException re) { Slog.e(LOG_TAG, "Failed registering death link: " + re); destroyUiAutomationService(); @@ -149,10 +147,10 @@ class UiAutomationManager { synchronized (mLock) { if (useAccessibility() && ((mUiAutomationService == null) - || (serviceClient == null) - || (mUiAutomationService.mServiceInterface == null) - || (serviceClient.asBinder() - != mUiAutomationService.mServiceInterface.asBinder()))) { + || (serviceClient == null) + || (mUiAutomationService.mClient == null) + || (serviceClient.asBinder() + != mUiAutomationService.mClient.asBinder()))) { throw new IllegalStateException("UiAutomationService " + serviceClient + " not registered!"); } @@ -230,8 +228,7 @@ class UiAutomationManager { private void destroyUiAutomationService() { synchronized (mLock) { if (mUiAutomationService != null) { - mUiAutomationService.mServiceInterface.asBinder().unlinkToDeath( - mUiAutomationService, 0); + mUiAutomationService.mClient.asBinder().unlinkToDeath(mUiAutomationService, 0); mUiAutomationService.onRemoved(); mUiAutomationService.resetLocked(); mUiAutomationService = null; @@ -271,40 +268,48 @@ class UiAutomationManager { void connectServiceUnknownThread() { // This needs to be done on the main thread - mMainHandler.post(() -> { - try { - final IAccessibilityServiceClient serviceInterface; - final UiAutomationService uiAutomationService; - synchronized (mLock) { - serviceInterface = mServiceInterface; - uiAutomationService = mUiAutomationService; - if (serviceInterface == null) { - mService = null; - } else { - mService = mServiceInterface.asBinder(); - mService.linkToDeath(this, 0); + mMainHandler.post( + () -> { + try { + final IAccessibilityServiceClient client; + final UiAutomationService uiAutomationService; + synchronized (mLock) { + client = mClient; + uiAutomationService = mUiAutomationService; + if (client == null) { + mClientBinder = null; + } else { + mClientBinder = mClient.asBinder(); + mClientBinder.linkToDeath(this, 0); + } + } + // If the client is null, the UiAutomation has been shut down on + // another thread. + if (client != null && uiAutomationService != null) { + uiAutomationService.addWindowTokensForAllDisplays(); + if (mTrace.isA11yTracingEnabledForTypes( + AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) { + mTrace.logTrace( + "UiAutomationService.connectServiceUnknownThread", + AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT, + "serviceConnection=" + + this + + ";connectionId=" + + mId + + "windowToken=" + + mOverlayWindowTokens.get( + Display.DEFAULT_DISPLAY)); + } + client.init( + this, + mId, + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY)); + } + } catch (RemoteException re) { + Slog.w(LOG_TAG, "Error initializing connection", re); + destroyUiAutomationService(); } - } - // If the serviceInterface is null, the UiAutomation has been shut down on - // another thread. - if (serviceInterface != null && uiAutomationService != null) { - uiAutomationService.addWindowTokensForAllDisplays(); - if (mTrace.isA11yTracingEnabledForTypes( - AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) { - mTrace.logTrace("UiAutomationService.connectServiceUnknownThread", - AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT, - "serviceConnection=" + this + ";connectionId=" + mId - + "windowToken=" - + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY)); - } - serviceInterface.init(this, mId, - mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY)); - } - } catch (RemoteException re) { - Slog.w(LOG_TAG, "Error initializing connection", re); - destroyUiAutomationService(); - } - }); + }); } @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 0ed239e442e7..0cbbf6da022b 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -240,10 +240,7 @@ public class TouchExplorer extends BaseEventStreamTransformation } private void clear(MotionEvent event, int policyFlags) { - if (mState.isTouchExploring() || Flags.sendHoverEventsBasedOnEventStream()) { - // If a touch exploration gesture is in progress send events for its end. - sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); - } + sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); mDraggingPointerId = INVALID_POINTER_ID; // Send exit to any pointers that we have delivered as part of delegating or dragging. mDispatcher.sendUpForInjectedDownPointers(event, policyFlags); @@ -562,10 +559,7 @@ public class TouchExplorer extends BaseEventStreamTransformation // clear any hover events that might have been queued and never sent. mSendHoverEnterAndMoveDelayed.clear(); mSendHoverExitDelayed.cancel(); - // If a touch exploration gesture is in progress send events for its end. - if (mState.isTouchExploring() || Flags.sendHoverEventsBasedOnEventStream()) { - sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); - } + sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); if (mState.isClear()) { if (!mSendHoverEnterAndMoveDelayed.isPending()) { // Queue a delayed transition to STATE_TOUCH_EXPLORING. @@ -1599,9 +1593,7 @@ public class TouchExplorer extends BaseEventStreamTransformation if (mEvents.size() == 0) { return; } - if (Flags.sendHoverEventsBasedOnEventStream()) { - sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags); - } + sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags); // Send an accessibility event to announce the touch exploration start. mDispatcher.sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START); if (isSendMotionEventsEnabled()) { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java index 6b6b39df24d7..11b8ccb70dfb 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java @@ -47,7 +47,6 @@ import android.util.MathUtils; import android.util.Slog; import android.util.SparseArray; import android.util.TypedValue; -import android.view.Display; import android.view.DisplayInfo; import android.view.MagnificationSpec; import android.view.View; @@ -65,6 +64,7 @@ import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.accessibility.AccessibilityTraceManager; import com.android.server.accessibility.Flags; +import com.android.server.input.InputManagerInternal; import com.android.server.wm.WindowManagerInternal; import java.util.ArrayList; @@ -397,7 +397,7 @@ public class FullScreenMagnificationController implements mCurrentMagnificationSpec.offsetX, mCurrentMagnificationSpec.offsetY)) { sendSpecToAnimation(mCurrentMagnificationSpec, null); } - onMagnificationChangedLocked(); + onMagnificationChangedLocked(/* isScaleTransient= */ false); } magnified.recycle(); } @@ -475,8 +475,16 @@ public class FullScreenMagnificationController implements return mIdOfLastServiceToMagnify; } + /** + * This is invoked whenever magnification change happens. + * + * @param isScaleTransient represents that if the scale is being changed and the changed + * value may be short lived and be updated again soon. + * Calling the method usually notifies input manager to update the + * cursor scale, but setting this value {@code true} prevents it. + */ @GuardedBy("mLock") - void onMagnificationChangedLocked() { + void onMagnificationChangedLocked(boolean isScaleTransient) { final float scale = getScale(); final float centerX = getCenterX(); final float centerY = getCenterY(); @@ -499,6 +507,10 @@ public class FullScreenMagnificationController implements } else { hideThumbnail(); } + + if (!isScaleTransient) { + notifyScaleForInput(mDisplayId, scale); + } } @GuardedBy("mLock") @@ -612,8 +624,9 @@ public class FullScreenMagnificationController implements * Directly Zooms out the scale to 1f with animating the transition. This method is * triggered only by service automatically, such as when user context changed. */ + @GuardedBy("mLock") void zoomOutFromService() { - setScaleAndCenter(1.0f, Float.NaN, Float.NaN, + setScaleAndCenter(1.0f, Float.NaN, Float.NaN, /* isScaleTransient= */ false, transformToStubCallback(true), AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); mZoomedOutFromService = true; @@ -641,7 +654,7 @@ public class FullScreenMagnificationController implements setActivated(false); if (changed) { spec.clear(); - onMagnificationChangedLocked(); + onMagnificationChangedLocked(/* isScaleTransient= */ false); } mIdOfLastServiceToMagnify = INVALID_SERVICE_ID; sendSpecToAnimation(spec, animationCallback); @@ -652,7 +665,7 @@ public class FullScreenMagnificationController implements } @GuardedBy("mLock") - boolean setScale(float scale, float pivotX, float pivotY, + boolean setScale(float scale, float pivotX, float pivotY, boolean isScaleTransient, boolean animate, int id) { if (!mRegistered) { return false; @@ -675,19 +688,20 @@ public class FullScreenMagnificationController implements final float centerX = normPivotX + offsetX; final float centerY = normPivotY + offsetY; mIdOfLastServiceToMagnify = id; - return setScaleAndCenter(scale, centerX, centerY, transformToStubCallback(animate), id); + return setScaleAndCenter(scale, centerX, centerY, isScaleTransient, + transformToStubCallback(animate), id); } @GuardedBy("mLock") boolean setScaleAndCenter(float scale, float centerX, float centerY, - MagnificationAnimationCallback animationCallback, int id) { + boolean isScaleTransient, MagnificationAnimationCallback animationCallback, + int id) { if (!mRegistered) { return false; } - // If the border implementation is on system ui side but the connection is not + // The border implementation is on system ui side but the connection is not // established, the fullscreen magnification should not work. - if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder() - && !mMagnificationConnectionStateSupplier.get()) { + if (!mMagnificationConnectionStateSupplier.get()) { return false; } if (DEBUG) { @@ -697,7 +711,7 @@ public class FullScreenMagnificationController implements + animationCallback + ", id = " + id + ")"); } boolean changed = setActivated(true); - changed |= updateMagnificationSpecLocked(scale, centerX, centerY); + changed |= updateMagnificationSpecLocked(scale, centerX, centerY, isScaleTransient); sendSpecToAnimation(mCurrentMagnificationSpec, animationCallback); if (isActivated() && (id != INVALID_SERVICE_ID)) { mIdOfLastServiceToMagnify = id; @@ -774,7 +788,9 @@ public class FullScreenMagnificationController implements * @return {@code true} if the magnification spec changed or {@code false} * otherwise */ - boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY) { + @GuardedBy("mLock") + boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY, + boolean isScaleTransient) { // Handle defaults. if (Float.isNaN(centerX)) { centerX = getCenterX(); @@ -802,7 +818,7 @@ public class FullScreenMagnificationController implements changed |= updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY); if (changed) { - onMagnificationChangedLocked(); + onMagnificationChangedLocked(isScaleTransient); } return changed; @@ -817,7 +833,7 @@ public class FullScreenMagnificationController implements final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX; final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY; if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) { - onMagnificationChangedLocked(); + onMagnificationChangedLocked(/* isScaleTransient= */ false); } if (id != INVALID_SERVICE_ID) { mIdOfLastServiceToMagnify = id; @@ -862,7 +878,7 @@ public class FullScreenMagnificationController implements } synchronized (mLock) { mCurrentMagnificationSpec.setTo(lastSpecSent); - onMagnificationChangedLocked(); + onMagnificationChangedLocked(/* isScaleTransient= */ false); } } }); @@ -956,6 +972,7 @@ public class FullScreenMagnificationController implements context, traceManager, LocalServices.getService(WindowManagerInternal.class), + LocalServices.getService(InputManagerInternal.class), new Handler(context.getMainLooper()), context.getResources().getInteger(R.integer.config_longAnimTime)), lock, @@ -1465,20 +1482,24 @@ public class FullScreenMagnificationController implements * @param scale the target scale, must be >= 1 * @param pivotX the screen-relative X coordinate around which to scale * @param pivotY the screen-relative Y coordinate around which to scale + * @param isScaleTransient {@code true} if the scale is for a short time and potentially changed + * soon. {@code false} otherwise. * @param animate {@code true} to animate the transition, {@code false} * to transition immediately * @param id the ID of the service requesting the change * @return {@code true} if the magnification spec changed, {@code false} if * the spec did not change */ + @SuppressWarnings("GuardedBy") + // errorprone cannot recognize an inner class guarded by an outer class member. public boolean setScale(int displayId, float scale, float pivotX, float pivotY, - boolean animate, int id) { + boolean isScaleTransient, boolean animate, int id) { synchronized (mLock) { final DisplayMagnification display = mDisplays.get(displayId); if (display == null) { return false; } - return display.setScale(scale, pivotX, pivotY, animate, id); + return display.setScale(scale, pivotX, pivotY, isScaleTransient, animate, id); } } @@ -1497,6 +1518,8 @@ public class FullScreenMagnificationController implements * @return {@code true} if the magnification spec changed, {@code false} if * the spec did not change */ + @SuppressWarnings("GuardedBy") + // errorprone cannot recognize an inner class guarded by an outer class member. public boolean setCenter(int displayId, float centerX, float centerY, boolean animate, int id) { synchronized (mLock) { final DisplayMagnification display = mDisplays.get(displayId); @@ -1504,7 +1527,7 @@ public class FullScreenMagnificationController implements return false; } return display.setScaleAndCenter(Float.NaN, centerX, centerY, - animate ? STUB_ANIMATION_CALLBACK : null, id); + /* isScaleTransient= */ false, animate ? STUB_ANIMATION_CALLBACK : null, id); } } @@ -1527,7 +1550,32 @@ public class FullScreenMagnificationController implements */ public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY, boolean animate, int id) { - return setScaleAndCenter(displayId, scale, centerX, centerY, + return setScaleAndCenter(displayId, scale, centerX, centerY, /* isScaleTransient= */ false, + transformToStubCallback(animate), id); + } + + /** + * Sets the scale and center of the magnified region, optionally + * animating the transition. If animation is disabled, the transition + * is immediate. + * + * @param displayId The logical display id. + * @param scale the target scale, or {@link Float#NaN} to leave unchanged + * @param centerX the screen-relative X coordinate around which to + * center and scale, or {@link Float#NaN} to leave unchanged + * @param centerY the screen-relative Y coordinate around which to + * center and scale, or {@link Float#NaN} to leave unchanged + * @param isScaleTransient {@code true} if the scale is for a short time and potentially changed + * soon. {@code false} otherwise. + * @param animate {@code true} to animate the transition, {@code false} + * to transition immediately + * @param id the ID of the service requesting the change + * @return {@code true} if the magnification spec changed, {@code false} if + * the spec did not change + */ + public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY, + boolean isScaleTransient, boolean animate, int id) { + return setScaleAndCenter(displayId, scale, centerX, centerY, isScaleTransient, transformToStubCallback(animate), id); } @@ -1542,20 +1590,25 @@ public class FullScreenMagnificationController implements * center and scale, or {@link Float#NaN} to leave unchanged * @param centerY the screen-relative Y coordinate around which to * center and scale, or {@link Float#NaN} to leave unchanged + * @param isScaleTransient {@code true} if the scale is for a short time and potentially changed + * soon. {@code false} otherwise. * @param animationCallback Called when the animation result is valid. * {@code null} to transition immediately * @param id the ID of the service requesting the change * @return {@code true} if the magnification spec changed, {@code false} if * the spec did not change */ + @SuppressWarnings("GuardedBy") + // errorprone cannot recognize an inner class guarded by an outer class member. public boolean setScaleAndCenter(int displayId, float scale, float centerX, float centerY, - MagnificationAnimationCallback animationCallback, int id) { + boolean isScaleTransient, MagnificationAnimationCallback animationCallback, int id) { synchronized (mLock) { final DisplayMagnification display = mDisplays.get(displayId); if (display == null) { return false; } - return display.setScaleAndCenter(scale, centerX, centerY, animationCallback, id); + return display.setScaleAndCenter(scale, centerX, centerY, isScaleTransient, + animationCallback, id); } } @@ -1570,6 +1623,8 @@ public class FullScreenMagnificationController implements * screen pixels. * @param id the ID of the service requesting the change */ + @SuppressWarnings("GuardedBy") + // errorprone cannot recognize an inner class guarded by an outer class member. public void offsetMagnifiedRegion(int displayId, float offsetX, float offsetY, int id) { synchronized (mLock) { final DisplayMagnification display = mDisplays.get(displayId); @@ -1637,9 +1692,12 @@ public class FullScreenMagnificationController implements * <strong>if scale is >= {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}</strong>. * We assume if the scale is < {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}, there * will be no obvious magnification effect. + * Only the value of the default display is persisted in user's settings. */ public void persistScale(int displayId) { - final float scale = getScale(Display.DEFAULT_DISPLAY); + final float scale = getScale(displayId); + notifyScaleForInput(displayId, scale); + if (scale < MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) { return; } @@ -1665,6 +1723,8 @@ public class FullScreenMagnificationController implements * * @param displayId The logical display id. */ + @SuppressWarnings("GuardedBy") + // errorprone cannot recognize an inner class guarded by an outer class member. private void zoomOutFromService(int displayId) { synchronized (mLock) { final DisplayMagnification display = mDisplays.get(displayId); @@ -1691,6 +1751,20 @@ public class FullScreenMagnificationController implements } /** + * Notifies input manager that magnification scale changed non-transiently + * so that pointer cursor is scaled as well. + * + * @param displayId The logical display id. + * @param scale The new scale factor. + */ + public void notifyScaleForInput(int displayId, float scale) { + if (Flags.magnificationEnlargePointerBugfix()) { + mControllerCtx.getInputManager() + .setAccessibilityPointerIconScaleFactor(displayId, scale); + } + } + + /** * Resets all displays' magnification if last magnifying service is disabled. * * @param connectionId @@ -2166,6 +2240,7 @@ public class FullScreenMagnificationController implements private final Context mContext; private final AccessibilityTraceManager mTrace; private final WindowManagerInternal mWindowManager; + private final InputManagerInternal mInputManager; private final Handler mHandler; private final Long mAnimationDuration; @@ -2175,11 +2250,13 @@ public class FullScreenMagnificationController implements public ControllerContext(@NonNull Context context, @NonNull AccessibilityTraceManager traceManager, @NonNull WindowManagerInternal windowManager, + @NonNull InputManagerInternal inputManager, @NonNull Handler handler, long animationDuration) { mContext = context; mTrace = traceManager; mWindowManager = windowManager; + mInputManager = inputManager; mHandler = handler; mAnimationDuration = animationDuration; } @@ -2209,6 +2286,14 @@ public class FullScreenMagnificationController implements } /** + * @return InputManagerInternal + */ + @NonNull + public InputManagerInternal getInputManager() { + return mInputManager; + } + + /** * @return Handler for main looper */ @NonNull diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index a19fdddea49c..d11ae0a6ad97 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -58,8 +58,6 @@ import android.util.TypedValue; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; -import android.view.MotionEvent.PointerCoords; -import android.view.MotionEvent.PointerProperties; import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector.OnScaleGestureListener; import android.view.VelocityTracker; @@ -155,9 +153,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH @VisibleForTesting State mCurrentState; @VisibleForTesting State mPreviousState; - private PointerCoords[] mTempPointerCoords; - private PointerProperties[] mTempPointerProperties; - @VisibleForTesting static final int OVERSCROLL_NONE = 0; @VisibleForTesting static final int OVERSCROLL_LEFT_EDGE = 1; @VisibleForTesting static final int OVERSCROLL_RIGHT_EDGE = 2; @@ -345,7 +340,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH @Override void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (Flags.enableMagnificationFollowsMouse()) { + if (Flags.enableMagnificationFollowsMouseBugfix()) { if (mFullScreenMagnificationController.isActivated(mDisplayId)) { // TODO(b/354696546): Allow mouse/stylus to activate whichever display they are // over, rather than only interacting with the current display. @@ -430,38 +425,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mPanningScalingState.clear(); } - private PointerCoords[] getTempPointerCoordsWithMinSize(int size) { - final int oldSize = (mTempPointerCoords != null) ? mTempPointerCoords.length : 0; - if (oldSize < size) { - PointerCoords[] oldTempPointerCoords = mTempPointerCoords; - mTempPointerCoords = new PointerCoords[size]; - if (oldTempPointerCoords != null) { - System.arraycopy(oldTempPointerCoords, 0, mTempPointerCoords, 0, oldSize); - } - } - for (int i = oldSize; i < size; i++) { - mTempPointerCoords[i] = new PointerCoords(); - } - return mTempPointerCoords; - } - - private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) { - final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length - : 0; - if (oldSize < size) { - PointerProperties[] oldTempPointerProperties = mTempPointerProperties; - mTempPointerProperties = new PointerProperties[size]; - if (oldTempPointerProperties != null) { - System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, - oldSize); - } - } - for (int i = oldSize; i < size; i++) { - mTempPointerProperties[i] = new PointerProperties(); - } - return mTempPointerProperties; - } - @VisibleForTesting void transitionTo(State state) { if (DEBUG_STATE_TRANSITIONS) { @@ -617,7 +580,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } if (DEBUG_PANNING_SCALING) Slog.i(mLogTag, "Scaled content to: " + scale + "x"); - mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, false, + mFullScreenMagnificationController.setScale(mDisplayId, scale, pivotX, pivotY, + /* isScaleTransient= */ true, /* animate= */ false, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); checkShouldDetectPassPersistedScale(); @@ -1206,7 +1170,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH protected void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (Flags.enableMagnificationFollowsMouse() + if (Flags.enableMagnificationFollowsMouseBugfix() && !event.isFromSource(SOURCE_TOUCHSCREEN)) { // Only touch events need to be cached and sent later. return; @@ -1974,6 +1938,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH /* scale= */ scale, /* centerX= */ mPivotEdge.x, /* centerY= */ mPivotEdge.y, + /* isScaleTransient= */ true, /* animate= */ true, /* id= */ AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); if (scale == 1.0f) { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java index 1489d16c3764..058b2be5f4b3 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java @@ -27,6 +27,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID; import android.accessibilityservice.MagnificationConfig; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; @@ -50,7 +51,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityManagerService; import com.android.server.wm.WindowManagerInternal; -import com.android.window.flags.Flags; import java.util.concurrent.Executor; @@ -101,6 +101,7 @@ public class MagnificationController implements MagnificationConnectionManager.C private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; /** Whether the platform supports window magnification feature. */ private final boolean mSupportWindowMagnification; + private final MagnificationScaleStepProvider mScaleStepProvider; private final Executor mBackgroundExecutor; @@ -131,6 +132,14 @@ public class MagnificationController implements MagnificationConnectionManager.C .UiChangesForAccessibilityCallbacks> mAccessibilityCallbacksDelegateArray = new SparseArray<>(); + // Direction magnifier scale can be altered. + public static final int ZOOM_DIRECTION_IN = 0; + public static final int ZOOM_DIRECTION_OUT = 1; + + @IntDef({ZOOM_DIRECTION_IN, ZOOM_DIRECTION_OUT}) + public @interface ZoomDirection { + } + /** * A callback to inform the magnification transition result on the given display. */ @@ -144,6 +153,41 @@ public class MagnificationController implements MagnificationConnectionManager.C void onResult(int displayId, boolean success); } + + /** + * An interface to configure how much the magnification scale should be affected when moving in + * steps. + */ + public interface MagnificationScaleStepProvider { + /** + * Calculate the next value given which direction (in/out) to adjust the magnification + * scale. + * + * @param currentScale The current magnification scale value. + * @param direction Whether to zoom in or out. + * @return The next scale value. + */ + float nextScaleStep(float currentScale, @ZoomDirection int direction); + } + + public static class DefaultMagnificationScaleStepProvider implements + MagnificationScaleStepProvider { + // Factor of magnification scale. For example, when this value is 1.189, scale + // value will be changed x1.000, x1.189, x1.414, x1.681, x2.000, ... + // Note: this value is 2.0 ^ (1 / 4). + public static final float ZOOM_STEP_SCALE_FACTOR = 1.18920712f; + + @Override + public float nextScaleStep(float currentScale, @ZoomDirection int direction) { + final int stepDelta = direction == ZOOM_DIRECTION_IN ? 1 : -1; + final long scaleIndex = Math.round( + Math.log(currentScale) / Math.log(ZOOM_STEP_SCALE_FACTOR)); + final float nextScale = (float) Math.pow(ZOOM_STEP_SCALE_FACTOR, + scaleIndex + stepDelta); + return MagnificationScaleProvider.constrainScale(nextScale); + } + } + public MagnificationController(AccessibilityManagerService ams, Object lock, Context context, MagnificationScaleProvider scaleProvider, Executor backgroundExecutor) { @@ -156,6 +200,7 @@ public class MagnificationController implements MagnificationConnectionManager.C .getAccessibilityController().setUiChangesForAccessibilityCallbacks(this); mSupportWindowMagnification = context.getPackageManager().hasSystemFeature( FEATURE_WINDOW_MAGNIFICATION); + mScaleStepProvider = new DefaultMagnificationScaleStepProvider(); mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context); mAlwaysOnMagnificationFeatureFlag.addOnChangedListener( @@ -176,7 +221,8 @@ public class MagnificationController implements MagnificationConnectionManager.C public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) { if (getFullScreenMagnificationController().isActivated(displayId)) { getFullScreenMagnificationController().setScaleAndCenter(displayId, scale, - Float.NaN, Float.NaN, false, MAGNIFICATION_GESTURE_HANDLER_ID); + Float.NaN, Float.NaN, /* isScaleTransient= */ !updatePersistence, false, + MAGNIFICATION_GESTURE_HANDLER_ID); if (updatePersistence) { getFullScreenMagnificationController().persistScale(displayId); } @@ -371,7 +417,7 @@ public class MagnificationController implements MagnificationConnectionManager.C } screenMagnificationController.setScaleAndCenter(displayId, targetScale, magnificationCenter.x, magnificationCenter.y, - magnificationAnimationCallback, id); + /* isScaleTransient= */ false, magnificationAnimationCallback, id); } else { if (screenMagnificationController.isRegistered(displayId)) { screenMagnificationController.reset(displayId, false); @@ -587,10 +633,8 @@ public class MagnificationController implements MagnificationConnectionManager.C @Override public void onFullScreenMagnificationActivationState(int displayId, boolean activated) { - if (Flags.alwaysDrawMagnificationFullscreenBorder()) { - getMagnificationConnectionManager() - .onFullscreenMagnificationActivationChanged(displayId, activated); - } + getMagnificationConnectionManager() + .onFullscreenMagnificationActivationChanged(displayId, activated); if (activated) { synchronized (mLock) { @@ -890,6 +934,37 @@ public class MagnificationController implements MagnificationConnectionManager.C return isActivated; } + /** + * Scales the magnifier on the given display one step in/out based on the zoomIn param. + * + * @param displayId The logical display id. + * @param direction Whether the scale should be zoomed in or out. + * @return {@code true} if the magnification scale was affected. + */ + public boolean scaleMagnificationByStep(int displayId, @ZoomDirection int direction) { + if (getFullScreenMagnificationController().isActivated(displayId)) { + final float magnificationScale = getFullScreenMagnificationController().getScale( + displayId); + final float nextMagnificationScale = mScaleStepProvider.nextScaleStep( + magnificationScale, direction); + getFullScreenMagnificationController().setScaleAndCenter(displayId, + nextMagnificationScale, + Float.NaN, Float.NaN, true, MAGNIFICATION_GESTURE_HANDLER_ID); + return nextMagnificationScale != magnificationScale; + } + + if (getMagnificationConnectionManager().isWindowMagnifierEnabled(displayId)) { + final float magnificationScale = getMagnificationConnectionManager().getScale( + displayId); + final float nextMagnificationScale = mScaleStepProvider.nextScaleStep( + magnificationScale, direction); + getMagnificationConnectionManager().setScale(displayId, nextMagnificationScale); + return nextMagnificationScale != magnificationScale; + } + + return false; + } + private final class DisableMagnificationCallback implements MagnificationAnimationCallback { private final TransitionCallBack mTransitionCallBack; diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java index 446123f07f64..fa86ba39bb1a 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java @@ -146,7 +146,8 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo } break; case SOURCE_MOUSE: case SOURCE_STYLUS: { - if (magnificationShortcutExists() && Flags.enableMagnificationFollowsMouse()) { + if (magnificationShortcutExists() + && Flags.enableMagnificationFollowsMouseBugfix()) { handleMouseOrStylusEvent(event, rawEvent, policyFlags); } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java index 6b48d2bacf9d..a4568aaa7a0d 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java @@ -31,7 +31,6 @@ import android.view.ScaleGestureDetector; import android.view.ViewConfiguration; import com.android.internal.R; -import com.android.server.accessibility.Flags; /** * Handles the behavior while receiving scaling and panning gestures if it's enabled. @@ -73,13 +72,9 @@ class PanningScalingHandler extends mMaxScale = maxScale; mMinScale = minScale; mBlockScroll = blockScroll; - if (Flags.pinchZoomZeroMinSpan()) { - mScaleGestureDetector = new ScaleGestureDetector(context, - ViewConfiguration.get(context).getScaledTouchSlop() * 2, - /* minSpan= */ 0, Handler.getMain(), this); - } else { - mScaleGestureDetector = new ScaleGestureDetector(context, this, Handler.getMain()); - } + mScaleGestureDetector = new ScaleGestureDetector(context, + ViewConfiguration.get(context).getScaledTouchSlop() * 2, + /* minSpan= */ 0, Handler.getMain(), this); mScrollGestureDetector = new GestureDetector(context, this, Handler.getMain()); mScaleGestureDetector.setQuickScaleEnabled(false); mMagnificationDelegate = magnificationDelegate; |