blob: 4a33da680585783d92fe052e92964341e47b4822 [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();
465 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
466 getController().performAccessibilityShortcut();
467
468 verifyZeroInteractions(mToast);
Rhed Jaof526c992019-11-29 22:49:11 +0800469 verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
Rhed Jao6dad25d2019-10-22 22:15:05 +0800470 }
471
472 @Test
473 public void testOnAccessibilityShortcut_showsWarningDialog_shouldTtsSpokenPrompt()
474 throws Exception {
Rhed Jao6182af72018-08-22 18:48:59 +0800475 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
476 configureValidShortcutService();
477 configureTtsSpokenPromptEnabled();
478 configureHandlerCallbackInvocation();
479 AccessibilityShortcutController accessibilityShortcutController = getController();
480 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
481 accessibilityShortcutController.performAccessibilityShortcut();
482
483 verify(mAlertDialog).show();
484 ArgumentCaptor<TextToSpeech.OnInitListener> onInitCap = ArgumentCaptor.forClass(
485 TextToSpeech.OnInitListener.class);
486 verify(mFrameworkObjectProvider).getTextToSpeech(any(), onInitCap.capture());
487 onInitCap.getValue().onInit(TextToSpeech.SUCCESS);
488 verify(mTextToSpeech).speak(any(), eq(TextToSpeech.QUEUE_FLUSH), any(), any());
489 ArgumentCaptor<DialogInterface.OnDismissListener> onDismissCap = ArgumentCaptor.forClass(
490 DialogInterface.OnDismissListener.class);
491 verify(mAlertDialog).setOnDismissListener(onDismissCap.capture());
492 onDismissCap.getValue().onDismiss(mAlertDialog);
493 verify(mTextToSpeech).shutdown();
494 verify(mRingtone, times(0)).play();
495 }
496
497 @Test
Rhed Jao6dad25d2019-10-22 22:15:05 +0800498 public void testOnAccessibilityShortcut_showsWarningDialog_ttsInitFail_noSpokenPrompt()
499 throws Exception {
Rhed Jao6182af72018-08-22 18:48:59 +0800500 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
501 configureValidShortcutService();
502 configureTtsSpokenPromptEnabled();
503 configureHandlerCallbackInvocation();
504 AccessibilityShortcutController accessibilityShortcutController = getController();
505 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
506 accessibilityShortcutController.performAccessibilityShortcut();
507
508 verify(mAlertDialog).show();
509 ArgumentCaptor<TextToSpeech.OnInitListener> onInitCap = ArgumentCaptor.forClass(
510 TextToSpeech.OnInitListener.class);
511 verify(mFrameworkObjectProvider).getTextToSpeech(any(), onInitCap.capture());
512 onInitCap.getValue().onInit(TextToSpeech.ERROR);
513 verify(mTextToSpeech, times(0)).speak(any(), anyInt(), any(), any());
514 verify(mRingtone).play();
515 }
516
Dieter Hsu0dbbb422020-02-12 14:52:59 +0800517 @Test
518 public void testOnAccessibilityShortcut_showsWarningDialog_ttsLongTimeInit_retrySpoken()
519 throws Exception {
520 configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
521 configureValidShortcutService();
522 configureTtsSpokenPromptEnabled();
523 configureHandlerCallbackInvocation();
524 AccessibilityShortcutController accessibilityShortcutController = getController();
525 Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
526 Set<String> features = new HashSet<>();
527 features.add(TextToSpeech.Engine.KEY_FEATURE_NOT_INSTALLED);
528 doReturn(features, Collections.emptySet()).when(mVoice).getFeatures();
529 doReturn(TextToSpeech.LANG_NOT_SUPPORTED, TextToSpeech.LANG_AVAILABLE)
530 .when(mTextToSpeech).setLanguage(any());
531 accessibilityShortcutController.performAccessibilityShortcut();
532
533 verify(mAlertDialog).show();
534 ArgumentCaptor<TextToSpeech.OnInitListener> onInitCap = ArgumentCaptor.forClass(
535 TextToSpeech.OnInitListener.class);
536 verify(mFrameworkObjectProvider).getTextToSpeech(any(), onInitCap.capture());
537 onInitCap.getValue().onInit(TextToSpeech.SUCCESS);
538 verify(mTextToSpeech).speak(any(), eq(TextToSpeech.QUEUE_FLUSH), any(), any());
539 ArgumentCaptor<DialogInterface.OnDismissListener> onDismissCap = ArgumentCaptor.forClass(
540 DialogInterface.OnDismissListener.class);
541 verify(mAlertDialog).setOnDismissListener(onDismissCap.capture());
542 onDismissCap.getValue().onDismiss(mAlertDialog);
543 verify(mTextToSpeech).shutdown();
544 verify(mRingtone, times(0)).play();
545 }
546
Rhed Jao6dad25d2019-10-22 22:15:05 +0800547 private void configureNoShortcutService() throws Exception {
548 when(mAccessibilityManagerService
549 .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
550 .thenReturn(Collections.emptyList());
Phil Weaver106fe732016-11-22 18:18:39 -0800551 Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
552 }
553
Rhed Jao6dad25d2019-10-22 22:15:05 +0800554 private void configureValidShortcutService() throws Exception {
555 when(mAccessibilityManagerService
556 .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
557 .thenReturn(Collections.singletonList(SERVICE_NAME_STRING));
Phil Weaver106fe732016-11-22 18:18:39 -0800558 Settings.Secure.putString(
559 mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, SERVICE_NAME_STRING);
560 }
561
Rhed Jao6dad25d2019-10-22 22:15:05 +0800562 private void configureFirstFrameworkFeature() throws Exception {
Phil Weaverb9f06122017-11-30 10:48:17 -0800563 ComponentName featureComponentName =
564 (ComponentName) AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
565 .keySet().toArray()[0];
Rhed Jao6dad25d2019-10-22 22:15:05 +0800566 when(mAccessibilityManagerService
567 .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
568 .thenReturn(Collections.singletonList(featureComponentName.flattenToString()));
Phil Weaverb9f06122017-11-30 10:48:17 -0800569 Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
570 featureComponentName.flattenToString());
571 }
572
Phil Weaverce687c52017-03-15 08:51:52 -0700573 private void configureShortcutEnabled(int enabledValue) {
Phil Weaverce687c52017-03-15 08:51:52 -0700574 final boolean lockscreen;
575
576 switch (enabledValue) {
Phil Weaverce687c52017-03-15 08:51:52 -0700577 case ENABLED_INCLUDING_LOCK_SCREEN:
Phil Weaverce687c52017-03-15 08:51:52 -0700578 lockscreen = true;
579 break;
580 case ENABLED_EXCEPT_LOCK_SCREEN:
Phil Weaverce687c52017-03-15 08:51:52 -0700581 lockscreen = false;
582 break;
583 default:
584 throw new IllegalArgumentException();
585 }
586
Phil Weaverce687c52017-03-15 08:51:52 -0700587 Settings.Secure.putInt(
588 mContentResolver, ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, lockscreen ? 1 : 0);
589 }
590
Rhed Jao6182af72018-08-22 18:48:59 +0800591 private void configureTtsSpokenPromptEnabled() {
592 mServiceInfo.flags |= AccessibilityServiceInfo
593 .FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK;
594 }
595
Rhed Jao6dad25d2019-10-22 22:15:05 +0800596 private void configureRequestAccessibilityButton() {
597 mServiceInfo.flags |= AccessibilityServiceInfo
598 .FLAG_REQUEST_ACCESSIBILITY_BUTTON;
599 }
600
601 private void configureApplicationTargetSdkVersion(int versionCode) {
602 mApplicationInfo.targetSdkVersion = versionCode;
603 }
604
Rhed Jao6182af72018-08-22 18:48:59 +0800605 private void configureHandlerCallbackInvocation() {
606 doAnswer((InvocationOnMock invocation) -> {
607 Message m = (Message) invocation.getArguments()[0];
608 m.getCallback().run();
609 return true;
610 }).when(mHandler).sendMessageAtTime(any(), anyLong());
611 }
612
Phil Weaver106fe732016-11-22 18:18:39 -0800613 private AccessibilityShortcutController getController() {
614 AccessibilityShortcutController accessibilityShortcutController =
Phil Weaverce687c52017-03-15 08:51:52 -0700615 new AccessibilityShortcutController(mContext, mHandler, 0);
Phil Weaver106fe732016-11-22 18:18:39 -0800616 accessibilityShortcutController.mFrameworkObjectProvider = mFrameworkObjectProvider;
617 return accessibilityShortcutController;
618 }
619}