Home | History | Annotate | Download | only in policy
      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 
     17 package com.android.server.policy;
     18 
     19 import android.accessibilityservice.AccessibilityServiceInfo;
     20 import android.app.AlertDialog;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.DialogInterface;
     24 import android.content.pm.ApplicationInfo;
     25 import android.content.pm.ResolveInfo;
     26 import android.content.res.Resources;
     27 import android.os.Handler;
     28 import android.os.Vibrator;
     29 import android.provider.Settings;
     30 import android.support.test.runner.AndroidJUnit4;
     31 
     32 import android.test.mock.MockContentResolver;
     33 import android.text.TextUtils;
     34 import android.view.Window;
     35 import android.view.WindowManager;
     36 import android.view.accessibility.AccessibilityManager;
     37 import android.view.accessibility.IAccessibilityManager;
     38 import android.widget.Toast;
     39 import com.android.internal.R;
     40 import com.android.internal.util.test.FakeSettingsProvider;
     41 import com.android.server.policy.AccessibilityShortcutController.FrameworkObjectProvider;
     42 
     43 import org.junit.After;
     44 import org.junit.Before;
     45 import org.junit.Test;
     46 import org.junit.runner.RunWith;
     47 import org.mockito.ArgumentCaptor;
     48 import org.mockito.Mock;
     49 import org.mockito.MockitoAnnotations;
     50 
     51 import java.lang.reflect.Field;
     52 import java.util.Collections;
     53 
     54 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN;
     55 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ENABLED;
     56 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN;
     57 import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
     58 import static junit.framework.Assert.assertEquals;
     59 import static junit.framework.Assert.assertFalse;
     60 import static junit.framework.Assert.assertTrue;
     61 import static org.mockito.AdditionalMatchers.aryEq;
     62 import static org.mockito.Matchers.anyBoolean;
     63 import static org.mockito.Matchers.anyInt;
     64 import static org.mockito.Matchers.anyObject;
     65 import static org.mockito.Matchers.eq;
     66 import static org.mockito.Mockito.mock;
     67 import static org.mockito.Mockito.times;
     68 import static org.mockito.Mockito.verify;
     69 import static org.mockito.Mockito.verifyZeroInteractions;
     70 import static org.mockito.Mockito.when;
     71 
     72 @RunWith(AndroidJUnit4.class)
     73 public class AccessibilityShortcutControllerTest {
     74     private static final String SERVICE_NAME_STRING = "fake.package/fake.service.name";
     75     private static final long VIBRATOR_PATTERN_1 = 100L;
     76     private static final long VIBRATOR_PATTERN_2 = 150L;
     77     private static final int[] VIBRATOR_PATTERN_INT = {(int) VIBRATOR_PATTERN_1,
     78             (int) VIBRATOR_PATTERN_2};
     79     private static final long[] VIBRATOR_PATTERN_LONG = {VIBRATOR_PATTERN_1, VIBRATOR_PATTERN_2};
     80 
     81     // Convenience values for enabling/disabling to make code more readable
     82     private static final int DISABLED = 0;
     83     private static final int ENABLED_EXCEPT_LOCK_SCREEN = 1;
     84     private static final int ENABLED_INCLUDING_LOCK_SCREEN = 2;
     85     private static final int DISABLED_BUT_LOCK_SCREEN_ON = 3;
     86 
     87     private @Mock Context mContext;
     88     private @Mock FrameworkObjectProvider mFrameworkObjectProvider;
     89     private @Mock IAccessibilityManager mAccessibilityManagerService;
     90     private @Mock Handler mHandler;
     91     private @Mock AlertDialog.Builder mAlertDialogBuilder;
     92     private @Mock AlertDialog mAlertDialog;
     93     private @Mock AccessibilityServiceInfo mServiceInfo;
     94     private @Mock Resources mResources;
     95     private @Mock Toast mToast;
     96     private @Mock Vibrator mVibrator;
     97     private @Mock ApplicationInfo mApplicationInfo;
     98 
     99     private MockContentResolver mContentResolver;
    100     private WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
    101 
    102     @Before
    103     public void setUp() throws Exception {
    104         MockitoAnnotations.initMocks(this);
    105 
    106         when(mVibrator.hasVibrator()).thenReturn(true);
    107 
    108         when(mContext.getResources()).thenReturn(mResources);
    109         when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
    110         when(mContext.getSystemService(Context.VIBRATOR_SERVICE)).thenReturn(mVibrator);
    111 
    112         mContentResolver = new MockContentResolver(mContext);
    113         mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
    114         when(mContext.getContentResolver()).thenReturn(mContentResolver);
    115 
    116         when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(anyInt()))
    117                 .thenReturn(Collections.singletonList(mServiceInfo));
    118 
    119         // Use the extra level of indirection in the object to mock framework objects
    120         AccessibilityManager accessibilityManager =
    121                 new AccessibilityManager(mHandler, mAccessibilityManagerService, 0);
    122         when(mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext))
    123                 .thenReturn(accessibilityManager);
    124         when(mFrameworkObjectProvider.getAlertDialogBuilder(mContext))
    125                 .thenReturn(mAlertDialogBuilder);
    126         when(mFrameworkObjectProvider.makeToastFromText(eq(mContext), anyObject(), anyInt()))
    127                 .thenReturn(mToast);
    128 
    129         when(mResources.getString(anyInt())).thenReturn("Howdy %s");
    130         when(mResources.getIntArray(anyInt())).thenReturn(VIBRATOR_PATTERN_INT);
    131 
    132         ResolveInfo resolveInfo = mock(ResolveInfo.class);
    133         when(resolveInfo.loadLabel(anyObject())).thenReturn("Service name");
    134         when(mServiceInfo.getResolveInfo()).thenReturn(resolveInfo);
    135         when(mServiceInfo.getComponentName())
    136                 .thenReturn(ComponentName.unflattenFromString(SERVICE_NAME_STRING));
    137 
    138         when(mAlertDialogBuilder.setTitle(anyInt())).thenReturn(mAlertDialogBuilder);
    139         when(mAlertDialogBuilder.setCancelable(anyBoolean())).thenReturn(mAlertDialogBuilder);
    140         when(mAlertDialogBuilder.setMessage(anyObject())).thenReturn(mAlertDialogBuilder);
    141         when(mAlertDialogBuilder.setPositiveButton(anyInt(), anyObject()))
    142                 .thenReturn(mAlertDialogBuilder);
    143         when(mAlertDialogBuilder.setNegativeButton(anyInt(), anyObject()))
    144                 .thenReturn(mAlertDialogBuilder);
    145         when(mAlertDialogBuilder.setOnCancelListener(anyObject())).thenReturn(mAlertDialogBuilder);
    146         when(mAlertDialogBuilder.create()).thenReturn(mAlertDialog);
    147 
    148         mLayoutParams.privateFlags = 0;
    149         when(mToast.getWindowParams()).thenReturn(mLayoutParams);
    150 
    151         Window window = mock(Window.class);
    152         // Initialize the mWindowAttributes field which was not properly initialized during mock
    153         // creation.
    154         try {
    155             Field field = Window.class.getDeclaredField("mWindowAttributes");
    156             field.setAccessible(true);
    157             field.set(window, new WindowManager.LayoutParams());
    158         } catch (Exception e) {
    159             throw new RuntimeException("Unable to set mWindowAttributes", e);
    160         }
    161         when(mAlertDialog.getWindow()).thenReturn(window);
    162     }
    163 
    164     @After
    165     public void tearDown() {
    166     }
    167 
    168     @Test
    169     public void testShortcutAvailable_enabledButNoServiceWhenCreated_shouldReturnFalse() {
    170         configureNoShortcutService();
    171         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
    172         assertFalse(getController().isAccessibilityShortcutAvailable(false));
    173     }
    174 
    175     @Test
    176     public void testShortcutAvailable_enabledWithValidServiceWhenCreated_shouldReturnTrue() {
    177         configureValidShortcutService();
    178         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
    179         assertTrue(getController().isAccessibilityShortcutAvailable(false));
    180     }
    181 
    182     @Test
    183     public void testShortcutAvailable_disabledWithValidServiceWhenCreated_shouldReturnFalse() {
    184         configureValidShortcutService();
    185         configureShortcutEnabled(DISABLED_BUT_LOCK_SCREEN_ON);
    186         assertFalse(getController().isAccessibilityShortcutAvailable(false));
    187     }
    188 
    189     @Test
    190     public void testShortcutAvailable_onLockScreenButDisabledThere_shouldReturnFalse() {
    191         configureValidShortcutService();
    192         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
    193         assertFalse(getController().isAccessibilityShortcutAvailable(true));
    194     }
    195 
    196     @Test
    197     public void testShortcutAvailable_onLockScreenAndEnabledThere_shouldReturnTrue() {
    198         configureValidShortcutService();
    199         configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
    200         assertTrue(getController().isAccessibilityShortcutAvailable(true));
    201     }
    202 
    203     @Test
    204     public void testShortcutAvailable_whenServiceIdBecomesNull_shouldReturnFalse() {
    205         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
    206         configureValidShortcutService();
    207         AccessibilityShortcutController accessibilityShortcutController = getController();
    208         Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
    209         accessibilityShortcutController.onSettingsChanged();
    210         assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
    211     }
    212 
    213     @Test
    214     public void testShortcutAvailable_whenServiceIdBecomesNonNull_shouldReturnTrue() {
    215         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
    216         configureNoShortcutService();
    217         AccessibilityShortcutController accessibilityShortcutController = getController();
    218         configureValidShortcutService();
    219         accessibilityShortcutController.onSettingsChanged();
    220         assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
    221     }
    222 
    223     @Test
    224     public void testShortcutAvailable_whenShortcutBecomesDisabled_shouldReturnFalse() {
    225         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
    226         configureValidShortcutService();
    227         AccessibilityShortcutController accessibilityShortcutController = getController();
    228         configureShortcutEnabled(DISABLED);
    229         accessibilityShortcutController.onSettingsChanged();
    230         assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
    231     }
    232 
    233     @Test
    234     public void testShortcutAvailable_whenShortcutBecomesEnabled_shouldReturnTrue() {
    235         configureShortcutEnabled(DISABLED);
    236         configureValidShortcutService();
    237         AccessibilityShortcutController accessibilityShortcutController = getController();
    238         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
    239         accessibilityShortcutController.onSettingsChanged();
    240         assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
    241     }
    242 
    243     @Test
    244     public void testShortcutAvailable_whenLockscreenBecomesDisabled_shouldReturnFalse() {
    245         configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
    246         configureValidShortcutService();
    247         AccessibilityShortcutController accessibilityShortcutController = getController();
    248         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
    249         accessibilityShortcutController.onSettingsChanged();
    250         assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(true));
    251     }
    252 
    253     @Test
    254     public void testShortcutAvailable_whenLockscreenBecomesEnabled_shouldReturnTrue() {
    255         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
    256         configureValidShortcutService();
    257         AccessibilityShortcutController accessibilityShortcutController = getController();
    258         configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
    259         accessibilityShortcutController.onSettingsChanged();
    260         assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(true));
    261     }
    262 
    263     @Test
    264     public void testOnAccessibilityShortcut_vibrates() {
    265         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
    266         AccessibilityShortcutController accessibilityShortcutController = getController();
    267         accessibilityShortcutController.performAccessibilityShortcut();
    268         verify(mVibrator).vibrate(aryEq(VIBRATOR_PATTERN_LONG), eq(-1), anyObject());
    269     }
    270 
    271     @Test
    272     public void testOnAccessibilityShortcut_firstTime_showsWarningDialog()
    273             throws Exception {
    274         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
    275         configureValidShortcutService();
    276         AccessibilityShortcutController accessibilityShortcutController = getController();
    277         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
    278         accessibilityShortcutController.performAccessibilityShortcut();
    279 
    280         assertEquals(1, Settings.Secure.getInt(
    281                 mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0));
    282         verify(mResources).getString(R.string.accessibility_shortcut_toogle_warning);
    283         verify(mAlertDialog).show();
    284         verify(mAccessibilityManagerService).getInstalledAccessibilityServiceList(anyInt());
    285         verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut();
    286     }
    287 
    288     @Test
    289     public void testOnAccessibilityShortcut_withDialogShowing_callsServer()
    290         throws Exception {
    291         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
    292         configureValidShortcutService();
    293         AccessibilityShortcutController accessibilityShortcutController = getController();
    294         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
    295         accessibilityShortcutController.performAccessibilityShortcut();
    296         accessibilityShortcutController.performAccessibilityShortcut();
    297         verify(mToast).show();
    298         assertEquals(WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
    299                 mLayoutParams.privateFlags
    300                         & WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS);
    301         verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut();
    302     }
    303 
    304     @Test
    305     public void testOnAccessibilityShortcut_ifCanceledFirstTime_showsWarningDialog()
    306         throws Exception {
    307         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
    308         configureValidShortcutService();
    309         AccessibilityShortcutController accessibilityShortcutController = getController();
    310         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
    311         accessibilityShortcutController.performAccessibilityShortcut();
    312         ArgumentCaptor<AlertDialog.OnCancelListener> cancelListenerCaptor =
    313                 ArgumentCaptor.forClass(AlertDialog.OnCancelListener.class);
    314         verify(mAlertDialogBuilder).setOnCancelListener(cancelListenerCaptor.capture());
    315         // Call the cancel callback
    316         cancelListenerCaptor.getValue().onCancel(null);
    317 
    318         accessibilityShortcutController.performAccessibilityShortcut();
    319         verify(mAlertDialog, times(2)).show();
    320     }
    321 
    322     @Test
    323     public void testClickingDisableButtonInDialog_shouldClearShortcutId() {
    324         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
    325         configureValidShortcutService();
    326         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
    327         getController().performAccessibilityShortcut();
    328 
    329         ArgumentCaptor<DialogInterface.OnClickListener> captor =
    330                 ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
    331         verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.disable_accessibility_shortcut),
    332                 captor.capture());
    333         // Call the button callback
    334         captor.getValue().onClick(null, 0);
    335         assertTrue(TextUtils.isEmpty(
    336                 Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)));
    337     }
    338 
    339     @Test
    340     public void testClickingLeaveOnButtonInDialog_shouldLeaveShortcutReady() throws Exception {
    341         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
    342         configureValidShortcutService();
    343         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
    344         getController().performAccessibilityShortcut();
    345 
    346         ArgumentCaptor<DialogInterface.OnClickListener> captor =
    347             ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
    348         verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.leave_accessibility_shortcut_on),
    349             captor.capture());
    350         // Call the button callback, if one exists
    351         if (captor.getValue() != null) {
    352             captor.getValue().onClick(null, 0);
    353         }
    354         assertEquals(SERVICE_NAME_STRING,
    355                 Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
    356         assertEquals(1, Settings.Secure.getInt(
    357             mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN));
    358     }
    359 
    360     @Test
    361     public void testOnAccessibilityShortcut_afterDialogShown_shouldCallServer() throws Exception {
    362         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
    363         configureValidShortcutService();
    364         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
    365         getController().performAccessibilityShortcut();
    366 
    367         verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog);
    368         verify(mToast).show();
    369         verify(mAccessibilityManagerService).performAccessibilityShortcut();
    370     }
    371 
    372     private void configureNoShortcutService() {
    373         Settings.Secure.putString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "");
    374     }
    375 
    376     private void configureValidShortcutService() {
    377         Settings.Secure.putString(
    378                 mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, SERVICE_NAME_STRING);
    379     }
    380 
    381     private void configureShortcutEnabled(int enabledValue) {
    382         final boolean enabled;
    383         final boolean lockscreen;
    384 
    385         switch (enabledValue) {
    386             case DISABLED:
    387                 enabled = false;
    388                 lockscreen = false;
    389                 break;
    390             case DISABLED_BUT_LOCK_SCREEN_ON:
    391                 enabled = false;
    392                 lockscreen = true;
    393                 break;
    394             case ENABLED_INCLUDING_LOCK_SCREEN:
    395                 enabled = true;
    396                 lockscreen = true;
    397                 break;
    398             case ENABLED_EXCEPT_LOCK_SCREEN:
    399                 enabled = true;
    400                 lockscreen = false;
    401                 break;
    402             default:
    403                 throw new IllegalArgumentException();
    404         }
    405 
    406         Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_ENABLED, enabled ? 1 : 0);
    407         Settings.Secure.putInt(
    408                 mContentResolver, ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, lockscreen ? 1 : 0);
    409     }
    410 
    411     private AccessibilityShortcutController getController() {
    412         AccessibilityShortcutController accessibilityShortcutController =
    413                 new AccessibilityShortcutController(mContext, mHandler, 0);
    414         accessibilityShortcutController.mFrameworkObjectProvider = mFrameworkObjectProvider;
    415         return accessibilityShortcutController;
    416     }
    417 }
    418