summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Sally <sallyyuen@google.com> 2023-04-10 19:59:24 +0000
committer Sally <sallyyuen@google.com> 2023-04-20 18:40:32 +0000
commitf599bc42bed8d7f80b6db6f6712ad3319bc1214d (patch)
tree80b379a7ec5967e58f7150d4ab54250df38e4886
parentc468a9f9c78e38938e7910012e41469b3f5f681f (diff)
Add unit tests for ProxyManager
- Eliminate redundant parameters in ProxyManager#registerProxy - Also restrict visibility for some methods by making them private. Convert other methods that were package-private to public to be consistent internally and with other classes like A11yWindowManager Bug: 262244375 Test: atest ProxyManagerTest, AccessibilityManagerServiceTest, ProxyAccessibilityServiceConnectionTest Change-Id: I793d23a11a71d931d270fa4482f44af53e21db47
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java11
-rw-r--r--services/accessibility/java/com/android/server/accessibility/ProxyManager.java46
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java527
5 files changed, 581 insertions, 38 deletions
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index a3b4a0f51c75..d36c959a1b25 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1012,8 +1012,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
+ Binder.getCallingPid() + " for device id " + deviceId
+ " with package names " + Arrays.toString(client.mPackageNames));
}
- return IntPair.of(mProxyManager.getStateLocked(deviceId,
- mUiAutomationManager.isUiAutomationRunningLocked()),
+ return IntPair.of(mProxyManager.getStateLocked(deviceId),
client.mLastSentRelevantEventTypes);
}
mGlobalClients.register(callback, client);
@@ -1028,8 +1027,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
+ Binder.getCallingPid() + " for device id " + deviceId
+ " with package names " + Arrays.toString(client.mPackageNames));
}
- return IntPair.of(mProxyManager.getStateLocked(deviceId,
- mUiAutomationManager.isUiAutomationRunningLocked()),
+ return IntPair.of(mProxyManager.getStateLocked(deviceId),
client.mLastSentRelevantEventTypes);
}
userState.mUserClients.register(callback, client);
@@ -4024,9 +4022,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
final long identity = Binder.clearCallingIdentity();
try {
- mProxyManager.registerProxy(client, displayId, mContext,
- sIdCounter++, mMainHandler, mSecurityPolicy, this, getTraceManager(),
- mWindowManagerService);
+ mProxyManager.registerProxy(client, displayId, sIdCounter++, mSecurityPolicy,
+ this, getTraceManager(), mWindowManagerService);
synchronized (mLock) {
notifyClearAccessibilityCacheLocked();
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index d417197e1419..6dc8fb347904 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -61,7 +61,6 @@ import java.util.function.Consumer;
* proxy connection will belong to a separate user state.
*
* TODO(241117292): Remove or cut down during simultaneous user refactoring.
- * TODO(262244375): Add unit tests.
*/
public class ProxyManager {
private static final boolean DEBUG = false;
@@ -128,7 +127,7 @@ public class ProxyManager {
RemoteCallbackList<IAccessibilityManagerClient> getCurrentUserClientsLocked();
}
- ProxyManager(Object lock, AccessibilityWindowManager awm,
+ public ProxyManager(Object lock, AccessibilityWindowManager awm,
Context context, Handler mainHandler, UiAutomationManager uiAutomationManager,
SystemSupport systemSupport) {
mLock = lock;
@@ -144,9 +143,7 @@ public class ProxyManager {
* Creates the service connection.
*/
public void registerProxy(IAccessibilityServiceClient client, int displayId,
- Context context,
- int id, Handler mainHandler,
- AccessibilitySecurityPolicy securityPolicy,
+ int id, AccessibilitySecurityPolicy securityPolicy,
AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
AccessibilityTrace trace,
WindowManagerInternal windowManagerInternal) throws RemoteException {
@@ -169,8 +166,8 @@ public class ProxyManager {
info.setComponentName(new ComponentName(PROXY_COMPONENT_PACKAGE_NAME,
componentClassDisplayName));
ProxyAccessibilityServiceConnection connection =
- new ProxyAccessibilityServiceConnection(context, info.getComponentName(), info,
- id, mainHandler, mLock, securityPolicy, systemSupport, trace,
+ new ProxyAccessibilityServiceConnection(mContext, info.getComponentName(), info,
+ id, mMainHandler, mLock, securityPolicy, systemSupport, trace,
windowManagerInternal,
mA11yWindowManager, displayId, deviceId);
@@ -263,7 +260,7 @@ public class ProxyManager {
* When the connection is removed from tracking in ProxyManager, propagate changes to other a11y
* system components like the input filter and IAccessibilityManagerClients.
*/
- public void updateStateForRemovedDisplay(int displayId, int deviceId) {
+ private void updateStateForRemovedDisplay(int displayId, int deviceId) {
mA11yWindowManager.stopTrackingDisplayProxy(displayId);
// A11yInputFilter isn't thread-safe, so post on the system thread.
mMainHandler.post(
@@ -360,8 +357,9 @@ public class ProxyManager {
/**
* If there is at least one proxy, accessibility is enabled.
*/
- public int getStateLocked(int deviceId, boolean automationRunning) {
+ public int getStateLocked(int deviceId) {
int clientState = 0;
+ final boolean automationRunning = mUiAutomationManager.isUiAutomationRunningLocked();
if (automationRunning) {
clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
}
@@ -387,7 +385,7 @@ public class ProxyManager {
/**
* If there is at least one proxy, accessibility is enabled.
*/
- public int getStateForDisplayIdLocked(ProxyAccessibilityServiceConnection proxy) {
+ private int getStateForDisplayIdLocked(ProxyAccessibilityServiceConnection proxy) {
int clientState = 0;
if (proxy != null) {
clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
@@ -409,14 +407,14 @@ public class ProxyManager {
/**
* Gets the last state for a device.
*/
- public int getLastSentStateLocked(int deviceId) {
+ private int getLastSentStateLocked(int deviceId) {
return mLastStates.get(deviceId, 0);
}
/**
* Sets the last state for a device.
*/
- public void setLastStateLocked(int deviceId, int proxyState) {
+ private void setLastStateLocked(int deviceId, int proxyState) {
mLastStates.put(deviceId, proxyState);
}
@@ -429,7 +427,7 @@ public class ProxyManager {
* Virtual Device, the app clients will get the aggregated event types for all proxy-ed displays
* belonging to a VirtualDevice.
*/
- public void updateRelevantEventTypesLocked(int deviceId) {
+ private void updateRelevantEventTypesLocked(int deviceId) {
if (!isProxyedDeviceId(deviceId)) {
return;
}
@@ -452,7 +450,7 @@ public class ProxyManager {
/**
* Returns the relevant event types for a Client.
*/
- int computeRelevantEventTypesLocked(AccessibilityManagerService.Client client) {
+ public int computeRelevantEventTypesLocked(AccessibilityManagerService.Client client) {
int relevantEventTypes = 0;
for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
final ProxyAccessibilityServiceConnection proxy =
@@ -578,9 +576,8 @@ public class ProxyManager {
/**
* Updates the states of the app AccessibilityManagers.
*/
- public void scheduleUpdateProxyClientsIfNeededLocked(int deviceId) {
- final int proxyState = getStateLocked(deviceId,
- mUiAutomationManager.isUiAutomationRunningLocked());
+ private void scheduleUpdateProxyClientsIfNeededLocked(int deviceId) {
+ final int proxyState = getStateLocked(deviceId);
if (DEBUG) {
Slog.v(LOG_TAG, "State for device id " + deviceId + " is " + proxyState);
Slog.v(LOG_TAG, "Last state for device id " + deviceId + " is "
@@ -606,7 +603,7 @@ public class ProxyManager {
*
* @see AccessibilityManager.AccessibilityServicesStateChangeListener
*/
- public void scheduleNotifyProxyClientsOfServicesStateChangeLocked(int deviceId) {
+ private void scheduleNotifyProxyClientsOfServicesStateChangeLocked(int deviceId) {
if (DEBUG) {
Slog.v(LOG_TAG, "Notify services state change at device id " + deviceId);
}
@@ -625,7 +622,7 @@ public class ProxyManager {
/**
* Updates the focus appearance of AccessibilityManagerClients.
*/
- public void updateFocusAppearanceLocked(int deviceId) {
+ private void updateFocusAppearanceLocked(int deviceId) {
if (DEBUG) {
Slog.v(LOG_TAG, "Update proxy focus appearance at device id " + deviceId);
}
@@ -764,7 +761,7 @@ public class ProxyManager {
/**
* Sets a Client device id if the app uid belongs to the virtual device.
*/
- public void updateDeviceIdsIfNeededLocked(int deviceId) {
+ private void updateDeviceIdsIfNeededLocked(int deviceId) {
final RemoteCallbackList<IAccessibilityManagerClient> userClients =
mSystemSupport.getCurrentUserClientsLocked();
final RemoteCallbackList<IAccessibilityManagerClient> globalClients =
@@ -777,7 +774,7 @@ public class ProxyManager {
/**
* Updates the device ids of IAccessibilityManagerClients if needed.
*/
- public void updateDeviceIdsIfNeededLocked(int deviceId,
+ private void updateDeviceIdsIfNeededLocked(int deviceId,
@NonNull RemoteCallbackList<IAccessibilityManagerClient> clients) {
final VirtualDeviceManagerInternal localVdm = getLocalVdm();
if (localVdm == null) {
@@ -809,14 +806,17 @@ public class ProxyManager {
}
}
- void setAccessibilityInputFilter(AccessibilityInputFilter filter) {
+ /**
+ * Sets the input filter for enabling and disabling features for proxy displays.
+ */
+ public void setAccessibilityInputFilter(AccessibilityInputFilter filter) {
if (DEBUG) {
Slog.v(LOG_TAG, "Set proxy input filter to " + filter);
}
mA11yInputFilter = filter;
}
- VirtualDeviceManagerInternal getLocalVdm() {
+ private VirtualDeviceManagerInternal getLocalVdm() {
if (mLocalVdm == null) {
mLocalVdm = LocalServices.getService(VirtualDeviceManagerInternal.class);
}
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 96eca7171af0..8cfc150afe56 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -281,8 +281,8 @@ public class AccessibilityManagerServiceTest {
@Test
public void testRegisterProxy() throws Exception {
mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY);
- verify(mProxyManager).registerProxy(eq(mMockServiceClient), eq(TEST_DISPLAY),
- eq(mTestableContext), anyInt(), any(), eq(mMockSecurityPolicy),
+ verify(mProxyManager).registerProxy(eq(mMockServiceClient), eq(TEST_DISPLAY), anyInt(),
+ eq(mMockSecurityPolicy),
eq(mA11yms), eq(mA11yms.getTraceManager()),
eq(mMockWindowManagerService));
}
@@ -295,7 +295,7 @@ public class AccessibilityManagerServiceTest {
assertThrows(SecurityException.class,
() -> mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY));
- verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
+ verify(mProxyManager, never()).registerProxy(any(), anyInt(), anyInt(), any(),
any(), any(), any());
}
@@ -307,7 +307,7 @@ public class AccessibilityManagerServiceTest {
assertThrows(SecurityException.class,
() -> mA11yms.registerProxyForDisplay(mMockServiceClient, TEST_DISPLAY));
- verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
+ verify(mProxyManager, never()).registerProxy(any(), anyInt(), anyInt(), any(),
any(), any(), any());
}
@@ -316,7 +316,7 @@ public class AccessibilityManagerServiceTest {
public void testRegisterProxyForDefaultDisplay() throws Exception {
assertThrows(IllegalArgumentException.class,
() -> mA11yms.registerProxyForDisplay(mMockServiceClient, Display.DEFAULT_DISPLAY));
- verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
+ verify(mProxyManager, never()).registerProxy(any(), anyInt(), anyInt(), any(),
any(), any(), any());
}
@@ -325,7 +325,7 @@ public class AccessibilityManagerServiceTest {
public void testRegisterProxyForInvalidDisplay() throws Exception {
assertThrows(IllegalArgumentException.class,
() -> mA11yms.registerProxyForDisplay(mMockServiceClient, Display.INVALID_DISPLAY));
- verify(mProxyManager, never()).registerProxy(any(), anyInt(), any(), anyInt(), any(), any(),
+ verify(mProxyManager, never()).registerProxy(any(), anyInt(), anyInt(), any(),
any(), any(), any());
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
index dd44a7968639..6ac3658bc3b4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java
@@ -29,12 +29,14 @@ import android.accessibilityservice.AccessibilityTrace;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Color;
import android.os.Handler;
import android.view.accessibility.AccessibilityEvent;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -79,12 +81,15 @@ public class ProxyAccessibilityServiceConnectionTest {
WindowManagerInternal mMockWindowManagerInternal;
ProxyAccessibilityServiceConnection mProxyConnection;
AccessibilityServiceInfo mAccessibilityServiceInfo;
+ private int mFocusStrokeWidthDefaultValue;
+ private int mFocusColorDefaultValue;
@Before
public void setup() {
final Resources resources = getInstrumentation().getContext().getResources();
MockitoAnnotations.initMocks(this);
when(mMockContext.getResources()).thenReturn(resources);
+ when(mMockSecurityPolicy.checkAccessibilityAccess(any())).thenReturn(true);
mAccessibilityServiceInfo = new AccessibilityServiceInfo();
mProxyConnection = new ProxyAccessibilityServiceConnection(mMockContext, COMPONENT_NAME,
@@ -92,10 +97,13 @@ public class ProxyAccessibilityServiceConnectionTest {
getInstrumentation().getContext().getMainLooper()),
mMockLock, mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace,
mMockWindowManagerInternal, mMockA11yWindowManager, DISPLAY_ID, DEVICE_ID);
+
+ mFocusStrokeWidthDefaultValue = mProxyConnection.getFocusStrokeWidthLocked();
+ mFocusColorDefaultValue = mProxyConnection.getFocusColorLocked();
}
@Test
- public void testSetInstalledAndEnabledServices_clientChanged() {
+ public void testSetInstalledAndEnabledServices_updateInfos_notifiesSystemOfProxyChange() {
final List<AccessibilityServiceInfo> infos = new ArrayList<>();
final AccessibilityServiceInfo info1 = new AccessibilityServiceInfo();
infos.add(info1);
@@ -106,6 +114,17 @@ public class ProxyAccessibilityServiceConnectionTest {
}
@Test
+ public void testSetFocusAppearance_updateAppearance_notifiesSystemOfProxyChange() {
+ final int updatedWidth = mFocusStrokeWidthDefaultValue + 10;
+ final int updatedColor = mFocusColorDefaultValue
+ == Color.BLUE ? Color.RED : Color.BLUE;
+
+ mProxyConnection.setFocusAppearance(updatedWidth, updatedColor);
+
+ verify(mMockSystemSupport).onProxyChanged(DEVICE_ID);
+ }
+
+ @Test
public void testSetInstalledAndEnabledServices_returnList() {
final List<AccessibilityServiceInfo> infos = new ArrayList<>();
final AccessibilityServiceInfo info1 = new AccessibilityServiceInfo();
@@ -196,7 +215,7 @@ public class ProxyAccessibilityServiceConnectionTest {
}
@Test
- public void testSetServiceInfo_setIllegalOperationExceptionThrown_() {
+ public void testSetServiceInfo_setIllegalOperationExceptionThrown() {
UnsupportedOperationException thrown =
assertThrows(
UnsupportedOperationException.class,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
new file mode 100644
index 000000000000..6c7b995d07a2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
@@ -0,0 +1,527 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.accessibility;
+
+import static com.android.server.accessibility.ProxyAccessibilityServiceConnectionTest.INTERACTIVE_UI_TIMEOUT_100MS;
+import static com.android.server.accessibility.ProxyAccessibilityServiceConnectionTest.INTERACTIVE_UI_TIMEOUT_200MS;
+import static com.android.server.accessibility.ProxyAccessibilityServiceConnectionTest.NON_INTERACTIVE_UI_TIMEOUT_100MS;
+import static com.android.server.accessibility.ProxyAccessibilityServiceConnectionTest.NON_INTERACTIVE_UI_TIMEOUT_200MS;
+import static com.android.server.accessibility.ProxyManager.PROXY_COMPONENT_CLASS_NAME;
+import static com.android.server.accessibility.ProxyManager.PROXY_COMPONENT_PACKAGE_NAME;
+
+import static org.junit.Assert.fail;
+
+import android.accessibilityservice.AccessibilityGestureEvent;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityTrace;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.accessibilityservice.MagnificationConfig;
+import android.companion.virtual.IVirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManagerClient;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.R;
+import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
+import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.util.IntPair;
+import com.android.server.LocalServices;
+import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.InstrumentationRegistry;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for ProxyManager.
+ */
+public class ProxyManagerTest {
+ private static final int DISPLAY_ID = 1000;
+ private static final int DISPLAY_2_ID = 1001;
+ private static final int DEVICE_ID = 10;
+ private static final int STREAMED_CALLING_UID = 9876;
+
+ @Mock private Context mMockContext;
+ @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
+ @Mock private AccessibilityWindowManager mMockA11yWindowManager;
+ @Mock private ProxyManager.SystemSupport mMockProxySystemSupport;
+ @Mock private AbstractAccessibilityServiceConnection.SystemSupport mMockConnectionSystemSupport;
+ @Mock private AccessibilityTrace mMockA11yTrace;
+ @Mock private WindowManagerInternal mMockWindowManagerInternal;
+ @Mock private IAccessibilityServiceClient mMockAccessibilityServiceClient;
+ @Mock private IBinder mMockServiceAsBinder;
+ @Mock private VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
+ @Mock private IVirtualDeviceManager mMockIVirtualDeviceManager;
+
+ private int mFocusStrokeWidthDefaultValue;
+ private int mFocusColorDefaultValue;
+
+ private MessageCapturingHandler mMessageCapturingHandler = new MessageCapturingHandler(null);
+ private ProxyManager mProxyManager;
+
+ @Before
+ public void setup() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+ final Resources resources = InstrumentationRegistry.getContext().getResources();
+
+ mFocusStrokeWidthDefaultValue =
+ resources.getDimensionPixelSize(R.dimen.accessibility_focus_highlight_stroke_width);
+ mFocusColorDefaultValue = resources.getColor(R.color.accessibility_focus_highlight_color);
+ when(mMockContext.getResources()).thenReturn(resources);
+
+ when(mMockVirtualDeviceManagerInternal.getDeviceIdsForUid(anyInt())).thenReturn(
+ new ArraySet(Set.of(DEVICE_ID)));
+ LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+ LocalServices.addService(VirtualDeviceManagerInternal.class,
+ mMockVirtualDeviceManagerInternal);
+
+ when(mMockIVirtualDeviceManager.getDeviceIdForDisplayId(anyInt())).thenReturn(DEVICE_ID);
+ final VirtualDeviceManager virtualDeviceManager =
+ new VirtualDeviceManager(mMockIVirtualDeviceManager, mMockContext);
+ when(mMockContext.getSystemServiceName(VirtualDeviceManager.class)).thenReturn(
+ Context.VIRTUAL_DEVICE_SERVICE);
+ when(mMockContext.getSystemService(VirtualDeviceManager.class))
+ .thenReturn(virtualDeviceManager);
+
+ when(mMockA11yTrace.isA11yTracingEnabled()).thenReturn(false);
+
+ final RemoteCallbackList<IAccessibilityManagerClient> userClients =
+ new RemoteCallbackList<>();
+ final RemoteCallbackList<IAccessibilityManagerClient> globalClients =
+ new RemoteCallbackList<>();
+ when(mMockProxySystemSupport.getCurrentUserClientsLocked()).thenReturn(userClients);
+ when(mMockProxySystemSupport.getGlobalClientsLocked()).thenReturn(globalClients);
+
+ when(mMockAccessibilityServiceClient.asBinder()).thenReturn(mMockServiceAsBinder);
+
+ mProxyManager = new ProxyManager(new Object(), mMockA11yWindowManager, mMockContext,
+ mMessageCapturingHandler, new UiAutomationManager(new Object()),
+ mMockProxySystemSupport);
+ }
+
+ @After
+ public void tearDown() {
+ mMessageCapturingHandler.removeAllMessages();
+ }
+
+ /**
+ * Tests that the proxy’s backing AccessibilityServiceClient is initialized when registering a
+ * proxy.
+ */
+ @Test
+ public void registerProxy_always_connectsServiceClient() throws RemoteException {
+ registerProxy(DISPLAY_ID);
+ verify(mMockAccessibilityServiceClient).init(any(), anyInt(), any());
+ }
+
+ /** Tests that unregistering a proxy removes its display from tracking. */
+ @Test
+ public void unregisterProxy_always_stopsTrackingDisplay() {
+ registerProxy(DISPLAY_ID);
+
+ mProxyManager.unregisterProxy(DISPLAY_ID);
+
+ verify(mMockA11yWindowManager).stopTrackingDisplayProxy(DISPLAY_ID);
+ assertThat(mProxyManager.isProxyedDisplay(DISPLAY_ID)).isFalse();
+ }
+ /**
+ * Tests that unregistering a proxied display of a virtual device, where that virtual device
+ * owned only that one proxied display, removes the device from tracking.
+ */
+ @Test
+ public void unregisterProxy_deviceAssociatedWithSingleDisplay_stopsTrackingDevice() {
+ registerProxy(DISPLAY_ID);
+
+ mProxyManager.unregisterProxy(DISPLAY_ID);
+
+ assertThat(mProxyManager.isProxyedDeviceId(DEVICE_ID)).isFalse();
+ verify(mMockProxySystemSupport).removeDeviceIdLocked(DEVICE_ID);
+ }
+
+ /**
+ * Tests that unregistering a proxied display of a virtual device, where that virtual device
+ * owns more than one proxied display, does not remove the device from tracking.
+ */
+ @Test
+ public void unregisterProxy_deviceAssociatedWithMultipleDisplays_tracksRemainingProxy() {
+ registerProxy(DISPLAY_ID);
+ registerProxy(DISPLAY_2_ID);
+
+ mProxyManager.unregisterProxy(DISPLAY_ID);
+
+ assertThat(mProxyManager.isProxyedDeviceId(DEVICE_ID)).isTrue();
+ verify(mMockProxySystemSupport, never()).removeDeviceIdLocked(DEVICE_ID);
+ }
+
+ /**
+ * Tests that changing a proxy, e.g. registering/unregistering a proxy or updating its service
+ * info, notifies the apps being streamed and AccessibilityManagerService.
+ */
+ @Test
+ public void testOnProxyChanged_always_propagatesChange() {
+ registerProxy(DISPLAY_ID);
+ mMessageCapturingHandler.sendAllMessages();
+
+ mProxyManager.onProxyChanged(DEVICE_ID);
+
+ // Messages to notify IAccessibilityManagerClients should be posted.
+ assertThat(mMessageCapturingHandler.hasMessages()).isTrue();
+
+ verify(mMockProxySystemSupport).updateWindowsForAccessibilityCallbackLocked();
+ verify(mMockProxySystemSupport).notifyClearAccessibilityCacheLocked();
+ }
+
+ /**
+ * Tests that getting the first device id for an app uid, such as when an app queries for
+ * device-specific state, returns the right device id.
+ */
+ @Test
+ public void testGetFirstDeviceForUid_streamedAppQueriesState_getsHostDeviceId() {
+ registerProxy(DISPLAY_ID);
+ assertThat(mProxyManager.getFirstDeviceIdForUidLocked(STREAMED_CALLING_UID))
+ .isEqualTo(DEVICE_ID);
+ }
+
+ /**
+ * Tests that the app client state retrieved for a device reflects that touch exploration is
+ * enabled since a proxy info has requested touch exploration.
+ */
+ @Test
+ public void testGetClientState_proxyWantsTouchExploration_returnsTouchExplorationEnabled() {
+ registerProxy(DISPLAY_ID);
+
+ final AccessibilityServiceInfo secondDisplayInfo = new AccessibilityServiceInfo();
+ secondDisplayInfo.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
+ AccessibilityServiceClientImpl client = new AccessibilityServiceClientImpl(
+ secondDisplayInfo);
+ registerProxy(DISPLAY_2_ID, client);
+
+ final int deviceClientState = mProxyManager.getStateLocked(DEVICE_ID);
+ assertThat((deviceClientState
+ & AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED) != 0).isTrue();
+ }
+
+ /**
+ * Tests that the highest interactive and non-interactive timeout is returned if there are
+ * multiple proxied displays belonging to a device.
+ */
+ @Test
+ public void testGetRecommendedTimeout_multipleProxies_returnsHighestTimeout() {
+ final AccessibilityServiceInfo firstDisplayInfo = new AccessibilityServiceInfo();
+ firstDisplayInfo.setInteractiveUiTimeoutMillis(INTERACTIVE_UI_TIMEOUT_100MS);
+ firstDisplayInfo.setNonInteractiveUiTimeoutMillis(NON_INTERACTIVE_UI_TIMEOUT_200MS);
+
+ final AccessibilityServiceInfo secondDisplayInfo = new AccessibilityServiceInfo();
+ secondDisplayInfo.setInteractiveUiTimeoutMillis(INTERACTIVE_UI_TIMEOUT_200MS);
+ secondDisplayInfo.setNonInteractiveUiTimeoutMillis(NON_INTERACTIVE_UI_TIMEOUT_100MS);
+
+ registerProxy(DISPLAY_ID, new AccessibilityServiceClientImpl(firstDisplayInfo));
+ registerProxy(DISPLAY_2_ID, new AccessibilityServiceClientImpl(secondDisplayInfo));
+
+ final long timeout = mProxyManager.getRecommendedTimeoutMillisLocked(DEVICE_ID);
+ final int interactiveTimeout = IntPair.first(timeout);
+ final int nonInteractiveTimeout = IntPair.second(timeout);
+
+ assertThat(interactiveTimeout).isEqualTo(INTERACTIVE_UI_TIMEOUT_200MS);
+ assertThat(nonInteractiveTimeout).isEqualTo(NON_INTERACTIVE_UI_TIMEOUT_200MS);
+ }
+ /**
+ * Tests that getting the installed and enabled services returns the info of the registered
+ * proxy. (The component name reflects the display id.)
+ */
+ @Test
+ public void testGetInstalledAndEnabledServices_defaultInfo_returnsInfoForDisplayId() {
+ final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ registerProxy(DISPLAY_ID, new AccessibilityServiceClientImpl(info));
+ final List<AccessibilityServiceInfo> installedAndEnabledServices =
+ mProxyManager.getInstalledAndEnabledServiceInfosLocked(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK, DEVICE_ID);
+ assertThat(installedAndEnabledServices.size()).isEqualTo(1);
+ AccessibilityServiceInfo proxyInfo = installedAndEnabledServices.get(0);
+
+ assertThat(proxyInfo.getComponentName()).isEqualTo(new ComponentName(
+ PROXY_COMPONENT_PACKAGE_NAME, PROXY_COMPONENT_CLASS_NAME + DISPLAY_ID));
+ }
+
+ /**
+ * Tests that the app client state retrieved for a device reflects that accessibility is
+ * enabled.
+ */
+ @Test
+ public void testGetClientState_always_returnsAccessibilityEnabled() {
+ registerProxy(DISPLAY_ID);
+
+ final int deviceClientState = mProxyManager.getStateLocked(DEVICE_ID);
+ assertThat((deviceClientState
+ & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0).isTrue();
+ }
+
+ /**
+ * Tests that the manager can retrieve interactive windows if a proxy sets
+ * AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS.
+ */
+ @Test
+ public void testCanRetrieveInteractiveWindows_atLeastOneProxyWantsWindows_returnsTrue() {
+ registerProxy(DISPLAY_ID);
+
+ final AccessibilityServiceInfo secondDisplayInfo = new AccessibilityServiceInfo();
+ secondDisplayInfo.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ registerProxy(DISPLAY_2_ID, new AccessibilityServiceClientImpl(secondDisplayInfo));
+
+ assertThat(mProxyManager.canRetrieveInteractiveWindowsLocked()).isTrue();
+ }
+
+ /**
+ * Tests that getting service interfaces to interrupt when AccessibilityManager#interrupt
+ * returns the registered proxy interface.
+ */
+ @Test
+ public void testGetServiceInterfacesForInterrupt_defaultProxy_returnsProxyInterface() {
+ registerProxy(DISPLAY_ID);
+ final List<IAccessibilityServiceClient> interfacesToInterrupt = new ArrayList<>();
+ mProxyManager.addServiceInterfacesLocked(interfacesToInterrupt, DEVICE_ID);
+
+ assertThat(interfacesToInterrupt.size()).isEqualTo(1);
+ assertThat(interfacesToInterrupt.get(0).asBinder()).isEqualTo(mMockServiceAsBinder);
+ }
+
+ /** Tests that the default timeout (0) is returned when the proxy is registered. */
+ @Test
+ public void getRecommendedTimeout_defaultProxyInfo_getsDefaultTimeout() {
+ registerProxy(DISPLAY_ID);
+ final long timeout = mProxyManager.getRecommendedTimeoutMillisLocked(DEVICE_ID);
+ final int interactiveTimeout = IntPair.first(timeout);
+ final int nonInteractiveTimeout = IntPair.second(timeout);
+
+ assertThat(interactiveTimeout).isEqualTo(0);
+ assertThat(nonInteractiveTimeout).isEqualTo(0);
+ }
+
+ /** Tests that the manager returns the updated timeout when the proxy’s timeout is updated. */
+ @Test
+ public void getRecommendedTimeout_updateTimeout_getsUpdatedTimeout() {
+ registerProxy(DISPLAY_ID);
+
+ mProxyManager.updateTimeoutsIfNeeded(NON_INTERACTIVE_UI_TIMEOUT_100MS,
+ INTERACTIVE_UI_TIMEOUT_200MS);
+
+ final long updatedTimeout = mProxyManager.getRecommendedTimeoutMillisLocked(DEVICE_ID);
+ final int updatedInteractiveTimeout = IntPair.first(updatedTimeout);
+ final int updatedNonInteractiveTimeout = IntPair.second(updatedTimeout);
+
+ assertThat(updatedInteractiveTimeout).isEqualTo(INTERACTIVE_UI_TIMEOUT_200MS);
+ assertThat(updatedNonInteractiveTimeout).isEqualTo(NON_INTERACTIVE_UI_TIMEOUT_100MS);
+ }
+
+ /** Tests that the system’s default focus color is returned. */
+ @Test
+ public void testGetFocusColor_defaultProxy_getsDefaultSystemColor() {
+ registerProxy(DISPLAY_ID);
+ final int focusColor = mProxyManager.getFocusColorLocked(DEVICE_ID);
+ assertThat(focusColor).isEqualTo(mFocusColorDefaultValue);
+ }
+
+ /** Tests that the system’s default focus stroke width is returned. */
+ @Test
+ public void testGetFocusStrokeWidth_defaultProxy_getsDefaultSystemWidth() {
+ registerProxy(DISPLAY_ID);
+ final int focusStrokeWidth = mProxyManager.getFocusStrokeWidthLocked(DEVICE_ID);
+ assertThat(focusStrokeWidth).isEqualTo(mFocusStrokeWidthDefaultValue);
+ }
+
+ private void registerProxy(int displayId) {
+ try {
+ mProxyManager.registerProxy(mMockAccessibilityServiceClient, displayId, anyInt(),
+ mMockSecurityPolicy, mMockConnectionSystemSupport,
+ mMockA11yTrace, mMockWindowManagerInternal);
+ } catch (RemoteException e) {
+ fail("Failed to register proxy " + e);
+ }
+ }
+
+ private void registerProxy(int displayId, AccessibilityServiceClientImpl serviceClient) {
+ try {
+ mProxyManager.registerProxy(serviceClient, displayId, anyInt(),
+ mMockSecurityPolicy, mMockConnectionSystemSupport,
+ mMockA11yTrace, mMockWindowManagerInternal);
+ } catch (RemoteException e) {
+ fail("Failed to register proxy " + e);
+ }
+ }
+
+ /**
+ * IAccessibilityServiceClient implementation.
+ * A proxy connection does not populate non-default AccessibilityServiceInfo values until the
+ * proxy is connected in A11yDisplayProxy#onServiceConnected. For tests that check for
+ * non-default values, populate immediately in this testing class, since a real Service is not
+ * being used and connected.
+ */
+ static class AccessibilityServiceClientImpl extends IAccessibilityServiceClient.Stub {
+ List<AccessibilityServiceInfo> mInstalledAndEnabledServices;
+
+ AccessibilityServiceClientImpl(AccessibilityServiceInfo
+ installedAndEnabledService) {
+ mInstalledAndEnabledServices = List.of(installedAndEnabledService);
+ }
+
+ @Override
+ public void init(IAccessibilityServiceConnection connection, int connectionId,
+ IBinder windowToken) throws RemoteException {
+ connection.setInstalledAndEnabledServices(mInstalledAndEnabledServices);
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event, boolean serviceWantsEvent)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void onInterrupt() throws RemoteException {
+
+ }
+
+ @Override
+ public void onGesture(AccessibilityGestureEvent gestureEvent) throws RemoteException {
+
+ }
+
+ @Override
+ public void clearAccessibilityCache() throws RemoteException {
+
+ }
+
+ @Override
+ public void onKeyEvent(KeyEvent event, int sequence) throws RemoteException {
+
+ }
+
+ @Override
+ public void onMagnificationChanged(int displayId, Region region, MagnificationConfig config)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent event) throws RemoteException {
+
+ }
+
+ @Override
+ public void onTouchStateChanged(int displayId, int state) throws RemoteException {
+
+ }
+
+ @Override
+ public void onSoftKeyboardShowModeChanged(int showMode) throws RemoteException {
+
+ }
+
+ @Override
+ public void onPerformGestureResult(int sequence, boolean completedSuccessfully)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void onFingerprintCapturingGesturesChanged(boolean capturing)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void onFingerprintGesture(int gesture) throws RemoteException {
+
+ }
+
+ @Override
+ public void onAccessibilityButtonClicked(int displayId) throws RemoteException {
+
+ }
+
+ @Override
+ public void onAccessibilityButtonAvailabilityChanged(boolean available)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void onSystemActionsChanged() throws RemoteException {
+
+ }
+
+ @Override
+ public void createImeSession(IAccessibilityInputMethodSessionCallback callback)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void setImeSessionEnabled(IAccessibilityInputMethodSession session, boolean enabled)
+ throws RemoteException {
+
+ }
+
+ @Override
+ public void bindInput() throws RemoteException {
+
+ }
+
+ @Override
+ public void unbindInput() throws RemoteException {
+
+ }
+
+ @Override
+ public void startInput(IRemoteAccessibilityInputConnection connection,
+ EditorInfo editorInfo, boolean restarting) throws RemoteException {
+
+ }
+ }
+}