diff options
| author | 2023-04-10 19:59:24 +0000 | |
|---|---|---|
| committer | 2023-04-20 18:40:32 +0000 | |
| commit | f599bc42bed8d7f80b6db6f6712ad3319bc1214d (patch) | |
| tree | 80b379a7ec5967e58f7150d4ab54250df38e4886 | |
| parent | c468a9f9c78e38938e7910012e41469b3f5f681f (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
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 { + + } + } +} |