/* * 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.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY; import static android.accessibilityservice.AccessibilityService.ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS; import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_HOME; import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT_STATUS; 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.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; 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.AccessibilityService; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.IAccessibilityServiceClient; import android.accessibilityservice.MagnificationConfig; 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.hardware.display.DisplayManager; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.IPowerManager; import android.os.IThermalService; import android.os.PowerManager; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; import android.util.Pair; import android.view.Display; import android.view.KeyEvent; import android.view.MagnificationSpec; 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.accessibility.magnification.MagnificationProcessor; import com.android.server.accessibility.test.MessageCapturingHandler; import com.android.server.wm.WindowManagerInternal; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; 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 WINDOWID_ONSECONDDISPLAY = 14; private static final int SECONDARY_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1; 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 Pair FAKE_MATRIX_AND_MAG_SPEC = new Pair<>(new float[9], new MagnificationSpec()); 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 mA11yWindowInfos = new ArrayList<>(); private final List mA11yWindowInfosOnSecondDisplay = new ArrayList<>(); private Callable[] mFindA11yNodesFunctions; private Callable mPerformA11yAction; private ArrayList mDisplayList = new ArrayList<>(Arrays.asList( Display.DEFAULT_DISPLAY, SECONDARY_DISPLAY_ID)); @Mock private Context mMockContext; @Mock private IPowerManager mMockIPowerManager; @Mock private IThermalService mMockIThermalService; @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 AccessibilityTrace mMockA11yTrace; @Mock private WindowManagerInternal mMockWindowManagerInternal; @Mock private SystemActionPerformer mMockSystemActionPerformer; @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 MagnificationProcessor mMockMagnificationProcessor; @Mock private RemoteCallback.OnResultListener mMockListener; @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.getMagnificationProcessor()) .thenReturn(mMockMagnificationProcessor); when(mMockSystemSupport.getWindowTransformationMatrixAndMagnificationSpec(anyInt())) .thenReturn(FAKE_MATRIX_AND_MAG_SPEC); PowerManager powerManager = new PowerManager(mMockContext, mMockIPowerManager, mMockIThermalService, mHandler); when(mMockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager); when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); when(mMockPackageManager.hasSystemFeature(FEATURE_FINGERPRINT)).thenReturn(true); when(mMockA11yTrace.isA11yTracingEnabled()).thenReturn(false); // Fake a11yWindowInfo and remote a11y connection for tests. addA11yWindowInfo(mA11yWindowInfos, WINDOWID, false, Display.DEFAULT_DISPLAY); addA11yWindowInfo(mA11yWindowInfos, PIP_WINDOWID, true, Display.DEFAULT_DISPLAY); addA11yWindowInfo(mA11yWindowInfosOnSecondDisplay, WINDOWID_ONSECONDDISPLAY, false, SECONDARY_DISPLAY_ID); when(mMockA11yWindowManager.getDisplayListLocked(anyInt())).thenReturn(mDisplayList); when(mMockA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)) .thenReturn(mA11yWindowInfos); when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(WINDOWID)) .thenReturn(mA11yWindowInfos.get(0)); when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(PIP_WINDOWID)) .thenReturn(mA11yWindowInfos.get(1)); when(mMockA11yWindowManager.getDisplayIdByUserIdAndWindowIdLocked(USER_ID, WINDOWID_ONSECONDDISPLAY)).thenReturn(SECONDARY_DISPLAY_ID); when(mMockA11yWindowManager.getWindowListLocked(SECONDARY_DISPLAY_ID)) .thenReturn(mA11yWindowInfosOnSecondDisplay); when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(WINDOWID_ONSECONDDISPLAY)) .thenReturn(mA11yWindowInfosOnSecondDisplay.get(0)); 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, mMockA11yTrace, mMockWindowManagerInternal, mMockSystemActionPerformer, 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 setServiceInfo_ChangePackageNames_updateSuccess() { assertTrue(mServiceConnection.mPackageNames.isEmpty()); final AccessibilityServiceInfo serviceInfo = new AccessibilityServiceInfo(); updateServiceInfo(serviceInfo, 0, 0, A11Y_SERVICE_FLAG, new String[] {PACKAGE_NAME1, PACKAGE_NAME2}, 1000); mServiceConnection.setServiceInfo(serviceInfo); assertEquals(serviceInfo.packageNames.length, mServiceConnection.mPackageNames.size()); assertTrue(mServiceConnection.mPackageNames.containsAll( Arrays.asList(mServiceConnection.getServiceInfo().packageNames))); updateServiceInfo(serviceInfo, 0, 0, A11Y_SERVICE_FLAG, null, 1000); mServiceConnection.setServiceInfo(serviceInfo); assertTrue(mServiceConnection.mPackageNames.isEmpty()); } @Test public void setServiceInfo_ChangeAccessibilityTool_updateFails() { assertFalse(mSpyServiceInfo.isAccessibilityTool()); final AccessibilityServiceInfo serviceInfo = new AccessibilityServiceInfo(); serviceInfo.setAccessibilityTool(true); mServiceConnection.setServiceInfo(serviceInfo); // isAccessibilityTool should not be dynamically updatable assertFalse(mSpyServiceInfo.isAccessibilityTool()); } @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() { final AccessibilityWindowInfo.WindowListSparseArray allWindows = mServiceConnection.getWindows(); assertEquals(2, allWindows.size()); assertThat(allWindows.get(Display.DEFAULT_DISPLAY), is(mA11yWindowInfos)); assertEquals(2, allWindows.get(Display.DEFAULT_DISPLAY).size()); assertThat(allWindows.get(SECONDARY_DISPLAY_ID), is(mA11yWindowInfosOnSecondDisplay)); assertEquals(1, allWindows.get(SECONDARY_DISPLAY_ID).size()); } @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(Display.DEFAULT_DISPLAY)).thenReturn(null); when(mMockA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)) .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(Display.DEFAULT_DISPLAY)).thenReturn(null); when(mMockA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY)) .thenReturn(false); mServiceConnection.getWindow(WINDOWID); verify(mMockSystemSupport).onClientChangeLocked(false); } @Test public void getWindow_onNonDefaultDisplay() { assertThat(mServiceConnection.getWindow(WINDOWID_ONSECONDDISPLAY), is(mA11yWindowInfosOnSecondDisplay.get(0))); } @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 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(), nullable(float[].class)); verify(mMockSecurityPolicy).computeValidReportedPackages(any(), anyInt()); verifyReplaceActions(captor.getValue()); } @Test public void findAccessibilityNodeInfosByText_withPipWindow_shouldReplaceCallback() throws RemoteException { final ArgumentCaptor 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(), nullable(float[].class)); verify(mMockSecurityPolicy).computeValidReportedPackages(any(), anyInt()); verifyReplaceActions(captor.getValue()); } @Test public void findAccessibilityNodeInfoByAccessibilityId_withPipWindow_shouldReplaceCallback() throws RemoteException { final ArgumentCaptor 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(), nullable(float[].class), any()); verify(mMockSecurityPolicy).computeValidReportedPackages(any(), anyInt()); verifyReplaceActions(captor.getValue()); } @Test public void findFocus_withPipWindow_shouldReplaceCallback() throws RemoteException { final ArgumentCaptor 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(), nullable(float[].class)); verify(mMockSecurityPolicy).computeValidReportedPackages(any(), anyInt()); verifyReplaceActions(captor.getValue()); } @Test public void focusSearch_withPipWindow_shouldReplaceCallback() throws RemoteException { final ArgumentCaptor 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(), nullable(float[].class)); 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(eq(Display.DEFAULT_DISPLAY), 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(mMockSystemActionPerformer).performSystemAction(GLOBAL_ACTION_HOME); } @Test public void getSystemActions() { List actions = mServiceConnection.getSystemActions(); verify(mMockSystemActionPerformer).getSystemActions(); } @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(mMockMagnificationProcessor.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(mMockMagnificationProcessor.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_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(mMockMagnificationProcessor).getFullscreenMagnificationRegion(eq(displayId), any(), anyBoolean()); when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2); final Region result = mServiceConnection.getMagnificationRegion(displayId); assertThat(result.isEmpty(), is(true)); } @Test public void getCurrentMagnificationRegion_returnRegion() { final int displayId = 1; final Region region = new Region(10, 20, 100, 200); doAnswer((invocation) -> { ((Region) invocation.getArguments()[1]).set(region); return null; }).when(mMockMagnificationProcessor).getCurrentMagnificationRegion(eq(displayId), any(), anyBoolean()); final Region result = mServiceConnection.getCurrentMagnificationRegion(displayId); assertEquals(result, region); } @Test public void getMagnificationCenterX_serviceNotBelongCurrentUser_returnZero() { final int displayId = 1; final float centerX = 480.0f; when(mMockMagnificationProcessor.getCenterX(displayId, /* canControlMagnification= */ true)).thenReturn(centerX); when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2); final float result = mServiceConnection.getMagnificationCenterX(displayId); assertThat(result, is(0.0f)); } @Test public void getMagnificationCenterY_serviceNotBelongCurrentUser_returnZero() { final int displayId = 1; final float centerY = 640.0f; when(mMockMagnificationProcessor.getCenterY(displayId, /* canControlMagnification= */ true)).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(mMockMagnificationProcessor.resetFullscreenMagnification(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(mMockMagnificationProcessor.resetFullscreenMagnification(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(mMockMagnificationProcessor.resetFullscreenMagnification(displayId, true)).thenReturn( true); when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2); final boolean result = mServiceConnection.resetMagnification(displayId, true); assertThat(result, is(false)); } @Test public void setMagnificationConfig_cantControlMagnification_returnFalse() { final int displayId = 1; final float scale = 1.8f; final float centerX = 50.5f; final float centerY = 100.5f; MagnificationConfig config = new MagnificationConfig.Builder() .setScale(scale) .setCenterX(centerX) .setCenterY(centerY).build(); when(mMockMagnificationProcessor.setMagnificationConfig(displayId, config, true, SERVICE_ID)).thenReturn(true); when(mMockSecurityPolicy.canControlMagnification(mServiceConnection)).thenReturn(false); final boolean result = mServiceConnection.setMagnificationConfig(displayId, config, true); assertFalse(result); } @Test public void setMagnificationConfig_serviceNotBelongCurrentUser_returnFalse() { final int displayId = 1; final float scale = 1.8f; final float centerX = 50.5f; final float centerY = 100.5f; MagnificationConfig config = new MagnificationConfig.Builder() .setScale(scale) .setCenterX(centerX) .setCenterY(centerY).build(); when(mMockMagnificationProcessor.setMagnificationConfig(displayId, config, true, SERVICE_ID)).thenReturn(true); when(mMockSystemSupport.getCurrentUserIdLocked()).thenReturn(USER_ID2); final boolean result = mServiceConnection.setMagnificationConfig(displayId, config, true); assertFalse(result); } @Test (expected = SecurityException.class) public void takeScreenshot_withoutCapability_throwSecurityException() { // no canTakeScreenshot, should throw security exception. when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false); mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY, new RemoteCallback((result) -> { })); } @Test public void takeScreenshot_NoA11yAccess_returnErrorCode() throws InterruptedException { // no checkAccessibilityAccess, should return error code. when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true); when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false); mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY, new RemoteCallback(mMockListener)); mHandler.sendLastMessage(); verify(mMockListener).onResult(Mockito.argThat( bundle -> ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS == bundle.getInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS))); } @Test public void takeScreenshot_invalidDisplay_returnErrorCode() throws InterruptedException { when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true); when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(true); final DisplayManager displayManager = new DisplayManager(mMockContext); when(mMockContext.getSystemService(Context.DISPLAY_SERVICE)).thenReturn(displayManager); mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY + 1, new RemoteCallback(mMockListener)); mHandler.sendLastMessage(); verify(mMockListener).onResult(Mockito.argThat( bundle -> ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY == bundle.getInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS))); } 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 infos, int windowId, boolean isPip, int displayId) { final AccessibilityWindowInfo info = AccessibilityWindowInfo.obtain(); info.setId(windowId); info.setDisplayId(displayId); 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 replacerList = Arrays.asList(nodeFromReplacer); replacedCallback.setFindAccessibilityNodeInfoResult(nodeFromApp, INTERACTION_ID); replacedCallback.setFindAccessibilityNodeInfosResult(replacerList, INTERACTION_ID + 1); final ArgumentCaptor 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, AccessibilityTrace trace, WindowManagerInternal windowManagerInternal, SystemActionPerformer systemActionPerfomer, AccessibilityWindowManager a11yWindowManager) { super(context, componentName, accessibilityServiceInfo, id, mainHandler, lock, securityPolicy, systemSupport, trace, windowManagerInternal, systemActionPerfomer, a11yWindowManager); mResolvedUserId = USER_ID; } @Override protected boolean hasRightsToCurrentUserLocked() { 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 switchToInputMethod(String imeId) { return false; } @Override public int setInputMethodEnabled(String imeId, boolean enabled) throws RemoteException { return AccessibilityService.SoftKeyboardController.ENABLE_IME_FAIL_UNKNOWN; } @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) {} } }