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