1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.accessibility;
18 
19 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_AUDIBLE;
20 import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
21 import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
22 import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
23 import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
24 
25 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
26 
27 import android.accessibilityservice.AccessibilityServiceInfo;
28 import android.accessibilityservice.AccessibilityTrace;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.res.Resources;
32 import android.graphics.Color;
33 import android.os.Handler;
34 import android.view.accessibility.AccessibilityEvent;
35 
36 import static com.google.common.truth.Truth.assertThat;
37 
38 import static org.junit.Assert.assertThrows;
39 import static org.mockito.ArgumentMatchers.any;
40 import static org.mockito.Mockito.verify;
41 import static org.mockito.Mockito.when;
42 
43 import com.android.server.wm.WindowManagerInternal;
44 
45 import org.junit.Before;
46 import org.junit.Test;
47 import org.mockito.Mock;
48 import org.mockito.MockitoAnnotations;
49 
50 import java.util.ArrayList;
51 import java.util.List;
52 
53 public class ProxyAccessibilityServiceConnectionTest {
54     private static final int DISPLAY_ID = 1000;
55     private static final int DEVICE_ID = 2000;
56     private static final int CONNECTION_ID = 1000;
57     private static final ComponentName COMPONENT_NAME = new ComponentName(
58             "com.android.server.accessibility", ".ProxyAccessibilityServiceConnectionTest");
59     public static final int NON_INTERACTIVE_UI_TIMEOUT_100MS = 100;
60     public static final int NON_INTERACTIVE_UI_TIMEOUT_200MS = 200;
61     public static final int INTERACTIVE_UI_TIMEOUT_100MS = 100;
62     public static final int INTERACTIVE_UI_TIMEOUT_200MS = 200;
63     public static final int NOTIFICATION_TIMEOUT_100MS = 100;
64     public static final int NOTIFICATION_TIMEOUT_200MS = 200;
65     public static final String PACKAGE_1 = "package 1";
66     public static final String PACKAGE_2 = "package 2";
67     public static final String PACKAGE_3 = "package 3";
68 
69     @Mock
70     Context mMockContext;
71     @Mock
72     Object mMockLock;
73     @Mock
74     AccessibilitySecurityPolicy mMockSecurityPolicy;
75     @Mock
76     AccessibilityWindowManager mMockA11yWindowManager;
77     @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
78     @Mock
79     AccessibilityTrace mMockA11yTrace;
80     @Mock
81     WindowManagerInternal mMockWindowManagerInternal;
82     ProxyAccessibilityServiceConnection mProxyConnection;
83     AccessibilityServiceInfo mAccessibilityServiceInfo;
84     private int mFocusStrokeWidthDefaultValue;
85     private int mFocusColorDefaultValue;
86 
87     @Before
setup()88     public void setup() {
89         final Resources resources = getInstrumentation().getContext().getResources();
90         MockitoAnnotations.initMocks(this);
91         when(mMockContext.getResources()).thenReturn(resources);
92         when(mMockSecurityPolicy.checkAccessibilityAccess(any())).thenReturn(true);
93 
94         mAccessibilityServiceInfo = new AccessibilityServiceInfo();
95         mProxyConnection = new ProxyAccessibilityServiceConnection(mMockContext, COMPONENT_NAME,
96                 mAccessibilityServiceInfo, CONNECTION_ID , new Handler(
97                         getInstrumentation().getContext().getMainLooper()),
98                 mMockLock, mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace,
99                 mMockWindowManagerInternal, mMockA11yWindowManager, DISPLAY_ID, DEVICE_ID);
100 
101         mFocusStrokeWidthDefaultValue = mProxyConnection.getFocusStrokeWidthLocked();
102         mFocusColorDefaultValue = mProxyConnection.getFocusColorLocked();
103     }
104 
105     @Test
testSetInstalledAndEnabledServices_updateInfos_notifiesSystemOfProxyChange()106     public void testSetInstalledAndEnabledServices_updateInfos_notifiesSystemOfProxyChange() {
107         final List<AccessibilityServiceInfo> infos = new ArrayList<>();
108         final AccessibilityServiceInfo info1 = new AccessibilityServiceInfo();
109         infos.add(info1);
110 
111         mProxyConnection.setInstalledAndEnabledServices(infos);
112 
113         verify(mMockSystemSupport).onProxyChanged(DEVICE_ID);
114     }
115 
116     @Test
testSetFocusAppearance_updateAppearance_notifiesSystemOfProxyChange()117     public void testSetFocusAppearance_updateAppearance_notifiesSystemOfProxyChange() {
118         final int updatedWidth = mFocusStrokeWidthDefaultValue + 10;
119         final int updatedColor = mFocusColorDefaultValue
120                 == Color.BLUE ? Color.RED : Color.BLUE;
121 
122         mProxyConnection.setFocusAppearance(updatedWidth, updatedColor);
123 
124         verify(mMockSystemSupport).onProxyChanged(DEVICE_ID);
125     }
126 
127     @Test
testSetInstalledAndEnabledServices_returnList()128     public void testSetInstalledAndEnabledServices_returnList() {
129         final List<AccessibilityServiceInfo> infos = new ArrayList<>();
130         final AccessibilityServiceInfo info1 = new AccessibilityServiceInfo();
131         final AccessibilityServiceInfo info2 = new AccessibilityServiceInfo();
132         infos.add(info1);
133         infos.add(info2);
134 
135         mProxyConnection.setInstalledAndEnabledServices(infos);
136         final List<AccessibilityServiceInfo> infoList =
137                 mProxyConnection.getInstalledAndEnabledServices();
138 
139         assertThat(infoList.size()).isEqualTo(2);
140         assertThat(infoList.get(0)).isEqualTo(info1);
141         assertThat(infoList.get(1)).isEqualTo(info2);
142     }
143 
144     @Test
testSetInstalledAndEnabledServices_defaultNamesPopulated()145     public void testSetInstalledAndEnabledServices_defaultNamesPopulated() {
146         final List<AccessibilityServiceInfo> infos = new ArrayList<>();
147         final AccessibilityServiceInfo info1 = new AccessibilityServiceInfo();
148         infos.add(info1);
149 
150         mProxyConnection.setInstalledAndEnabledServices(infos);
151         final List<AccessibilityServiceInfo> infoList =
152                 mProxyConnection.getInstalledAndEnabledServices();
153 
154         assertThat(infoList.get(0).getComponentName()).isNotNull();
155         assertThat(infoList.get(0).getResolveInfo()).isNotNull();
156     }
157 
158     @Test
testSetInstalledAndEnabledServices_connectionInfoIsUnion()159     public void testSetInstalledAndEnabledServices_connectionInfoIsUnion() {
160         final List<AccessibilityServiceInfo> infos = new ArrayList<>();
161         final AccessibilityServiceInfo info1 = new AccessibilityServiceInfo();
162         info1.setAccessibilityTool(true);
163         info1.packageNames = new String[]{PACKAGE_1, PACKAGE_2};
164         info1.setInteractiveUiTimeoutMillis(INTERACTIVE_UI_TIMEOUT_200MS);
165         info1.setNonInteractiveUiTimeoutMillis(NON_INTERACTIVE_UI_TIMEOUT_100MS);
166         info1.notificationTimeout = NOTIFICATION_TIMEOUT_100MS;
167         info1.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED;
168         info1.feedbackType = FEEDBACK_AUDIBLE;
169         info1.flags = FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
170         infos.add(info1);
171 
172         final AccessibilityServiceInfo info2 = new AccessibilityServiceInfo();
173         info2.packageNames = new String[]{PACKAGE_2, PACKAGE_3};
174         info2.setInteractiveUiTimeoutMillis(INTERACTIVE_UI_TIMEOUT_100MS);
175         info2.setNonInteractiveUiTimeoutMillis(NON_INTERACTIVE_UI_TIMEOUT_200MS);
176         info2.notificationTimeout = NOTIFICATION_TIMEOUT_200MS;
177         info2.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
178         info2.feedbackType = FEEDBACK_GENERIC;
179         info2.flags = FLAG_REQUEST_TOUCH_EXPLORATION_MODE | FLAG_REPORT_VIEW_IDS;
180         infos.add(info2);
181 
182         mProxyConnection.setInstalledAndEnabledServices(infos);
183 
184         assertThat(mAccessibilityServiceInfo.isAccessibilityTool()).isTrue();
185         // Package 1, 2, 3
186         assertThat(mAccessibilityServiceInfo.packageNames).asList().containsExactly(
187                 PACKAGE_1, PACKAGE_2, PACKAGE_3);
188         assertThat(mAccessibilityServiceInfo.getInteractiveUiTimeoutMillis()).isEqualTo(
189                 INTERACTIVE_UI_TIMEOUT_200MS);
190         assertThat(mAccessibilityServiceInfo.getNonInteractiveUiTimeoutMillis()).isEqualTo(
191                 NON_INTERACTIVE_UI_TIMEOUT_200MS);
192         assertThat(mAccessibilityServiceInfo.notificationTimeout).isEqualTo(
193                 NOTIFICATION_TIMEOUT_200MS);
194         assertThat(mAccessibilityServiceInfo.eventTypes).isEqualTo(
195                 AccessibilityEvent.TYPES_ALL_MASK);
196         assertThat(mAccessibilityServiceInfo.feedbackType).isEqualTo(FEEDBACK_AUDIBLE
197                 | FEEDBACK_GENERIC);
198         assertThat(mAccessibilityServiceInfo.flags).isEqualTo(FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
199                 | FLAG_REQUEST_TOUCH_EXPLORATION_MODE | FLAG_REPORT_VIEW_IDS);
200     }
201 
202     @Test
testSetInstalledAndEnabledServices_emptyPackageNames_packageNamesIsNull()203     public void testSetInstalledAndEnabledServices_emptyPackageNames_packageNamesIsNull() {
204         final List<AccessibilityServiceInfo> infos = new ArrayList<>();
205         final AccessibilityServiceInfo info1 = new AccessibilityServiceInfo();
206         info1.packageNames = new String[]{PACKAGE_1, PACKAGE_2};
207         infos.add(info1);
208         final AccessibilityServiceInfo info2 = new AccessibilityServiceInfo();
209         infos.add(info2);
210 
211         mProxyConnection.setInstalledAndEnabledServices(infos);
212 
213         final String[] packageNames = mAccessibilityServiceInfo.packageNames;
214         assertThat(packageNames).isNull();
215     }
216 
217     @Test
testSetServiceInfo_setIllegalOperationExceptionThrown()218     public void testSetServiceInfo_setIllegalOperationExceptionThrown() {
219         UnsupportedOperationException thrown =
220                 assertThrows(
221                         UnsupportedOperationException.class,
222                         () -> mProxyConnection.setServiceInfo(new AccessibilityServiceInfo()));
223 
224         assertThat(thrown).hasMessageThat().contains("setServiceInfo is not supported");
225     }
226 
227     @Test
testDisableSelf_setIllegalOperationExceptionThrown_()228     public void testDisableSelf_setIllegalOperationExceptionThrown_() {
229         UnsupportedOperationException thrown =
230                 assertThrows(
231                         UnsupportedOperationException.class,
232                         () -> mProxyConnection.disableSelf());
233 
234         assertThat(thrown).hasMessageThat().contains("disableSelf is not supported");
235     }
236 }
237