Merge "Expose more SystemAPI methods on AccessibilityDisplayProxy"
This commit is contained in:
@@ -16120,6 +16120,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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -458,14 +458,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.
|
||||
*
|
||||
|
||||
@@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -860,7 +860,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);
|
||||
@@ -872,7 +873,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);
|
||||
}
|
||||
}
|
||||
@@ -1003,6 +1006,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
notifyAccessibilityServicesDelayedLocked(event, false);
|
||||
notifyAccessibilityServicesDelayedLocked(event, true);
|
||||
mUiAutomationManager.sendAccessibilityEventLocked(event);
|
||||
mProxyManager.sendAccessibilityEvent(event);
|
||||
}
|
||||
|
||||
private void sendAccessibilityEventToInputFilter(AccessibilityEvent event) {
|
||||
@@ -1143,9 +1147,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;
|
||||
@@ -1153,6 +1157,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
interfacesToInterrupt.add(a11yServiceInterface);
|
||||
}
|
||||
}
|
||||
mProxyManager.addServiceInterfaces(interfacesToInterrupt);
|
||||
}
|
||||
for (int i = 0, count = interfacesToInterrupt.size(); i < count; i++) {
|
||||
try {
|
||||
@@ -1941,6 +1946,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
mUiAutomationManager.getServiceInfo(), client)
|
||||
? mUiAutomationManager.getRelevantEventTypes()
|
||||
: 0;
|
||||
relevantEventTypes |= mProxyManager.getRelevantEventTypes();
|
||||
return relevantEventTypes;
|
||||
}
|
||||
|
||||
@@ -2178,21 +2184,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2434,7 +2444,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++) {
|
||||
@@ -3657,7 +3668,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user