blob: c17c36eba2dc519812fda9d7971a366bdd412859 [file] [log] [blame]
Phil Weaver106fe732016-11-22 18:18:39 -08001/*
2 * Copyright (C) 2016 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
Phil Weaverb9f06122017-11-30 10:48:17 -080017package com.android.internal.accessibility;
Phil Weaver106fe732016-11-22 18:18:39 -080018
Rhed Jao6182af72018-08-22 18:48:59 +080019import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN;
Rhed Jao6182af72018-08-22 18:48:59 +080020import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN;
21import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
Rhed Jao6dad25d2019-10-22 22:15:05 +080022import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
Rhed Jao6182af72018-08-22 18:48:59 +080023
24import static junit.framework.Assert.assertEquals;
25import static junit.framework.Assert.assertFalse;
26import static junit.framework.Assert.assertTrue;
27
28import static org.junit.Assert.fail;
29import static org.mockito.AdditionalMatchers.aryEq;
30import static org.mockito.ArgumentMatchers.any;
31import static org.mockito.ArgumentMatchers.anyLong;
32import static org.mockito.Matchers.anyBoolean;
33import static org.mockito.Matchers.anyInt;
34import static org.mockito.Matchers.anyObject;
35import static org.mockito.Matchers.eq;
36import static org.mockito.Mockito.atLeastOnce;
37import static org.mockito.Mockito.doAnswer;
Dieter Hsu0dbbb422020-02-12 14:52:59 +080038import static org.mockito.Mockito.doReturn;
Rhed Jao6182af72018-08-22 18:48:59 +080039import static org.mockito.Mockito.mock;
40import static org.mockito.Mockito.times;
41import static org.mockito.Mockito.verify;
42import static org.mockito.Mockito.verifyZeroInteractions;
43import static org.mockito.Mockito.when;
44
Phil Weaver106fe732016-11-22 18:18:39 -080045import android.accessibilityservice.AccessibilityServiceInfo;
46import android.app.AlertDialog;
47import android.content.ComponentName;
48import android.content.Context;
49import android.content.DialogInterface;
Phil Weaver32ea3722017-03-13 11:32:01 -070050import android.content.pm.ApplicationInfo;
Phil Weaverb9f06122017-11-30 10:48:17 -080051import android.content.pm.PackageManager;
Phil Weaver106fe732016-11-22 18:18:39 -080052import android.content.pm.ResolveInfo;
Rhed Jao6dad25d2019-10-22 22:15:05 +080053import android.content.pm.ServiceInfo;
Phil Weaver106fe732016-11-22 18:18:39 -080054import android.content.res.Resources;
Rhed Jao6182af72018-08-22 18:48:59 +080055import android.media.Ringtone;
Rhed Jao6dad25d2019-10-22 22:15:05 +080056import android.os.Build;
Phil Weaver106fe732016-11-22 18:18:39 -080057import android.os.Handler;
Rhed Jao6182af72018-08-22 18:48:59 +080058import android.os.Message;
Phil Weaver32ea3722017-03-13 11:32:01 -070059import android.os.Vibrator;
Phil Weaver106fe732016-11-22 18:18:39 -080060import android.provider.Settings;
Rhed Jao6182af72018-08-22 18:48:59 +080061import android.speech.tts.TextToSpeech;
62import android.speech.tts.Voice;
Phil Weaver106fe732016-11-22 18:18:39 -080063import android.test.mock.MockContentResolver;
64import android.text.TextUtils;
65import android.view.Window;
66import android.view.WindowManager;
67import android.view.accessibility.AccessibilityManager;
68import android.view.accessibility.IAccessibilityManager;
69import android.widget.Toast;
Rhed Jao6182af72018-08-22 18:48:59 +080070
Tadashi G. Takaokab4470f22019-01-15 18:29:15 +090071import androidx.test.runner.AndroidJUnit4;
72
Phil Weaver106fe732016-11-22 18:18:39 -080073import com.android.internal.R;
Phil Weaverb9f06122017-11-30 10:48:17 -080074import com.android.internal.accessibility.AccessibilityShortcutController.FrameworkObjectProvider;
Rhed Jao6182af72018-08-22 18:48:59 +080075import com.android.internal.util.test.FakeSettingsProvider;
Phil Weaver106fe732016-11-22 18:18:39 -080076
Dieter Hsuf2da6b62019-07-16 19:59:16 +080077import org.junit.AfterClass;
Phil Weaver106fe732016-11-22 18:18:39 -080078import org.junit.Before;
79import org.junit.Test;
80import org.junit.runner.RunWith;
81import org.mockito.ArgumentCaptor;
82import org.mockito.Mock;
83import org.mockito.MockitoAnnotations;
Rhed Jao6182af72018-08-22 18:48:59 +080084import org.mockito.invocation.InvocationOnMock;
Phil Weaver106fe732016-11-22 18:18:39 -080085
Paul Duffin192bb0b2017-03-09 18:49:41 +000086import java.lang.reflect.Field;
Phil Weaver106fe732016-11-22 18:18:39 -080087import java.util.Collections;
Dieter Hsu0dbbb422020-02-12 14:52:59 +080088import java.util.HashSet;
Phil Weaverb9f06122017-11-30 10:48:17 -080089import java.util.Map;
Dieter Hsu0dbbb422020-02-12 14:52:59 +080090import java.util.Set;
Phil Weaver106fe732016-11-22 18:18:39 -080091
Phil Weaver106fe732016-11-22 18:18:39 -080092
93@RunWith(AndroidJUnit4.class)
94public class AccessibilityShortcutControllerTest {
95 private static final String SERVICE_NAME_STRING = "fake.package/fake.service.name";
menghanli3cba1dc2020-03-18 20:54:27 +080096 private static final CharSequence PACKAGE_NAME_STRING = "Service name";
Phil Weaverb9f06122017-11-30 10:48:17 -080097 private static final String SERVICE_NAME_SUMMARY = "Summary";
Phil Weaver32ea3722017-03-13 11:32:01 -070098 private static final long VIBRATOR_PATTERN_1 = 100L;
99 private static final long VIBRATOR_PATTERN_2 = 150L;
100 private static final int[] VIBRATOR_PATTERN_INT = {(int) VIBRATOR_PATTERN_1,
101 (int) VIBRATOR_PATTERN_2};
102 private static final long[] VIBRATOR_PATTERN_LONG = {VIBRATOR_PATTERN_1, VIBRATOR_PATTERN_2};
Phil Weaver106fe732016-11-22 18:18:39 -0800103
Phil Weaverce687c52017-03-15 08:51:52 -0700104 // Convenience values for enabling/disabling to make code more readable
Phil Weaverce687c52017-03-15 08:51:52 -0700105 private static final int ENABLED_EXCEPT_LOCK_SCREEN = 1;
106 private static final int ENABLED_INCLUDING_LOCK_SCREEN = 2;
Phil Weaverce687c52017-03-15 08:51:52 -0700107
Phil Weaver106fe732016-11-22 18:18:39 -0800108 private @Mock Context mContext;
109 private @Mock FrameworkObjectProvider mFrameworkObjectProvider;
110 private @Mock IAccessibilityManager mAccessibilityManagerService;
111 private @Mock Handler mHandler;
112 private @Mock AlertDialog.Builder mAlertDialogBuilder;
113 private @Mock AlertDialog mAlertDialog;
114 private @Mock AccessibilityServiceInfo mServiceInfo;
115 private @Mock Resources mResources;
116 private @Mock Toast mToast;
Phil Weaver32ea3722017-03-13 11:32:01 -0700117 private @Mock Vibrator mVibrator;
118 private @Mock ApplicationInfo mApplicationInfo;
Phil Weaverb9f06122017-11-30 10:48:17 -0800119 private @Mock PackageManager mPackageManager;
Rhed Jao6182af72018-08-22 18:48:59 +0800120 private @Mock TextToSpeech mTextToSpeech;
121 private @Mock Voice mVoice;
122 private @Mock Ringtone mRingtone;
Phil Weaver106fe732016-11-22 18:18:39 -0800123
124 private MockContentResolver mContentResolver;
Phil Weaver71922972017-02-14 12:36:47 -0800125 private WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
Phil Weaver106fe732016-11-22 18:18:39 -0800126
127 @Before
128 public void setUp() throws Exception {
129 MockitoAnnotations.initMocks(this);
130
Phil Weaver32ea3722017-03-13 11:32:01 -0700131 when(mVibrator.hasVibrator()).thenReturn(true);
132
133 when(mContext.getResources()).thenReturn(mResources);
134 when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
135 when(mContext.getSystemService(Context.VIBRATOR_SERVICE)).thenReturn(mVibrator);
Phil Weaverb9f06122017-11-30 10:48:17 -0800136 when(mContext.getPackageManager()).thenReturn(mPackageManager);
137
138 // We're not checking the text. Just prevent us crashing when getting text.
139 when(mPackageManager.getText(any(), anyInt(), any())).thenReturn("text");
Phil Weaver32ea3722017-03-13 11:32:01 -0700140
Phil Weaver106fe732016-11-22 18:18:39 -0800141 mContentResolver = new MockContentResolver(mContext);
142 mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
Dieter Hsuf2da6b62019-07-16 19:59:16 +0800143 FakeSettingsProvider.clearSettingsProvider();
Phil Weaver106fe732016-11-22 18:18:39 -0800144 when(mContext.getContentResolver()).thenReturn(mContentResolver);
Phil Weaver106fe732016-11-22 18:18:39 -0800145
146 when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(anyInt()))
147 .thenReturn(Collections.singletonList(mServiceInfo));
148
149 // Use the extra level of indirection in the object to mock framework objects
150 AccessibilityManager accessibilityManager =
151 new AccessibilityManager(mHandler, mAccessibilityManagerService, 0);
152 when(mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext))
153 .thenReturn(accessibilityManager);
menghanli3cba1dc2020-03-18 20:54:27 +0800154 when(mContext.getSystemService(Context.ACCESSIBILITY_SERVICE))
155 .thenReturn(accessibilityManager);
Phil Weaver106fe732016-11-22 18:18:39 -0800156 when(mFrameworkObjectProvider.getAlertDialogBuilder(mContext))
157 .thenReturn(mAlertDialogBuilder);
158 when(mFrameworkObjectProvider.makeToastFromText(eq(mContext), anyObject(), anyInt()))
159 .thenReturn(mToast);
Phil Weaverb9f06122017-11-30 10:48:17 -0800160 when(mFrameworkObjectProvider.getSystemUiContext()).thenReturn(mContext);
Rhed Jao6182af72018-08-22 18:48:59 +0800161 when(mFrameworkObjectProvider.getTextToSpeech(eq(mContext), any()))
162 .thenReturn(mTextToSpeech);
163 when(mFrameworkObjectProvider.getRingtone(eq(mContext), any())).thenReturn(mRingtone);
Phil Weaver106fe732016-11-22 18:18:39 -0800164
165 when(mResources.getString(anyInt())).thenReturn("Howdy %s");
Rhed Jao6dad25d2019-10-22 22:15:05 +0800166 when(mResources.getString(R.string.config_defaultAccessibilityService)).thenReturn(null);
Phil Weaver32ea3722017-03-13 11:32:01 -0700167 when(mResources.getIntArray(anyInt())).thenReturn(VIBRATOR_PATTERN_INT);
168
Phil Weaver106fe732016-11-22 18:18:39 -0800169 ResolveInfo resolveInfo = mock(ResolveInfo.class);
Rhed Jao6dad25d2019-10-22 22:15:05 +0800170 resolveInfo.serviceInfo = mock(ServiceInfo.class);
171 resolveInfo.serviceInfo.applicationInfo = mApplicationInfo;
menghanli3cba1dc2020-03-18 20:54:27 +0800172 when(resolveInfo.loadLabel(anyObject())).thenReturn(PACKAGE_NAME_STRING);
Phil Weaver106fe732016-11-22 18:18:39 -0800173 when(mServiceInfo.getResolveInfo()).thenReturn(resolveInfo);
174 when(mServiceInfo.getComponentName())
175 .thenReturn(ComponentName.unflattenFromString(SERVICE_NAME_STRING));
Phil Weaverb9f06122017-11-30 10:48:17 -0800176 when(mServiceInfo.loadSummary(any())).thenReturn(SERVICE_NAME_SUMMARY);
Phil Weaver106fe732016-11-22 18:18:39 -0800177
menghanli3cba1dc2020-03-18 20:54:27 +0800178 when(mAlertDialogBuilder.setTitle(anyObject())).thenReturn(mAlertDialogBuilder);
Phil Weaver106fe732016-11-22 18:18:39 -0800179 when(mAlertDialogBuilder.setCancelable(anyBoolean())).thenReturn(mAlertDialogBuilder);
180 when(mAlertDialogBuilder.setMessage(anyObject())).thenReturn(mAlertDialogBuilder);
181 when(mAlertDialogBuilder.setPositiveButton(anyInt(), anyObject()))
182 .thenReturn(mAlertDialogBuilder);
183 when(mAlertDialogBuilder.setNegativeButton(anyInt(), anyObject()))
184 .thenReturn(mAlertDialogBuilder);
185 when(mAlertDialogBuilder.setOnCancelListener(anyObject())).thenReturn(mAlertDialogBuilder);
186 when(mAlertDialogBuilder.create()).thenReturn(mAlertDialog);
187
Phil Weaver71922972017-02-14 12:36:47 -0800188 mLayoutParams.privateFlags = 0;
189 when(mToast.getWindowParams()).thenReturn(mLayoutParams);
190
Phil Weaver106fe732016-11-22 18:18:39 -0800191 Window window = mock(Window.class);
Paul Duffin192bb0b2017-03-09 18:49:41 +0000192 // Initialize the mWindowAttributes field which was not properly initialized during mock
193 // creation.
194 try {
195 Field field = Window.class.getDeclaredField("mWindowAttributes");
196 field.setAccessible(true);
197 field.set(window, new WindowManager.LayoutParams());
198 } catch (Exception e) {
199 throw new RuntimeException("Unable to set mWindowAttributes", e);
200 }
Phil Weaver106fe732016-11-22 18:18:39 -0800201 when(mAlertDialog.getWindow()).thenReturn(window);
Rhed Jao6182af72018-08-22 18:48:59 +0800202
203 when(mTextToSpeech.getVoice()).thenReturn(mVoice);
Phil Weaver106fe732016-11-22 18:18:39 -0800204 }
205
Dieter Hsuf2da6b62019-07-16 19:59:16 +0800206 @AfterClass
207 public static void cleanUpSettingsProvider() {
208 FakeSettingsProvider.clearSettingsProvider();
Phil Weaver106fe732016-11-22 18:18:39 -0800209 }
210
211 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800212 public void testShortcutAvailable_enabledButNoServiceWhenCreated_shouldReturnFalse()
213 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700214 configureNoShortcutService();
215 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
216 assertFalse(getController().isAccessibilityShortcutAvailable(false));
Phil Weaver106fe732016-11-22 18:18:39 -0800217 }
218
219 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800220 public void testShortcutAvailable_enabledWithValidServiceWhenCreated_shouldReturnTrue()
221 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700222 configureValidShortcutService();
223 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
224 assertTrue(getController().isAccessibilityShortcutAvailable(false));
225 }
226
227 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800228 public void testShortcutAvailable_onLockScreenButDisabledThere_shouldReturnFalse()
229 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700230 configureValidShortcutService();
231 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
232 assertFalse(getController().isAccessibilityShortcutAvailable(true));
233 }
234
235 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800236 public void testShortcutAvailable_onLockScreenAndEnabledThere_shouldReturnTrue()
237 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700238 configureValidShortcutService();
239 configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
240 assertTrue(getController().isAccessibilityShortcutAvailable(true));
Phil Weaver106fe732016-11-22 18:18:39 -0800241 }
242
243 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800244 public void testShortcutAvailable_onLockScreenAndLockScreenPreferenceUnset() throws Exception {
Phil Weaverc5865d62018-03-02 16:00:43 -0800245 // When the user hasn't specified a lock screen preference, we allow from the lock screen
246 // as long as the user has agreed to enable the shortcut
247 configureValidShortcutService();
248 configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
249 Settings.Secure.putString(
250 mContentResolver, ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, null);
251 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
252 assertFalse(getController().isAccessibilityShortcutAvailable(true));
253 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
254 assertTrue(getController().isAccessibilityShortcutAvailable(true));
255 }
256
257 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800258 public void testShortcutAvailable_whenServiceIdBecomesNull_shouldReturnFalse()
259 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700260 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
261 configureValidShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800262 AccessibilityShortcutController accessibilityShortcutController = getController();
Rhed Jao6dad25d2019-10-22 22:15:05 +0800263 configureNoShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800264 accessibilityShortcutController.onSettingsChanged();
Phil Weaverce687c52017-03-15 08:51:52 -0700265 assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
Phil Weaver106fe732016-11-22 18:18:39 -0800266 }
267
268 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800269 public void testShortcutAvailable_whenServiceIdBecomesNonNull_shouldReturnTrue()
270 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700271 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
272 configureNoShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800273 AccessibilityShortcutController accessibilityShortcutController = getController();
Phil Weaverce687c52017-03-15 08:51:52 -0700274 configureValidShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800275 accessibilityShortcutController.onSettingsChanged();
Phil Weaverce687c52017-03-15 08:51:52 -0700276 assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
277 }
278
279 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800280 public void testShortcutAvailable_whenShortcutBecomesEnabled_shouldReturnTrue()
281 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700282 configureValidShortcutService();
283 AccessibilityShortcutController accessibilityShortcutController = getController();
284 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
285 accessibilityShortcutController.onSettingsChanged();
286 assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
287 }
288
289 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800290 public void testShortcutAvailable_whenLockscreenBecomesDisabled_shouldReturnFalse()
291 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700292 configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
293 configureValidShortcutService();
294 AccessibilityShortcutController accessibilityShortcutController = getController();
295 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
296 accessibilityShortcutController.onSettingsChanged();
297 assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(true));
298 }
299
300 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800301 public void testShortcutAvailable_whenLockscreenBecomesEnabled_shouldReturnTrue()
302 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700303 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
304 configureValidShortcutService();
305 AccessibilityShortcutController accessibilityShortcutController = getController();
306 configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
307 accessibilityShortcutController.onSettingsChanged();
308 assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(true));
Phil Weaver106fe732016-11-22 18:18:39 -0800309 }
310
311 @Test
Phil Weaver32ea3722017-03-13 11:32:01 -0700312 public void testOnAccessibilityShortcut_vibrates() {
Phil Weaverce687c52017-03-15 08:51:52 -0700313 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
Phil Weaver32ea3722017-03-13 11:32:01 -0700314 AccessibilityShortcutController accessibilityShortcutController = getController();
315 accessibilityShortcutController.performAccessibilityShortcut();
316 verify(mVibrator).vibrate(aryEq(VIBRATOR_PATTERN_LONG), eq(-1), anyObject());
317 }
318
319 @Test
Phil Weaver106fe732016-11-22 18:18:39 -0800320 public void testOnAccessibilityShortcut_firstTime_showsWarningDialog()
321 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700322 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
323 configureValidShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800324 AccessibilityShortcutController accessibilityShortcutController = getController();
325 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
326 accessibilityShortcutController.performAccessibilityShortcut();
327
328 assertEquals(1, Settings.Secure.getInt(
329 mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0));
menghanli3cba1dc2020-03-18 20:54:27 +0800330 verify(mResources).getString(
331 R.string.accessibility_shortcut_single_service_warning_title, PACKAGE_NAME_STRING);
Phil Weaver106fe732016-11-22 18:18:39 -0800332 verify(mAlertDialog).show();
Rhed Jao6182af72018-08-22 18:48:59 +0800333 verify(mAccessibilityManagerService, atLeastOnce()).getInstalledAccessibilityServiceList(
334 anyInt());
Rhed Jaof526c992019-11-29 22:49:11 +0800335 verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut(null);
Rhed Jao6182af72018-08-22 18:48:59 +0800336 verify(mFrameworkObjectProvider, times(0)).getTextToSpeech(any(), any());
Phil Weaver106fe732016-11-22 18:18:39 -0800337 }
338
339 @Test
340 public void testOnAccessibilityShortcut_withDialogShowing_callsServer()
341 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700342 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
343 configureValidShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800344 AccessibilityShortcutController accessibilityShortcutController = getController();
345 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
346 accessibilityShortcutController.performAccessibilityShortcut();
347 accessibilityShortcutController.performAccessibilityShortcut();
348 verify(mToast).show();
Bernardo Rufino0f64b3c2020-02-14 13:45:20 +0000349 // TODO(b/149408635): Reintroduce assertion
350 // assertEquals(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
351 // mLayoutParams.privateFlags
352 // & WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
Rhed Jaof526c992019-11-29 22:49:11 +0800353 verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut(null);
Phil Weaver106fe732016-11-22 18:18:39 -0800354 }
355
356 @Test
357 public void testOnAccessibilityShortcut_ifCanceledFirstTime_showsWarningDialog()
358 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700359 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
360 configureValidShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800361 AccessibilityShortcutController accessibilityShortcutController = getController();
362 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
363 accessibilityShortcutController.performAccessibilityShortcut();
364 ArgumentCaptor<AlertDialog.OnCancelListener> cancelListenerCaptor =
365 ArgumentCaptor.forClass(AlertDialog.OnCancelListener.class);
366 verify(mAlertDialogBuilder).setOnCancelListener(cancelListenerCaptor.capture());
367 // Call the cancel callback
368 cancelListenerCaptor.getValue().onCancel(null);
369
370 accessibilityShortcutController.performAccessibilityShortcut();
371 verify(mAlertDialog, times(2)).show();
372 }
373
374 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800375 public void testClickingDisableButtonInDialog_shouldClearShortcutId() throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700376 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
377 configureValidShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800378 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
379 getController().performAccessibilityShortcut();
380
381 ArgumentCaptor<DialogInterface.OnClickListener> captor =
382 ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
menghanli3cba1dc2020-03-18 20:54:27 +0800383 verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
Phil Weaver106fe732016-11-22 18:18:39 -0800384 captor.capture());
menghanli3cba1dc2020-03-18 20:54:27 +0800385 // Call the button callback, if one exists
386 if (captor.getValue() != null) {
387 captor.getValue().onClick(null, 0);
388 }
Phil Weaver106fe732016-11-22 18:18:39 -0800389 assertTrue(TextUtils.isEmpty(
390 Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)));
menghanli3cba1dc2020-03-18 20:54:27 +0800391 assertEquals(0, Settings.Secure.getInt(
392 mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN));
Phil Weaver106fe732016-11-22 18:18:39 -0800393 }
394
395 @Test
menghanli3cba1dc2020-03-18 20:54:27 +0800396 public void testClickingTurnOnButtonInDialog_shouldLeaveShortcutReady() throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700397 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
398 configureValidShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800399 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
400 getController().performAccessibilityShortcut();
401
402 ArgumentCaptor<DialogInterface.OnClickListener> captor =
403 ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
menghanli3cba1dc2020-03-18 20:54:27 +0800404 verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.accessibility_shortcut_on),
405 captor.capture());
Phil Weaver106fe732016-11-22 18:18:39 -0800406 // Call the button callback, if one exists
407 if (captor.getValue() != null) {
408 captor.getValue().onClick(null, 0);
409 }
410 assertEquals(SERVICE_NAME_STRING,
411 Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
412 assertEquals(1, Settings.Secure.getInt(
menghanli3cba1dc2020-03-18 20:54:27 +0800413 mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN));
Phil Weaver106fe732016-11-22 18:18:39 -0800414 }
415
416 @Test
417 public void testOnAccessibilityShortcut_afterDialogShown_shouldCallServer() throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700418 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
419 configureValidShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800420 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
421 getController().performAccessibilityShortcut();
422
423 verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog);
424 verify(mToast).show();
Rhed Jaof526c992019-11-29 22:49:11 +0800425 verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
Phil Weaver106fe732016-11-22 18:18:39 -0800426 }
427
Phil Weaverb9f06122017-11-30 10:48:17 -0800428 @Test
429 public void getFrameworkFeatureMap_shouldBeNonNullAndUnmodifiable() {
430 Map<ComponentName, AccessibilityShortcutController.ToggleableFrameworkFeatureInfo>
431 frameworkFeatureMap =
432 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
433 assertTrue("Framework features not supported", frameworkFeatureMap.size() > 0);
434
435 try {
436 frameworkFeatureMap.clear();
437 fail("Framework feature map should be unmodifieable");
438 } catch (UnsupportedOperationException e) {
439 // Expected
440 }
441 }
442
443 @Test
Phil Weaver0f9aa4c2018-02-26 13:17:28 -0800444 public void testOnAccessibilityShortcut_forServiceWithNoSummary_doesNotCrash()
445 throws Exception {
446 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
447 configureValidShortcutService();
448 when(mServiceInfo.loadSummary(any())).thenReturn(null);
449 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
450 getController().performAccessibilityShortcut();
Rhed Jaof526c992019-11-29 22:49:11 +0800451 verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
Phil Weaver0f9aa4c2018-02-26 13:17:28 -0800452 }
453
454 @Test
Phil Weaverb9f06122017-11-30 10:48:17 -0800455 public void testOnAccessibilityShortcut_forFrameworkFeature_callsServiceWithNoToast()
456 throws Exception {
457 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
458 configureFirstFrameworkFeature();
459 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
460 getController().performAccessibilityShortcut();
461
462 verifyZeroInteractions(mToast);
Rhed Jaof526c992019-11-29 22:49:11 +0800463 verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
Phil Weaverb9f06122017-11-30 10:48:17 -0800464 }
465
Rhed Jao6182af72018-08-22 18:48:59 +0800466 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800467 public void testOnAccessibilityShortcut_sdkGreaterThanQ_reqA11yButton_callsServiceWithNoToast()
468 throws Exception {
469 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
470 configureValidShortcutService();
471 configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
472 configureRequestAccessibilityButton();
Rhed Jao8fcba032020-04-15 23:06:08 +0800473 configureEnabledService();
Rhed Jao6dad25d2019-10-22 22:15:05 +0800474 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
475 getController().performAccessibilityShortcut();
476
477 verifyZeroInteractions(mToast);
Rhed Jaof526c992019-11-29 22:49:11 +0800478 verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
Rhed Jao6dad25d2019-10-22 22:15:05 +0800479 }
480
481 @Test
482 public void testOnAccessibilityShortcut_showsWarningDialog_shouldTtsSpokenPrompt()
483 throws Exception {
Rhed Jao6182af72018-08-22 18:48:59 +0800484 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
485 configureValidShortcutService();
486 configureTtsSpokenPromptEnabled();
487 configureHandlerCallbackInvocation();
488 AccessibilityShortcutController accessibilityShortcutController = getController();
489 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
490 accessibilityShortcutController.performAccessibilityShortcut();
491
492 verify(mAlertDialog).show();
493 ArgumentCaptor<TextToSpeech.OnInitListener> onInitCap = ArgumentCaptor.forClass(
494 TextToSpeech.OnInitListener.class);
495 verify(mFrameworkObjectProvider).getTextToSpeech(any(), onInitCap.capture());
496 onInitCap.getValue().onInit(TextToSpeech.SUCCESS);
497 verify(mTextToSpeech).speak(any(), eq(TextToSpeech.QUEUE_FLUSH), any(), any());
498 ArgumentCaptor<DialogInterface.OnDismissListener> onDismissCap = ArgumentCaptor.forClass(
499 DialogInterface.OnDismissListener.class);
500 verify(mAlertDialog).setOnDismissListener(onDismissCap.capture());
501 onDismissCap.getValue().onDismiss(mAlertDialog);
502 verify(mTextToSpeech).shutdown();
503 verify(mRingtone, times(0)).play();
504 }
505
506 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800507 public void testOnAccessibilityShortcut_showsWarningDialog_ttsInitFail_noSpokenPrompt()
508 throws Exception {
Rhed Jao6182af72018-08-22 18:48:59 +0800509 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
510 configureValidShortcutService();
511 configureTtsSpokenPromptEnabled();
512 configureHandlerCallbackInvocation();
513 AccessibilityShortcutController accessibilityShortcutController = getController();
514 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
515 accessibilityShortcutController.performAccessibilityShortcut();
516
517 verify(mAlertDialog).show();
518 ArgumentCaptor<TextToSpeech.OnInitListener> onInitCap = ArgumentCaptor.forClass(
519 TextToSpeech.OnInitListener.class);
520 verify(mFrameworkObjectProvider).getTextToSpeech(any(), onInitCap.capture());
521 onInitCap.getValue().onInit(TextToSpeech.ERROR);
522 verify(mTextToSpeech, times(0)).speak(any(), anyInt(), any(), any());
523 verify(mRingtone).play();
524 }
525
Dieter Hsu0dbbb422020-02-12 14:52:59 +0800526 @Test
527 public void testOnAccessibilityShortcut_showsWarningDialog_ttsLongTimeInit_retrySpoken()
528 throws Exception {
529 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
530 configureValidShortcutService();
531 configureTtsSpokenPromptEnabled();
532 configureHandlerCallbackInvocation();
533 AccessibilityShortcutController accessibilityShortcutController = getController();
534 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
535 Set<String> features = new HashSet<>();
536 features.add(TextToSpeech.Engine.KEY_FEATURE_NOT_INSTALLED);
537 doReturn(features, Collections.emptySet()).when(mVoice).getFeatures();
538 doReturn(TextToSpeech.LANG_NOT_SUPPORTED, TextToSpeech.LANG_AVAILABLE)
539 .when(mTextToSpeech).setLanguage(any());
540 accessibilityShortcutController.performAccessibilityShortcut();
541
542 verify(mAlertDialog).show();
543 ArgumentCaptor<TextToSpeech.OnInitListener> onInitCap = ArgumentCaptor.forClass(
544 TextToSpeech.OnInitListener.class);
545 verify(mFrameworkObjectProvider).getTextToSpeech(any(), onInitCap.capture());
546 onInitCap.getValue().onInit(TextToSpeech.SUCCESS);
547 verify(mTextToSpeech).speak(any(), eq(TextToSpeech.QUEUE_FLUSH), any(), any());
548 ArgumentCaptor<DialogInterface.OnDismissListener> onDismissCap = ArgumentCaptor.forClass(
549 DialogInterface.OnDismissListener.class);
550 verify(mAlertDialog).setOnDismissListener(onDismissCap.capture());
551 onDismissCap.getValue().onDismiss(mAlertDialog);
552 verify(mTextToSpeech).shutdown();
553 verify(mRingtone, times(0)).play();
554 }
555
Rhed Jao6dad25d2019-10-22 22:15:05 +0800556 private void configureNoShortcutService() throws Exception {
557 when(mAccessibilityManagerService
558 .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
559 .thenReturn(Collections.emptyList());
Phil Weaver106fe732016-11-22 18:18:39 -0800560 Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
561 }
562
Rhed Jao6dad25d2019-10-22 22:15:05 +0800563 private void configureValidShortcutService() throws Exception {
564 when(mAccessibilityManagerService
565 .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
566 .thenReturn(Collections.singletonList(SERVICE_NAME_STRING));
Phil Weaver106fe732016-11-22 18:18:39 -0800567 Settings.Secure.putString(
568 mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, SERVICE_NAME_STRING);
569 }
570
Rhed Jao6dad25d2019-10-22 22:15:05 +0800571 private void configureFirstFrameworkFeature() throws Exception {
Phil Weaverb9f06122017-11-30 10:48:17 -0800572 ComponentName featureComponentName =
573 (ComponentName) AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
574 .keySet().toArray()[0];
Rhed Jao6dad25d2019-10-22 22:15:05 +0800575 when(mAccessibilityManagerService
576 .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
577 .thenReturn(Collections.singletonList(featureComponentName.flattenToString()));
Phil Weaverb9f06122017-11-30 10:48:17 -0800578 Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
579 featureComponentName.flattenToString());
580 }
581
Phil Weaverce687c52017-03-15 08:51:52 -0700582 private void configureShortcutEnabled(int enabledValue) {
Phil Weaverce687c52017-03-15 08:51:52 -0700583 final boolean lockscreen;
584
585 switch (enabledValue) {
Phil Weaverce687c52017-03-15 08:51:52 -0700586 case ENABLED_INCLUDING_LOCK_SCREEN:
Phil Weaverce687c52017-03-15 08:51:52 -0700587 lockscreen = true;
588 break;
589 case ENABLED_EXCEPT_LOCK_SCREEN:
Phil Weaverce687c52017-03-15 08:51:52 -0700590 lockscreen = false;
591 break;
592 default:
593 throw new IllegalArgumentException();
594 }
595
Phil Weaverce687c52017-03-15 08:51:52 -0700596 Settings.Secure.putInt(
597 mContentResolver, ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, lockscreen ? 1 : 0);
598 }
599
Rhed Jao6182af72018-08-22 18:48:59 +0800600 private void configureTtsSpokenPromptEnabled() {
601 mServiceInfo.flags |= AccessibilityServiceInfo
602 .FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK;
603 }
604
Rhed Jao6dad25d2019-10-22 22:15:05 +0800605 private void configureRequestAccessibilityButton() {
606 mServiceInfo.flags |= AccessibilityServiceInfo
607 .FLAG_REQUEST_ACCESSIBILITY_BUTTON;
608 }
609
610 private void configureApplicationTargetSdkVersion(int versionCode) {
611 mApplicationInfo.targetSdkVersion = versionCode;
612 }
613
Rhed Jao6182af72018-08-22 18:48:59 +0800614 private void configureHandlerCallbackInvocation() {
615 doAnswer((InvocationOnMock invocation) -> {
616 Message m = (Message) invocation.getArguments()[0];
617 m.getCallback().run();
618 return true;
619 }).when(mHandler).sendMessageAtTime(any(), anyLong());
620 }
621
Rhed Jao8fcba032020-04-15 23:06:08 +0800622 private void configureEnabledService() throws Exception {
623 when(mAccessibilityManagerService.getEnabledAccessibilityServiceList(anyInt(), anyInt()))
624 .thenReturn(Collections.singletonList(mServiceInfo));
625 }
626
Phil Weaver106fe732016-11-22 18:18:39 -0800627 private AccessibilityShortcutController getController() {
628 AccessibilityShortcutController accessibilityShortcutController =
Phil Weaverce687c52017-03-15 08:51:52 -0700629 new AccessibilityShortcutController(mContext, mHandler, 0);
Phil Weaver106fe732016-11-22 18:18:39 -0800630 accessibilityShortcutController.mFrameworkObjectProvider = mFrameworkObjectProvider;
631 return accessibilityShortcutController;
632 }
633}