1 /* 2 * Copyright (C) 2017 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.settings.accessibility; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import static org.mockito.ArgumentMatchers.any; 22 import static org.mockito.ArgumentMatchers.anyBoolean; 23 import static org.mockito.ArgumentMatchers.eq; 24 import static org.mockito.ArgumentMatchers.isNull; 25 import static org.mockito.Mockito.atLeast; 26 import static org.mockito.Mockito.doReturn; 27 import static org.mockito.Mockito.verify; 28 import static org.mockito.Mockito.when; 29 30 import static java.util.Collections.singletonList; 31 32 import android.accessibilityservice.AccessibilityServiceInfo; 33 import android.accessibilityservice.AccessibilityShortcutInfo; 34 import android.content.ComponentName; 35 import android.content.ContentResolver; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.content.IntentFilter; 39 import android.content.pm.ActivityInfo; 40 import android.content.pm.ApplicationInfo; 41 import android.content.pm.ResolveInfo; 42 import android.content.pm.ServiceInfo; 43 import android.os.Build; 44 import android.os.Bundle; 45 import android.provider.Settings; 46 import android.view.accessibility.AccessibilityManager; 47 48 import androidx.fragment.app.FragmentActivity; 49 import androidx.preference.PreferenceManager; 50 import androidx.test.core.app.ApplicationProvider; 51 52 import com.android.internal.content.PackageMonitor; 53 import com.android.settings.R; 54 import com.android.settings.testutils.XmlTestUtils; 55 import com.android.settings.testutils.shadow.ShadowDeviceConfig; 56 import com.android.settings.testutils.shadow.ShadowFragment; 57 import com.android.settings.testutils.shadow.ShadowUserManager; 58 import com.android.settingslib.RestrictedPreference; 59 import com.android.settingslib.search.SearchIndexableRaw; 60 61 import org.junit.Before; 62 import org.junit.Rule; 63 import org.junit.Test; 64 import org.junit.runner.RunWith; 65 import org.mockito.ArgumentCaptor; 66 import org.mockito.Mock; 67 import org.mockito.Mockito; 68 import org.mockito.Spy; 69 import org.mockito.junit.MockitoJUnit; 70 import org.mockito.junit.MockitoRule; 71 import org.robolectric.RobolectricTestRunner; 72 import org.robolectric.annotation.Config; 73 import org.robolectric.shadow.api.Shadow; 74 import org.robolectric.shadows.ShadowAccessibilityManager; 75 import org.xmlpull.v1.XmlPullParserException; 76 77 import java.io.IOException; 78 import java.util.ArrayList; 79 import java.util.List; 80 81 /** Test for {@link AccessibilitySettings}. */ 82 @RunWith(RobolectricTestRunner.class) 83 public class AccessibilitySettingsTest { 84 private static final String PACKAGE_NAME = "com.android.test"; 85 private static final String CLASS_NAME = PACKAGE_NAME + ".test_a11y_service"; 86 private static final ComponentName COMPONENT_NAME = new ComponentName(PACKAGE_NAME, CLASS_NAME); 87 private static final int ON = 1; 88 private static final int OFF = 0; 89 private static final String EMPTY_STRING = ""; 90 private static final String DEFAULT_SUMMARY = "default summary"; 91 private static final String DEFAULT_DESCRIPTION = "default description"; 92 private static final String DEFAULT_LABEL = "default label"; 93 private static final Boolean SERVICE_ENABLED = true; 94 private static final Boolean SERVICE_DISABLED = false; 95 @Rule 96 public final MockitoRule mocks = MockitoJUnit.rule(); 97 @Spy 98 private final Context mContext = ApplicationProvider.getApplicationContext(); 99 @Spy 100 private final AccessibilityServiceInfo mServiceInfo = getMockAccessibilityServiceInfo( 101 PACKAGE_NAME, CLASS_NAME); 102 @Spy 103 private final AccessibilitySettings mFragment = new AccessibilitySettings(); 104 @Mock 105 private AccessibilityShortcutInfo mShortcutInfo; 106 @Mock 107 private FragmentActivity mActivity; 108 @Mock 109 private ContentResolver mContentResolver; 110 @Mock 111 private PreferenceManager mPreferenceManager; 112 private ShadowAccessibilityManager mShadowAccessibilityManager; 113 114 @Before setup()115 public void setup() { 116 mShadowAccessibilityManager = Shadow.extract(AccessibilityManager.getInstance(mContext)); 117 mShadowAccessibilityManager.setInstalledAccessibilityServiceList(new ArrayList<>()); 118 when(mFragment.getContext()).thenReturn(mContext); 119 when(mFragment.getActivity()).thenReturn(mActivity); 120 when(mActivity.getContentResolver()).thenReturn(mContentResolver); 121 when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager); 122 when(mFragment.getPreferenceManager().getContext()).thenReturn(mContext); 123 mContext.setTheme(R.style.Theme_AppCompat); 124 } 125 126 @Test getNonIndexableKeys_existInXmlLayout()127 public void getNonIndexableKeys_existInXmlLayout() { 128 final List<String> niks = AccessibilitySettings.SEARCH_INDEX_DATA_PROVIDER 129 .getNonIndexableKeys(mContext); 130 final List<String> keys = 131 XmlTestUtils.getKeysFromPreferenceXml(mContext, R.xml.accessibility_settings); 132 133 assertThat(keys).containsAtLeastElementsIn(niks); 134 } 135 136 @Test getRawDataToIndex_isNull()137 public void getRawDataToIndex_isNull() { 138 final List<SearchIndexableRaw> indexableRawList = 139 AccessibilitySettings.SEARCH_INDEX_DATA_PROVIDER.getRawDataToIndex(mContext, true); 140 141 assertThat(indexableRawList).isNull(); 142 } 143 144 @Test 145 @Config(shadows = {ShadowDeviceConfig.class}) isRampingRingerEnabled_settingsFlagOn_Enabled()146 public void isRampingRingerEnabled_settingsFlagOn_Enabled() { 147 Settings.Global.putInt( 148 mContext.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, ON); 149 assertThat(AccessibilitySettings.isRampingRingerEnabled(mContext)).isTrue(); 150 } 151 152 @Test 153 @Config(shadows = {ShadowDeviceConfig.class}) isRampingRingerEnabled_settingsFlagOff_Disabled()154 public void isRampingRingerEnabled_settingsFlagOff_Disabled() { 155 Settings.Global.putInt( 156 mContext.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, OFF); 157 assertThat(AccessibilitySettings.isRampingRingerEnabled(mContext)).isFalse(); 158 } 159 160 @Test getServiceSummary_serviceCrash_showsStopped()161 public void getServiceSummary_serviceCrash_showsStopped() { 162 mServiceInfo.crashed = true; 163 164 final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext, 165 mServiceInfo, SERVICE_ENABLED); 166 167 assertThat(summary).isEqualTo( 168 mContext.getString(R.string.accessibility_summary_state_stopped)); 169 } 170 171 @Test getServiceSummary_invisibleToggle_shortcutDisabled_showsOffSummary()172 public void getServiceSummary_invisibleToggle_shortcutDisabled_showsOffSummary() { 173 setInvisibleToggleFragmentType(mServiceInfo); 174 doReturn(DEFAULT_SUMMARY).when(mServiceInfo).loadSummary(any()); 175 176 final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext, 177 mServiceInfo, SERVICE_ENABLED); 178 179 assertThat(summary).isEqualTo( 180 mContext.getString(R.string.preference_summary_default_combination, 181 mContext.getString(R.string.accessibility_summary_shortcut_disabled), 182 DEFAULT_SUMMARY)); 183 } 184 185 @Test getServiceSummary_enableService_showsEnabled()186 public void getServiceSummary_enableService_showsEnabled() { 187 doReturn(EMPTY_STRING).when(mServiceInfo).loadSummary(any()); 188 189 final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext, 190 mServiceInfo, SERVICE_ENABLED); 191 192 assertThat(summary).isEqualTo( 193 mContext.getString(R.string.accessibility_summary_state_enabled)); 194 } 195 196 @Test getServiceSummary_disableService_showsDisabled()197 public void getServiceSummary_disableService_showsDisabled() { 198 doReturn(EMPTY_STRING).when(mServiceInfo).loadSummary(any()); 199 200 final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext, 201 mServiceInfo, SERVICE_DISABLED); 202 203 assertThat(summary).isEqualTo( 204 mContext.getString(R.string.accessibility_summary_state_disabled)); 205 } 206 207 @Test getServiceSummary_enableServiceAndHasSummary_showsEnabledSummary()208 public void getServiceSummary_enableServiceAndHasSummary_showsEnabledSummary() { 209 final String service_enabled = mContext.getString( 210 R.string.accessibility_summary_state_enabled); 211 doReturn(DEFAULT_SUMMARY).when(mServiceInfo).loadSummary(any()); 212 213 final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext, 214 mServiceInfo, SERVICE_ENABLED); 215 216 assertThat(summary).isEqualTo( 217 mContext.getString(R.string.preference_summary_default_combination, service_enabled, 218 DEFAULT_SUMMARY)); 219 } 220 221 @Test getServiceSummary_disableServiceAndHasSummary_showsCombineDisabledSummary()222 public void getServiceSummary_disableServiceAndHasSummary_showsCombineDisabledSummary() { 223 final String service_disabled = mContext.getString( 224 R.string.accessibility_summary_state_disabled); 225 doReturn(DEFAULT_SUMMARY).when(mServiceInfo).loadSummary(any()); 226 227 final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext, 228 mServiceInfo, SERVICE_DISABLED); 229 230 assertThat(summary).isEqualTo( 231 mContext.getString(R.string.preference_summary_default_combination, 232 service_disabled, DEFAULT_SUMMARY)); 233 } 234 235 @Test getServiceDescription_serviceCrash_showsStopped()236 public void getServiceDescription_serviceCrash_showsStopped() { 237 mServiceInfo.crashed = true; 238 239 final CharSequence description = AccessibilitySettings.getServiceDescription(mContext, 240 mServiceInfo, SERVICE_ENABLED); 241 242 assertThat(description).isEqualTo( 243 mContext.getString(R.string.accessibility_description_state_stopped)); 244 } 245 246 @Test getServiceDescription_haveDescription_showsDescription()247 public void getServiceDescription_haveDescription_showsDescription() { 248 doReturn(DEFAULT_DESCRIPTION).when(mServiceInfo).loadDescription(any()); 249 250 final CharSequence description = AccessibilitySettings.getServiceDescription(mContext, 251 mServiceInfo, SERVICE_ENABLED); 252 253 assertThat(description).isEqualTo(DEFAULT_DESCRIPTION); 254 } 255 256 @Test createAccessibilityServicePreferenceList_hasOneInfo_containsSameKey()257 public void createAccessibilityServicePreferenceList_hasOneInfo_containsSameKey() { 258 final String key = COMPONENT_NAME.flattenToString(); 259 final AccessibilitySettings.RestrictedPreferenceHelper helper = 260 new AccessibilitySettings.RestrictedPreferenceHelper(mContext); 261 final List<AccessibilityServiceInfo> infoList = new ArrayList<>( 262 singletonList(mServiceInfo)); 263 264 final List<RestrictedPreference> preferenceList = 265 helper.createAccessibilityServicePreferenceList(infoList); 266 RestrictedPreference preference = preferenceList.get(0); 267 268 assertThat(preference.getKey()).isEqualTo(key); 269 } 270 271 @Test createAccessibilityActivityPreferenceList_hasOneInfo_containsSameKey()272 public void createAccessibilityActivityPreferenceList_hasOneInfo_containsSameKey() { 273 final String key = COMPONENT_NAME.flattenToString(); 274 final AccessibilitySettings.RestrictedPreferenceHelper helper = 275 new AccessibilitySettings.RestrictedPreferenceHelper(mContext); 276 setMockAccessibilityShortcutInfo(mShortcutInfo); 277 final List<AccessibilityShortcutInfo> infoList = new ArrayList<>( 278 singletonList(mShortcutInfo)); 279 280 final List<RestrictedPreference> preferenceList = 281 helper.createAccessibilityActivityPreferenceList(infoList); 282 RestrictedPreference preference = preferenceList.get(0); 283 284 assertThat(preference.getKey()).isEqualTo(key); 285 } 286 287 @Test 288 @Config(shadows = {ShadowFragment.class, ShadowUserManager.class}) onCreate_haveRegisterToSpecificUrisAndActions()289 public void onCreate_haveRegisterToSpecificUrisAndActions() { 290 final ArgumentCaptor<IntentFilter> captor = ArgumentCaptor.forClass(IntentFilter.class); 291 final IntentFilter intentFilter; 292 mFragment.onAttach(mContext); 293 294 mFragment.onCreate(Bundle.EMPTY); 295 296 verify(mContentResolver).registerContentObserver( 297 eq(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS)), 298 anyBoolean(), 299 any(SettingsContentObserver.class)); 300 verify(mContentResolver).registerContentObserver(eq(Settings.Secure.getUriFor( 301 Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)), anyBoolean(), 302 any(SettingsContentObserver.class)); 303 verify(mActivity, atLeast(1)).registerReceiver(any(PackageMonitor.class), captor.capture(), 304 isNull(), any()); 305 intentFilter = captor.getAllValues().get(/* first time */ 0); 306 assertThat(intentFilter.hasAction(Intent.ACTION_PACKAGE_ADDED)).isTrue(); 307 assertThat(intentFilter.hasAction(Intent.ACTION_PACKAGE_REMOVED)).isTrue(); 308 } 309 310 @Test 311 @Config(shadows = {ShadowFragment.class, ShadowUserManager.class}) onDestroy_unregisterObserverAndReceiver()312 public void onDestroy_unregisterObserverAndReceiver() { 313 setupFragment(); 314 mFragment.onPause(); 315 mFragment.onStop(); 316 317 mFragment.onDestroy(); 318 319 verify(mContentResolver).unregisterContentObserver(any(SettingsContentObserver.class)); 320 verify(mActivity).unregisterReceiver(any(PackageMonitor.class)); 321 322 } 323 324 @Test 325 @Config(shadows = {ShadowFragment.class, ShadowUserManager.class}) onContentChanged_updatePreferenceInForeground_preferenceUpdated()326 public void onContentChanged_updatePreferenceInForeground_preferenceUpdated() { 327 setupFragment(); 328 mShadowAccessibilityManager.setInstalledAccessibilityServiceList( 329 singletonList(mServiceInfo)); 330 331 mFragment.onContentChanged(); 332 333 RestrictedPreference preference = mFragment.getPreferenceScreen().findPreference( 334 COMPONENT_NAME.flattenToString()); 335 336 assertThat(preference).isNotNull(); 337 338 } 339 340 @Test 341 @Config(shadows = {ShadowFragment.class, ShadowUserManager.class}) onContentChanged_updatePreferenceInBackground_preferenceUpdated()342 public void onContentChanged_updatePreferenceInBackground_preferenceUpdated() { 343 setupFragment(); 344 mFragment.onPause(); 345 mFragment.onStop(); 346 347 mShadowAccessibilityManager.setInstalledAccessibilityServiceList( 348 singletonList(mServiceInfo)); 349 350 mFragment.onContentChanged(); 351 mFragment.onStart(); 352 353 RestrictedPreference preference = mFragment.getPreferenceScreen().findPreference( 354 COMPONENT_NAME.flattenToString()); 355 356 assertThat(preference).isNotNull(); 357 358 } 359 getMockAccessibilityServiceInfo(String packageName, String className)360 private AccessibilityServiceInfo getMockAccessibilityServiceInfo(String packageName, 361 String className) { 362 final ApplicationInfo applicationInfo = new ApplicationInfo(); 363 final ServiceInfo serviceInfo = new ServiceInfo(); 364 applicationInfo.packageName = packageName; 365 serviceInfo.packageName = packageName; 366 serviceInfo.name = className; 367 serviceInfo.applicationInfo = applicationInfo; 368 369 final ResolveInfo resolveInfo = new ResolveInfo(); 370 resolveInfo.serviceInfo = serviceInfo; 371 try { 372 final AccessibilityServiceInfo info = new AccessibilityServiceInfo(resolveInfo, 373 mContext); 374 info.setComponentName(new ComponentName(packageName, className)); 375 return info; 376 } catch (XmlPullParserException | IOException e) { 377 // Do nothing 378 } 379 380 return null; 381 } 382 setMockAccessibilityShortcutInfo(AccessibilityShortcutInfo mockInfo)383 private void setMockAccessibilityShortcutInfo(AccessibilityShortcutInfo mockInfo) { 384 final ActivityInfo activityInfo = Mockito.mock(ActivityInfo.class); 385 when(mockInfo.getActivityInfo()).thenReturn(activityInfo); 386 when(activityInfo.loadLabel(any())).thenReturn(DEFAULT_LABEL); 387 when(mockInfo.loadSummary(any())).thenReturn(DEFAULT_SUMMARY); 388 when(mockInfo.loadDescription(any())).thenReturn(DEFAULT_DESCRIPTION); 389 when(mockInfo.getComponentName()).thenReturn(COMPONENT_NAME); 390 } 391 setInvisibleToggleFragmentType(AccessibilityServiceInfo info)392 private void setInvisibleToggleFragmentType(AccessibilityServiceInfo info) { 393 info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.R; 394 info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON; 395 } 396 setupFragment()397 private void setupFragment() { 398 mFragment.onAttach(mContext); 399 mFragment.onCreate(Bundle.EMPTY); 400 mFragment.onStart(); 401 mFragment.onResume(); 402 } 403 } 404