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
This commit is contained in:
Sally
2023-04-10 19:59:24 +00:00
parent c468a9f9c7
commit f599bc42be
5 changed files with 581 additions and 38 deletions

View File

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

View File

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

View File

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

View File

@@ -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);
@@ -105,6 +113,17 @@ public class ProxyAccessibilityServiceConnectionTest {
verify(mMockSystemSupport).onProxyChanged(DEVICE_ID);
}
@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<>();
@@ -196,7 +215,7 @@ public class ProxyAccessibilityServiceConnectionTest {
}
@Test
public void testSetServiceInfo_setIllegalOperationExceptionThrown_() {
public void testSetServiceInfo_setIllegalOperationExceptionThrown() {
UnsupportedOperationException thrown =
assertThrows(
UnsupportedOperationException.class,

View File

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