1 /*
2  * Copyright (C) 2021 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.app.AlarmManager.RTC_WAKEUP;
20 
21 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
22 
23 import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_A11Y_VIEW_AND_CONTROL_ACCESS;
24 
25 import static com.google.common.truth.Truth.assertThat;
26 
27 import static junit.framework.Assert.assertEquals;
28 
29 import static org.mockito.ArgumentMatchers.any;
30 import static org.mockito.ArgumentMatchers.anyLong;
31 import static org.mockito.ArgumentMatchers.eq;
32 import static org.mockito.Mockito.never;
33 import static org.mockito.Mockito.verify;
34 import static org.mockito.Mockito.when;
35 
36 import android.accessibilityservice.AccessibilityServiceInfo;
37 import android.app.AlarmManager;
38 import android.app.Notification;
39 import android.app.NotificationManager;
40 import android.app.StatusBarManager;
41 import android.content.ComponentName;
42 import android.content.Context;
43 import android.content.Intent;
44 import android.content.pm.ResolveInfo;
45 import android.content.pm.ServiceInfo;
46 import android.os.Bundle;
47 import android.os.UserHandle;
48 import android.provider.Settings;
49 import android.testing.TestableContext;
50 import android.util.ArraySet;
51 
52 import com.google.common.collect.ImmutableSet;
53 
54 import org.junit.Before;
55 import org.junit.Rule;
56 import org.junit.Test;
57 import org.mockito.ArgumentCaptor;
58 import org.mockito.Mock;
59 import org.mockito.Mockito;
60 import org.mockito.MockitoAnnotations;
61 
62 import java.util.ArrayList;
63 import java.util.HashSet;
64 import java.util.List;
65 import java.util.Set;
66 
67 /**
68  * Tests for the {@link PolicyWarningUIController}.
69  */
70 public class PolicyWarningUIControllerTest {
71     private static final int TEST_USER_ID = UserHandle.USER_SYSTEM;
72     private static final ComponentName TEST_COMPONENT_NAME = new ComponentName(
73             "com.android.server.accessibility", "PolicyWarningUIControllerTest");
74 
75     private static final ComponentName TEST_COMPONENT_NAME2 = new ComponentName(
76             "com.android.server.accessibility", "nonAccessibilityToolService");
77     private final List<AccessibilityServiceInfo> mEnabledServiceList = new ArrayList<>();
78 
79     @Rule
80     public final A11yTestableContext mContext = new A11yTestableContext(
81             getInstrumentation().getTargetContext());
82     @Mock
83     private AlarmManager mAlarmManager;
84     @Mock
85     private NotificationManager mNotificationManager;
86     @Mock
87     private StatusBarManager mStatusBarManager;
88     @Mock
89     private ServiceInfo mMockServiceInfo;
90     @Mock
91     private Context mSpyContext;
92 
93     private PolicyWarningUIController mPolicyWarningUIController;
94     private FakeNotificationController mFakeNotificationController;
95 
96     @Before
setUp()97     public void setUp() {
98         MockitoAnnotations.initMocks(this);
99         mContext.addMockSystemService(AlarmManager.class, mAlarmManager);
100         mContext.addMockSystemService(NotificationManager.class, mNotificationManager);
101         mContext.addMockSystemService(StatusBarManager.class, mStatusBarManager);
102         mFakeNotificationController = new FakeNotificationController(mContext);
103         mPolicyWarningUIController = new PolicyWarningUIController(
104                 getInstrumentation().getTargetContext().getMainThreadHandler(), mContext,
105                 mFakeNotificationController);
106         mEnabledServiceList.clear();
107         Settings.Secure.putStringForUser(mContext.getContentResolver(),
108                 Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES,
109                 "", TEST_USER_ID);
110         mPolicyWarningUIController.enableSendingNonA11yToolNotification(true);
111         mPolicyWarningUIController.onSwitchUser(TEST_USER_ID, new HashSet<>());
112         getInstrumentation().waitForIdleSync();
113     }
114 
115     @Test
receiveActionSendNotification_isNonA11yCategoryService_sendNotification()116     public void receiveActionSendNotification_isNonA11yCategoryService_sendNotification() {
117         addEnabledServiceInfo(TEST_COMPONENT_NAME, false);
118 
119         mFakeNotificationController.onReceive(mContext,
120                 PolicyWarningUIController.createIntent(mContext, TEST_USER_ID,
121                         PolicyWarningUIController.ACTION_SEND_NOTIFICATION,
122                         TEST_COMPONENT_NAME));
123 
124         verify(mNotificationManager).notify(eq(TEST_COMPONENT_NAME.flattenToShortString()),
125                 eq(NOTE_A11Y_VIEW_AND_CONTROL_ACCESS), any(Notification.class));
126     }
127 
128     @Test
receiveActionSendNotification_sendNotificationDisabled_doNothing()129     public void receiveActionSendNotification_sendNotificationDisabled_doNothing() {
130         mPolicyWarningUIController.enableSendingNonA11yToolNotification(false);
131         addEnabledServiceInfo(TEST_COMPONENT_NAME, false);
132 
133         mFakeNotificationController.onReceive(mContext,
134                 PolicyWarningUIController.createIntent(mContext, TEST_USER_ID,
135                         PolicyWarningUIController.ACTION_SEND_NOTIFICATION,
136                         TEST_COMPONENT_NAME));
137 
138         verify(mNotificationManager, never()).notify(eq(TEST_COMPONENT_NAME.flattenToShortString()),
139                 eq(NOTE_A11Y_VIEW_AND_CONTROL_ACCESS), any(Notification.class));
140     }
141 
142     @Test
receiveActionSendNotificationWithNotifiedService_doNothing()143     public void receiveActionSendNotificationWithNotifiedService_doNothing() {
144         Settings.Secure.putStringForUser(mContext.getContentResolver(),
145                 Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES,
146                 TEST_COMPONENT_NAME.flattenToShortString(), TEST_USER_ID);
147         mEnabledServiceList.clear();
148         mPolicyWarningUIController.onSwitchUser(TEST_USER_ID, new HashSet<>());
149         getInstrumentation().waitForIdleSync();
150         addEnabledServiceInfo(TEST_COMPONENT_NAME, false);
151 
152         mFakeNotificationController.onReceive(mContext,
153                 PolicyWarningUIController.createIntent(mContext, TEST_USER_ID,
154                         PolicyWarningUIController.ACTION_SEND_NOTIFICATION,
155                         TEST_COMPONENT_NAME));
156 
157         verify(mNotificationManager, never()).notify(eq(TEST_COMPONENT_NAME.flattenToShortString()),
158                 eq(NOTE_A11Y_VIEW_AND_CONTROL_ACCESS), any(Notification.class));
159     }
160 
161     @Test
receiveActionA11ySettings_launchA11ySettingsAndDismissNotification()162     public void receiveActionA11ySettings_launchA11ySettingsAndDismissNotification() {
163         mFakeNotificationController.onReceive(mContext,
164                 PolicyWarningUIController.createIntent(mContext, TEST_USER_ID,
165                         PolicyWarningUIController.ACTION_A11Y_SETTINGS,
166                         TEST_COMPONENT_NAME));
167 
168         verifyLaunchA11ySettings();
169         verify(mNotificationManager).cancel(TEST_COMPONENT_NAME.flattenToShortString(),
170                 NOTE_A11Y_VIEW_AND_CONTROL_ACCESS);
171         assertNotifiedSettingsEqual(TEST_USER_ID, TEST_COMPONENT_NAME.flattenToShortString());
172     }
173 
174     @Test
receiveActionDismissNotification_addToNotifiedSettings()175     public void receiveActionDismissNotification_addToNotifiedSettings() {
176         mFakeNotificationController.onReceive(mContext,
177                 PolicyWarningUIController.createIntent(mContext, TEST_USER_ID,
178                         PolicyWarningUIController.ACTION_DISMISS_NOTIFICATION,
179                         TEST_COMPONENT_NAME));
180 
181         assertNotifiedSettingsEqual(TEST_USER_ID, TEST_COMPONENT_NAME.flattenToShortString());
182     }
183 
184     @Test
onEnabledServicesChangedLocked_serviceDisabled_removedFromNotifiedSettings()185     public void onEnabledServicesChangedLocked_serviceDisabled_removedFromNotifiedSettings() {
186         final Set<ComponentName> enabledServices = new HashSet<>();
187         enabledServices.add(TEST_COMPONENT_NAME);
188         mPolicyWarningUIController.onEnabledServicesChanged(TEST_USER_ID, enabledServices);
189         getInstrumentation().waitForIdleSync();
190         receiveActionDismissNotification_addToNotifiedSettings();
191 
192         mPolicyWarningUIController.onEnabledServicesChanged(TEST_USER_ID, new HashSet<>());
193         getInstrumentation().waitForIdleSync();
194 
195         assertNotifiedSettingsEqual(TEST_USER_ID, "");
196     }
197 
198     @Test
onNonA11yCategoryServiceBound_setAlarm()199     public void onNonA11yCategoryServiceBound_setAlarm() {
200         mPolicyWarningUIController.onNonA11yCategoryServiceBound(TEST_USER_ID, TEST_COMPONENT_NAME);
201         getInstrumentation().waitForIdleSync();
202 
203         verify(mAlarmManager).set(eq(RTC_WAKEUP), anyLong(),
204                 eq(PolicyWarningUIController.createPendingIntent(mContext, TEST_USER_ID,
205                         PolicyWarningUIController.ACTION_SEND_NOTIFICATION, TEST_COMPONENT_NAME)));
206     }
207 
208     @Test
onNonA11yCategoryServiceUnbound_cancelAlarm()209     public void onNonA11yCategoryServiceUnbound_cancelAlarm() {
210         mPolicyWarningUIController.onNonA11yCategoryServiceUnbound(TEST_USER_ID,
211                 TEST_COMPONENT_NAME);
212         getInstrumentation().waitForIdleSync();
213 
214         verify(mAlarmManager).cancel(
215                 eq(PolicyWarningUIController.createPendingIntent(mContext, TEST_USER_ID,
216                         PolicyWarningUIController.ACTION_SEND_NOTIFICATION, TEST_COMPONENT_NAME)));
217     }
218 
219     @Test
onSwitchUserLocked_hasAlarmAndSentNotification_cancelNotification()220     public void onSwitchUserLocked_hasAlarmAndSentNotification_cancelNotification() {
221         addEnabledServiceInfo(TEST_COMPONENT_NAME2, false);
222         final Set<ComponentName> enabledNonA11yServices = new ArraySet<>();
223         enabledNonA11yServices.add(TEST_COMPONENT_NAME);
224         enabledNonA11yServices.add(TEST_COMPONENT_NAME2);
225         mPolicyWarningUIController.onEnabledServicesChanged(TEST_USER_ID,
226                 enabledNonA11yServices);
227         mPolicyWarningUIController.onNonA11yCategoryServiceBound(TEST_USER_ID, TEST_COMPONENT_NAME);
228         mFakeNotificationController.onReceive(mContext,
229                 PolicyWarningUIController.createIntent(mContext, TEST_USER_ID,
230                         PolicyWarningUIController.ACTION_SEND_NOTIFICATION,
231                         TEST_COMPONENT_NAME2));
232         getInstrumentation().waitForIdleSync();
233 
234         mPolicyWarningUIController.onSwitchUser(TEST_USER_ID,
235                 ImmutableSet.copyOf(new ArraySet<>()));
236         getInstrumentation().waitForIdleSync();
237 
238         verify(mNotificationManager).cancel(TEST_COMPONENT_NAME2.flattenToShortString(),
239                 NOTE_A11Y_VIEW_AND_CONTROL_ACCESS);
240     }
241 
assertNotifiedSettingsEqual(int userId, String settingString)242     private void assertNotifiedSettingsEqual(int userId, String settingString) {
243         final String notifiedServicesSetting = Settings.Secure.getStringForUser(
244                 mContext.getContentResolver(),
245                 Settings.Secure.NOTIFIED_NON_ACCESSIBILITY_CATEGORY_SERVICES,
246                 userId);
247         assertEquals(settingString, notifiedServicesSetting);
248     }
249 
verifyLaunchA11ySettings()250     private void verifyLaunchA11ySettings() {
251         final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
252         final ArgumentCaptor<UserHandle> userHandleCaptor = ArgumentCaptor.forClass(
253                 UserHandle.class);
254         verify(mSpyContext).startActivityAsUser(intentCaptor.capture(),
255                 any(), userHandleCaptor.capture());
256         assertThat(intentCaptor.getValue().getAction()).isEqualTo(
257                 Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
258         assertThat(userHandleCaptor.getValue().getIdentifier()).isEqualTo(TEST_USER_ID);
259         verify(mStatusBarManager).collapsePanels();
260     }
261 
addEnabledServiceInfo(ComponentName componentName, boolean isAccessibilityTool)262     private void addEnabledServiceInfo(ComponentName componentName, boolean isAccessibilityTool) {
263         final AccessibilityServiceInfo a11yServiceInfo = Mockito.mock(
264                 AccessibilityServiceInfo.class);
265         when(a11yServiceInfo.getComponentName()).thenReturn(componentName);
266         when(a11yServiceInfo.isAccessibilityTool()).thenReturn(isAccessibilityTool);
267         final ResolveInfo resolveInfo = Mockito.mock(ResolveInfo.class);
268         when(a11yServiceInfo.getResolveInfo()).thenReturn(resolveInfo);
269         resolveInfo.serviceInfo = mMockServiceInfo;
270         mEnabledServiceList.add(a11yServiceInfo);
271     }
272 
273     private class A11yTestableContext extends TestableContext {
A11yTestableContext(Context base)274         A11yTestableContext(Context base) {
275             super(base);
276         }
277 
278         @Override
startActivityAsUser(Intent intent, Bundle options, UserHandle user)279         public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
280             mSpyContext.startActivityAsUser(intent, options, user);
281         }
282     }
283 
284     private class FakeNotificationController extends
285             PolicyWarningUIController.NotificationController {
FakeNotificationController(Context context)286         FakeNotificationController(Context context) {
287             super(context);
288         }
289 
290         @Override
getEnabledServiceInfos()291         protected List<AccessibilityServiceInfo> getEnabledServiceInfos() {
292             return mEnabledServiceList;
293         }
294     }
295 }
296