blob: b21504c73772d3d1622ee56afe4d1b6e2aa0086b [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";
Phil Weaverb9f06122017-11-30 10:48:17 -080096 private static final String SERVICE_NAME_SUMMARY = "Summary";
Phil Weaver32ea3722017-03-13 11:32:01 -070097 private static final long VIBRATOR_PATTERN_1 = 100L;
98 private static final long VIBRATOR_PATTERN_2 = 150L;
99 private static final int[] VIBRATOR_PATTERN_INT = {(int) VIBRATOR_PATTERN_1,
100 (int) VIBRATOR_PATTERN_2};
101 private static final long[] VIBRATOR_PATTERN_LONG = {VIBRATOR_PATTERN_1, VIBRATOR_PATTERN_2};
Phil Weaver106fe732016-11-22 18:18:39 -0800102
Phil Weaverce687c52017-03-15 08:51:52 -0700103 // Convenience values for enabling/disabling to make code more readable
Phil Weaverce687c52017-03-15 08:51:52 -0700104 private static final int ENABLED_EXCEPT_LOCK_SCREEN = 1;
105 private static final int ENABLED_INCLUDING_LOCK_SCREEN = 2;
Phil Weaverce687c52017-03-15 08:51:52 -0700106
Phil Weaver106fe732016-11-22 18:18:39 -0800107 private @Mock Context mContext;
108 private @Mock FrameworkObjectProvider mFrameworkObjectProvider;
109 private @Mock IAccessibilityManager mAccessibilityManagerService;
110 private @Mock Handler mHandler;
111 private @Mock AlertDialog.Builder mAlertDialogBuilder;
112 private @Mock AlertDialog mAlertDialog;
113 private @Mock AccessibilityServiceInfo mServiceInfo;
114 private @Mock Resources mResources;
115 private @Mock Toast mToast;
Phil Weaver32ea3722017-03-13 11:32:01 -0700116 private @Mock Vibrator mVibrator;
117 private @Mock ApplicationInfo mApplicationInfo;
Phil Weaverb9f06122017-11-30 10:48:17 -0800118 private @Mock PackageManager mPackageManager;
Rhed Jao6182af72018-08-22 18:48:59 +0800119 private @Mock TextToSpeech mTextToSpeech;
120 private @Mock Voice mVoice;
121 private @Mock Ringtone mRingtone;
Phil Weaver106fe732016-11-22 18:18:39 -0800122
123 private MockContentResolver mContentResolver;
Phil Weaver71922972017-02-14 12:36:47 -0800124 private WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
Phil Weaver106fe732016-11-22 18:18:39 -0800125
126 @Before
127 public void setUp() throws Exception {
128 MockitoAnnotations.initMocks(this);
129
Phil Weaver32ea3722017-03-13 11:32:01 -0700130 when(mVibrator.hasVibrator()).thenReturn(true);
131
132 when(mContext.getResources()).thenReturn(mResources);
133 when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
134 when(mContext.getSystemService(Context.VIBRATOR_SERVICE)).thenReturn(mVibrator);
Phil Weaverb9f06122017-11-30 10:48:17 -0800135 when(mContext.getPackageManager()).thenReturn(mPackageManager);
136
137 // We're not checking the text. Just prevent us crashing when getting text.
138 when(mPackageManager.getText(any(), anyInt(), any())).thenReturn("text");
Phil Weaver32ea3722017-03-13 11:32:01 -0700139
Phil Weaver106fe732016-11-22 18:18:39 -0800140 mContentResolver = new MockContentResolver(mContext);
141 mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
Dieter Hsuf2da6b62019-07-16 19:59:16 +0800142 FakeSettingsProvider.clearSettingsProvider();
Phil Weaver106fe732016-11-22 18:18:39 -0800143 when(mContext.getContentResolver()).thenReturn(mContentResolver);
Phil Weaver106fe732016-11-22 18:18:39 -0800144
145 when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(anyInt()))
146 .thenReturn(Collections.singletonList(mServiceInfo));
147
148 // Use the extra level of indirection in the object to mock framework objects
149 AccessibilityManager accessibilityManager =
150 new AccessibilityManager(mHandler, mAccessibilityManagerService, 0);
151 when(mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext))
152 .thenReturn(accessibilityManager);
153 when(mFrameworkObjectProvider.getAlertDialogBuilder(mContext))
154 .thenReturn(mAlertDialogBuilder);
155 when(mFrameworkObjectProvider.makeToastFromText(eq(mContext), anyObject(), anyInt()))
156 .thenReturn(mToast);
Phil Weaverb9f06122017-11-30 10:48:17 -0800157 when(mFrameworkObjectProvider.getSystemUiContext()).thenReturn(mContext);
Rhed Jao6182af72018-08-22 18:48:59 +0800158 when(mFrameworkObjectProvider.getTextToSpeech(eq(mContext), any()))
159 .thenReturn(mTextToSpeech);
160 when(mFrameworkObjectProvider.getRingtone(eq(mContext), any())).thenReturn(mRingtone);
Phil Weaver106fe732016-11-22 18:18:39 -0800161
162 when(mResources.getString(anyInt())).thenReturn("Howdy %s");
Rhed Jao6dad25d2019-10-22 22:15:05 +0800163 when(mResources.getString(R.string.config_defaultAccessibilityService)).thenReturn(null);
Phil Weaver32ea3722017-03-13 11:32:01 -0700164 when(mResources.getIntArray(anyInt())).thenReturn(VIBRATOR_PATTERN_INT);
165
Phil Weaver106fe732016-11-22 18:18:39 -0800166 ResolveInfo resolveInfo = mock(ResolveInfo.class);
Rhed Jao6dad25d2019-10-22 22:15:05 +0800167 resolveInfo.serviceInfo = mock(ServiceInfo.class);
168 resolveInfo.serviceInfo.applicationInfo = mApplicationInfo;
Phil Weaver106fe732016-11-22 18:18:39 -0800169 when(resolveInfo.loadLabel(anyObject())).thenReturn("Service name");
170 when(mServiceInfo.getResolveInfo()).thenReturn(resolveInfo);
171 when(mServiceInfo.getComponentName())
172 .thenReturn(ComponentName.unflattenFromString(SERVICE_NAME_STRING));
Phil Weaverb9f06122017-11-30 10:48:17 -0800173 when(mServiceInfo.loadSummary(any())).thenReturn(SERVICE_NAME_SUMMARY);
Phil Weaver106fe732016-11-22 18:18:39 -0800174
175 when(mAlertDialogBuilder.setTitle(anyInt())).thenReturn(mAlertDialogBuilder);
176 when(mAlertDialogBuilder.setCancelable(anyBoolean())).thenReturn(mAlertDialogBuilder);
177 when(mAlertDialogBuilder.setMessage(anyObject())).thenReturn(mAlertDialogBuilder);
178 when(mAlertDialogBuilder.setPositiveButton(anyInt(), anyObject()))
179 .thenReturn(mAlertDialogBuilder);
180 when(mAlertDialogBuilder.setNegativeButton(anyInt(), anyObject()))
181 .thenReturn(mAlertDialogBuilder);
182 when(mAlertDialogBuilder.setOnCancelListener(anyObject())).thenReturn(mAlertDialogBuilder);
183 when(mAlertDialogBuilder.create()).thenReturn(mAlertDialog);
184
Phil Weaver71922972017-02-14 12:36:47 -0800185 mLayoutParams.privateFlags = 0;
186 when(mToast.getWindowParams()).thenReturn(mLayoutParams);
187
Phil Weaver106fe732016-11-22 18:18:39 -0800188 Window window = mock(Window.class);
Paul Duffin192bb0b2017-03-09 18:49:41 +0000189 // Initialize the mWindowAttributes field which was not properly initialized during mock
190 // creation.
191 try {
192 Field field = Window.class.getDeclaredField("mWindowAttributes");
193 field.setAccessible(true);
194 field.set(window, new WindowManager.LayoutParams());
195 } catch (Exception e) {
196 throw new RuntimeException("Unable to set mWindowAttributes", e);
197 }
Phil Weaver106fe732016-11-22 18:18:39 -0800198 when(mAlertDialog.getWindow()).thenReturn(window);
Rhed Jao6182af72018-08-22 18:48:59 +0800199
200 when(mTextToSpeech.getVoice()).thenReturn(mVoice);
Phil Weaver106fe732016-11-22 18:18:39 -0800201 }
202
Dieter Hsuf2da6b62019-07-16 19:59:16 +0800203 @AfterClass
204 public static void cleanUpSettingsProvider() {
205 FakeSettingsProvider.clearSettingsProvider();
Phil Weaver106fe732016-11-22 18:18:39 -0800206 }
207
208 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800209 public void testShortcutAvailable_enabledButNoServiceWhenCreated_shouldReturnFalse()
210 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700211 configureNoShortcutService();
212 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
213 assertFalse(getController().isAccessibilityShortcutAvailable(false));
Phil Weaver106fe732016-11-22 18:18:39 -0800214 }
215
216 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800217 public void testShortcutAvailable_enabledWithValidServiceWhenCreated_shouldReturnTrue()
218 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700219 configureValidShortcutService();
220 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
221 assertTrue(getController().isAccessibilityShortcutAvailable(false));
222 }
223
224 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800225 public void testShortcutAvailable_onLockScreenButDisabledThere_shouldReturnFalse()
226 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700227 configureValidShortcutService();
228 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
229 assertFalse(getController().isAccessibilityShortcutAvailable(true));
230 }
231
232 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800233 public void testShortcutAvailable_onLockScreenAndEnabledThere_shouldReturnTrue()
234 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700235 configureValidShortcutService();
236 configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
237 assertTrue(getController().isAccessibilityShortcutAvailable(true));
Phil Weaver106fe732016-11-22 18:18:39 -0800238 }
239
240 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800241 public void testShortcutAvailable_onLockScreenAndLockScreenPreferenceUnset() throws Exception {
Phil Weaverc5865d62018-03-02 16:00:43 -0800242 // When the user hasn't specified a lock screen preference, we allow from the lock screen
243 // as long as the user has agreed to enable the shortcut
244 configureValidShortcutService();
245 configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
246 Settings.Secure.putString(
247 mContentResolver, ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, null);
248 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
249 assertFalse(getController().isAccessibilityShortcutAvailable(true));
250 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
251 assertTrue(getController().isAccessibilityShortcutAvailable(true));
252 }
253
254 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800255 public void testShortcutAvailable_whenServiceIdBecomesNull_shouldReturnFalse()
256 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700257 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
258 configureValidShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800259 AccessibilityShortcutController accessibilityShortcutController = getController();
Rhed Jao6dad25d2019-10-22 22:15:05 +0800260 configureNoShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800261 accessibilityShortcutController.onSettingsChanged();
Phil Weaverce687c52017-03-15 08:51:52 -0700262 assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
Phil Weaver106fe732016-11-22 18:18:39 -0800263 }
264
265 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800266 public void testShortcutAvailable_whenServiceIdBecomesNonNull_shouldReturnTrue()
267 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700268 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
269 configureNoShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800270 AccessibilityShortcutController accessibilityShortcutController = getController();
Phil Weaverce687c52017-03-15 08:51:52 -0700271 configureValidShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800272 accessibilityShortcutController.onSettingsChanged();
Phil Weaverce687c52017-03-15 08:51:52 -0700273 assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
274 }
275
276 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800277 public void testShortcutAvailable_whenShortcutBecomesEnabled_shouldReturnTrue()
278 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700279 configureValidShortcutService();
280 AccessibilityShortcutController accessibilityShortcutController = getController();
281 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
282 accessibilityShortcutController.onSettingsChanged();
283 assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
284 }
285
286 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800287 public void testShortcutAvailable_whenLockscreenBecomesDisabled_shouldReturnFalse()
288 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700289 configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
290 configureValidShortcutService();
291 AccessibilityShortcutController accessibilityShortcutController = getController();
292 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
293 accessibilityShortcutController.onSettingsChanged();
294 assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(true));
295 }
296
297 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800298 public void testShortcutAvailable_whenLockscreenBecomesEnabled_shouldReturnTrue()
299 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700300 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
301 configureValidShortcutService();
302 AccessibilityShortcutController accessibilityShortcutController = getController();
303 configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
304 accessibilityShortcutController.onSettingsChanged();
305 assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(true));
Phil Weaver106fe732016-11-22 18:18:39 -0800306 }
307
308 @Test
Phil Weaver32ea3722017-03-13 11:32:01 -0700309 public void testOnAccessibilityShortcut_vibrates() {
Phil Weaverce687c52017-03-15 08:51:52 -0700310 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
Phil Weaver32ea3722017-03-13 11:32:01 -0700311 AccessibilityShortcutController accessibilityShortcutController = getController();
312 accessibilityShortcutController.performAccessibilityShortcut();
313 verify(mVibrator).vibrate(aryEq(VIBRATOR_PATTERN_LONG), eq(-1), anyObject());
314 }
315
316 @Test
Phil Weaver106fe732016-11-22 18:18:39 -0800317 public void testOnAccessibilityShortcut_firstTime_showsWarningDialog()
318 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700319 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
320 configureValidShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800321 AccessibilityShortcutController accessibilityShortcutController = getController();
322 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
323 accessibilityShortcutController.performAccessibilityShortcut();
324
325 assertEquals(1, Settings.Secure.getInt(
326 mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0));
327 verify(mResources).getString(R.string.accessibility_shortcut_toogle_warning);
328 verify(mAlertDialog).show();
Rhed Jao6182af72018-08-22 18:48:59 +0800329 verify(mAccessibilityManagerService, atLeastOnce()).getInstalledAccessibilityServiceList(
330 anyInt());
Rhed Jaof526c992019-11-29 22:49:11 +0800331 verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut(null);
Rhed Jao6182af72018-08-22 18:48:59 +0800332 verify(mFrameworkObjectProvider, times(0)).getTextToSpeech(any(), any());
Phil Weaver106fe732016-11-22 18:18:39 -0800333 }
334
335 @Test
336 public void testOnAccessibilityShortcut_withDialogShowing_callsServer()
337 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700338 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
339 configureValidShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800340 AccessibilityShortcutController accessibilityShortcutController = getController();
341 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
342 accessibilityShortcutController.performAccessibilityShortcut();
343 accessibilityShortcutController.performAccessibilityShortcut();
344 verify(mToast).show();
Bernardo Rufino0f64b3c2020-02-14 13:45:20 +0000345 // TODO(b/149408635): Reintroduce assertion
346 // assertEquals(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
347 // mLayoutParams.privateFlags
348 // & WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
Rhed Jaof526c992019-11-29 22:49:11 +0800349 verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut(null);
Phil Weaver106fe732016-11-22 18:18:39 -0800350 }
351
352 @Test
353 public void testOnAccessibilityShortcut_ifCanceledFirstTime_showsWarningDialog()
354 throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700355 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
356 configureValidShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800357 AccessibilityShortcutController accessibilityShortcutController = getController();
358 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
359 accessibilityShortcutController.performAccessibilityShortcut();
360 ArgumentCaptor<AlertDialog.OnCancelListener> cancelListenerCaptor =
361 ArgumentCaptor.forClass(AlertDialog.OnCancelListener.class);
362 verify(mAlertDialogBuilder).setOnCancelListener(cancelListenerCaptor.capture());
363 // Call the cancel callback
364 cancelListenerCaptor.getValue().onCancel(null);
365
366 accessibilityShortcutController.performAccessibilityShortcut();
367 verify(mAlertDialog, times(2)).show();
368 }
369
370 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800371 public void testClickingDisableButtonInDialog_shouldClearShortcutId() throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700372 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
373 configureValidShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800374 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
375 getController().performAccessibilityShortcut();
376
377 ArgumentCaptor<DialogInterface.OnClickListener> captor =
378 ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
379 verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.disable_accessibility_shortcut),
380 captor.capture());
381 // Call the button callback
382 captor.getValue().onClick(null, 0);
383 assertTrue(TextUtils.isEmpty(
384 Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)));
385 }
386
387 @Test
388 public void testClickingLeaveOnButtonInDialog_shouldLeaveShortcutReady() throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700389 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
390 configureValidShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800391 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
392 getController().performAccessibilityShortcut();
393
394 ArgumentCaptor<DialogInterface.OnClickListener> captor =
395 ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
396 verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.leave_accessibility_shortcut_on),
397 captor.capture());
398 // Call the button callback, if one exists
399 if (captor.getValue() != null) {
400 captor.getValue().onClick(null, 0);
401 }
402 assertEquals(SERVICE_NAME_STRING,
403 Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
404 assertEquals(1, Settings.Secure.getInt(
405 mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN));
406 }
407
408 @Test
409 public void testOnAccessibilityShortcut_afterDialogShown_shouldCallServer() throws Exception {
Phil Weaverce687c52017-03-15 08:51:52 -0700410 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
411 configureValidShortcutService();
Phil Weaver106fe732016-11-22 18:18:39 -0800412 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
413 getController().performAccessibilityShortcut();
414
415 verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog);
416 verify(mToast).show();
Rhed Jaof526c992019-11-29 22:49:11 +0800417 verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
Phil Weaver106fe732016-11-22 18:18:39 -0800418 }
419
Phil Weaverb9f06122017-11-30 10:48:17 -0800420 @Test
421 public void getFrameworkFeatureMap_shouldBeNonNullAndUnmodifiable() {
422 Map<ComponentName, AccessibilityShortcutController.ToggleableFrameworkFeatureInfo>
423 frameworkFeatureMap =
424 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
425 assertTrue("Framework features not supported", frameworkFeatureMap.size() > 0);
426
427 try {
428 frameworkFeatureMap.clear();
429 fail("Framework feature map should be unmodifieable");
430 } catch (UnsupportedOperationException e) {
431 // Expected
432 }
433 }
434
435 @Test
Phil Weaver0f9aa4c2018-02-26 13:17:28 -0800436 public void testOnAccessibilityShortcut_forServiceWithNoSummary_doesNotCrash()
437 throws Exception {
438 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
439 configureValidShortcutService();
440 when(mServiceInfo.loadSummary(any())).thenReturn(null);
441 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
442 getController().performAccessibilityShortcut();
Rhed Jaof526c992019-11-29 22:49:11 +0800443 verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
Phil Weaver0f9aa4c2018-02-26 13:17:28 -0800444 }
445
446 @Test
Phil Weaverb9f06122017-11-30 10:48:17 -0800447 public void testOnAccessibilityShortcut_forFrameworkFeature_callsServiceWithNoToast()
448 throws Exception {
449 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
450 configureFirstFrameworkFeature();
451 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
452 getController().performAccessibilityShortcut();
453
454 verifyZeroInteractions(mToast);
Rhed Jaof526c992019-11-29 22:49:11 +0800455 verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
Phil Weaverb9f06122017-11-30 10:48:17 -0800456 }
457
Rhed Jao6182af72018-08-22 18:48:59 +0800458 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800459 public void testOnAccessibilityShortcut_sdkGreaterThanQ_reqA11yButton_callsServiceWithNoToast()
460 throws Exception {
461 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
462 configureValidShortcutService();
463 configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
464 configureRequestAccessibilityButton();
Rhed Jao8fcba032020-04-15 23:06:08 +0800465 configureEnabledService();
Rhed Jao6dad25d2019-10-22 22:15:05 +0800466 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
467 getController().performAccessibilityShortcut();
468
469 verifyZeroInteractions(mToast);
Rhed Jaof526c992019-11-29 22:49:11 +0800470 verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
Rhed Jao6dad25d2019-10-22 22:15:05 +0800471 }
472
473 @Test
474 public void testOnAccessibilityShortcut_showsWarningDialog_shouldTtsSpokenPrompt()
475 throws Exception {
Rhed Jao6182af72018-08-22 18:48:59 +0800476 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
477 configureValidShortcutService();
478 configureTtsSpokenPromptEnabled();
479 configureHandlerCallbackInvocation();
480 AccessibilityShortcutController accessibilityShortcutController = getController();
481 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
482 accessibilityShortcutController.performAccessibilityShortcut();
483
484 verify(mAlertDialog).show();
485 ArgumentCaptor<TextToSpeech.OnInitListener> onInitCap = ArgumentCaptor.forClass(
486 TextToSpeech.OnInitListener.class);
487 verify(mFrameworkObjectProvider).getTextToSpeech(any(), onInitCap.capture());
488 onInitCap.getValue().onInit(TextToSpeech.SUCCESS);
489 verify(mTextToSpeech).speak(any(), eq(TextToSpeech.QUEUE_FLUSH), any(), any());
490 ArgumentCaptor<DialogInterface.OnDismissListener> onDismissCap = ArgumentCaptor.forClass(
491 DialogInterface.OnDismissListener.class);
492 verify(mAlertDialog).setOnDismissListener(onDismissCap.capture());
493 onDismissCap.getValue().onDismiss(mAlertDialog);
494 verify(mTextToSpeech).shutdown();
495 verify(mRingtone, times(0)).play();
496 }
497
498 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800499 public void testOnAccessibilityShortcut_showsWarningDialog_ttsInitFail_noSpokenPrompt()
500 throws Exception {
Rhed Jao6182af72018-08-22 18:48:59 +0800501 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
502 configureValidShortcutService();
503 configureTtsSpokenPromptEnabled();
504 configureHandlerCallbackInvocation();
505 AccessibilityShortcutController accessibilityShortcutController = getController();
506 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
507 accessibilityShortcutController.performAccessibilityShortcut();
508
509 verify(mAlertDialog).show();
510 ArgumentCaptor<TextToSpeech.OnInitListener> onInitCap = ArgumentCaptor.forClass(
511 TextToSpeech.OnInitListener.class);
512 verify(mFrameworkObjectProvider).getTextToSpeech(any(), onInitCap.capture());
513 onInitCap.getValue().onInit(TextToSpeech.ERROR);
514 verify(mTextToSpeech, times(0)).speak(any(), anyInt(), any(), any());
515 verify(mRingtone).play();
516 }
517
Dieter Hsu0dbbb422020-02-12 14:52:59 +0800518 @Test
519 public void testOnAccessibilityShortcut_showsWarningDialog_ttsLongTimeInit_retrySpoken()
520 throws Exception {
521 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
522 configureValidShortcutService();
523 configureTtsSpokenPromptEnabled();
524 configureHandlerCallbackInvocation();
525 AccessibilityShortcutController accessibilityShortcutController = getController();
526 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
527 Set<String> features = new HashSet<>();
528 features.add(TextToSpeech.Engine.KEY_FEATURE_NOT_INSTALLED);
529 doReturn(features, Collections.emptySet()).when(mVoice).getFeatures();
530 doReturn(TextToSpeech.LANG_NOT_SUPPORTED, TextToSpeech.LANG_AVAILABLE)
531 .when(mTextToSpeech).setLanguage(any());
532 accessibilityShortcutController.performAccessibilityShortcut();
533
534 verify(mAlertDialog).show();
535 ArgumentCaptor<TextToSpeech.OnInitListener> onInitCap = ArgumentCaptor.forClass(
536 TextToSpeech.OnInitListener.class);
537 verify(mFrameworkObjectProvider).getTextToSpeech(any(), onInitCap.capture());
538 onInitCap.getValue().onInit(TextToSpeech.SUCCESS);
539 verify(mTextToSpeech).speak(any(), eq(TextToSpeech.QUEUE_FLUSH), any(), any());
540 ArgumentCaptor<DialogInterface.OnDismissListener> onDismissCap = ArgumentCaptor.forClass(
541 DialogInterface.OnDismissListener.class);
542 verify(mAlertDialog).setOnDismissListener(onDismissCap.capture());
543 onDismissCap.getValue().onDismiss(mAlertDialog);
544 verify(mTextToSpeech).shutdown();
545 verify(mRingtone, times(0)).play();
546 }
547
Rhed Jao6dad25d2019-10-22 22:15:05 +0800548 private void configureNoShortcutService() throws Exception {
549 when(mAccessibilityManagerService
550 .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
551 .thenReturn(Collections.emptyList());
Phil Weaver106fe732016-11-22 18:18:39 -0800552 Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
553 }
554
Rhed Jao6dad25d2019-10-22 22:15:05 +0800555 private void configureValidShortcutService() throws Exception {
556 when(mAccessibilityManagerService
557 .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
558 .thenReturn(Collections.singletonList(SERVICE_NAME_STRING));
Phil Weaver106fe732016-11-22 18:18:39 -0800559 Settings.Secure.putString(
560 mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, SERVICE_NAME_STRING);
561 }
562
Rhed Jao6dad25d2019-10-22 22:15:05 +0800563 private void configureFirstFrameworkFeature() throws Exception {
Phil Weaverb9f06122017-11-30 10:48:17 -0800564 ComponentName featureComponentName =
565 (ComponentName) AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
566 .keySet().toArray()[0];
Rhed Jao6dad25d2019-10-22 22:15:05 +0800567 when(mAccessibilityManagerService
568 .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
569 .thenReturn(Collections.singletonList(featureComponentName.flattenToString()));
Phil Weaverb9f06122017-11-30 10:48:17 -0800570 Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
571 featureComponentName.flattenToString());
572 }
573
Phil Weaverce687c52017-03-15 08:51:52 -0700574 private void configureShortcutEnabled(int enabledValue) {
Phil Weaverce687c52017-03-15 08:51:52 -0700575 final boolean lockscreen;
576
577 switch (enabledValue) {
Phil Weaverce687c52017-03-15 08:51:52 -0700578 case ENABLED_INCLUDING_LOCK_SCREEN:
Phil Weaverce687c52017-03-15 08:51:52 -0700579 lockscreen = true;
580 break;
581 case ENABLED_EXCEPT_LOCK_SCREEN:
Phil Weaverce687c52017-03-15 08:51:52 -0700582 lockscreen = false;
583 break;
584 default:
585 throw new IllegalArgumentException();
586 }
587
Phil Weaverce687c52017-03-15 08:51:52 -0700588 Settings.Secure.putInt(
589 mContentResolver, ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, lockscreen ? 1 : 0);
590 }
591
Rhed Jao6182af72018-08-22 18:48:59 +0800592 private void configureTtsSpokenPromptEnabled() {
593 mServiceInfo.flags |= AccessibilityServiceInfo
594 .FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK;
595 }
596
Rhed Jao6dad25d2019-10-22 22:15:05 +0800597 private void configureRequestAccessibilityButton() {
598 mServiceInfo.flags |= AccessibilityServiceInfo
599 .FLAG_REQUEST_ACCESSIBILITY_BUTTON;
600 }
601
602 private void configureApplicationTargetSdkVersion(int versionCode) {
603 mApplicationInfo.targetSdkVersion = versionCode;
604 }
605
Rhed Jao6182af72018-08-22 18:48:59 +0800606 private void configureHandlerCallbackInvocation() {
607 doAnswer((InvocationOnMock invocation) -> {
608 Message m = (Message) invocation.getArguments()[0];
609 m.getCallback().run();
610 return true;
611 }).when(mHandler).sendMessageAtTime(any(), anyLong());
612 }
613
Rhed Jao8fcba032020-04-15 23:06:08 +0800614 private void configureEnabledService() throws Exception {
615 when(mAccessibilityManagerService.getEnabledAccessibilityServiceList(anyInt(), anyInt()))
616 .thenReturn(Collections.singletonList(mServiceInfo));
617 }
618
Phil Weaver106fe732016-11-22 18:18:39 -0800619 private AccessibilityShortcutController getController() {
620 AccessibilityShortcutController accessibilityShortcutController =
Phil Weaverce687c52017-03-15 08:51:52 -0700621 new AccessibilityShortcutController(mContext, mHandler, 0);
Phil Weaver106fe732016-11-22 18:18:39 -0800622 accessibilityShortcutController.mFrameworkObjectProvider = mFrameworkObjectProvider;
623 return accessibilityShortcutController;
624 }
625}