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