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