1 /* 2 * Copyright (C) 2022 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.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM; 20 21 import android.accessibilityservice.AccessibilityServiceInfo; 22 import android.accessibilityservice.AccessibilityShortcutInfo; 23 import android.app.AppOpsManager; 24 import android.app.admin.DevicePolicyManager; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.pm.ActivityInfo; 28 import android.content.pm.PackageManager; 29 import android.content.pm.ResolveInfo; 30 import android.graphics.Color; 31 import android.graphics.drawable.Drawable; 32 import android.os.Bundle; 33 import android.os.UserHandle; 34 import android.text.TextUtils; 35 36 import androidx.core.content.ContextCompat; 37 38 import com.android.settings.R; 39 import com.android.settings.Utils; 40 import com.android.settingslib.RestrictedLockUtils; 41 import com.android.settingslib.RestrictedLockUtilsInternal; 42 import com.android.settingslib.RestrictedPreference; 43 import com.android.settingslib.accessibility.AccessibilityUtils; 44 45 import java.util.ArrayList; 46 import java.util.List; 47 import java.util.Set; 48 49 /** 50 * This class helps setup RestrictedPreference for accessibility. 51 */ 52 public class RestrictedPreferenceHelper { 53 // Index of the first preference in a preference category. 54 private static final int FIRST_PREFERENCE_IN_CATEGORY_INDEX = -1; 55 56 private final Context mContext; 57 private final DevicePolicyManager mDpm; 58 private final PackageManager mPm; 59 private final AppOpsManager mAppOps; 60 RestrictedPreferenceHelper(Context context)61 public RestrictedPreferenceHelper(Context context) { 62 mContext = context; 63 mDpm = context.getSystemService(DevicePolicyManager.class); 64 mPm = context.getPackageManager(); 65 mAppOps = context.getSystemService(AppOpsManager.class); 66 } 67 68 /** 69 * Creates the list of {@link RestrictedPreference} with the installedServices arguments. 70 * 71 * @param installedServices The list of {@link AccessibilityServiceInfo}s of the 72 * installed accessibility services 73 * @return The list of {@link RestrictedPreference} 74 */ createAccessibilityServicePreferenceList( List<AccessibilityServiceInfo> installedServices)75 public List<RestrictedPreference> createAccessibilityServicePreferenceList( 76 List<AccessibilityServiceInfo> installedServices) { 77 78 final Set<ComponentName> enabledServices = 79 AccessibilityUtils.getEnabledServicesFromSettings(mContext); 80 final List<String> permittedServices = mDpm.getPermittedAccessibilityServices( 81 UserHandle.myUserId()); 82 final int installedServicesSize = installedServices.size(); 83 84 final List<RestrictedPreference> preferenceList = new ArrayList<>( 85 installedServicesSize); 86 87 for (int i = 0; i < installedServicesSize; ++i) { 88 final AccessibilityServiceInfo info = installedServices.get(i); 89 final ResolveInfo resolveInfo = info.getResolveInfo(); 90 final String packageName = resolveInfo.serviceInfo.packageName; 91 final ComponentName componentName = new ComponentName(packageName, 92 resolveInfo.serviceInfo.name); 93 94 final String key = componentName.flattenToString(); 95 final CharSequence title = resolveInfo.loadLabel(mPm); 96 final boolean serviceEnabled = enabledServices.contains(componentName); 97 final CharSequence summary = AccessibilitySettings.getServiceSummary( 98 mContext, info, serviceEnabled); 99 final String fragment = getAccessibilityServiceFragmentTypeName(info); 100 101 Drawable icon = resolveInfo.loadIcon(mPm); 102 if (resolveInfo.getIconResource() == 0) { 103 icon = ContextCompat.getDrawable(mContext, 104 R.drawable.ic_accessibility_generic); 105 } 106 107 final RestrictedPreference preference = createRestrictedPreference(key, title, 108 summary, icon, fragment, packageName, 109 resolveInfo.serviceInfo.applicationInfo.uid); 110 111 setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled); 112 113 final String prefKey = preference.getKey(); 114 final int imageRes = info.getAnimatedImageRes(); 115 final CharSequence intro = info.loadIntro(mPm); 116 final CharSequence description = AccessibilitySettings.getServiceDescription( 117 mContext, info, serviceEnabled); 118 final String htmlDescription = info.loadHtmlDescription(mPm); 119 final String settingsClassName = info.getSettingsActivityName(); 120 final String tileServiceClassName = info.getTileServiceName(); 121 122 putBasicExtras(preference, prefKey, title, intro, description, imageRes, 123 htmlDescription, componentName); 124 putServiceExtras(preference, resolveInfo, serviceEnabled); 125 putSettingsExtras(preference, packageName, settingsClassName); 126 putTileServiceExtras(preference, packageName, tileServiceClassName); 127 128 preferenceList.add(preference); 129 } 130 return preferenceList; 131 } 132 133 /** 134 * Creates the list of {@link RestrictedPreference} with the installedShortcuts arguments. 135 * 136 * @param installedShortcuts The list of {@link AccessibilityShortcutInfo}s of the 137 * installed accessibility shortcuts 138 * @return The list of {@link RestrictedPreference} 139 */ createAccessibilityActivityPreferenceList( List<AccessibilityShortcutInfo> installedShortcuts)140 public List<RestrictedPreference> createAccessibilityActivityPreferenceList( 141 List<AccessibilityShortcutInfo> installedShortcuts) { 142 final Set<ComponentName> enabledServices = 143 AccessibilityUtils.getEnabledServicesFromSettings(mContext); 144 final List<String> permittedServices = mDpm.getPermittedAccessibilityServices( 145 UserHandle.myUserId()); 146 147 final int installedShortcutsSize = installedShortcuts.size(); 148 final List<RestrictedPreference> preferenceList = new ArrayList<>( 149 installedShortcutsSize); 150 151 for (int i = 0; i < installedShortcutsSize; ++i) { 152 final AccessibilityShortcutInfo info = installedShortcuts.get(i); 153 final ActivityInfo activityInfo = info.getActivityInfo(); 154 final ComponentName componentName = info.getComponentName(); 155 156 final String key = componentName.flattenToString(); 157 final CharSequence title = activityInfo.loadLabel(mPm); 158 final String summary = info.loadSummary(mPm); 159 final String fragment = 160 LaunchAccessibilityActivityPreferenceFragment.class.getName(); 161 162 Drawable icon = activityInfo.loadIcon(mPm); 163 if (activityInfo.getIconResource() == 0) { 164 icon = ContextCompat.getDrawable(mContext, R.drawable.ic_accessibility_generic); 165 } 166 167 final RestrictedPreference preference = createRestrictedPreference(key, title, 168 summary, icon, fragment, componentName.getPackageName(), 169 activityInfo.applicationInfo.uid); 170 final boolean serviceEnabled = enabledServices.contains(componentName); 171 172 setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled); 173 174 final String prefKey = preference.getKey(); 175 final CharSequence intro = info.loadIntro(mPm); 176 final String description = info.loadDescription(mPm); 177 final int imageRes = info.getAnimatedImageRes(); 178 final String htmlDescription = info.loadHtmlDescription(mPm); 179 final String settingsClassName = info.getSettingsActivityName(); 180 final String tileServiceClassName = info.getTileServiceName(); 181 182 putBasicExtras(preference, prefKey, title, intro, description, imageRes, 183 htmlDescription, componentName); 184 putSettingsExtras(preference, componentName.getPackageName(), settingsClassName); 185 putTileServiceExtras(preference, componentName.getPackageName(), 186 tileServiceClassName); 187 188 preferenceList.add(preference); 189 } 190 return preferenceList; 191 } 192 getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info)193 private String getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info) { 194 final int type = AccessibilityUtil.getAccessibilityServiceFragmentType(info); 195 switch (type) { 196 case AccessibilityUtil.AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE: 197 return VolumeShortcutToggleAccessibilityServicePreferenceFragment.class.getName(); 198 case AccessibilityUtil.AccessibilityServiceFragmentType.INVISIBLE_TOGGLE: 199 return InvisibleToggleAccessibilityServicePreferenceFragment.class.getName(); 200 case AccessibilityUtil.AccessibilityServiceFragmentType.TOGGLE: 201 return ToggleAccessibilityServicePreferenceFragment.class.getName(); 202 default: 203 throw new IllegalArgumentException( 204 "Unsupported accessibility fragment type " + type); 205 } 206 } 207 createRestrictedPreference(String key, CharSequence title, CharSequence summary, Drawable icon, String fragment, String packageName, int uid)208 private RestrictedPreference createRestrictedPreference(String key, CharSequence title, 209 CharSequence summary, Drawable icon, String fragment, String packageName, int uid) { 210 final RestrictedPreference preference = new RestrictedPreference(mContext, packageName, 211 uid); 212 213 preference.setKey(key); 214 preference.setTitle(title); 215 preference.setSummary(summary); 216 preference.setIcon(Utils.getAdaptiveIcon(mContext, icon, Color.WHITE)); 217 preference.setFragment(fragment); 218 preference.setIconSize(ICON_SIZE_MEDIUM); 219 preference.setPersistent(false); // Disable SharedPreferences. 220 preference.setOrder(FIRST_PREFERENCE_IN_CATEGORY_INDEX); 221 222 return preference; 223 } 224 setRestrictedPreferenceEnabled(RestrictedPreference preference, final List<String> permittedServices, boolean serviceEnabled)225 private void setRestrictedPreferenceEnabled(RestrictedPreference preference, 226 final List<String> permittedServices, boolean serviceEnabled) { 227 // permittedServices null means all accessibility services are allowed. 228 boolean serviceAllowed = permittedServices == null || permittedServices.contains( 229 preference.getPackageName()); 230 boolean appOpsAllowed; 231 if (serviceAllowed) { 232 try { 233 final int mode = mAppOps.noteOpNoThrow( 234 AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, 235 preference.getUid(), preference.getPackageName()); 236 final boolean ecmEnabled = mContext.getResources().getBoolean( 237 com.android.internal.R.bool.config_enhancedConfirmationModeEnabled); 238 appOpsAllowed = !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED; 239 serviceAllowed = appOpsAllowed; 240 } catch (Exception e) { 241 // Allow service in case if app ops is not available in testing. 242 appOpsAllowed = true; 243 } 244 } else { 245 appOpsAllowed = false; 246 } 247 if (serviceAllowed || serviceEnabled) { 248 preference.setEnabled(true); 249 } else { 250 // Disable accessibility service that are not permitted. 251 final RestrictedLockUtils.EnforcedAdmin admin = 252 RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed( 253 mContext, preference.getPackageName(), UserHandle.myUserId()); 254 255 if (admin != null) { 256 preference.setDisabledByAdmin(admin); 257 } else if (!appOpsAllowed) { 258 preference.setDisabledByAppOps(true); 259 } else { 260 preference.setEnabled(false); 261 } 262 } 263 } 264 265 /** Puts the basic extras into {@link RestrictedPreference}'s getExtras(). */ putBasicExtras(RestrictedPreference preference, String prefKey, CharSequence title, CharSequence intro, CharSequence summary, int imageRes, String htmlDescription, ComponentName componentName)266 private void putBasicExtras(RestrictedPreference preference, String prefKey, 267 CharSequence title, CharSequence intro, CharSequence summary, int imageRes, 268 String htmlDescription, ComponentName componentName) { 269 final Bundle extras = preference.getExtras(); 270 extras.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY, prefKey); 271 extras.putCharSequence(AccessibilitySettings.EXTRA_TITLE, title); 272 extras.putCharSequence(AccessibilitySettings.EXTRA_INTRO, intro); 273 extras.putCharSequence(AccessibilitySettings.EXTRA_SUMMARY, summary); 274 extras.putParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME, componentName); 275 extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, imageRes); 276 extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription); 277 } 278 279 /** 280 * Puts the service extras into {@link RestrictedPreference}'s getExtras(). 281 * 282 * <p><b>Note:</b> Called by {@link AccessibilityServiceInfo}.</p> 283 * 284 * @param preference The preference we are configuring. 285 * @param resolveInfo The service resolve info. 286 * @param serviceEnabled Whether the accessibility service is enabled. 287 */ putServiceExtras(RestrictedPreference preference, ResolveInfo resolveInfo, Boolean serviceEnabled)288 private void putServiceExtras(RestrictedPreference preference, ResolveInfo resolveInfo, 289 Boolean serviceEnabled) { 290 final Bundle extras = preference.getExtras(); 291 292 extras.putParcelable(AccessibilitySettings.EXTRA_RESOLVE_INFO, resolveInfo); 293 extras.putBoolean(AccessibilitySettings.EXTRA_CHECKED, serviceEnabled); 294 } 295 296 /** 297 * Puts the settings extras into {@link RestrictedPreference}'s getExtras(). 298 * 299 * <p><b>Note:</b> Called when settings UI is needed.</p> 300 * 301 * @param preference The preference we are configuring. 302 * @param packageName Package of accessibility feature. 303 * @param settingsClassName The component name of an activity that allows the user to modify 304 * the settings for this accessibility feature. 305 */ putSettingsExtras(RestrictedPreference preference, String packageName, String settingsClassName)306 private void putSettingsExtras(RestrictedPreference preference, String packageName, 307 String settingsClassName) { 308 final Bundle extras = preference.getExtras(); 309 310 if (!TextUtils.isEmpty(settingsClassName)) { 311 extras.putString(AccessibilitySettings.EXTRA_SETTINGS_TITLE, 312 mContext.getText(R.string.accessibility_menu_item_settings).toString()); 313 extras.putString(AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME, 314 new ComponentName(packageName, settingsClassName).flattenToString()); 315 } 316 } 317 318 /** 319 * Puts the information about a particular application 320 * {@link android.service.quicksettings.TileService} into {@link RestrictedPreference}'s 321 * getExtras(). 322 * 323 * <p><b>Note:</b> Called when a tooltip of 324 * {@link android.service.quicksettings.TileService} is needed.</p> 325 * 326 * @param preference The preference we are configuring. 327 * @param packageName Package of accessibility feature. 328 * @param tileServiceClassName The component name of tileService is associated with this 329 * accessibility feature. 330 */ putTileServiceExtras(RestrictedPreference preference, String packageName, String tileServiceClassName)331 private void putTileServiceExtras(RestrictedPreference preference, String packageName, 332 String tileServiceClassName) { 333 final Bundle extras = preference.getExtras(); 334 if (!TextUtils.isEmpty(tileServiceClassName)) { 335 extras.putString(AccessibilitySettings.EXTRA_TILE_SERVICE_COMPONENT_NAME, 336 new ComponentName(packageName, tileServiceClassName).flattenToString()); 337 } 338 } 339 } 340