Add tests for AbstractAccessibilityServiceConnection
Bug: 133635779
Test: atest AbstractAccessibilityServiceConnectionTest
Change-Id: Idfcf3790a1a45dfa4423d57e6d2d13c6a302e11e
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index c7f4154..1873de5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -297,7 +297,7 @@
}
public boolean canReceiveEventsLocked() {
- return (mEventTypes != 0 && mFeedbackType != 0 && mService != null);
+ return (mEventTypes != 0 && mService != null);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
new file mode 100644
index 0000000..a4267b8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -0,0 +1,797 @@
+/*
+ * Copyright (C) 2019 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 android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_HOME;
+import static android.accessibilityservice.AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION;
+import static android.accessibilityservice.AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES;
+import static android.accessibilityservice.AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS;
+import static android.accessibilityservice.AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION;
+import static android.accessibilityservice.AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT;
+import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
+import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_HAPTIC;
+import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_SPOKEN;
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS;
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES;
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
+import static android.view.View.FOCUS_DOWN;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
+import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import static android.view.accessibility.AccessibilityNodeInfo.FOCUS_INPUT;
+import static android.view.accessibility.AccessibilityNodeInfo.ROOT_NODE_ID;
+
+import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.graphics.Region;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.testing.DexmakerShareClassLoaderRule;
+import android.view.KeyEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.accessibility.IAccessibilityInteractionConnection;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+
+import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * Tests for the AbstractAccessibilityServiceConnection
+ */
+public class AbstractAccessibilityServiceConnectionTest {
+ private static final ComponentName COMPONENT_NAME = new ComponentName(
+ "com.android.server.accessibility", ".AbstractAccessibilityServiceConnectionTest");
+ private static final String PACKAGE_NAME1 = "com.android.server.accessibility1";
+ private static final String PACKAGE_NAME2 = "com.android.server.accessibility2";
+ private static final String VIEWID_RESOURCE_NAME = "test_viewid_resource_name";
+ private static final String VIEW_TEXT = "test_view_text";
+ private static final int WINDOWID = 12;
+ private static final int PIP_WINDOWID = 13;
+ private static final int SERVICE_ID = 42;
+ private static final int A11Y_SERVICE_CAPABILITY = CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
+ | CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
+ | CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
+ | CAPABILITY_CAN_CONTROL_MAGNIFICATION
+ | CAPABILITY_CAN_PERFORM_GESTURES;
+ private static final int A11Y_SERVICE_FLAG = DEFAULT
+ | FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ | FLAG_REPORT_VIEW_IDS
+ | FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+ | FLAG_REQUEST_FILTER_KEY_EVENTS
+ | FLAG_REQUEST_FINGERPRINT_GESTURES
+ | FLAG_REQUEST_ACCESSIBILITY_BUTTON
+ | FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+ private static final int USER_ID = 1;
+ private static final int USER_ID2 = 2;
+ private static final int INTERACTION_ID = 199;
+ private static final int PID = Process.myPid();
+ private static final long TID = Process.myTid();
+ private static final int UID = Process.myUid();
+
+ private AbstractAccessibilityServiceConnection mServiceConnection;
+ private MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
+ private final List<AccessibilityWindowInfo> mA11yWindowInfos = new ArrayList<>();
+ private Callable[] mFindA11yNodesFunctions;
+ private Callable<Boolean> mPerformA11yAction;
+
+ // To mock package-private class.
+ @Rule public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
+ new DexmakerShareClassLoaderRule();
+
+ @Mock private Context mMockContext;
+ @Mock private IPowerManager mMockIPowerManager;
+ @Mock private PackageManager mMockPackageManager;
+ @Spy private AccessibilityServiceInfo mSpyServiceInfo = new AccessibilityServiceInfo();
+ @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
+ @Mock private AccessibilityWindowManager mMockA11yWindowManager;
+ @Mock private AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
+ @Mock private WindowManagerInternal mMockWindowManagerInternal;
+ @Mock private GlobalActionPerformer mMockGlobalActionPerformer;
+ @Mock private IBinder mMockService;
+ @Mock private IAccessibilityServiceClient mMockServiceInterface;
+ @Mock private KeyEventDispatcher mMockKeyEventDispatcher;
+ @Mock private IAccessibilityInteractionConnection mMockIA11yInteractionConnection;
+ @Mock private IAccessibilityInteractionConnectionCallback mMockCallback;
+ @Mock private FingerprintGestureDispatcher mMockFingerprintGestureDispatcher;
+ @Mock private MagnificationController mMockMagnificationController;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID);
+ when(mMockSystemSupport.getKeyEventDispatcher()).thenReturn(mMockKeyEventDispatcher);
+ when(mMockSystemSupport.getFingerprintGestureDispatcher())
+ .thenReturn(mMockFingerprintGestureDispatcher);
+ when(mMockSystemSupport.getMagnificationController())
+ .thenReturn(mMockMagnificationController);
+
+ PowerManager powerManager =
+ new PowerManager(mMockContext, mMockIPowerManager, mHandler);
+ when(mMockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.hasSystemFeature(FEATURE_FINGERPRINT)).thenReturn(true);
+
+ // Fake a11yWindowInfo and remote a11y connection for tests.
+ addA11yWindowInfo(mA11yWindowInfos, WINDOWID, false);
+ addA11yWindowInfo(mA11yWindowInfos, PIP_WINDOWID, true);
+ when(mMockA11yWindowManager.getWindowListLocked()).thenReturn(mA11yWindowInfos);
+ when(mMockA11yWindowManager.findA11yWindowInfoById(WINDOWID))
+ .thenReturn(mA11yWindowInfos.get(0));
+ when(mMockA11yWindowManager.findA11yWindowInfoById(PIP_WINDOWID))
+ .thenReturn(mA11yWindowInfos.get(1));
+ final RemoteAccessibilityConnection conn = getRemoteA11yConnection(
+ WINDOWID, mMockIA11yInteractionConnection, PACKAGE_NAME1);
+ final RemoteAccessibilityConnection connPip = getRemoteA11yConnection(
+ PIP_WINDOWID, mMockIA11yInteractionConnection, PACKAGE_NAME2);
+ when(mMockA11yWindowManager.getConnectionLocked(USER_ID, WINDOWID)).thenReturn(conn);
+ when(mMockA11yWindowManager.getConnectionLocked(USER_ID, PIP_WINDOWID)).thenReturn(connPip);
+ when(mMockA11yWindowManager.getPictureInPictureActionReplacingConnection())
+ .thenReturn(connPip);
+
+ // Update a11yServiceInfo to full capability, full flags and target sdk jelly bean
+ final ResolveInfo mockResolveInfo = mock(ResolveInfo.class);
+ mockResolveInfo.serviceInfo = mock(ServiceInfo.class);
+ mockResolveInfo.serviceInfo.applicationInfo = mock(ApplicationInfo.class);
+ mockResolveInfo.serviceInfo.applicationInfo.targetSdkVersion =
+ Build.VERSION_CODES.JELLY_BEAN;
+ doReturn(mockResolveInfo).when(mSpyServiceInfo).getResolveInfo();
+ mSpyServiceInfo.setCapabilities(A11Y_SERVICE_CAPABILITY);
+ updateServiceInfo(mSpyServiceInfo, 0, 0, A11Y_SERVICE_FLAG, null, 0);
+
+ mServiceConnection = new TestAccessibilityServiceConnection(mMockContext, COMPONENT_NAME,
+ mSpyServiceInfo, SERVICE_ID, mHandler, new Object(), mMockSecurityPolicy,
+ mMockSystemSupport, mMockWindowManagerInternal, mMockGlobalActionPerformer,
+ mMockA11yWindowManager);
+ // Assume that the service is connected
+ mServiceConnection.mService = mMockService;
+ mServiceConnection.mServiceInterface = mMockServiceInterface;
+
+ // Update security policy for this service
+ when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(true);
+ when(mMockSecurityPolicy.canRetrieveWindowsLocked(mServiceConnection)).thenReturn(true);
+ when(mMockSecurityPolicy.canGetAccessibilityNodeInfoLocked(
+ eq(USER_ID), eq(mServiceConnection), anyInt())).thenReturn(true);
+ when(mMockSecurityPolicy.canControlMagnification(mServiceConnection)).thenReturn(true);
+
+ // init test functions for accessAccessibilityNodeInfo test case.
+ initTestFunctions();
+ }
+
+ @Test
+ public void getCapabilities() {
+ assertThat(mServiceConnection.getCapabilities(), is(A11Y_SERVICE_CAPABILITY));
+ }
+
+ @Test
+ public void onKeyEvent() throws RemoteException {
+ final int sequenceNumber = 100;
+ final KeyEvent mockKeyEvent = mock(KeyEvent.class);
+
+ mServiceConnection.onKeyEvent(mockKeyEvent, sequenceNumber);
+ verify(mMockServiceInterface).onKeyEvent(mockKeyEvent, sequenceNumber);
+ }
+
+ @Test
+ public void setServiceInfo_invokeOnClientChange() {
+ final AccessibilityServiceInfo serviceInfo = new AccessibilityServiceInfo();
+ updateServiceInfo(serviceInfo,
+ TYPE_VIEW_CLICKED | TYPE_VIEW_LONG_CLICKED,
+ FEEDBACK_SPOKEN | FEEDBACK_HAPTIC,
+ A11Y_SERVICE_FLAG,
+ new String[] {PACKAGE_NAME1, PACKAGE_NAME2},
+ 1000);
+
+ mServiceConnection.setServiceInfo(serviceInfo);
+ verify(mMockSystemSupport).onClientChangeLocked(true);
+ }
+
+ @Test
+ public void canReceiveEvents_hasEventType_returnTrue() {
+ final AccessibilityServiceInfo serviceInfo = new AccessibilityServiceInfo();
+ updateServiceInfo(serviceInfo,
+ TYPE_VIEW_CLICKED | TYPE_VIEW_LONG_CLICKED, 0,
+ 0, null, 0);
+
+ mServiceConnection.setServiceInfo(serviceInfo);
+ assertThat(mServiceConnection.canReceiveEventsLocked(), is(true));
+ }
+
+ @Test
+ public void setOnKeyEventResult() {
+ final int sequenceNumber = 100;
+ final boolean handled = true;
+ mServiceConnection.setOnKeyEventResult(handled, sequenceNumber);
+
+ verify(mMockKeyEventDispatcher).setOnKeyEventResult(
+ mServiceConnection, handled, sequenceNumber);
+ }
+
+ @Test
+ public void getWindows() {
+ assertThat(mServiceConnection.getWindows(), is(mA11yWindowInfos));
+ }
+
+ @Test
+ public void getWindows_returnNull() {
+ // no canRetrieveWindows, should return null
+ when(mMockSecurityPolicy.canRetrieveWindowsLocked(mServiceConnection)).thenReturn(false);
+ assertThat(mServiceConnection.getWindows(), is(nullValue()));
+
+ // no checkAccessibilityAccess, should return null
+ when(mMockSecurityPolicy.canRetrieveWindowsLocked(mServiceConnection)).thenReturn(true);
+ when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false);
+ assertThat(mServiceConnection.getWindows(), is(nullValue()));
+ }
+
+ @Test
+ public void getWindows_notTrackingWindows_invokeOnClientChange() {
+ when(mMockA11yWindowManager.getWindowListLocked()).thenReturn(null);
+ when(mMockA11yWindowManager.isTrackingWindowsLocked()).thenReturn(false);
+
+ mServiceConnection.getWindows();
+ verify(mMockSystemSupport).onClientChangeLocked(false);
+ }
+
+ @Test
+ public void getWindow() {
+ assertThat(mServiceConnection.getWindow(WINDOWID), is(mA11yWindowInfos.get(0)));
+ }
+
+ @Test
+ public void getWindow_returnNull() {
+ // no canRetrieveWindows, should return null
+ when(mMockSecurityPolicy.canRetrieveWindowsLocked(mServiceConnection)).thenReturn(false);
+ assertThat(mServiceConnection.getWindow(WINDOWID), is(nullValue()));
+
+ // no checkAccessibilityAccess, should return null
+ when(mMockSecurityPolicy.canRetrieveWindowsLocked(mServiceConnection)).thenReturn(true);
+ when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false);
+ assertThat(mServiceConnection.getWindow(WINDOWID), is(nullValue()));
+ }
+
+ @Test
+ public void getWindow_notTrackingWindows_invokeOnClientChange() {
+ when(mMockA11yWindowManager.getWindowListLocked()).thenReturn(null);
+ when(mMockA11yWindowManager.isTrackingWindowsLocked()).thenReturn(false);
+
+ mServiceConnection.getWindow(WINDOWID);
+ verify(mMockSystemSupport).onClientChangeLocked(false);
+ }
+
+ @Test
+ public void accessAccessibilityNodeInfo_whenCantGetInfo_returnNullOrFalse()
+ throws Exception {
+ when(mMockSecurityPolicy.canGetAccessibilityNodeInfoLocked(
+ USER_ID, mServiceConnection, WINDOWID)).thenReturn(false);
+ for (int i = 0; i < mFindA11yNodesFunctions.length; i++) {
+ assertThat(mFindA11yNodesFunctions[i].call(), is(nullValue()));
+ }
+ assertThat(mPerformA11yAction.call(), is(false));
+
+ verifyNoMoreInteractions(mMockIA11yInteractionConnection);
+ verify(mMockSecurityPolicy, never()).computeValidReportedPackages(any(), anyInt());
+ }
+
+ @Test
+ public void accessAccessibilityNodeInfo_whenNoA11yAccess_returnNullOrFalse()
+ throws Exception {
+ when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false);
+ for (int i = 0; i < mFindA11yNodesFunctions.length; i++) {
+ assertThat(mFindA11yNodesFunctions[i].call(), is(nullValue()));
+ }
+ assertThat(mPerformA11yAction.call(), is(false));
+
+ verifyNoMoreInteractions(mMockIA11yInteractionConnection);
+ verify(mMockSecurityPolicy, never()).computeValidReportedPackages(any(), anyInt());
+ }
+
+ @Test
+ public void accessAccessibilityNodeInfo_whenNoRemoteA11yConnection_returnNullOrFalse()
+ throws Exception {
+ when(mMockA11yWindowManager.getConnectionLocked(USER_ID, WINDOWID)).thenReturn(null);
+ for (int i = 0; i < mFindA11yNodesFunctions.length; i++) {
+ assertThat(mFindA11yNodesFunctions[i].call(), is(nullValue()));
+ }
+ assertThat(mPerformA11yAction.call(), is(false));
+
+ verifyNoMoreInteractions(mMockIA11yInteractionConnection);
+ verify(mMockSecurityPolicy, never()).computeValidReportedPackages(any(), anyInt());
+ }
+
+ @Test
+ public void findAccessibilityNodeInfosByViewId_withPipWindow_shouldReplaceCallback()
+ throws RemoteException {
+ final ArgumentCaptor<IAccessibilityInteractionConnectionCallback> captor =
+ ArgumentCaptor.forClass(IAccessibilityInteractionConnectionCallback.class);
+ mServiceConnection.findAccessibilityNodeInfosByViewId(PIP_WINDOWID, ROOT_NODE_ID,
+ VIEWID_RESOURCE_NAME, INTERACTION_ID, mMockCallback, TID);
+ verify(mMockIA11yInteractionConnection).findAccessibilityNodeInfosByViewId(
+ eq(ROOT_NODE_ID), eq(VIEWID_RESOURCE_NAME), any(), eq(INTERACTION_ID),
+ captor.capture(), anyInt(), eq(PID), eq(TID), any());
+ verify(mMockSecurityPolicy).computeValidReportedPackages(any(), anyInt());
+ verifyReplaceActions(captor.getValue());
+ }
+
+ @Test
+ public void findAccessibilityNodeInfosByText_withPipWindow_shouldReplaceCallback()
+ throws RemoteException {
+ final ArgumentCaptor<IAccessibilityInteractionConnectionCallback> captor =
+ ArgumentCaptor.forClass(IAccessibilityInteractionConnectionCallback.class);
+ mServiceConnection.findAccessibilityNodeInfosByText(PIP_WINDOWID, ROOT_NODE_ID,
+ VIEW_TEXT, INTERACTION_ID, mMockCallback, TID);
+ verify(mMockIA11yInteractionConnection).findAccessibilityNodeInfosByText(
+ eq(ROOT_NODE_ID), eq(VIEW_TEXT), any(), eq(INTERACTION_ID),
+ captor.capture(), anyInt(), eq(PID), eq(TID), any());
+ verify(mMockSecurityPolicy).computeValidReportedPackages(any(), anyInt());
+ verifyReplaceActions(captor.getValue());
+ }
+
+ @Test
+ public void findAccessibilityNodeInfoByAccessibilityId_withPipWindow_shouldReplaceCallback()
+ throws RemoteException {
+ final ArgumentCaptor<IAccessibilityInteractionConnectionCallback> captor =
+ ArgumentCaptor.forClass(IAccessibilityInteractionConnectionCallback.class);
+ mServiceConnection.findAccessibilityNodeInfoByAccessibilityId(PIP_WINDOWID, ROOT_NODE_ID,
+ INTERACTION_ID, mMockCallback, 0, TID, null);
+ verify(mMockIA11yInteractionConnection).findAccessibilityNodeInfoByAccessibilityId(
+ eq(ROOT_NODE_ID), any(), eq(INTERACTION_ID), captor.capture(), anyInt(),
+ eq(PID), eq(TID), any(), any());
+ verify(mMockSecurityPolicy).computeValidReportedPackages(any(), anyInt());
+ verifyReplaceActions(captor.getValue());
+ }
+
+ @Test
+ public void findFocus_withPipWindow_shouldReplaceCallback()
+ throws RemoteException {
+ final ArgumentCaptor<IAccessibilityInteractionConnectionCallback> captor =
+ ArgumentCaptor.forClass(IAccessibilityInteractionConnectionCallback.class);
+ mServiceConnection.findFocus(PIP_WINDOWID, ROOT_NODE_ID, FOCUS_INPUT, INTERACTION_ID,
+ mMockCallback, TID);
+ verify(mMockIA11yInteractionConnection).findFocus(eq(ROOT_NODE_ID), eq(FOCUS_INPUT),
+ any(), eq(INTERACTION_ID), captor.capture(), anyInt(), eq(PID), eq(TID), any());
+ verify(mMockSecurityPolicy).computeValidReportedPackages(any(), anyInt());
+ verifyReplaceActions(captor.getValue());
+ }
+
+ @Test
+ public void focusSearch_withPipWindow_shouldReplaceCallback()
+ throws RemoteException {
+ final ArgumentCaptor<IAccessibilityInteractionConnectionCallback> captor =
+ ArgumentCaptor.forClass(IAccessibilityInteractionConnectionCallback.class);
+ mServiceConnection.focusSearch(PIP_WINDOWID, ROOT_NODE_ID, FOCUS_DOWN, INTERACTION_ID,
+ mMockCallback, TID);
+ verify(mMockIA11yInteractionConnection).focusSearch(eq(ROOT_NODE_ID), eq(FOCUS_DOWN),
+ any(), eq(INTERACTION_ID), captor.capture(), anyInt(), eq(PID), eq(TID), any());
+ verify(mMockSecurityPolicy).computeValidReportedPackages(any(), anyInt());
+ verifyReplaceActions(captor.getValue());
+ }
+
+ @Test
+ public void performAccessibilityAction_withPipWindow_invokeGetPipReplacingConnection()
+ throws RemoteException {
+ mServiceConnection.performAccessibilityAction(PIP_WINDOWID, ROOT_NODE_ID,
+ ACTION_ACCESSIBILITY_FOCUS, null, INTERACTION_ID, mMockCallback, TID);
+
+ verify(mMockIPowerManager).userActivity(anyLong(), anyInt(), anyInt());
+ verify(mMockIA11yInteractionConnection).performAccessibilityAction(eq(ROOT_NODE_ID),
+ eq(ACTION_ACCESSIBILITY_FOCUS), any(), eq(INTERACTION_ID), eq(mMockCallback),
+ anyInt(), eq(PID), eq(TID));
+ verify(mMockA11yWindowManager).getPictureInPictureActionReplacingConnection();
+ }
+
+ @Test
+ public void performAccessibilityAction_withClick_shouldNotifyOutsideTouch()
+ throws RemoteException {
+ mServiceConnection.performAccessibilityAction(WINDOWID, ROOT_NODE_ID,
+ ACTION_CLICK, null, INTERACTION_ID, mMockCallback, TID);
+ mServiceConnection.performAccessibilityAction(PIP_WINDOWID, ROOT_NODE_ID,
+ ACTION_LONG_CLICK, null, INTERACTION_ID, mMockCallback, TID);
+ verify(mMockA11yWindowManager).notifyOutsideTouch(eq(USER_ID), eq(WINDOWID));
+ verify(mMockA11yWindowManager).notifyOutsideTouch(eq(USER_ID), eq(PIP_WINDOWID));
+ }
+
+ @Test
+ public void performGlobalAction() {
+ mServiceConnection.performGlobalAction(GLOBAL_ACTION_HOME);
+ verify(mMockGlobalActionPerformer).performGlobalAction(GLOBAL_ACTION_HOME);
+ }
+
+ @Test
+ public void isFingerprintGestureDetectionAvailable_hasFingerPrintSupport_returnTrue() {
+ when(mMockFingerprintGestureDispatcher.isFingerprintGestureDetectionAvailable())
+ .thenReturn(true);
+ final boolean result = mServiceConnection.isFingerprintGestureDetectionAvailable();
+ assertThat(result, is(true));
+ }
+
+ @Test
+ public void isFingerprintGestureDetectionAvailable_noFingerPrintSupport_returnFalse() {
+ when(mMockFingerprintGestureDispatcher.isFingerprintGestureDetectionAvailable())
+ .thenReturn(true);
+
+ // Return false if device does not support fingerprint
+ when(mMockPackageManager.hasSystemFeature(FEATURE_FINGERPRINT)).thenReturn(false);
+ boolean result = mServiceConnection.isFingerprintGestureDetectionAvailable();
+ assertThat(result, is(false));
+
+ // Return false if service does not have flag
+ when(mMockPackageManager.hasSystemFeature(FEATURE_FINGERPRINT)).thenReturn(true);
+ mSpyServiceInfo.flags = A11Y_SERVICE_FLAG & ~FLAG_REQUEST_FINGERPRINT_GESTURES;
+ mServiceConnection.setServiceInfo(mSpyServiceInfo);
+ result = mServiceConnection.isFingerprintGestureDetectionAvailable();
+ assertThat(result, is(false));
+ }
+
+ @Test
+ public void getMagnificationScale() {
+ final int displayId = 1;
+ final float scale = 2.0f;
+ when(mMockMagnificationController.getScale(displayId)).thenReturn(scale);
+
+ final float result = mServiceConnection.getMagnificationScale(displayId);
+ assertThat(result, is(scale));
+ }
+
+ @Test
+ public void getMagnificationScale_serviceNotBelongCurrentUser_returnNoScale() {
+ final int displayId = 1;
+ final float scale = 2.0f;
+ when(mMockMagnificationController.getScale(displayId)).thenReturn(scale);
+ when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
+
+ final float result = mServiceConnection.getMagnificationScale(displayId);
+ assertThat(result, is(1.0f));
+ }
+
+ @Test
+ public void getMagnificationRegion_notRegistered_shouldRegisterThenUnregister() {
+ final int displayId = 1;
+ final Region region = new Region(10, 20, 100, 200);
+ doAnswer((invocation) -> {
+ ((Region) invocation.getArguments()[1]).set(region);
+ return null;
+ }).when(mMockMagnificationController).getMagnificationRegion(eq(displayId), any());
+ when(mMockMagnificationController.isRegistered(displayId)).thenReturn(false);
+
+ final Region result = mServiceConnection.getMagnificationRegion(displayId);
+ assertThat(result, is(region));
+ verify(mMockMagnificationController).register(displayId);
+ verify(mMockMagnificationController).unregister(displayId);
+ }
+
+ @Test
+ public void getMagnificationRegion_serviceNotBelongCurrentUser_returnEmptyRegion() {
+ final int displayId = 1;
+ final Region region = new Region(10, 20, 100, 200);
+ doAnswer((invocation) -> {
+ ((Region) invocation.getArguments()[1]).set(region);
+ return null;
+ }).when(mMockMagnificationController).getMagnificationRegion(eq(displayId), any());
+ when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
+
+ final Region result = mServiceConnection.getMagnificationRegion(displayId);
+ assertThat(result.isEmpty(), is(true));
+ }
+
+ @Test
+ public void getMagnificationCenterX_notRegistered_shouldRegisterThenUnregister() {
+ final int displayId = 1;
+ final float centerX = 480.0f;
+ when(mMockMagnificationController.getCenterX(displayId)).thenReturn(centerX);
+ when(mMockMagnificationController.isRegistered(displayId)).thenReturn(false);
+
+ final float result = mServiceConnection.getMagnificationCenterX(displayId);
+ assertThat(result, is(centerX));
+ verify(mMockMagnificationController).register(displayId);
+ verify(mMockMagnificationController).unregister(displayId);
+ }
+
+ @Test
+ public void getMagnificationCenterX_serviceNotBelongCurrentUser_returnZero() {
+ final int displayId = 1;
+ final float centerX = 480.0f;
+ when(mMockMagnificationController.getCenterX(displayId)).thenReturn(centerX);
+ when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
+
+ final float result = mServiceConnection.getMagnificationCenterX(displayId);
+ assertThat(result, is(0.0f));
+ }
+
+ @Test
+ public void getMagnificationCenterY_notRegistered_shouldRegisterThenUnregister() {
+ final int displayId = 1;
+ final float centerY = 640.0f;
+ when(mMockMagnificationController.getCenterY(displayId)).thenReturn(centerY);
+ when(mMockMagnificationController.isRegistered(displayId)).thenReturn(false);
+
+ final float result = mServiceConnection.getMagnificationCenterY(displayId);
+ assertThat(result, is(centerY));
+ verify(mMockMagnificationController).register(displayId);
+ verify(mMockMagnificationController).unregister(displayId);
+ }
+
+ @Test
+ public void getMagnificationCenterY_serviceNotBelongCurrentUser_returnZero() {
+ final int displayId = 1;
+ final float centerY = 640.0f;
+ when(mMockMagnificationController.getCenterY(displayId)).thenReturn(centerY);
+ when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
+
+ final float result = mServiceConnection.getMagnificationCenterY(displayId);
+ assertThat(result, is(0.0f));
+ }
+
+ @Test
+ public void resetMagnification() {
+ final int displayId = 1;
+ when(mMockMagnificationController.reset(displayId, true)).thenReturn(true);
+
+ final boolean result = mServiceConnection.resetMagnification(displayId, true);
+ assertThat(result, is(true));
+ }
+
+ @Test
+ public void resetMagnification_cantControlMagnification_returnFalse() {
+ final int displayId = 1;
+ when(mMockMagnificationController.reset(displayId, true)).thenReturn(true);
+ when(mMockSecurityPolicy.canControlMagnification(mServiceConnection)).thenReturn(false);
+
+ final boolean result = mServiceConnection.resetMagnification(displayId, true);
+ assertThat(result, is(false));
+ }
+
+ @Test
+ public void resetMagnification_serviceNotBelongCurrentUser_returnFalse() {
+ final int displayId = 1;
+ when(mMockMagnificationController.reset(displayId, true)).thenReturn(true);
+ when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
+
+ final boolean result = mServiceConnection.resetMagnification(displayId, true);
+ assertThat(result, is(false));
+ }
+
+ @Test
+ public void setMagnificationScaleAndCenter_notRegistered_shouldRegister() {
+ final int displayId = 1;
+ final float scale = 1.8f;
+ final float centerX = 50.5f;
+ final float centerY = 100.5f;
+ when(mMockMagnificationController.setScaleAndCenter(displayId,
+ scale, centerX, centerY, true, SERVICE_ID)).thenReturn(true);
+ when(mMockMagnificationController.isRegistered(displayId)).thenReturn(false);
+
+ final boolean result = mServiceConnection.setMagnificationScaleAndCenter(
+ displayId, scale, centerX, centerY, true);
+ assertThat(result, is(true));
+ verify(mMockMagnificationController).register(displayId);
+ }
+
+ @Test
+ public void setMagnificationScaleAndCenter_cantControlMagnification_returnFalse() {
+ final int displayId = 1;
+ final float scale = 1.8f;
+ final float centerX = 50.5f;
+ final float centerY = 100.5f;
+ when(mMockMagnificationController.setScaleAndCenter(displayId,
+ scale, centerX, centerY, true, SERVICE_ID)).thenReturn(true);
+ when(mMockSecurityPolicy.canControlMagnification(mServiceConnection)).thenReturn(false);
+
+ final boolean result = mServiceConnection.setMagnificationScaleAndCenter(
+ displayId, scale, centerX, centerY, true);
+ assertThat(result, is(false));
+ }
+
+ @Test
+ public void setMagnificationScaleAndCenter_serviceNotBelongCurrentUser_returnFalse() {
+ final int displayId = 1;
+ final float scale = 1.8f;
+ final float centerX = 50.5f;
+ final float centerY = 100.5f;
+ when(mMockMagnificationController.setScaleAndCenter(displayId,
+ scale, centerX, centerY, true, SERVICE_ID)).thenReturn(true);
+ when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2);
+
+ final boolean result = mServiceConnection.setMagnificationScaleAndCenter(
+ displayId, scale, centerX, centerY, true);
+ assertThat(result, is(false));
+ }
+
+ private void updateServiceInfo(AccessibilityServiceInfo serviceInfo, int eventType,
+ int feedbackType, int flags, String[] packageNames, int notificationTimeout) {
+ serviceInfo.eventTypes = eventType;
+ serviceInfo.feedbackType = feedbackType;
+ serviceInfo.flags = flags;
+ serviceInfo.packageNames = packageNames;
+ serviceInfo.notificationTimeout = notificationTimeout;
+ }
+
+ private AccessibilityWindowInfo addA11yWindowInfo(List<AccessibilityWindowInfo> infos,
+ int windowId, boolean isPip) {
+ final AccessibilityWindowInfo info = AccessibilityWindowInfo.obtain();
+ info.setId(windowId);
+ info.setPictureInPicture(isPip);
+ infos.add(info);
+ return info;
+ }
+
+ private RemoteAccessibilityConnection getRemoteA11yConnection(int windowId,
+ IAccessibilityInteractionConnection connection,
+ String packageName) {
+ return mMockA11yWindowManager.new RemoteAccessibilityConnection(
+ windowId, connection, packageName, UID, USER_ID);
+ }
+
+ private void initTestFunctions() {
+ // Init functions for accessibility nodes finding and searching by different filter rules.
+ // We group them together for the tests because they have similar implementation.
+ mFindA11yNodesFunctions = new Callable[] {
+ // findAccessibilityNodeInfosByViewId
+ () -> mServiceConnection.findAccessibilityNodeInfosByViewId(WINDOWID,
+ ROOT_NODE_ID, VIEWID_RESOURCE_NAME, INTERACTION_ID,
+ mMockCallback, TID),
+ // findAccessibilityNodeInfosByText
+ () -> mServiceConnection.findAccessibilityNodeInfosByText(WINDOWID,
+ ROOT_NODE_ID, VIEW_TEXT, INTERACTION_ID, mMockCallback, TID),
+ // findAccessibilityNodeInfoByAccessibilityId
+ () -> mServiceConnection.findAccessibilityNodeInfoByAccessibilityId(WINDOWID,
+ ROOT_NODE_ID, INTERACTION_ID, mMockCallback, 0, TID, null),
+ // findFocus
+ () -> mServiceConnection.findFocus(WINDOWID, ROOT_NODE_ID, FOCUS_INPUT,
+ INTERACTION_ID, mMockCallback, TID),
+ // focusSearch
+ () -> mServiceConnection.focusSearch(WINDOWID, ROOT_NODE_ID, FOCUS_DOWN,
+ INTERACTION_ID, mMockCallback, TID)
+ };
+ // performAccessibilityAction
+ mPerformA11yAction = () -> mServiceConnection.performAccessibilityAction(WINDOWID,
+ ROOT_NODE_ID, ACTION_ACCESSIBILITY_FOCUS, null, INTERACTION_ID,
+ mMockCallback, TID);
+ }
+
+ private void verifyReplaceActions(IAccessibilityInteractionConnectionCallback replacedCallback)
+ throws RemoteException {
+ final AccessibilityNodeInfo nodeFromApp = AccessibilityNodeInfo.obtain();
+ nodeFromApp.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, WINDOWID);
+
+ final AccessibilityNodeInfo nodeFromReplacer = AccessibilityNodeInfo.obtain();
+ nodeFromReplacer.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID,
+ AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID);
+ nodeFromReplacer.addAction(AccessibilityAction.ACTION_CLICK);
+ nodeFromReplacer.addAction(AccessibilityAction.ACTION_EXPAND);
+ final List<AccessibilityNodeInfo> replacerList = Arrays.asList(nodeFromReplacer);
+
+ replacedCallback.setFindAccessibilityNodeInfoResult(nodeFromApp, INTERACTION_ID);
+ replacedCallback.setFindAccessibilityNodeInfosResult(replacerList, INTERACTION_ID + 1);
+
+ final ArgumentCaptor<AccessibilityNodeInfo> captor =
+ ArgumentCaptor.forClass(AccessibilityNodeInfo.class);
+ verify(mMockCallback).setFindAccessibilityNodeInfoResult(captor.capture(),
+ eq(INTERACTION_ID));
+ assertThat(captor.getValue().getActionList(),
+ hasItems(AccessibilityAction.ACTION_CLICK, AccessibilityAction.ACTION_EXPAND));
+ }
+
+ private static class TestAccessibilityServiceConnection
+ extends AbstractAccessibilityServiceConnection {
+ int mResolvedUserId;
+
+ TestAccessibilityServiceConnection(Context context, ComponentName componentName,
+ AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
+ Object lock, AccessibilitySecurityPolicy securityPolicy,
+ SystemSupport systemSupport, WindowManagerInternal windowManagerInternal,
+ GlobalActionPerformer globalActionPerfomer,
+ AccessibilityWindowManager a11yWindowManager) {
+ super(context, componentName, accessibilityServiceInfo, id, mainHandler, lock,
+ securityPolicy, systemSupport, windowManagerInternal, globalActionPerfomer,
+ a11yWindowManager);
+ mResolvedUserId = USER_ID;
+ }
+
+ @Override
+ protected boolean isCalledForCurrentUserLocked() {
+ return mResolvedUserId == mSystemSupport.getCurrentUserIdLocked();
+ }
+
+ @Override
+ public void disableSelf() throws RemoteException {}
+
+ @Override
+ public boolean setSoftKeyboardShowMode(int showMode) throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public int getSoftKeyboardShowMode() throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public boolean isAccessibilityButtonAvailable() throws RemoteException {
+ return false;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {}
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {}
+
+ @Override
+ public void binderDied() {}
+
+ @Override
+ public boolean isCapturingFingerprintGestures() {
+ return mCaptureFingerprintGestures;
+ }
+
+ @Override
+ public void onFingerprintGestureDetectionActiveChanged(boolean active) {}
+
+ @Override
+ public void onFingerprintGesture(int gesture) {}
+ }
+}