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) {}
+    }
+}