AccessibilityShortcutControllerTest.java revision 32ea37255dbb9c5d7d40b73e435d12df934a803a
1/*
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
17package com.android.server.policy;
18
19import android.accessibilityservice.AccessibilityServiceInfo;
20import android.app.AlertDialog;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.DialogInterface;
24import android.content.pm.ApplicationInfo;
25import android.content.pm.ResolveInfo;
26import android.content.res.Resources;
27import android.os.Handler;
28import android.os.Vibrator;
29import android.provider.Settings;
30import android.support.test.runner.AndroidJUnit4;
31
32import android.test.mock.MockContentResolver;
33import android.text.TextUtils;
34import android.view.Window;
35import android.view.WindowManager;
36import android.view.accessibility.AccessibilityManager;
37import android.view.accessibility.IAccessibilityManager;
38import android.widget.Toast;
39import com.android.internal.R;
40import com.android.internal.util.test.FakeSettingsProvider;
41import com.android.server.policy.AccessibilityShortcutController.FrameworkObjectProvider;
42
43import org.junit.After;
44import org.junit.Before;
45import org.junit.Test;
46import org.junit.runner.RunWith;
47import org.mockito.ArgumentCaptor;
48import org.mockito.Mock;
49import org.mockito.MockitoAnnotations;
50import org.mockito.internal.util.reflection.Whitebox;
51
52import java.util.Collections;
53
54import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN;
55import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
56import static junit.framework.Assert.assertEquals;
57import static junit.framework.Assert.assertFalse;
58import static junit.framework.Assert.assertTrue;
59import static org.mockito.AdditionalMatchers.aryEq;
60import static org.mockito.Matchers.anyBoolean;
61import static org.mockito.Matchers.anyInt;
62import static org.mockito.Matchers.anyObject;
63import static org.mockito.Matchers.eq;
64import static org.mockito.Mockito.mock;
65import static org.mockito.Mockito.times;
66import static org.mockito.Mockito.verify;
67import static org.mockito.Mockito.verifyZeroInteractions;
68import static org.mockito.Mockito.when;
69
70@RunWith(AndroidJUnit4.class)
71public class AccessibilityShortcutControllerTest {
72    private static final String SERVICE_NAME_STRING = "fake.package/fake.service.name";
73    private static final long VIBRATOR_PATTERN_1 = 100L;
74    private static final long VIBRATOR_PATTERN_2 = 150L;
75    private static final int[] VIBRATOR_PATTERN_INT = {(int) VIBRATOR_PATTERN_1,
76            (int) VIBRATOR_PATTERN_2};
77    private static final long[] VIBRATOR_PATTERN_LONG = {VIBRATOR_PATTERN_1, VIBRATOR_PATTERN_2};
78
79    private @Mock Context mContext;
80    private @Mock FrameworkObjectProvider mFrameworkObjectProvider;
81    private @Mock IAccessibilityManager mAccessibilityManagerService;
82    private @Mock Handler mHandler;
83    private @Mock AlertDialog.Builder mAlertDialogBuilder;
84    private @Mock AlertDialog mAlertDialog;
85    private @Mock AccessibilityServiceInfo mServiceInfo;
86    private @Mock Resources mResources;
87    private @Mock Toast mToast;
88    private @Mock Vibrator mVibrator;
89    private @Mock ApplicationInfo mApplicationInfo;
90
91    private MockContentResolver mContentResolver;
92    private WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
93
94    @Before
95    public void setUp() throws Exception {
96        MockitoAnnotations.initMocks(this);
97
98        when(mVibrator.hasVibrator()).thenReturn(true);
99
100        when(mContext.getResources()).thenReturn(mResources);
101        when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
102        when(mContext.getSystemService(Context.VIBRATOR_SERVICE)).thenReturn(mVibrator);
103
104        mContentResolver = new MockContentResolver(mContext);
105        mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
106        when(mContext.getContentResolver()).thenReturn(mContentResolver);
107
108        when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(anyInt()))
109                .thenReturn(Collections.singletonList(mServiceInfo));
110
111        // Use the extra level of indirection in the object to mock framework objects
112        AccessibilityManager accessibilityManager =
113                new AccessibilityManager(mHandler, mAccessibilityManagerService, 0);
114        when(mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext))
115                .thenReturn(accessibilityManager);
116        when(mFrameworkObjectProvider.getAlertDialogBuilder(mContext))
117                .thenReturn(mAlertDialogBuilder);
118        when(mFrameworkObjectProvider.makeToastFromText(eq(mContext), anyObject(), anyInt()))
119                .thenReturn(mToast);
120
121        when(mResources.getString(anyInt())).thenReturn("Howdy %s");
122        when(mResources.getIntArray(anyInt())).thenReturn(VIBRATOR_PATTERN_INT);
123
124        ResolveInfo resolveInfo = mock(ResolveInfo.class);
125        when(resolveInfo.loadLabel(anyObject())).thenReturn("Service name");
126        when(mServiceInfo.getResolveInfo()).thenReturn(resolveInfo);
127        when(mServiceInfo.getComponentName())
128                .thenReturn(ComponentName.unflattenFromString(SERVICE_NAME_STRING));
129
130        when(mAlertDialogBuilder.setTitle(anyInt())).thenReturn(mAlertDialogBuilder);
131        when(mAlertDialogBuilder.setCancelable(anyBoolean())).thenReturn(mAlertDialogBuilder);
132        when(mAlertDialogBuilder.setMessage(anyObject())).thenReturn(mAlertDialogBuilder);
133        when(mAlertDialogBuilder.setPositiveButton(anyInt(), anyObject()))
134                .thenReturn(mAlertDialogBuilder);
135        when(mAlertDialogBuilder.setNegativeButton(anyInt(), anyObject()))
136                .thenReturn(mAlertDialogBuilder);
137        when(mAlertDialogBuilder.setOnCancelListener(anyObject())).thenReturn(mAlertDialogBuilder);
138        when(mAlertDialogBuilder.create()).thenReturn(mAlertDialog);
139
140        mLayoutParams.privateFlags = 0;
141        when(mToast.getWindowParams()).thenReturn(mLayoutParams);
142
143        Window window = mock(Window.class);
144        Whitebox.setInternalState(window, "mWindowAttributes", new WindowManager.LayoutParams());
145        when(mAlertDialog.getWindow()).thenReturn(window);
146    }
147
148    @After
149    public void tearDown() {
150    }
151
152    @Test
153    public void testShortcutAvailable_withNullServiceIdWhenCreated_shouldReturnFalse() {
154        configureShortcutDisabled();
155        assertFalse(getController().isAccessibilityShortcutAvailable());
156    }
157
158    @Test
159    public void testShortcutAvailable_withNonNullServiceIdWhenCreated_shouldReturnTrue() {
160        configureShortcutEnabled();
161        assertTrue(getController().isAccessibilityShortcutAvailable());
162    }
163
164    @Test
165    public void testShortcutAvailable_whenServiceIdBecomesNull_shouldReturnFalse() {
166        configureShortcutEnabled();
167        AccessibilityShortcutController accessibilityShortcutController = getController();
168        Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
169        accessibilityShortcutController.onSettingsChanged();
170        assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable());
171    }
172
173    @Test
174    public void testShortcutAvailable_whenServiceIdBecomesNonNull_shouldReturnTrue() {
175        configureShortcutDisabled();
176        AccessibilityShortcutController accessibilityShortcutController = getController();
177        configureShortcutEnabled();
178        accessibilityShortcutController.onSettingsChanged();
179        assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable());
180    }
181
182    @Test
183    public void testOnAccessibilityShortcut_vibrates() {
184        configureShortcutEnabled();
185        AccessibilityShortcutController accessibilityShortcutController = getController();
186        accessibilityShortcutController.performAccessibilityShortcut();
187        verify(mVibrator).vibrate(aryEq(VIBRATOR_PATTERN_LONG), eq(-1), anyObject());
188    }
189
190    @Test
191    public void testOnAccessibilityShortcut_firstTime_showsWarningDialog()
192            throws Exception {
193        configureShortcutEnabled();
194        AccessibilityShortcutController accessibilityShortcutController = getController();
195        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
196        accessibilityShortcutController.performAccessibilityShortcut();
197
198        assertEquals(1, Settings.Secure.getInt(
199                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0));
200        verify(mResources).getString(R.string.accessibility_shortcut_toogle_warning);
201        verify(mAlertDialog).show();
202        verify(mAccessibilityManagerService).getInstalledAccessibilityServiceList(anyInt());
203        verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut();
204    }
205
206    @Test
207    public void testOnAccessibilityShortcut_withDialogShowing_callsServer()
208        throws Exception {
209        configureShortcutEnabled();
210        AccessibilityShortcutController accessibilityShortcutController = getController();
211        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
212        accessibilityShortcutController.performAccessibilityShortcut();
213        accessibilityShortcutController.performAccessibilityShortcut();
214        verify(mToast).show();
215        assertEquals(WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
216                mLayoutParams.privateFlags
217                        & WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS);
218        verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut();
219    }
220
221    @Test
222    public void testOnAccessibilityShortcut_ifCanceledFirstTime_showsWarningDialog()
223        throws Exception {
224        configureShortcutEnabled();
225        AccessibilityShortcutController accessibilityShortcutController = getController();
226        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
227        accessibilityShortcutController.performAccessibilityShortcut();
228        ArgumentCaptor<AlertDialog.OnCancelListener> cancelListenerCaptor =
229                ArgumentCaptor.forClass(AlertDialog.OnCancelListener.class);
230        verify(mAlertDialogBuilder).setOnCancelListener(cancelListenerCaptor.capture());
231        // Call the cancel callback
232        cancelListenerCaptor.getValue().onCancel(null);
233
234        accessibilityShortcutController.performAccessibilityShortcut();
235        verify(mAlertDialog, times(2)).show();
236    }
237
238    @Test
239    public void testClickingDisableButtonInDialog_shouldClearShortcutId() {
240        configureShortcutEnabled();
241        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
242        getController().performAccessibilityShortcut();
243
244        ArgumentCaptor<DialogInterface.OnClickListener> captor =
245                ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
246        verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.disable_accessibility_shortcut),
247                captor.capture());
248        // Call the button callback
249        captor.getValue().onClick(null, 0);
250        assertTrue(TextUtils.isEmpty(
251                Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)));
252    }
253
254    @Test
255    public void testClickingLeaveOnButtonInDialog_shouldLeaveShortcutReady() throws Exception {
256        configureShortcutEnabled();
257        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
258        getController().performAccessibilityShortcut();
259
260        ArgumentCaptor<DialogInterface.OnClickListener> captor =
261            ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
262        verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.leave_accessibility_shortcut_on),
263            captor.capture());
264        // Call the button callback, if one exists
265        if (captor.getValue() != null) {
266            captor.getValue().onClick(null, 0);
267        }
268        assertEquals(SERVICE_NAME_STRING,
269                Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
270        assertEquals(1, Settings.Secure.getInt(
271            mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN));
272    }
273
274    @Test
275    public void testOnAccessibilityShortcut_afterDialogShown_shouldCallServer() throws Exception {
276        configureShortcutEnabled();
277        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
278        getController().performAccessibilityShortcut();
279
280        verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog);
281        verify(mToast).show();
282        verify(mAccessibilityManagerService).performAccessibilityShortcut();
283    }
284
285    private void configureShortcutDisabled() {
286        Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
287    }
288
289    private void configureShortcutEnabled() {
290        Settings.Secure.putString(
291                mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, SERVICE_NAME_STRING);
292    }
293
294    private AccessibilityShortcutController getController() {
295        AccessibilityShortcutController accessibilityShortcutController =
296                new AccessibilityShortcutController(mContext, mHandler);
297        accessibilityShortcutController.mFrameworkObjectProvider = mFrameworkObjectProvider;
298        return accessibilityShortcutController;
299    }
300}
301