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