1 /* 2 * Copyright (C) 2023 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 android.app.ecm; 18 19 import static android.annotation.SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION; 20 21 import android.annotation.FlaggedApi; 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.RequiresPermission; 25 import android.annotation.SdkConstant; 26 import android.annotation.SystemApi; 27 import android.annotation.SystemService; 28 import android.annotation.TargetApi; 29 import android.app.AppOpsManager; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.pm.PackageManager; 33 import android.content.pm.PackageManager.NameNotFoundException; 34 import android.os.Build; 35 import android.os.RemoteException; 36 import android.os.UserHandle; 37 import android.permission.flags.Flags; 38 import android.util.ArraySet; 39 40 import java.lang.annotation.Retention; 41 42 /** 43 * This class provides the core API for ECM (Enhanced Confirmation Mode). ECM is a feature that 44 * restricts access to protected **settings** (i.e., sensitive resources) by restricted **apps** 45 * (apps from from dangerous sources, such as sideloaded packages or packages downloaded from a web 46 * browser), or restricts settings globally based on device state. 47 * 48 * <p>Specifically, this class provides the ability to: 49 * 50 * <ol> 51 * <li>Check whether a setting is restricted from an app ({@link #isRestricted}) 52 * <li>Get an intent that will open the "Restricted setting" dialog ({@link 53 * #createRestrictedSettingDialogIntent}) (a dialog that informs the user that the operation 54 * they've attempted to perform is restricted) 55 * <li>Check whether an app is eligible to have its restriction status cleared ({@link 56 * #isClearRestrictionAllowed}) 57 * <li>Clear an app's restriction status (i.e., un-restrict it). ({@link #clearRestriction}) 58 * </ol> 59 * 60 * <p>Methods of this class will generally accept an app (identified by a packageName and a user) 61 * and a "setting" (a string representing the "sensitive resource") as arguments. ECM's exact 62 * behavior will generally depend on what restriction state ECM considers each setting and app. For 63 * example: 64 * 65 * <ol> 66 * <li>A setting may be considered by ECM to be either **protected** or **not protected**. In 67 * general, this should be considered hardcoded into ECM's implementation: nothing can 68 * "protect" or "unprotect" a setting. 69 * <li>An app may be considered as being **not restricted** or **restricted**. A restricted app 70 * will be restricted from accessing all protected settings. Whether ECM considers any 71 * particular app restricted is an implementation detail of ECM. However, the user is able to 72 * clear any restricted app's restriction status (i.e, un-restrict it), after which ECM will 73 * consider the app **not restricted**. 74 * <li>A setting may be globally restricted based on device state. In this case, any app may be 75 * automatically considered *restricted*, regardless of the app's restriction state. Users 76 * cannot un-restrict the app, in these cases. 77 * </ol> 78 * 79 * Why is ECM needed? Consider the following (pre-ECM) scenario: 80 * 81 * <ol> 82 * <li>The user downloads and installs an apk file from a browser. 83 * <li>The user opens Settings -> Accessibility 84 * <li>The user tries to register the app as an accessibility service. 85 * <li>The user is shown a permission prompt "Allow _ to have full control of your device?" 86 * <li>The user clicks "Allow" 87 * <li>The downloaded app now has full control of the device. 88 * </ol> 89 * 90 * The purpose of ECM is to add more friction to this scenario. 91 * 92 * <p>With ECM, this scenario becomes: 93 * 94 * <ol> 95 * <li>The user downloads and installs an apk file from a browser. 96 * <li>The user goes into Settings -> Accessibility. 97 * <li>The user tries to register the app as an accessibility service. 98 * <li>The user is presented with a "Restricted setting" dialog explaining that the attempted 99 * action has been restricted. (No "allow" button is shown, but a link is given to a screen 100 * with intentionally-obscure instructions on how to proceed.) 101 * <li>The user must now navigate to Settings -> Apps -> [app] 102 * <li>The user then must click on "..." (top-right corner hamburger menu), then click "Allow 103 * restricted settings" 104 * <li>The user goes (again) into Settings -> Accessibility and (again) tries to register the app 105 * as an accessibility service. 106 * <li>The user is shown a permission prompt "Allow _ to have full control of your device?" 107 * <li>The user clicks "Allow" 108 * <li>The downloaded app now has full control of the device. 109 * </ol> 110 * 111 * And, expanding on the above scenario, the role that this class plays is as follows: 112 * 113 * <ol> 114 * <li>The user downloads and installs an apk file from a browser. 115 * <li>The user goes into Settings -> Accessibility. 116 * <p>**This screen then calls {@link #isRestricted}, which checks whether each app listed 117 * on-screen is restricted from the accessibility service setting. It uses this to visually 118 * "gray out" restricted apps.** 119 * <li>The user tries to register the app as an accessibility service. 120 * <p>**This screen then calls {@link #createRestrictedSettingDialogIntent} and starts the 121 * intent. This opens the "Restricted setting" dialog.** 122 * <li>The user is presented with a "Restricted setting" dialog explaining that the attempted 123 * action is restricted. (No "allow" button is shown, but a link is given to a screen with 124 * intentionally-obscure instructions on how to proceed.) 125 * <p>**Upon opening, this dialog marks the app as eligible to have its restriction status 126 * cleared.** 127 * <li>The user must now navigate to Settings -> Apps -> [app]. 128 * <p>**This screen calls {@link #isClearRestrictionAllowed} to check whether the app is 129 * eligible to have its restriction status cleared. If this returns {@code true}, this screen 130 * should then show a "Allow restricted setting" button inside the top-right hamburger menu.** 131 * <li>The user then must click on "..." (top-right corner hamburger menu), then click "Allow 132 * restricted settings". 133 * <p>**In response, this screen should now call {@link #clearRestriction}.** 134 * <li>The user goes (again) into Settings -> Accessibility and (again) tries to register the app 135 * as an accessibility service. 136 * <li>The user is shown a permission prompt "Allow _ to have full control of your device?" 137 * <li>The user clicks "Allow" 138 * <li>The downloaded app now has full control of the device. 139 * </ol> 140 * 141 * @hide 142 */ 143 @SystemApi 144 @FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) 145 @TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) 146 @SystemService(Context.ECM_ENHANCED_CONFIRMATION_SERVICE) 147 public final class EnhancedConfirmationManager { 148 /* 149 * At the API level, we use the following terminology: 150 * 151 * - The capability of an app to access a setting may be considered (by ECM) to be *restricted* 152 * or *not restricted*. 153 * - A setting may be considered (by ECM) to be *protected* or *not protected*. 154 * - The state of an app may be considered (by ECM) to be *restricted* or *not restricted* 155 * 156 * In this implementation, however, the state of an app is considered either **guarded** or 157 * **not guarded**; these terms can generally be considered synonymous with **restricted** and 158 * **not restricted**. (Keeping in mind that, the capability of any app to access any 159 * non-protected setting will always be considered "not restricted", even if the state of the 160 * app is considered "restricted".). An app can also be in a third state: **guarded and 161 * acknowledged**, which corresponds with an app that is restricted and is eligible to have its 162 * restriction status cleared. 163 * 164 * Currently, the ECM state of any given app is stored in the OP_ACCESS_RESTRICTED_SETTINGS 165 * appop (though this may change in the future): 166 * 167 * - MODE_ALLOWED means the app is explicitly **not guarded**. (U- default) 168 * - MODE_ERRORED means the app is explicitly **guarded**. (Only settable in U-.) 169 * - MODE_IGNORED means the app is explicitly **guarded and acknowledged**. (An app enters this 170 * state as soon as the "Restricted setting" dialog has been shown to the user. If an app is 171 * in this state, Settings is now allowed to provide the user with the option to clear the 172 * restriction.) 173 * - MODE_DEFAULT means the app's ECM state should be decided lazily. (V+ default) (That is, 174 * each time a caller checks whether or not an app is considered guarded by ECM, we'll run an 175 * heuristic to determine this.) 176 * 177 * Some notes on compatibility: 178 * 179 * - On U-, MODE_ALLOWED is the default mode of OP_ACCESS_RESTRICTED_SETTINGS. On both U- and 180 * V+, this is also the mode after the app's restriction has been cleared. 181 * - In U-, the mode needed to be explicitly set (for example, by a browser that allows a 182 * dangerous app to be installed) to MODE_ERRORED to indicate that an app is guarded. In V+, 183 * we no longer allow an app to be placed into MODE_ERRORED, but for compatibility, we still 184 * recognize MODE_ERRORED to indicate that an app is explicitly guarded. 185 * - In V+, the default mode is MODE_DEFAULT. Unlike U-, this potentially affects *all* apps, 186 * not just the ones which have been explicitly marked as **guarded**. 187 * 188 * Regarding ECM "setting"s: a setting may be any abstract resource identified by a string. ECM 189 * may consider any particular setting **protected** or **not protected**. For now, the set of 190 * protected settings is hardcoded, but this may evolve in the future. 191 * 192 * TODO(b/320512579): These methods currently enforce UPDATE_APP_OPS_STATS, 193 * UPDATE_APP_OPS_STATS, and, for setter methods, MANAGE_APP_OPS_MODES. We should add 194 * RequiresPermission annotations, but we can't, because some of these permissions are hidden 195 * API. Either upgrade these to SystemApi or enforce a different permission, then add the 196 * appropriate RequiresPermission annotation. 197 */ 198 199 /** 200 * Shows the "Restricted setting" dialog. Opened when a setting is blocked. 201 */ 202 @SdkConstant(BROADCAST_INTENT_ACTION) 203 public static final String ACTION_SHOW_ECM_RESTRICTED_SETTING_DIALOG = 204 "android.app.ecm.action.SHOW_ECM_RESTRICTED_SETTING_DIALOG"; 205 206 /** 207 * The setting is restricted because of the phone state of the device 208 * @hide 209 */ 210 public static final String REASON_PHONE_STATE = "phone_state"; 211 212 /** 213 * The setting is restricted because the restricted app op is set for the given package 214 * @hide 215 */ 216 public static final String REASON_PACKAGE_RESTRICTED = "package_restricted"; 217 218 219 /** A map of ECM states to their corresponding app op states */ 220 @Retention(java.lang.annotation.RetentionPolicy.SOURCE) 221 @IntDef(prefix = {"ECM_STATE_"}, value = {EcmState.ECM_STATE_NOT_GUARDED, 222 EcmState.ECM_STATE_GUARDED, EcmState.ECM_STATE_GUARDED_AND_ACKNOWLEDGED, 223 EcmState.ECM_STATE_IMPLICIT}) 224 private @interface EcmState { 225 int ECM_STATE_NOT_GUARDED = AppOpsManager.MODE_ALLOWED; 226 int ECM_STATE_GUARDED = AppOpsManager.MODE_ERRORED; 227 int ECM_STATE_GUARDED_AND_ACKNOWLEDGED = AppOpsManager.MODE_IGNORED; 228 int ECM_STATE_IMPLICIT = AppOpsManager.MODE_DEFAULT; 229 } 230 231 private static final String LOG_TAG = EnhancedConfirmationManager.class.getSimpleName(); 232 233 private static final ArraySet<String> PROTECTED_SETTINGS = new ArraySet<>(); 234 235 static { 236 PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE); 237 // TODO(b/310654015): Add other explicitly protected settings 238 } 239 240 private final @NonNull Context mContext; 241 private final PackageManager mPackageManager; 242 243 private final @NonNull IEnhancedConfirmationManager mService; 244 245 /** 246 * @hide 247 */ EnhancedConfirmationManager(@onNull Context context, @NonNull IEnhancedConfirmationManager service)248 public EnhancedConfirmationManager(@NonNull Context context, 249 @NonNull IEnhancedConfirmationManager service) { 250 mContext = context; 251 mPackageManager = context.getPackageManager(); 252 mService = service; 253 } 254 255 /** 256 * Check whether a setting is restricted from an app. 257 * 258 * <p>This is {@code true} when the setting is a protected setting (i.e., a sensitive resource), 259 * and the app is restricted (i.e., considered dangerous), and the user has not yet cleared the 260 * app's restriction status (i.e., by clicking "Allow restricted settings" for this app). 261 * 262 * @param packageName package name of the application to check for 263 * @param settingIdentifier identifier of the resource to check to check for 264 * @return {@code true} if the setting is restricted from the app 265 * @throws NameNotFoundException if the provided package was not found 266 */ 267 @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) isRestricted(@onNull String packageName, @NonNull String settingIdentifier)268 public boolean isRestricted(@NonNull String packageName, @NonNull String settingIdentifier) 269 throws NameNotFoundException { 270 try { 271 return mService.isRestricted(packageName, settingIdentifier, 272 mContext.getUser().getIdentifier()); 273 } catch (IllegalArgumentException e) { 274 throw new NameNotFoundException(packageName); 275 } catch (RemoteException e) { 276 throw e.rethrowFromSystemServer(); 277 } 278 } 279 280 /** 281 * Clear an app's restriction status (i.e., un-restrict it). 282 * 283 * <p>After this is called, the app will no longer be restricted from accessing any protected 284 * setting by ECM. This method should be called when the user clicks "Allow restricted settings" 285 * for the app. 286 * 287 * @param packageName package name of the application to remove protection from 288 * @throws NameNotFoundException if the provided package was not found 289 */ 290 @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) clearRestriction(@onNull String packageName)291 public void clearRestriction(@NonNull String packageName) throws NameNotFoundException { 292 try { 293 mService.clearRestriction(packageName, mContext.getUser().getIdentifier()); 294 } catch (IllegalArgumentException e) { 295 throw new NameNotFoundException(packageName); 296 } catch (RemoteException e) { 297 throw e.rethrowFromSystemServer(); 298 } 299 } 300 301 /** 302 * Check whether the provided app is eligible to have its restriction status cleared (i.e., the 303 * app is restricted, and the "Restricted setting" dialog has been presented to the user). 304 * 305 * <p>The Settings UI should use method this to check whether to present the user with the 306 * "Allow restricted settings" button. 307 * 308 * @param packageName package name of the application to check for 309 * @return {@code true} if the settings UI should present the user with the ability to clear 310 * restrictions from the provided app 311 * @throws NameNotFoundException if the provided package was not found 312 */ 313 @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) isClearRestrictionAllowed(@onNull String packageName)314 public boolean isClearRestrictionAllowed(@NonNull String packageName) 315 throws NameNotFoundException { 316 try { 317 return mService.isClearRestrictionAllowed(packageName, 318 mContext.getUser().getIdentifier()); 319 } catch (IllegalArgumentException e) { 320 throw new NameNotFoundException(packageName); 321 } catch (RemoteException e) { 322 throw e.rethrowFromSystemServer(); 323 } 324 } 325 326 /** 327 * Mark the app as eligible to have its restriction status cleared. 328 * 329 * <p>This should be called from the "Restricted setting" dialog (which {@link 330 * #createRestrictedSettingDialogIntent} directs to) upon being presented to the user. 331 * 332 * <p>This restriction clearing does not apply to any settings that are restricted based on 333 * global device state 334 * 335 * @param packageName package name of the application which should be considered acknowledged 336 * @throws NameNotFoundException if the provided package was not found 337 */ 338 @RequiresPermission(android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES) setClearRestrictionAllowed(@onNull String packageName)339 public void setClearRestrictionAllowed(@NonNull String packageName) 340 throws NameNotFoundException { 341 try { 342 mService.setClearRestrictionAllowed(packageName, mContext.getUser().getIdentifier()); 343 } catch (IllegalArgumentException e) { 344 throw new NameNotFoundException(packageName); 345 } catch (RemoteException e) { 346 throw e.rethrowFromSystemServer(); 347 } 348 } 349 350 /** 351 * Gets an intent that will open the "Restricted setting" dialog for the specified package 352 * and setting. 353 * 354 * <p>The "Restricted setting" dialog is a dialog that informs the user that the operation 355 * they've attempted to perform is restricted, and provides them with a link explaining how to 356 * proceed. 357 * 358 * @param packageName package name of the restricted application 359 * @param settingIdentifier identifier of the restricted setting 360 * @throws NameNotFoundException if the provided package was not found 361 */ createRestrictedSettingDialogIntent(@onNull String packageName, @NonNull String settingIdentifier)362 public @NonNull Intent createRestrictedSettingDialogIntent(@NonNull String packageName, 363 @NonNull String settingIdentifier) throws NameNotFoundException { 364 Intent intent = new Intent(ACTION_SHOW_ECM_RESTRICTED_SETTING_DIALOG); 365 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); 366 int uid = getPackageUid(packageName); 367 intent.putExtra(Intent.EXTRA_UID, uid); 368 intent.putExtra(Intent.EXTRA_SUBJECT, settingIdentifier); 369 try { 370 String restrictionReason = mService.getRestrictionReason(packageName, 371 settingIdentifier, UserHandle.getUserHandleForUid(uid).getIdentifier()); 372 intent.putExtra(Intent.EXTRA_REASON, restrictionReason); 373 } catch (SecurityException | RemoteException e) { 374 // The caller of this method does not have permission to read the ECM state, so we 375 // won't include it in the return 376 } 377 return intent; 378 } 379 getPackageUid(String packageName)380 private int getPackageUid(String packageName) throws NameNotFoundException { 381 return mPackageManager.getApplicationInfoAsUser(packageName, /* flags */ 0, 382 mContext.getUser()).uid; 383 } 384 } 385