diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 352b4f98edd31..4f30e8ab4835c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -15928,6 +15928,12 @@ package android.view.accessibility { public abstract class AccessibilityDisplayProxy { ctor public AccessibilityDisplayProxy(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.List); method public int getDisplayId(); + method @NonNull public final java.util.List getInstalledAndEnabledServices(); + method @NonNull public java.util.List getWindows(); + method public void interrupt(); + method public void onAccessibilityEvent(@NonNull android.view.accessibility.AccessibilityEvent); + method public void onProxyConnected(); + method public void setInstalledAndEnabledServices(@NonNull java.util.List); } public final class AccessibilityManager { diff --git a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java index 85f5056e41160..44b6deb3015fc 100644 --- a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java +++ b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java @@ -20,13 +20,18 @@ import android.accessibilityservice.AccessibilityGestureEvent; import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; +import android.accessibilityservice.IAccessibilityServiceConnection; import android.accessibilityservice.MagnificationConfig; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.ResolveInfo; import android.graphics.Region; import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.inputmethod.EditorInfo; @@ -34,14 +39,15 @@ import android.view.inputmethod.EditorInfo; import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback; import com.android.internal.inputmethod.RemoteAccessibilityInputConnection; +import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; /** * Allows a privileged app - an app with MANAGE_ACCESSIBILITY permission and SystemAPI access - to * interact with the windows in the display that this proxy represents. Proxying the default display - * or a display that is not tracked will throw an exception. Only the real user has access to global - * clients like SystemUI. + * or a display that is not tracked by accessibility, such as private displays, will throw an + * exception. Only the real user has access to global clients like SystemUI. * *

* To register and unregister a proxy, use @@ -49,7 +55,16 @@ import java.util.concurrent.Executor; * and {@link AccessibilityManager#unregisterDisplayProxy(AccessibilityDisplayProxy)}. If the app * that has registered the proxy dies, the system will remove the proxy. * - * TODO(241429275): Complete proxy impl and add additional support (if necessary) like cache methods + *

+ * Avoid using the app's main thread. Proxy methods such as {@link #getWindows} and node methods + * like {@link AccessibilityNodeInfo#getChild(int)} will happen frequently. Node methods may also + * wait on the displayed app's UI thread to obtain accurate screen data. + * + *

+ * To get a list of {@link AccessibilityServiceInfo}s that have populated {@link ComponentName}s and + * {@link ResolveInfo}s, retrieve the list using {@link #getInstalledAndEnabledServices()} after + * {@link #onProxyConnected()} has been called. + * * @hide */ @SystemApi @@ -91,7 +106,134 @@ public abstract class AccessibilityDisplayProxy { } /** - * An IAccessibilityServiceClient that handles interrupts and accessibility events. + * Handles {@link android.view.accessibility.AccessibilityEvent}s. + *

+ * AccessibilityEvents represent changes to the UI, or what parts of the node tree have changed. + * AccessibilityDisplayProxy should use these to query new UI and send appropriate feedback + * to their users. + *

+ * For example, a {@link AccessibilityEvent#TYPE_WINDOWS_CHANGED} indicates a change in windows, + * so a proxy may query {@link #getWindows} to obtain updated UI and potentially inform of a new + * window title. Or a proxy may emit an earcon on a + * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. + */ + public void onAccessibilityEvent(@NonNull AccessibilityEvent event) { + // Default no-op + } + + /** + * Handles a successful system connection after + * {@link AccessibilityManager#registerDisplayProxy(AccessibilityDisplayProxy)} is called. + * + *

+ * At this point, querying for UI is available and {@link AccessibilityEvent}s will begin being + * sent. An AccessibilityDisplayProxy may instantiate core infrastructure components here. + */ + public void onProxyConnected() { + // Default no-op + } + + /** + * Handles a request to interrupt the accessibility feedback. + *

+ * AccessibilityDisplayProxy should interrupt the accessibility activity occurring on its + * display. For example, a screen reader may interrupt speech. + * + * @see AccessibilityManager#interrupt() + * @see AccessibilityService#onInterrupt() + */ + public void interrupt() { + // Default no-op + } + + /** + * Gets the focus of the window specified by {@code windowInfo}. + * + * @param windowInfo the window to search + * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or + * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}. + * @return The node info of the focused view or null. + * @hide + * TODO(254545943): Do not expose until support for accessibility focus and/or input is in place + */ + @Nullable + public AccessibilityNodeInfo findFocus(@NonNull AccessibilityWindowInfo windowInfo, int focus) { + AccessibilityNodeInfo windowRoot = windowInfo.getRoot(); + return windowRoot != null ? windowRoot.findFocus(focus) : null; + } + + /** + * Gets the windows of the tracked display. + * + * @see AccessibilityService#getWindows() + */ + @NonNull + public List getWindows() { + return AccessibilityInteractionClient.getInstance().getWindowsOnDisplay(mConnectionId, + mDisplayId); + } + + /** + * Sets the list of {@link AccessibilityServiceInfo}s describing the services interested in the + * {@link AccessibilityDisplayProxy}'s display. + * + *

These represent a11y features and services that are installed and running. These should + * not include {@link AccessibilityService}s installed on the phone. + * + * @param installedAndEnabledServices the list of installed and running a11y services. + */ + public void setInstalledAndEnabledServices( + @NonNull List installedAndEnabledServices) { + mInstalledAndEnabledServices = installedAndEnabledServices; + sendServiceInfos(); + } + + /** + * Sets the {@link AccessibilityServiceInfo} for this service if the latter is + * properly set and there is an {@link IAccessibilityServiceConnection} to the + * AccessibilityManagerService. + */ + private void sendServiceInfos() { + IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); + if (mInstalledAndEnabledServices != null && mInstalledAndEnabledServices.size() > 0 + && connection != null) { + try { + connection.setInstalledAndEnabledServices(mInstalledAndEnabledServices); + AccessibilityInteractionClient.getInstance().clearCache(mConnectionId); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfos", re); + re.rethrowFromSystemServer(); + } + } + mInstalledAndEnabledServices = null; + } + + /** + * Gets the list of {@link AccessibilityServiceInfo}s describing the services interested in the + * {@link AccessibilityDisplayProxy}'s display. + * + * @return The {@link AccessibilityServiceInfo}s of interested services. + * @see AccessibilityServiceInfo + */ + @NonNull + public final List getInstalledAndEnabledServices() { + IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); + if (connection != null) { + try { + return connection.getInstalledAndEnabledServices(); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re); + re.rethrowFromSystemServer(); + } + } + return Collections.emptyList(); + } + + /** + * An IAccessibilityServiceClient that handles interrupts, accessibility events, and system + * connection. */ private class IAccessibilityServiceClientImpl extends AccessibilityService.IAccessibilityServiceClientWrapper { @@ -100,17 +242,24 @@ public abstract class AccessibilityDisplayProxy { super(context, executor, new AccessibilityService.Callbacks() { @Override public void onAccessibilityEvent(AccessibilityEvent event) { - // TODO: call AccessiiblityProxy.onAccessibilityEvent + // TODO(254545943): Remove check when event processing is done more upstream in + // AccessibilityManagerService. + if (event.getDisplayId() == mDisplayId) { + AccessibilityDisplayProxy.this.onAccessibilityEvent(event); + } } @Override public void onInterrupt() { - // TODO: call AccessiiblityProxy.onInterrupt + AccessibilityDisplayProxy.this.interrupt(); } + @Override public void onServiceConnected() { - // TODO: send service infos and call AccessiiblityProxy.onProxyConnected + AccessibilityDisplayProxy.this.sendServiceInfos(); + AccessibilityDisplayProxy.this.onProxyConnected(); } + @Override public void init(int connectionId, IBinder windowToken) { mConnectionId = connectionId; diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index e3ffc9dcbbdec..7b1cec752cf21 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -447,14 +447,20 @@ public final class AccessibilityInteractionClient * @return The {@link AccessibilityWindowInfo} list. */ public List getWindows(int connectionId) { - final SparseArray> windows = - getWindowsOnAllDisplays(connectionId); - if (windows.size() > 0) { - return windows.valueAt(Display.DEFAULT_DISPLAY); - } - return Collections.emptyList(); + return getWindowsOnDisplay(connectionId, Display.DEFAULT_DISPLAY); } + /** + * Gets the info for all windows of the specified display. + * + * @param connectionId The id of a connection for interacting with the system. + * @return The {@link AccessibilityWindowInfo} list belonging to {@code displayId}. + */ + public List getWindowsOnDisplay(int connectionId, int displayId) { + final SparseArray> windows = + getWindowsOnAllDisplays(connectionId); + return windows.get(displayId, Collections.emptyList()); + } /** * Gets the info for all windows of all displays. * diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java index ee1e10f9009ea..7cbf3ffa509cc 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java @@ -263,10 +263,24 @@ public class AccessibilityManagerTest { } private class MyAccessibilityProxy extends AccessibilityDisplayProxy { - // TODO(241429275): Will override A11yProxy methods in the future. MyAccessibilityProxy(int displayId, @NonNull List serviceInfos) { super(displayId, Executors.newSingleThreadExecutor(), serviceInfos); } + + @Override + public void onAccessibilityEvent(@NonNull AccessibilityEvent event) { + + } + + @Override + public void onProxyConnected() { + + } + + @Override + public void interrupt() { + + } } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index e3ae03cbcdd80..2647ca3dbeb98 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -859,7 +859,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid()); } return IntPair.of( - getClientStateLocked(userState), + combineUserStateAndProxyState(getClientStateLocked(userState), + mProxyManager.getStateLocked()), client.mLastSentRelevantEventTypes); } else { userState.mUserClients.register(callback, client); @@ -871,7 +872,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub + " and userId:" + mCurrentUserId); } return IntPair.of( - (resolvedUserId == mCurrentUserId) ? getClientStateLocked(userState) : 0, + (resolvedUserId == mCurrentUserId) ? combineUserStateAndProxyState( + getClientStateLocked(userState), mProxyManager.getStateLocked()) + : 0, client.mLastSentRelevantEventTypes); } } @@ -1002,6 +1005,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub notifyAccessibilityServicesDelayedLocked(event, false); notifyAccessibilityServicesDelayedLocked(event, true); mUiAutomationManager.sendAccessibilityEventLocked(event); + mProxyManager.sendAccessibilityEvent(event); } private void sendAccessibilityEventToInputFilter(AccessibilityEvent event) { @@ -1142,9 +1146,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } List services = getUserStateLocked(resolvedUserId).mBoundServices; - int numServices = services.size(); + int numServices = services.size() + mProxyManager.getNumProxys(); interfacesToInterrupt = new ArrayList<>(numServices); - for (int i = 0; i < numServices; i++) { + for (int i = 0; i < services.size(); i++) { AccessibilityServiceConnection service = services.get(i); IBinder a11yServiceBinder = service.mService; IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface; @@ -1152,6 +1156,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub interfacesToInterrupt.add(a11yServiceInterface); } } + mProxyManager.addServiceInterfaces(interfacesToInterrupt); } for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) { try { @@ -1940,6 +1945,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mUiAutomationManager.getServiceInfo(), client) ? mUiAutomationManager.getRelevantEventTypes() : 0; + relevantEventTypes |= mProxyManager.getRelevantEventTypes(); return relevantEventTypes; } @@ -2177,21 +2183,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub updateAccessibilityEnabledSettingLocked(userState); } - void scheduleUpdateClientsIfNeeded(AccessibilityUserState userState) { - synchronized (mLock) { - scheduleUpdateClientsIfNeededLocked(userState); - } + private int combineUserStateAndProxyState(int userState, int proxyState) { + return userState | proxyState; } void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) { final int clientState = getClientStateLocked(userState); - if (userState.getLastSentClientStateLocked() != clientState + final int proxyState = mProxyManager.getStateLocked(); + if ((userState.getLastSentClientStateLocked() != clientState + || mProxyManager.getLastSentStateLocked() != proxyState) && (mGlobalClients.getRegisteredCallbackCount() > 0 - || userState.mUserClients.getRegisteredCallbackCount() > 0)) { + || userState.mUserClients.getRegisteredCallbackCount() > 0)) { userState.setLastSentClientStateLocked(clientState); + mProxyManager.setLastStateLocked(proxyState); + // Send both the user and proxy state to the app for now. + // TODO(b/250929565): Send proxy state to proxy clients mMainHandler.sendMessage(obtainMessage( AccessibilityManagerService::sendStateToAllClients, - this, clientState, userState.mUserId)); + this, combineUserStateAndProxyState(clientState, proxyState), + userState.mUserId)); } } @@ -2433,7 +2443,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // binding we do an update pass after each bind event, so we run this // code and register the callback if needed. - boolean observingWindows = mUiAutomationManager.canRetrieveInteractiveWindowsLocked(); + boolean observingWindows = mUiAutomationManager.canRetrieveInteractiveWindowsLocked() + || mProxyManager.canRetrieveInteractiveWindowsLocked(); List boundServices = userState.mBoundServices; final int boundServiceCount = boundServices.size(); for (int i = 0; !observingWindows && (i < boundServiceCount); i++) { @@ -3656,7 +3667,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub + "proxy-ed"); } - mProxyManager.registerProxy(client, displayId); + mProxyManager.registerProxy(client, displayId, mContext, + sIdCounter++, mMainHandler, mSecurityPolicy, this, getTraceManager(), + mWindowManagerService, mA11yWindowManager); return true; } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java index e5e1d027dd113..ce269f5eca592 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java @@ -696,7 +696,7 @@ public class AccessibilitySecurityPolicy { final ResolveInfo resolveInfo = service.getServiceInfo().getResolveInfo(); if (resolveInfo == null) { - // For InteractionBridge and UiAutomation + // For InteractionBridge, UiAutomation, and Proxy. return true; } diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java index 247f320adecd9..d7f9c12b78850 100644 --- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java @@ -16,8 +16,12 @@ package com.android.server.accessibility; +import static com.android.server.accessibility.ProxyManager.PROXY_COMPONENT_CLASS_NAME; +import static com.android.server.accessibility.ProxyManager.PROXY_COMPONENT_PACKAGE_NAME; + import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.AccessibilityTrace; +import android.accessibilityservice.IAccessibilityServiceClient; import android.accessibilityservice.MagnificationConfig; import android.annotation.NonNull; import android.content.ComponentName; @@ -31,6 +35,7 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.RemoteCallback; +import android.os.RemoteException; import android.view.KeyEvent; import android.view.accessibility.AccessibilityDisplayProxy; import android.view.accessibility.AccessibilityNodeInfo; @@ -53,10 +58,6 @@ import java.util.Set; * TODO(241429275): Initialize this when a proxy is registered. */ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection { - // Names used to populate ComponentName and ResolveInfo - private static final String PROXY_COMPONENT_PACKAGE_NAME = "ProxyPackage"; - private static final String PROXY_COMPONENT_CLASS_NAME = "ProxyClass"; - private int mDisplayId; private List mInstalledAndEnabledServices; @@ -75,6 +76,16 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon mDisplayId = displayId; } + /** + * 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)); + } + /** * Keeps mAccessibilityServiceInfo in sync with the proxy's list of AccessibilityServiceInfos. * @@ -89,7 +100,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon synchronized (mLock) { mInstalledAndEnabledServices = infos; final AccessibilityServiceInfo proxyInfo = mAccessibilityServiceInfo; - // Reset values. + // Reset values. mAccessibilityServiceInfo is not completely reset since it is final proxyInfo.flags = 0; proxyInfo.eventTypes = 0; proxyInfo.notificationTimeout = 0; diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java index a2ce61063aaf3..2184878b4c362 100644 --- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java +++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java @@ -14,9 +14,21 @@ * limitations under the License. */ package com.android.server.accessibility; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.IAccessibilityServiceClient; +import android.content.ComponentName; +import android.content.Context; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.SparseArray; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; -import java.util.HashSet; +import com.android.server.wm.WindowManagerInternal; + +import java.util.List; /** * Manages proxy connections. @@ -27,32 +39,185 @@ import java.util.HashSet; * TODO(241117292): Remove or cut down during simultaneous user refactoring. */ public class ProxyManager { + // Names used to populate ComponentName and ResolveInfo in connection.mA11yServiceInfo and in + // the infos of connection.setInstalledAndEnabledServices + static final String PROXY_COMPONENT_PACKAGE_NAME = "ProxyPackage"; + static final String PROXY_COMPONENT_CLASS_NAME = "ProxyClass"; + private final Object mLock; - private final HashSet mDisplayIds = new HashSet<>(); + + // Used to determine if we should notify AccessibilityManager clients of updates. + // TODO(254545943): Separate this so each display id has its own state. Currently there is no + // way to identify from AccessibilityManager which proxy state should be returned. + private int mLastState = -1; + + private SparseArray mProxyA11yServiceConnections = + new SparseArray<>(); ProxyManager(Object lock) { mLock = lock; } /** - * TODO: Create the proxy service connection. + * Creates the service connection. */ - public void registerProxy(IAccessibilityServiceClient client, int displayId) { - mDisplayIds.add(displayId); + public void registerProxy(IAccessibilityServiceClient client, int displayId, + Context context, + int id, Handler mainHandler, + AccessibilitySecurityPolicy securityPolicy, + AbstractAccessibilityServiceConnection.SystemSupport systemSupport, + AccessibilityTrace trace, + WindowManagerInternal windowManagerInternal, + AccessibilityWindowManager awm) throws RemoteException { + + // Set a default AccessibilityServiceInfo that is used before the proxy's info is + // populated. A proxy has the touch exploration and window capabilities. + AccessibilityServiceInfo info = new AccessibilityServiceInfo(); + info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION + | AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT); + final String componentClassDisplayName = PROXY_COMPONENT_CLASS_NAME + displayId; + info.setComponentName(new ComponentName(PROXY_COMPONENT_PACKAGE_NAME, + componentClassDisplayName)); + ProxyAccessibilityServiceConnection connection = + new ProxyAccessibilityServiceConnection(context, info.getComponentName(), info, + id, mainHandler, mLock, securityPolicy, systemSupport, trace, + windowManagerInternal, + awm, displayId); + + mProxyA11yServiceConnections.put(displayId, connection); + + // If the client dies, make sure to remove the connection. + IBinder.DeathRecipient deathRecipient = + new IBinder.DeathRecipient() { + @Override + public void binderDied() { + client.asBinder().unlinkToDeath(this, 0); + clearConnection(displayId); + } + }; + client.asBinder().linkToDeath(deathRecipient, 0); + // Notify apps that the service state has changed. + // A11yManager#A11yServicesStateChangeListener + connection.mSystemSupport.onClientChangeLocked(true); + + connection.initializeServiceInterface(client); } /** - * TODO: Unregister the proxy service connection based on display id. + * Unregister the proxy based on display id. */ public boolean unregisterProxy(int displayId) { - mDisplayIds.remove(displayId); - return true; + return clearConnection(displayId); + } + + private boolean clearConnection(int displayId) { + if (mProxyA11yServiceConnections.contains(displayId)) { + mProxyA11yServiceConnections.remove(displayId); + return true; + } + return false; } /** * Checks if a display id is being proxy-ed. */ public boolean isProxyed(int displayId) { - return mDisplayIds.contains(displayId); + return mProxyA11yServiceConnections.contains(displayId); } -} + + /** + * Sends AccessibilityEvents to all proxies. + * {@link android.view.accessibility.AccessibilityDisplayProxy} will filter based on display. + * TODO(b/250929565): Filtering should happen in the system, not in the proxy. + */ + public void sendAccessibilityEvent(AccessibilityEvent event) { + for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) { + ProxyAccessibilityServiceConnection proxy = + mProxyA11yServiceConnections.valueAt(i); + proxy.notifyAccessibilityEvent(event); + } + } + + /** + * Returns {@code true} if any proxy can retrieve windows. + * TODO(b/250929565): Retrieve per connection/user state. + */ + public boolean canRetrieveInteractiveWindowsLocked() { + boolean observingWindows = false; + for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) { + final ProxyAccessibilityServiceConnection proxy = + mProxyA11yServiceConnections.valueAt(i); + if (proxy.mRetrieveInteractiveWindows) { + observingWindows = true; + break; + } + } + return observingWindows; + } + + /** + * If there is at least one proxy, accessibility is enabled. + */ + public int getStateLocked() { + int clientState = 0; + final boolean a11yEnabled = mProxyA11yServiceConnections.size() > 0; + if (a11yEnabled) { + clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED; + } + return clientState; + // TODO(b/254545943): When A11yManager is separated, include support for other properties + // like isTouchExplorationEnabled. + } + + /** + * Gets the last state. + */ + public int getLastSentStateLocked() { + return mLastState; + } + + /** + * Sets the last state. + */ + public void setLastStateLocked(int proxyState) { + mLastState = proxyState; + } + + /** + * Returns the relevant event types of every proxy. + * TODO(254545943): When A11yManager is separated, return based on the A11yManager display. + */ + public int getRelevantEventTypes() { + int relevantEventTypes = 0; + for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) { + ProxyAccessibilityServiceConnection proxy = + mProxyA11yServiceConnections.valueAt(i); + relevantEventTypes |= proxy.getRelevantEventTypes(); + } + return relevantEventTypes; + } + + /** + * Gets the number of current proxy connections. + * @return + */ + public int getNumProxys() { + return mProxyA11yServiceConnections.size(); + } + + /** + * Adds the service interfaces to a list. + * @param interfaces + */ + public void addServiceInterfaces(List interfaces) { + for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) { + final ProxyAccessibilityServiceConnection proxy = + mProxyA11yServiceConnections.valueAt(i); + final IBinder proxyBinder = proxy.mService; + final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface; + if ((proxyBinder != null) && (proxyInterface != null)) { + interfaces.add(proxyInterface); + } + } + } +} \ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 0f09252b8ca12..52a550b1bcf2f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -58,7 +58,6 @@ import android.view.Display; import android.view.DisplayAdjustments; import android.view.DisplayInfo; import android.view.WindowManager; -import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.accessibility.AccessibilityWindowAttributes; import androidx.test.InstrumentationRegistry; @@ -106,8 +105,6 @@ public class AccessibilityManagerServiceTest { LABEL, DESCRIPTION, TEST_PENDING_INTENT); - private static final AccessibilityAction NEW_ACCESSIBILITY_ACTION = - new AccessibilityAction(ACTION_ID, LABEL); private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY + 1; @@ -282,10 +279,12 @@ public class AccessibilityManagerServiceTest { @Test public void testRegisterProxy() throws Exception { mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY); - verify(mProxyManager).registerProxy(mMockServiceClient, TEST_DISPLAY); + verify(mProxyManager).registerProxy(eq(mMockServiceClient), eq(TEST_DISPLAY), + eq(mTestableContext), anyInt(), any(), eq(mMockSecurityPolicy), + eq(mA11yms), eq(mA11yms.getTraceManager()), + eq(mMockWindowManagerService), eq(mMockA11yWindowManager)); } - @SmallTest @Test public void testRegisterProxyWithoutPermission() throws Exception { @@ -296,7 +295,8 @@ public class AccessibilityManagerServiceTest { Assert.fail(); } catch (SecurityException expected) { } - verify(mProxyManager, never()).registerProxy(mMockServiceClient, TEST_DISPLAY); + verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(), + any(), any(), any(), any()); } @SmallTest @@ -307,7 +307,8 @@ public class AccessibilityManagerServiceTest { Assert.fail(); } catch (IllegalArgumentException expected) { } - verify(mProxyManager, never()).registerProxy(mMockServiceClient, Display.DEFAULT_DISPLAY); + verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(), + any(), any(), any(), any()); } @SmallTest @@ -318,7 +319,30 @@ public class AccessibilityManagerServiceTest { Assert.fail(); } catch (IllegalArgumentException expected) { } - verify(mProxyManager, never()).registerProxy(mMockServiceClient, Display.INVALID_DISPLAY); + verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(), + any(), any(), any(), any()); + } + + @SmallTest + @Test + public void testUnRegisterProxyWithPermission() throws Exception { + mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY); + mA11yms.unregisterProxyForDisplay(TEST_DISPLAY); + + verify(mProxyManager).unregisterProxy(TEST_DISPLAY); + } + + @SmallTest + @Test + public void testUnRegisterProxyWithoutPermission() throws Exception { + doThrow(SecurityException.class).when(mMockSecurityPolicy) + .enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ACCESSIBILITY); + try { + mA11yms.unregisterProxyForDisplay(TEST_DISPLAY); + Assert.fail(); + } catch (SecurityException expected) { + } + verify(mProxyManager, never()).unregisterProxy(TEST_DISPLAY); } @SmallTest @@ -417,6 +441,8 @@ public class AccessibilityManagerServiceTest { @SmallTest @Test public void testOnClientChange_magnificationEnabledAndCapabilityAll_requestConnection() { + when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false); + final AccessibilityUserState userState = mA11yms.mUserStates.get( mA11yms.getCurrentUserIdLocked()); userState.mAccessibilityShortcutKeyTargets.add(MAGNIFICATION_CONTROLLER_NAME); @@ -432,6 +458,8 @@ public class AccessibilityManagerServiceTest { @SmallTest @Test public void testOnClientChange_boundServiceCanControlMagnification_requestConnection() { + when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false); + setupAccessibilityServiceConnection(0); when(mMockSecurityPolicy.canControlMagnification(any())).thenReturn(true);