Merge "Add AccessibilityDisplayProxy and register/unregister methods"
This commit is contained in:
@@ -15924,10 +15924,17 @@ package android.view {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public final class AccessibilityManager {
|
||||
method public int getAccessibilityWindowId(@Nullable android.os.IBinder);
|
||||
method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void performAccessibilityShortcut();
|
||||
method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public boolean registerDisplayProxy(@NonNull android.view.accessibility.AccessibilityDisplayProxy);
|
||||
method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void registerSystemAction(@NonNull android.app.RemoteAction, int);
|
||||
method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public boolean unregisterDisplayProxy(@NonNull android.view.accessibility.AccessibilityDisplayProxy);
|
||||
method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view.accessibility;
|
||||
|
||||
import android.accessibilityservice.AccessibilityGestureEvent;
|
||||
import android.accessibilityservice.AccessibilityService;
|
||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||
import android.accessibilityservice.IAccessibilityServiceClient;
|
||||
import android.accessibilityservice.MagnificationConfig;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.content.Context;
|
||||
import android.graphics.Region;
|
||||
import android.os.IBinder;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
||||
import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
|
||||
import com.android.internal.inputmethod.RemoteAccessibilityInputConnection;
|
||||
|
||||
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.
|
||||
*
|
||||
* <p>
|
||||
* To register and unregister a proxy, use
|
||||
* {@link AccessibilityManager#registerDisplayProxy(AccessibilityDisplayProxy)}
|
||||
* 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
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public abstract class AccessibilityDisplayProxy {
|
||||
private static final String LOG_TAG = "AccessibilityDisplayProxy";
|
||||
private static final int INVALID_CONNECTION_ID = -1;
|
||||
|
||||
private List<AccessibilityServiceInfo> mInstalledAndEnabledServices;
|
||||
private Executor mExecutor;
|
||||
private int mConnectionId = INVALID_CONNECTION_ID;
|
||||
private int mDisplayId;
|
||||
IAccessibilityServiceClient mServiceClient;
|
||||
|
||||
/**
|
||||
* Constructs an AccessibilityDisplayProxy instance.
|
||||
* @param displayId the id of the display to proxy.
|
||||
* @param executor the executor used to execute proxy callbacks.
|
||||
* @param installedAndEnabledServices the list of infos representing the installed and
|
||||
* enabled a11y services.
|
||||
*/
|
||||
public AccessibilityDisplayProxy(int displayId, @NonNull Executor executor,
|
||||
@NonNull List<AccessibilityServiceInfo> installedAndEnabledServices) {
|
||||
mDisplayId = displayId;
|
||||
mExecutor = executor;
|
||||
// Typically, the context is the Service context of an accessibility service.
|
||||
// Context is used for ResolveInfo check, which a proxy won't have, IME input
|
||||
// (FLAG_INPUT_METHOD_EDITOR), which the proxy doesn't need, and tracing
|
||||
// A11yInteractionClient methods.
|
||||
// TODO(254097475): Enable tracing, potentially without exposing Context.
|
||||
mServiceClient = new IAccessibilityServiceClientImpl(null, mExecutor);
|
||||
mInstalledAndEnabledServices = installedAndEnabledServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the display being proxy-ed.
|
||||
*/
|
||||
public int getDisplayId() {
|
||||
return mDisplayId;
|
||||
}
|
||||
|
||||
/**
|
||||
* An IAccessibilityServiceClient that handles interrupts and accessibility events.
|
||||
*/
|
||||
private class IAccessibilityServiceClientImpl extends
|
||||
AccessibilityService.IAccessibilityServiceClientWrapper {
|
||||
|
||||
IAccessibilityServiceClientImpl(Context context, Executor executor) {
|
||||
super(context, executor, new AccessibilityService.Callbacks() {
|
||||
@Override
|
||||
public void onAccessibilityEvent(AccessibilityEvent event) {
|
||||
// TODO: call AccessiiblityProxy.onAccessibilityEvent
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInterrupt() {
|
||||
// TODO: call AccessiiblityProxy.onInterrupt
|
||||
}
|
||||
@Override
|
||||
public void onServiceConnected() {
|
||||
// TODO: send service infos and call AccessiiblityProxy.onProxyConnected
|
||||
}
|
||||
@Override
|
||||
public void init(int connectionId, IBinder windowToken) {
|
||||
mConnectionId = connectionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onGesture(AccessibilityGestureEvent gestureInfo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyEvent(KeyEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMagnificationChanged(int displayId, @NonNull Region region,
|
||||
MagnificationConfig config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMotionEvent(MotionEvent event) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTouchStateChanged(int displayId, int state) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSoftKeyboardShowModeChanged(int showMode) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFingerprintCapturingGesturesChanged(boolean active) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFingerprintGesture(int gesture) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccessibilityButtonClicked(int displayId) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccessibilityButtonAvailabilityChanged(boolean available) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSystemActionsChanged() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createImeSession(IAccessibilityInputMethodSessionCallback callback) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startInput(@Nullable RemoteAccessibilityInputConnection inputConnection,
|
||||
@NonNull EditorInfo editorInfo, boolean restarting) {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1921,6 +1921,67 @@ public final class AccessibilityManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an {@link AccessibilityDisplayProxy}, so this proxy can access UI content specific
|
||||
* to its display.
|
||||
*
|
||||
* @param proxy the {@link AccessibilityDisplayProxy} to register.
|
||||
* @return {@code true} if the proxy is successfully registered.
|
||||
*
|
||||
* @throws IllegalArgumentException if the proxy's display is not currently tracked by a11y, is
|
||||
* {@link android.view.Display#DEFAULT_DISPLAY}, is or lower than
|
||||
* {@link android.view.Display#INVALID_DISPLAY}, or is already being proxy-ed.
|
||||
*
|
||||
* @throws SecurityException if the app does not hold the
|
||||
* {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
|
||||
public boolean registerDisplayProxy(@NonNull AccessibilityDisplayProxy proxy) {
|
||||
final IAccessibilityManager service;
|
||||
synchronized (mLock) {
|
||||
service = getServiceLocked();
|
||||
if (service == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return service.registerProxyForDisplay(proxy.mServiceClient, proxy.getDisplayId());
|
||||
} catch (RemoteException re) {
|
||||
throw re.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters an {@link AccessibilityDisplayProxy}.
|
||||
*
|
||||
* @return {@code true} if the proxy is successfully unregistered.
|
||||
*
|
||||
* @throws SecurityException if the app does not hold the
|
||||
* {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
|
||||
public boolean unregisterDisplayProxy(@NonNull AccessibilityDisplayProxy proxy) {
|
||||
final IAccessibilityManager service;
|
||||
synchronized (mLock) {
|
||||
service = getServiceLocked();
|
||||
if (service == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
return service.unregisterProxyForDisplay(proxy.getDisplayId());
|
||||
} catch (RemoteException re) {
|
||||
throw re.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
private IAccessibilityManager getServiceLocked() {
|
||||
if (mService == null) {
|
||||
tryConnectToServiceLocked(null);
|
||||
|
||||
@@ -109,9 +109,9 @@ interface IAccessibilityManager {
|
||||
|
||||
oneway void setAccessibilityWindowAttributes(int displayId, int windowId, int userId, in AccessibilityWindowAttributes attributes);
|
||||
|
||||
// Requires Manifest.permission.MANAGE_ACCESSIBILITY
|
||||
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
|
||||
boolean registerProxyForDisplay(IAccessibilityServiceClient proxy, int displayId);
|
||||
|
||||
// Requires Manifest.permission.MANAGE_ACCESSIBILITY
|
||||
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
|
||||
boolean unregisterProxyForDisplay(int displayId);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||
import android.accessibilityservice.IAccessibilityServiceClient;
|
||||
import android.app.Instrumentation;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.RemoteAction;
|
||||
@@ -34,6 +35,7 @@ import android.content.Intent;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
@@ -51,6 +53,7 @@ import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Tests for the AccessibilityManager by mocking the backing service.
|
||||
@@ -70,6 +73,7 @@ public class AccessibilityManagerTest {
|
||||
LABEL,
|
||||
DESCRIPTION,
|
||||
TEST_PENDING_INTENT);
|
||||
private static final int DISPLAY_ID = 22;
|
||||
|
||||
@Mock private IAccessibilityManager mMockService;
|
||||
private MessageCapturingHandler mHandler;
|
||||
@@ -224,4 +228,45 @@ public class AccessibilityManagerTest {
|
||||
assertEquals(mFocusColorDefaultValue,
|
||||
manager.getAccessibilityFocusColor());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterAccessibilityProxy() throws Exception {
|
||||
// Accessibility does not need to be enabled for a proxy to be registered.
|
||||
AccessibilityManager manager =
|
||||
new AccessibilityManager(mInstrumentation.getContext(), mHandler, mMockService,
|
||||
UserHandle.USER_CURRENT, true);
|
||||
|
||||
|
||||
ArrayList<AccessibilityServiceInfo> infos = new ArrayList<>();
|
||||
infos.add(new AccessibilityServiceInfo());
|
||||
AccessibilityDisplayProxy proxy = new MyAccessibilityProxy(DISPLAY_ID, infos);
|
||||
manager.registerDisplayProxy(proxy);
|
||||
// Cannot access proxy.mServiceClient directly due to visibility.
|
||||
verify(mMockService).registerProxyForDisplay(any(IAccessibilityServiceClient.class),
|
||||
any(Integer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnregisterAccessibilityProxy() throws Exception {
|
||||
// Accessibility does not need to be enabled for a proxy to be registered.
|
||||
final AccessibilityManager manager =
|
||||
new AccessibilityManager(mInstrumentation.getContext(), mHandler, mMockService,
|
||||
UserHandle.USER_CURRENT, true);
|
||||
|
||||
final ArrayList<AccessibilityServiceInfo> infos = new ArrayList<>();
|
||||
infos.add(new AccessibilityServiceInfo());
|
||||
|
||||
final AccessibilityDisplayProxy proxy = new MyAccessibilityProxy(DISPLAY_ID, infos);
|
||||
manager.registerDisplayProxy(proxy);
|
||||
manager.unregisterDisplayProxy(proxy);
|
||||
verify(mMockService).unregisterProxyForDisplay(proxy.getDisplayId());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3651,6 +3651,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|
||||
throw new IllegalArgumentException("The display " + displayId + " does not exist or is"
|
||||
+ " not tracked by accessibility.");
|
||||
}
|
||||
if (mProxyManager.isProxyed(displayId)) {
|
||||
throw new IllegalArgumentException("The display " + displayId + " is already being"
|
||||
+ "proxy-ed");
|
||||
}
|
||||
|
||||
mProxyManager.registerProxy(client, displayId);
|
||||
return true;
|
||||
|
||||
@@ -32,6 +32,7 @@ import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteCallback;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.accessibility.AccessibilityDisplayProxy;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -44,7 +45,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents the system connection to an {@link android.view.accessibility.AccessibilityProxy}.
|
||||
* Represents the system connection to an {@link AccessibilityDisplayProxy}.
|
||||
*
|
||||
* <p>Most methods are no-ops since this connection does not need to capture input or listen to
|
||||
* hardware-related changes.
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
package com.android.server.accessibility;
|
||||
import android.accessibilityservice.IAccessibilityServiceClient;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Manages proxy connections.
|
||||
*
|
||||
@@ -26,6 +28,7 @@ import android.accessibilityservice.IAccessibilityServiceClient;
|
||||
*/
|
||||
public class ProxyManager {
|
||||
private final Object mLock;
|
||||
private final HashSet<Integer> mDisplayIds = new HashSet<>();
|
||||
|
||||
ProxyManager(Object lock) {
|
||||
mLock = lock;
|
||||
@@ -35,12 +38,21 @@ public class ProxyManager {
|
||||
* TODO: Create the proxy service connection.
|
||||
*/
|
||||
public void registerProxy(IAccessibilityServiceClient client, int displayId) {
|
||||
mDisplayIds.add(displayId);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Unregister the proxy service connection based on display id.
|
||||
*/
|
||||
public boolean unregisterProxy(int displayId) {
|
||||
mDisplayIds.remove(displayId);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a display id is being proxy-ed.
|
||||
*/
|
||||
public boolean isProxyed(int displayId) {
|
||||
return mDisplayIds.contains(displayId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user