1 /* 2 * Copyright (C) 2024 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.security.advancedprotection; 18 19 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 20 import static android.os.UserManager.DISALLOW_CELLULAR_2G; 21 import static android.os.UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY; 22 23 import android.Manifest; 24 import android.annotation.CallbackExecutor; 25 import android.annotation.FlaggedApi; 26 import android.annotation.IntDef; 27 import android.annotation.NonNull; 28 import android.annotation.RequiresPermission; 29 import android.annotation.SystemApi; 30 import android.annotation.SystemService; 31 import android.app.admin.DevicePolicyManager; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.net.wifi.WifiManager; 35 import android.os.Binder; 36 import android.os.RemoteException; 37 import android.os.UserManager; 38 import android.security.Flags; 39 import android.util.Log; 40 41 import java.lang.annotation.Retention; 42 import java.lang.annotation.RetentionPolicy; 43 import java.util.List; 44 import java.util.Objects; 45 import java.util.Set; 46 import java.util.concurrent.ConcurrentHashMap; 47 import java.util.concurrent.Executor; 48 49 /** 50 * <p>Advanced Protection is a mode that users can enroll their device into, that enhances security 51 * by enabling features and restrictions across both the platform and user apps. 52 * 53 * <p>This class provides methods to query and control the advanced protection mode 54 * for the device. 55 */ 56 @FlaggedApi(Flags.FLAG_AAPM_API) 57 @SystemService(Context.ADVANCED_PROTECTION_SERVICE) 58 public final class AdvancedProtectionManager { 59 private static final String TAG = "AdvancedProtectionMgr"; 60 private static final String PKG_SETTINGS = "com.android.settings"; 61 62 //TODO(b/378931989): Switch to android.app.admin.DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY 63 //when the appropriate flag is launched. 64 private static final String MEMORY_TAGGING_POLICY = "memoryTagging"; 65 66 /** 67 * Advanced Protection's identifier for setting policies or restrictions in 68 * {@link DevicePolicyManager}. 69 * 70 * @hide */ 71 public static final String ADVANCED_PROTECTION_SYSTEM_ENTITY = 72 "android.security.advancedprotection"; 73 74 /** 75 * Feature identifier for disallowing connections to 2G networks. 76 * 77 * @see UserManager#DISALLOW_CELLULAR_2G 78 * @hide */ 79 @SystemApi 80 public static final int FEATURE_ID_DISALLOW_CELLULAR_2G = 0; 81 82 /** 83 * Feature identifier for disallowing installs of apps from unknown sources. 84 * 85 * @see UserManager#DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY 86 * @hide */ 87 @SystemApi 88 public static final int FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES = 1; 89 90 /** 91 * Feature identifier for disallowing USB connections. 92 * 93 * @hide */ 94 @SystemApi 95 public static final int FEATURE_ID_DISALLOW_USB = 2; 96 97 /** 98 * Feature identifier for disallowing connections to Wi-Fi Wired Equivalent Privacy (WEP) 99 * networks. 100 * 101 * @see WifiManager#isWepSupported() 102 * @hide */ 103 @SystemApi 104 public static final int FEATURE_ID_DISALLOW_WEP = 3; 105 106 /** 107 * Feature identifier for enabling the Memory Tagging Extension (MTE). MTE is a CPU extension 108 * that allows to protect against certain classes of security problems at a small runtime 109 * performance cost overhead. 110 * 111 * @see DevicePolicyManager#setMtePolicy(int) 112 * @hide */ 113 @SystemApi 114 public static final int FEATURE_ID_ENABLE_MTE = 4; 115 116 /** @hide */ 117 @IntDef(prefix = { "FEATURE_ID_" }, value = { 118 FEATURE_ID_DISALLOW_CELLULAR_2G, 119 FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES, 120 FEATURE_ID_DISALLOW_USB, 121 FEATURE_ID_DISALLOW_WEP, 122 FEATURE_ID_ENABLE_MTE, 123 }) 124 @Retention(RetentionPolicy.SOURCE) 125 public @interface FeatureId {} 126 127 /** @hide */ featureIdToString(@eatureId int featureId)128 public static String featureIdToString(@FeatureId int featureId) { 129 return switch(featureId) { 130 case FEATURE_ID_DISALLOW_CELLULAR_2G -> "DISALLOW_CELLULAR_2G"; 131 case FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES -> "DISALLOW_INSTALL_UNKNOWN_SOURCES"; 132 case FEATURE_ID_DISALLOW_USB -> "DISALLOW_USB"; 133 case FEATURE_ID_DISALLOW_WEP -> "DISALLOW_WEP"; 134 case FEATURE_ID_ENABLE_MTE -> "ENABLE_MTE"; 135 default -> "UNKNOWN"; 136 }; 137 } 138 139 private static final Set<Integer> ALL_FEATURE_IDS = Set.of( 140 FEATURE_ID_DISALLOW_CELLULAR_2G, 141 FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES, 142 FEATURE_ID_DISALLOW_USB, 143 FEATURE_ID_DISALLOW_WEP, 144 FEATURE_ID_ENABLE_MTE); 145 146 /** 147 * Activity Action: Show a dialog with disabled by advanced protection message. 148 * <p> If a user action or a setting toggle is disabled by advanced protection, this dialog can 149 * be triggered to let the user know about this. 150 * <p> 151 * Input: 152 * <p>{@link #EXTRA_SUPPORT_DIALOG_FEATURE}: The feature identifier. 153 * <p>{@link #EXTRA_SUPPORT_DIALOG_TYPE}: The type of the action. 154 * <p> 155 * Output: Nothing. 156 * 157 * @hide */ 158 public static final String ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG = 159 "android.security.advancedprotection.action.SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG"; 160 161 /** 162 * An int extra used with {@link #createSupportIntent} to identify the feature that needs to 163 * show a support dialog explaining it was disabled by advanced protection. 164 * 165 * @hide */ 166 @FeatureId 167 public static final String EXTRA_SUPPORT_DIALOG_FEATURE = 168 "android.security.advancedprotection.extra.SUPPORT_DIALOG_FEATURE"; 169 170 /** 171 * An int extra used with {@link #createSupportIntent} to identify the type of the action that 172 * needs to be explained in the support dialog. 173 * 174 * @hide */ 175 @SupportDialogType 176 public static final String EXTRA_SUPPORT_DIALOG_TYPE = 177 "android.security.advancedprotection.extra.SUPPORT_DIALOG_TYPE"; 178 179 /** 180 * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating an unknown action was blocked by 181 * advanced protection, hence the support dialog should display a default explanation. 182 * 183 * @hide */ 184 public static final int SUPPORT_DIALOG_TYPE_UNKNOWN = 0; 185 186 /** 187 * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating a user performed an action that was 188 * blocked by advanced protection. 189 * 190 * @hide */ 191 public static final int SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION = 1; 192 193 /** 194 * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating a user pressed on a setting toggle 195 * that was disabled by advanced protection. 196 * 197 * @hide */ 198 public static final int SUPPORT_DIALOG_TYPE_DISABLED_SETTING = 2; 199 200 /** @hide */ 201 @IntDef(prefix = { "SUPPORT_DIALOG_TYPE_" }, value = { 202 SUPPORT_DIALOG_TYPE_UNKNOWN, 203 SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION, 204 SUPPORT_DIALOG_TYPE_DISABLED_SETTING, 205 }) 206 @Retention(RetentionPolicy.SOURCE) 207 public @interface SupportDialogType {} 208 209 /** @hide */ supportDialogTypeToString(@upportDialogType int type)210 public static String supportDialogTypeToString(@SupportDialogType int type) { 211 return switch(type) { 212 case SUPPORT_DIALOG_TYPE_UNKNOWN -> "UNKNOWN"; 213 case SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION -> "BLOCKED_INTERACTION"; 214 case SUPPORT_DIALOG_TYPE_DISABLED_SETTING -> "DISABLED_SETTING"; 215 default -> "UNKNOWN"; 216 }; 217 } 218 219 private static final Set<Integer> ALL_SUPPORT_DIALOG_TYPES = Set.of( 220 SUPPORT_DIALOG_TYPE_UNKNOWN, 221 SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION, 222 SUPPORT_DIALOG_TYPE_DISABLED_SETTING); 223 224 private final ConcurrentHashMap<Callback, IAdvancedProtectionCallback> 225 mCallbackMap = new ConcurrentHashMap<>(); 226 227 @NonNull 228 private final IAdvancedProtectionService mService; 229 230 /** @hide */ 231 public AdvancedProtectionManager(@NonNull IAdvancedProtectionService service) { 232 mService = service; 233 } 234 235 /** 236 * Checks if advanced protection is enabled on the device. 237 * 238 * @return {@code true} if advanced protection is enabled, {@code false} otherwise. 239 */ 240 @RequiresPermission(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE) 241 public boolean isAdvancedProtectionEnabled() { 242 try { 243 return mService.isAdvancedProtectionEnabled(); 244 } catch (RemoteException e) { 245 throw e.rethrowFromSystemServer(); 246 } 247 } 248 249 /** 250 * Registers a {@link Callback} to be notified of changes to the Advanced Protection state. 251 * 252 * <p>The provided callback will be called on the specified executor with the updated 253 * state. Methods are called when the state changes, as well as once 254 * on initial registration. 255 * 256 * @param executor The executor of where the callback will execute. 257 * @param callback The {@link Callback} object to register.. 258 */ 259 @RequiresPermission(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE) 260 public void registerAdvancedProtectionCallback(@NonNull @CallbackExecutor Executor executor, 261 @NonNull Callback callback) { 262 if (mCallbackMap.get(callback) != null) { 263 Log.d(TAG, "registerAdvancedProtectionCallback callback already present"); 264 return; 265 } 266 267 IAdvancedProtectionCallback delegate = new IAdvancedProtectionCallback.Stub() { 268 @Override 269 public void onAdvancedProtectionChanged(boolean enabled) { 270 final long identity = Binder.clearCallingIdentity(); 271 try { 272 executor.execute(() -> callback.onAdvancedProtectionChanged(enabled)); 273 } finally { 274 Binder.restoreCallingIdentity(identity); 275 } 276 } 277 }; 278 279 try { 280 mService.registerAdvancedProtectionCallback(delegate); 281 } catch (RemoteException e) { 282 throw e.rethrowFromSystemServer(); 283 } 284 285 mCallbackMap.put(callback, delegate); 286 } 287 288 /** 289 * Unregister an existing {@link Callback}. 290 * 291 * @param callback The {@link Callback} object to unregister. 292 */ 293 @RequiresPermission(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE) 294 public void unregisterAdvancedProtectionCallback(@NonNull Callback callback) { 295 IAdvancedProtectionCallback delegate = mCallbackMap.get(callback); 296 if (delegate == null) { 297 Log.d(TAG, "unregisterAdvancedProtectionCallback callback not present"); 298 return; 299 } 300 301 try { 302 mService.unregisterAdvancedProtectionCallback(delegate); 303 } catch (RemoteException e) { 304 throw e.rethrowFromSystemServer(); 305 } 306 307 mCallbackMap.remove(callback); 308 } 309 310 /** 311 * Enables or disables advanced protection on the device. Can only be called by an admin user. 312 * 313 * @param enabled {@code true} to enable advanced protection, {@code false} to disable it. 314 * @hide 315 */ 316 @SystemApi 317 @RequiresPermission(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE) 318 public void setAdvancedProtectionEnabled(boolean enabled) { 319 try { 320 mService.setAdvancedProtectionEnabled(enabled); 321 } catch (RemoteException e) { 322 throw e.rethrowFromSystemServer(); 323 } 324 } 325 326 /** 327 * Returns the list of advanced protection features which are available on this device. 328 * 329 * @hide 330 */ 331 @SystemApi 332 @NonNull 333 @RequiresPermission(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE) 334 public List<AdvancedProtectionFeature> getAdvancedProtectionFeatures() { 335 try { 336 return mService.getAdvancedProtectionFeatures(); 337 } catch (RemoteException e) { 338 throw e.rethrowFromSystemServer(); 339 } 340 } 341 342 /** 343 * Called by a feature to display a support dialog when a feature was disabled by advanced 344 * protection. This returns an intent that can be used with 345 * {@link Context#startActivity(Intent)} to display the dialog. 346 * 347 * <p>Note that this method doesn't check if the feature is actually disabled, i.e. this method 348 * will always return an intent. 349 * 350 * @param featureId The feature identifier. 351 * @param type The type of the feature describing the action that needs to be explained 352 * in the dialog or {@link #SUPPORT_DIALOG_TYPE_UNKNOWN} for default 353 * explanation. 354 * @return Intent An intent to be used to start the dialog-activity that explains a feature was 355 * disabled by advanced protection. 356 * @hide 357 */ 358 public static @NonNull Intent createSupportIntent(@FeatureId int featureId, 359 @SupportDialogType int type) { 360 if (!ALL_FEATURE_IDS.contains(featureId)) { 361 throw new IllegalArgumentException(featureId + " is not a valid feature ID. See" 362 + " FEATURE_ID_* APIs."); 363 } 364 if (!ALL_SUPPORT_DIALOG_TYPES.contains(type)) { 365 throw new IllegalArgumentException(type + " is not a valid type. See" 366 + " SUPPORT_DIALOG_TYPE_* APIs."); 367 } 368 369 Intent intent = new Intent(ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG); 370 intent.setPackage(PKG_SETTINGS); 371 intent.setFlags(FLAG_ACTIVITY_NEW_TASK); 372 intent.putExtra(EXTRA_SUPPORT_DIALOG_FEATURE, featureId); 373 intent.putExtra(EXTRA_SUPPORT_DIALOG_TYPE, type); 374 return intent; 375 } 376 377 /** 378 * Called by a feature to display a support dialog when a feature was disabled by advanced 379 * protection based on a policy identifier or restriction. This returns an intent that can be 380 * used with {@link Context#startActivity(Intent)} to display the dialog. 381 * 382 * <p>At the moment, if the dialog is for {@link #FEATURE_ID_DISALLOW_CELLULAR_2G} or 383 * {@link #FEATURE_ID_ENABLE_MTE} and the provided type is 384 * {@link #SUPPORT_DIALOG_TYPE_UNKNOWN}, the type will be changed to 385 * {@link #SUPPORT_DIALOG_TYPE_DISABLED_SETTING} in the returned intent, as these features only 386 * have a disabled setting UI. 387 * 388 * <p>Note that this method doesn't check if the feature is actually disabled, i.e. this method 389 * will always return an intent. 390 * 391 * @param identifier The policy identifier or restriction. 392 * @param type The type of the feature describing the action that needs to be explained 393 * in the dialog or {@link #SUPPORT_DIALOG_TYPE_UNKNOWN} for default 394 * explanation. 395 * @return Intent An intent to be used to start the dialog-activity that explains a feature was 396 * disabled by advanced protection. 397 * @hide */ 398 public static @NonNull Intent createSupportIntentForPolicyIdentifierOrRestriction( 399 @NonNull String identifier, @SupportDialogType int type) { 400 Objects.requireNonNull(identifier); 401 if (!ALL_SUPPORT_DIALOG_TYPES.contains(type)) { 402 throw new IllegalArgumentException(type + " is not a valid type. See" 403 + " SUPPORT_DIALOG_TYPE_* APIs."); 404 } 405 final int featureId; 406 int dialogType = type; 407 if (DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY.equals(identifier)) { 408 featureId = FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES; 409 } else if (DISALLOW_CELLULAR_2G.equals(identifier)) { 410 featureId = FEATURE_ID_DISALLOW_CELLULAR_2G; 411 dialogType = (dialogType == SUPPORT_DIALOG_TYPE_UNKNOWN) 412 ? SUPPORT_DIALOG_TYPE_DISABLED_SETTING : dialogType; 413 } else if (MEMORY_TAGGING_POLICY.equals(identifier)) { 414 featureId = FEATURE_ID_ENABLE_MTE; 415 dialogType = (dialogType == SUPPORT_DIALOG_TYPE_UNKNOWN) 416 ? SUPPORT_DIALOG_TYPE_DISABLED_SETTING : dialogType; 417 } else { 418 throw new UnsupportedOperationException("Unsupported identifier: " + identifier); 419 } 420 return createSupportIntent(featureId, dialogType); 421 } 422 423 /** @hide */ 424 @RequiresPermission(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE) 425 public void logDialogShown(@FeatureId int featureId, @SupportDialogType int type, 426 boolean learnMoreClicked) { 427 try { 428 mService.logDialogShown(featureId, type, learnMoreClicked); 429 } catch (RemoteException e) { 430 throw e.rethrowFromSystemServer(); 431 } 432 } 433 434 /** 435 * A callback class for monitoring changes to Advanced Protection state 436 * 437 * <p>To register a callback, implement this interface, and register it with 438 * {@link AdvancedProtectionManager#registerAdvancedProtectionCallback(Executor, Callback)}. 439 * Methods are called when the state changes, as well as once on initial registration. 440 */ 441 @FlaggedApi(Flags.FLAG_AAPM_API) 442 public interface Callback { 443 /** 444 * Called when advanced protection state changes 445 * @param enabled the new state 446 */ 447 void onAdvancedProtectionChanged(boolean enabled); 448 } 449 } 450