Expose more SystemAPI methods on AccessibilityDisplayProxy

These are:
- onProxyConnected (equivalent to onServiceConnected)
- onAccessibilityEvent
- onInterrupt
- getWindows
- set/getInstalledAndEnabledServices

Connect the proxy client (IAccessibilityServiceClient) to a
proxy service connection
(ProxyAbstractAccessibilityServiceConnection) in ProxyManager,
so the A11yDisplayProxy can access the UI.

AccessibilityServices run on their own thread. To avoid using the
app's main thread, the app that registers the A11yDisplayProxy
can call it on a different thread. Add a note in the proxy class.

Future TODOs (b/254545943):

The a11y state returned to A11yManager includes state
of all proxies and AccessibilityServices.
Separation will require AccessibiilityManager to go from being a
process singleton to a per-display instance.

Input and a11y focus are singletons and may
shift between a proxy display and a display on the device.
Separation will require future changes.

ProxyManager behaves more like UiAutomationManager,
and centralizes proxy management. Separation into different user
states will require future refactoring.

Bug: 241429275
Test: atest AccessibilityDisplayProxyTest,
AccessibilityManagerServiceTest, manual test app that
registers/unregisters A11yDisplayProxy

Change-Id: I830c6613c2c2f8abcc2343c5110f4e7b7494fb0e
This commit is contained in:
sallyyuen
2022-11-03 13:57:59 -07:00
parent 0319d1cb84
commit 111ef0b932
9 changed files with 443 additions and 51 deletions

View File

@@ -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<android.accessibilityservice.AccessibilityServiceInfo>);
method public int getDisplayId();
method @NonNull public final java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAndEnabledServices();
method @NonNull public java.util.List<android.view.accessibility.AccessibilityWindowInfo> 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<android.accessibilityservice.AccessibilityServiceInfo>);
}
public final class AccessibilityManager {

View File

@@ -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.
*
* <p>
* 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
* <p>
* 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.
*
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
*
* <p>
* 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.
* <p>
* 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<AccessibilityWindowInfo> getWindows() {
return AccessibilityInteractionClient.getInstance().getWindowsOnDisplay(mConnectionId,
mDisplayId);
}
/**
* Sets the list of {@link AccessibilityServiceInfo}s describing the services interested in the
* {@link AccessibilityDisplayProxy}'s display.
*
* <p>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<AccessibilityServiceInfo> 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<AccessibilityServiceInfo> 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;

View File

@@ -447,14 +447,20 @@ public final class AccessibilityInteractionClient
* @return The {@link AccessibilityWindowInfo} list.
*/
public List<AccessibilityWindowInfo> getWindows(int connectionId) {
final SparseArray<List<AccessibilityWindowInfo>> 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<AccessibilityWindowInfo> getWindowsOnDisplay(int connectionId, int displayId) {
final SparseArray<List<AccessibilityWindowInfo>> windows =
getWindowsOnAllDisplays(connectionId);
return windows.get(displayId, Collections.emptyList());
}
/**
* Gets the info for all windows of all displays.
*

View File

@@ -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<AccessibilityServiceInfo> serviceInfos) {
super(displayId, Executors.newSingleThreadExecutor(), serviceInfos);
}
@Override
public void onAccessibilityEvent(@NonNull AccessibilityEvent event) {
}
@Override
public void onProxyConnected() {
}
@Override
public void interrupt() {
}
}
}

View File

@@ -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<AccessibilityServiceConnection> 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<AccessibilityServiceConnection> 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;
}

View File

@@ -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;
}

View File

@@ -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<AccessibilityServiceInfo> 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;

View File

@@ -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<Integer> 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<ProxyAccessibilityServiceConnection> 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<IAccessibilityServiceClient> 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);
}
}
}
}

View File

@@ -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);