1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.M; 4 import static android.os.Build.VERSION_CODES.P; 5 import static android.os.Build.VERSION_CODES.Q; 6 import static android.os.Build.VERSION_CODES.R; 7 import static android.os.Build.VERSION_CODES.S; 8 import static android.os.Build.VERSION_CODES.TIRAMISU; 9 import static java.util.stream.Collectors.toSet; 10 import static org.robolectric.shadow.api.Shadow.invokeConstructor; 11 import static org.robolectric.util.reflector.Reflector.reflector; 12 13 import android.annotation.RequiresApi; 14 import android.annotation.RequiresPermission; 15 import android.annotation.SystemApi; 16 import android.app.AppOpsManager; 17 import android.app.AppOpsManager.AttributedOpEntry; 18 import android.app.AppOpsManager.NoteOpEvent; 19 import android.app.AppOpsManager.OnOpChangedListener; 20 import android.app.AppOpsManager.OpEntry; 21 import android.app.AppOpsManager.OpEventProxyInfo; 22 import android.app.AppOpsManager.PackageOps; 23 import android.app.SyncNotedAppOp; 24 import android.content.AttributionSource; 25 import android.content.Context; 26 import android.content.pm.PackageManager.NameNotFoundException; 27 import android.media.AudioAttributes.AttributeUsage; 28 import android.os.Binder; 29 import android.os.Build; 30 import android.util.ArrayMap; 31 import android.util.LongSparseArray; 32 import android.util.LongSparseLongArray; 33 import com.android.internal.app.IAppOpsService; 34 import com.google.auto.value.AutoValue; 35 import com.google.common.base.Preconditions; 36 import com.google.common.collect.HashMultimap; 37 import com.google.common.collect.ImmutableList; 38 import com.google.common.collect.Multimap; 39 import com.google.common.collect.MultimapBuilder; 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.Collection; 43 import java.util.Collections; 44 import java.util.HashMap; 45 import java.util.HashSet; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.Objects; 49 import java.util.Set; 50 import java.util.stream.IntStream; 51 import javax.annotation.Nonnull; 52 import javax.annotation.Nullable; 53 import org.robolectric.RuntimeEnvironment; 54 import org.robolectric.annotation.ClassName; 55 import org.robolectric.annotation.HiddenApi; 56 import org.robolectric.annotation.Implementation; 57 import org.robolectric.annotation.Implements; 58 import org.robolectric.annotation.RealObject; 59 import org.robolectric.annotation.Resetter; 60 import org.robolectric.shadow.api.Shadow; 61 import org.robolectric.util.ReflectionHelpers; 62 import org.robolectric.util.ReflectionHelpers.ClassParameter; 63 import org.robolectric.util.reflector.Accessor; 64 import org.robolectric.util.reflector.ForType; 65 66 /** Shadow for {@link AppOpsManager}. */ 67 @Implements(value = AppOpsManager.class) 68 public class ShadowAppOpsManager { 69 70 // OpEntry fields that the shadow doesn't currently allow the test to configure. 71 protected static final long OP_TIME = 1400000000L; 72 protected static final long REJECT_TIME = 0L; 73 protected static final int DURATION = 10; 74 protected static final int PROXY_UID = 0; 75 protected static final String PROXY_PACKAGE = ""; 76 77 @RealObject private AppOpsManager realObject; 78 79 private static boolean staticallyInitialized; 80 81 // Recorded operations, keyed by (uid, packageName) 82 private static final Multimap<Key, Integer> storedOps = HashMultimap.create(); 83 // (uid, packageName, opCode) => opMode 84 private static final Map<Key, Integer> appModeMap = new HashMap<>(); 85 86 // (uid, packageName, opCode) 87 private static final Set<Key> longRunningOp = new HashSet<>(); 88 89 private static final Map<OnOpChangedListener, Set<Key>> appOpListeners = new ArrayMap<>(); 90 91 // op | (usage << 8) => ModeAndException 92 private static final Map<Integer, ModeAndException> audioRestrictions = new HashMap<>(); 93 94 private Context context; 95 96 @Implementation __constructor__(Context context, IAppOpsService service)97 protected void __constructor__(Context context, IAppOpsService service) { 98 this.context = context; 99 invokeConstructor( 100 AppOpsManager.class, 101 realObject, 102 ClassParameter.from(Context.class, context), 103 ClassParameter.from(IAppOpsService.class, service)); 104 } 105 106 @Implementation __staticInitializer__()107 protected static void __staticInitializer__() { 108 staticallyInitialized = true; 109 Shadow.directInitialize(AppOpsManager.class); 110 } 111 112 /** 113 * Change the operating mode for the given op in the given app package. You must pass in both the 114 * uid and name of the application whose mode is being modified; if these do not match, the 115 * modification will still be applied. 116 * 117 * <p>This method is public for testing {@link #checkOpNoThrow}. If {@link #checkOpNoThrow} is 118 * called afterwards with the {@code op}, {@code uid}, and {@code packageName} provided, it will 119 * return the {@code mode} set here. 120 * 121 * <p>The mode set by this method takes precedence over the mode set by {@link #setMode(String, 122 * int, String, int)}. This may not reflect the true implementation. 123 * 124 * @param op The operation to modify. One of the OPSTR_* constants. 125 * @param uid The user id of the application whose mode will be changed. 126 * @param packageName The name of the application package name whose mode will be changed. 127 */ 128 @Implementation(minSdk = P) 129 @HiddenApi 130 @SystemApi 131 @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) setMode(String op, int uid, String packageName, int mode)132 public void setMode(String op, int uid, String packageName, int mode) { 133 setMode(AppOpsManager.strOpToOp(op), uid, packageName, mode); 134 } 135 136 /** 137 * Int version of {@link #setMode(String, int, String, int)}. 138 * 139 * <p>This method is public for testing {@link #checkOpNoThrow}. If {@link #checkOpNoThrow} is 140 * called afterwards with the {@code op}, {@code uid}, and {@code packageName} provided, it will 141 * return the {@code mode} set here. 142 */ 143 @Implementation 144 @HiddenApi setMode(int op, int uid, String packageName, int mode)145 public void setMode(int op, int uid, String packageName, int mode) { 146 Integer oldMode = appModeMap.put(Key.create(uid, packageName, op), mode); 147 if (Objects.equals(oldMode, mode)) { 148 return; 149 } 150 151 for (Map.Entry<OnOpChangedListener, Set<Key>> entry : appOpListeners.entrySet()) { 152 for (Key key : entry.getValue()) { 153 if (op == key.getOpCode() 154 && (key.getPackageName() == null || key.getPackageName().equals(packageName))) { 155 entry.getKey().onOpChanged(getOpString(op), packageName); 156 } 157 } 158 } 159 } 160 161 /** 162 * Change the operating mode for the given op in the given uid space. 163 * 164 * <p>This method is public for testing {@link #checkOpNoThrow}. If {@link #checkOpNoThrow} is 165 * called afterwards with the {@code op} and {@code uid} provided, and any {@code packageName}, it 166 * will return the {@code mode} set here. 167 * 168 * <p>The mode set by {@link #setMode(String, int, String, int)} takes precedence over the mode 169 * set by this method. This may not reflect the true implementation. 170 * 171 * @param op The operation to modify. One of the OPSTR_* constants. 172 * @param uid The user id of the application whose mode will be changed. 173 */ 174 @Implementation(minSdk = P) 175 @HiddenApi 176 @SystemApi 177 @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES) setUidMode(String op, int uid, int mode)178 protected void setUidMode(String op, int uid, int mode) { 179 setUidMode(AppOpsManager.strOpToOp(op), uid, mode); 180 } 181 182 /** 183 * Int version of {@link #setUidMode(String, int, int)}. 184 * 185 * <p>This method is public for testing {@link #checkOpNoThrow}. If {@link #checkOpNoThrow} is 186 * called afterwards with the {@code op}, {@code ui}, and {@code packageName} provided, it will 187 * return the {@code mode} set here. 188 */ 189 @Implementation(minSdk = M) 190 @HiddenApi setUidMode(int op, int uid, int mode)191 protected void setUidMode(int op, int uid, int mode) { 192 Integer oldMode = appModeMap.put(Key.create(uid, null, op), mode); 193 if (Objects.equals(oldMode, mode)) { 194 return; 195 } 196 197 for (Map.Entry<OnOpChangedListener, Set<Key>> entry : appOpListeners.entrySet()) { 198 for (Key key : entry.getValue()) { 199 if (op == key.getOpCode()) { 200 entry.getKey().onOpChanged(getOpString(op), null); 201 } 202 } 203 } 204 } 205 getOpString(int opCode)206 protected String getOpString(int opCode) { 207 if (RuntimeEnvironment.getApiLevel() <= TIRAMISU) { 208 String[] sOpToString = ReflectionHelpers.getStaticField(AppOpsManager.class, "sOpToString"); 209 return sOpToString[opCode]; 210 } else { 211 Object[] sAppOpInfos = ReflectionHelpers.getStaticField(AppOpsManager.class, "sAppOpInfos"); 212 return reflector(AppOpInfoReflector.class, sAppOpInfos[opCode]).getName(); 213 } 214 } 215 216 /** 217 * Returns app op details for all packages for which one of {@link #setMode} methods was used to 218 * set the value of one of the given app ops (it does return those set to 'default' mode, while 219 * the true implementation usually doesn't). Also, we don't enforce any permission checks which 220 * might be needed in the true implementation. 221 * 222 * @param ops The set of operations you are interested in, or null if you want all of them. 223 * @return app ops information about each package, containing only ops that were specified as an 224 * argument 225 */ 226 @Implementation(minSdk = Q) 227 @HiddenApi 228 @SystemApi 229 @Nonnull getPackagesForOps(@ullable String[] ops)230 protected List<PackageOps> getPackagesForOps(@Nullable String[] ops) { 231 List<PackageOps> result = null; 232 233 if (ops == null) { 234 int[] intOps = null; 235 result = getPackagesForOps(intOps); 236 } else { 237 List<Integer> intOpsList = new ArrayList<>(); 238 for (String op : ops) { 239 intOpsList.add(AppOpsManager.strOpToOp(op)); 240 } 241 result = getPackagesForOps(intOpsList.stream().mapToInt(i -> i).toArray()); 242 } 243 244 return result != null ? result : new ArrayList<>(); 245 } 246 247 /** 248 * Returns app op details for all packages for which one of {@link #setMode} methods was used to 249 * set the value of one of the given app ops (it does return those set to 'default' mode, while 250 * the true implementation usually doesn't). Also, we don't enforce any permission checks which 251 * might be needed in the true implementation. 252 * 253 * @param ops The set of operations you are interested in, or null if you want all of them. 254 * @return app ops information about each package, containing only ops that were specified as an 255 * argument 256 */ 257 @Implementation 258 @HiddenApi getPackagesForOps(int[] ops)259 protected List<PackageOps> getPackagesForOps(int[] ops) { 260 Set<Integer> relevantOps; 261 if (ops != null) { 262 relevantOps = IntStream.of(ops).boxed().collect(toSet()); 263 } else { 264 relevantOps = new HashSet<>(); 265 } 266 267 // Aggregating op data per each package. 268 // (uid, packageName) => [(op, mode)] 269 Multimap<Key, OpEntry> perPackageMap = MultimapBuilder.hashKeys().hashSetValues().build(); 270 for (Map.Entry<Key, Integer> appOpInfo : appModeMap.entrySet()) { 271 Key key = appOpInfo.getKey(); 272 if (ops == null || relevantOps.contains(key.getOpCode())) { 273 Key packageKey = Key.create(key.getUid(), key.getPackageName(), null); 274 OpEntry opEntry = toOpEntry(key.getOpCode(), appOpInfo.getValue()); 275 perPackageMap.put(packageKey, opEntry); 276 } 277 } 278 279 List<PackageOps> result = new ArrayList<>(); 280 // Creating resulting PackageOps objects using all op info collected per package. 281 for (Map.Entry<Key, Collection<OpEntry>> packageInfo : perPackageMap.asMap().entrySet()) { 282 Key key = packageInfo.getKey(); 283 result.add( 284 new PackageOps( 285 key.getPackageName(), key.getUid(), new ArrayList<>(packageInfo.getValue()))); 286 } 287 288 return result.isEmpty() ? null : result; 289 } 290 291 @Implementation(minSdk = Q) unsafeCheckOpNoThrow(String op, int uid, String packageName)292 public int unsafeCheckOpNoThrow(String op, int uid, String packageName) { 293 return checkOpNoThrow(AppOpsManager.strOpToOp(op), uid, packageName); 294 } 295 296 @Implementation(minSdk = R) unsafeCheckOpRawNoThrow(int op, int uid, String packageName)297 protected int unsafeCheckOpRawNoThrow(int op, int uid, String packageName) { 298 Integer mode = appModeMap.get(Key.create(uid, packageName, op)); 299 if (mode != null) { 300 return mode; 301 } 302 mode = appModeMap.get(Key.create(uid, null, op)); 303 if (mode != null) { 304 return mode; 305 } 306 return AppOpsManager.MODE_ALLOWED; 307 } 308 309 /** 310 * Like {@link #unsafeCheckOpNoThrow(String, int, String)} but returns the <em>raw</em> mode 311 * associated with the op. Does not throw a security exception, does not translate {@link 312 * AppOpsManager#MODE_FOREGROUND}. 313 */ 314 @Implementation(minSdk = Q) unsafeCheckOpRawNoThrow(String op, int uid, String packageName)315 public int unsafeCheckOpRawNoThrow(String op, int uid, String packageName) { 316 return unsafeCheckOpRawNoThrow(AppOpsManager.strOpToOp(op), uid, packageName); 317 } 318 319 /** Stores a fake long-running operation. It does not throw if a wrong uid is passed. */ 320 @Implementation(minSdk = R) startOp( String op, int uid, String packageName, String attributionTag, String message)321 protected int startOp( 322 String op, int uid, String packageName, String attributionTag, String message) { 323 int mode = unsafeCheckOpRawNoThrow(op, uid, packageName); 324 if (mode == AppOpsManager.MODE_ALLOWED) { 325 longRunningOp.add(Key.create(uid, packageName, AppOpsManager.strOpToOp(op))); 326 } 327 return mode; 328 } 329 330 /** Stores a fake long-running operation. It does not throw if a wrong uid is passed. */ 331 @Implementation(maxSdk = Q) startOpNoThrow(int op, int uid, String packageName)332 protected int startOpNoThrow(int op, int uid, String packageName) { 333 int mode = unsafeCheckOpRawNoThrow(op, uid, packageName); 334 if (mode == AppOpsManager.MODE_ALLOWED) { 335 longRunningOp.add(Key.create(uid, packageName, op)); 336 } 337 return mode; 338 } 339 340 /** Stores a fake long-running operation. It does not throw if a wrong uid is passed. */ 341 @Implementation(minSdk = R) startOpNoThrow( String op, int uid, String packageName, String attributionTag, String message)342 protected int startOpNoThrow( 343 String op, int uid, String packageName, String attributionTag, String message) { 344 int mode = unsafeCheckOpRawNoThrow(op, uid, packageName); 345 if (mode == AppOpsManager.MODE_ALLOWED) { 346 longRunningOp.add(Key.create(uid, packageName, AppOpsManager.strOpToOp(op))); 347 } 348 return mode; 349 } 350 351 /** Removes a fake long-running operation from the set. */ 352 @Implementation(maxSdk = Q) finishOp(int op, int uid, String packageName)353 protected void finishOp(int op, int uid, String packageName) { 354 longRunningOp.remove(Key.create(uid, packageName, op)); 355 } 356 357 /** Removes a fake long-running operation from the set. */ 358 @Implementation(minSdk = R) finishOp(String op, int uid, String packageName, String attributionTag)359 protected void finishOp(String op, int uid, String packageName, String attributionTag) { 360 longRunningOp.remove(Key.create(uid, packageName, AppOpsManager.strOpToOp(op))); 361 } 362 363 /** Checks whether op was previously set using {@link #setMode} */ 364 @Implementation(minSdk = R) checkOp(String op, int uid, String packageName)365 protected int checkOp(String op, int uid, String packageName) { 366 return checkOpNoThrow(op, uid, packageName); 367 } 368 369 /** 370 * Checks whether the given op is active, i.e. did someone call {@link #startOp(String, int, 371 * String, String, String)} without {@link #finishOp(String, int, String, String)} yet. 372 */ 373 @Implementation(minSdk = R) isOpActive(String op, int uid, String packageName)374 public boolean isOpActive(String op, int uid, String packageName) { 375 return longRunningOp.contains(Key.create(uid, packageName, AppOpsManager.strOpToOp(op))); 376 } 377 378 @Implementation(minSdk = P) 379 @Deprecated // renamed to unsafeCheckOpNoThrow checkOpNoThrow(String op, int uid, String packageName)380 protected int checkOpNoThrow(String op, int uid, String packageName) { 381 return checkOpNoThrow(AppOpsManager.strOpToOp(op), uid, packageName); 382 } 383 384 /** 385 * Like {@link AppOpsManager#checkOp} but instead of throwing a {@link SecurityException} it 386 * returns {@link AppOpsManager#MODE_ERRORED}. 387 * 388 * <p>Made public for testing {@link #setMode} as the method is {@code @hide}. 389 */ 390 @Implementation 391 @HiddenApi checkOpNoThrow(int op, int uid, String packageName)392 public int checkOpNoThrow(int op, int uid, String packageName) { 393 int mode = unsafeCheckOpRawNoThrow(op, uid, packageName); 394 return mode == AppOpsManager.MODE_FOREGROUND ? AppOpsManager.MODE_ALLOWED : mode; 395 } 396 397 @Implementation noteOp(int op, int uid, String packageName)398 public int noteOp(int op, int uid, String packageName) { 399 return noteOpInternal(op, uid, packageName, "", ""); 400 } 401 noteOpInternal( int op, int uid, String packageName, String attributionTag, String message)402 private int noteOpInternal( 403 int op, int uid, String packageName, String attributionTag, String message) { 404 storedOps.put(Key.create(uid, packageName, null), op); 405 if (RuntimeEnvironment.getApiLevel() >= R) { 406 Object lock = ReflectionHelpers.getStaticField(AppOpsManager.class, "sLock"); 407 synchronized (lock) { 408 AppOpsManager.OnOpNotedCallback callback = 409 ReflectionHelpers.getStaticField(AppOpsManager.class, "sOnOpNotedCallback"); 410 if (callback != null) { 411 callback.onSelfNoted(new SyncNotedAppOp(op, attributionTag)); 412 } 413 } 414 } 415 416 // Permission check not currently implemented in this shadow. 417 return AppOpsManager.MODE_ALLOWED; 418 } 419 420 @Implementation(minSdk = R) noteOp(int op, int uid, String packageName, String attributionTag, String message)421 protected int noteOp(int op, int uid, String packageName, String attributionTag, String message) { 422 return noteOpInternal(op, uid, packageName, attributionTag, message); 423 } 424 425 @Implementation noteOpNoThrow(int op, int uid, String packageName)426 protected int noteOpNoThrow(int op, int uid, String packageName) { 427 storedOps.put(Key.create(uid, packageName, null), op); 428 return checkOpNoThrow(op, uid, packageName); 429 } 430 431 @Implementation(minSdk = R) noteOpNoThrow( int op, int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String message)432 protected int noteOpNoThrow( 433 int op, 434 int uid, 435 @Nullable String packageName, 436 @Nullable String attributionTag, 437 @Nullable String message) { 438 return noteOpNoThrow(op, uid, packageName); 439 } 440 441 @Implementation(minSdk = M, maxSdk = Q) 442 @HiddenApi noteProxyOpNoThrow(int op, String proxiedPackageName)443 protected int noteProxyOpNoThrow(int op, String proxiedPackageName) { 444 storedOps.put(Key.create(Binder.getCallingUid(), proxiedPackageName, null), op); 445 return checkOpNoThrow(op, Binder.getCallingUid(), proxiedPackageName); 446 } 447 448 @Implementation(minSdk = Q, maxSdk = Q) 449 @HiddenApi noteProxyOpNoThrow(int op, String proxiedPackageName, int proxiedUid)450 protected int noteProxyOpNoThrow(int op, String proxiedPackageName, int proxiedUid) { 451 storedOps.put(Key.create(proxiedUid, proxiedPackageName, null), op); 452 return checkOpNoThrow(op, proxiedUid, proxiedPackageName); 453 } 454 455 @Implementation(minSdk = R, maxSdk = R) 456 @HiddenApi noteProxyOpNoThrow( int op, String proxiedPackageName, int proxiedUid, String proxiedAttributionTag, String message)457 protected int noteProxyOpNoThrow( 458 int op, 459 String proxiedPackageName, 460 int proxiedUid, 461 String proxiedAttributionTag, 462 String message) { 463 storedOps.put(Key.create(proxiedUid, proxiedPackageName, null), op); 464 return checkOpNoThrow(op, proxiedUid, proxiedPackageName); 465 } 466 467 @RequiresApi(api = S) 468 @Implementation(minSdk = S) noteProxyOpNoThrow( int op, @ClassName("android.content.AttributionSource") Object attributionSource, String message, boolean skipProxyOperation)469 protected int noteProxyOpNoThrow( 470 int op, 471 @ClassName("android.content.AttributionSource") Object attributionSource, 472 String message, 473 boolean skipProxyOperation) { 474 Preconditions.checkArgument(attributionSource instanceof AttributionSource); 475 AttributionSource castedAttributionSource = (AttributionSource) attributionSource; 476 return noteProxyOpNoThrow( 477 op, 478 castedAttributionSource.getNextPackageName(), 479 castedAttributionSource.getNextUid(), 480 castedAttributionSource.getNextAttributionTag(), 481 message); 482 } 483 484 @Implementation 485 @HiddenApi getOpsForPackage(int uid, String packageName, int[] ops)486 public List<PackageOps> getOpsForPackage(int uid, String packageName, int[] ops) { 487 Set<Integer> opFilter = new HashSet<>(); 488 if (ops != null) { 489 for (int op : ops) { 490 opFilter.add(op); 491 } 492 } 493 494 List<OpEntry> opEntries = new ArrayList<>(); 495 for (Integer op : storedOps.get(Key.create(uid, packageName, null))) { 496 if (opFilter.isEmpty() || opFilter.contains(op)) { 497 opEntries.add(toOpEntry(op, AppOpsManager.MODE_ALLOWED)); 498 } 499 } 500 501 return ImmutableList.of(new PackageOps(packageName, uid, opEntries)); 502 } 503 504 @Implementation(minSdk = Q) 505 @HiddenApi 506 @SystemApi 507 @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) getOpsForPackage(int uid, String packageName, String[] ops)508 protected List<PackageOps> getOpsForPackage(int uid, String packageName, String[] ops) { 509 if (ops == null) { 510 int[] intOps = null; 511 return getOpsForPackage(uid, packageName, intOps); 512 } 513 Map<String, Integer> strOpToIntOp = 514 ReflectionHelpers.getStaticField(AppOpsManager.class, "sOpStrToOp"); 515 List<Integer> intOpsList = new ArrayList<>(); 516 for (String op : ops) { 517 Integer intOp = strOpToIntOp.get(op); 518 if (intOp != null) { 519 intOpsList.add(intOp); 520 } 521 } 522 523 return getOpsForPackage(uid, packageName, intOpsList.stream().mapToInt(i -> i).toArray()); 524 } 525 526 @Implementation checkPackage(int uid, String packageName)527 protected void checkPackage(int uid, String packageName) { 528 try { 529 // getPackageUid was introduced in API 24, so we call it on the shadow class 530 ShadowApplicationPackageManager shadowApplicationPackageManager = 531 Shadow.extract(context.getPackageManager()); 532 int packageUid = shadowApplicationPackageManager.getPackageUid(packageName, 0); 533 if (packageUid == uid) { 534 return; 535 } 536 throw new SecurityException("Package " + packageName + " belongs to " + packageUid); 537 } catch (NameNotFoundException e) { 538 throw new SecurityException("Package " + packageName + " doesn't belong to " + uid, e); 539 } 540 } 541 542 /** 543 * Sets audio restrictions. 544 * 545 * <p>This method is public for testing, as the original method is {@code @hide}. 546 */ 547 @Implementation 548 @HiddenApi setRestriction( int code, @AttributeUsage int usage, int mode, String[] exceptionPackages)549 public void setRestriction( 550 int code, @AttributeUsage int usage, int mode, String[] exceptionPackages) { 551 audioRestrictions.put( 552 getAudioRestrictionKey(code, usage), new ModeAndException(mode, exceptionPackages)); 553 } 554 555 @Nullable getRestriction(int code, @AttributeUsage int usage)556 public ModeAndException getRestriction(int code, @AttributeUsage int usage) { 557 // this gives us room for 256 op_codes. There are 78 as of P. 558 return audioRestrictions.get(getAudioRestrictionKey(code, usage)); 559 } 560 561 @Implementation startWatchingMode(int op, String packageName, OnOpChangedListener callback)562 protected void startWatchingMode(int op, String packageName, OnOpChangedListener callback) { 563 startWatchingModeImpl(op, packageName, 0, callback); 564 } 565 566 @Implementation(minSdk = Q) startWatchingMode( int op, String packageName, int flags, OnOpChangedListener callback)567 protected void startWatchingMode( 568 int op, String packageName, int flags, OnOpChangedListener callback) { 569 startWatchingModeImpl(op, packageName, flags, callback); 570 } 571 startWatchingModeImpl( int op, String packageName, int flags, OnOpChangedListener callback)572 private void startWatchingModeImpl( 573 int op, String packageName, int flags, OnOpChangedListener callback) { 574 Set<Key> keys = appOpListeners.get(callback); 575 if (keys == null) { 576 keys = new HashSet<>(); 577 appOpListeners.put(callback, keys); 578 } 579 keys.add(Key.create(null, packageName, op)); 580 } 581 582 @Implementation stopWatchingMode(OnOpChangedListener callback)583 protected void stopWatchingMode(OnOpChangedListener callback) { 584 appOpListeners.remove(callback); 585 } 586 toOpEntry(Integer op, int mode)587 protected OpEntry toOpEntry(Integer op, int mode) { 588 if (RuntimeEnvironment.getApiLevel() < Build.VERSION_CODES.M) { 589 return ReflectionHelpers.callConstructor( 590 OpEntry.class, 591 ClassParameter.from(int.class, op), 592 ClassParameter.from(int.class, mode), 593 ClassParameter.from(long.class, OP_TIME), 594 ClassParameter.from(long.class, REJECT_TIME), 595 ClassParameter.from(int.class, DURATION)); 596 } else if (RuntimeEnvironment.getApiLevel() < Build.VERSION_CODES.Q) { 597 return ReflectionHelpers.callConstructor( 598 OpEntry.class, 599 ClassParameter.from(int.class, op), 600 ClassParameter.from(int.class, mode), 601 ClassParameter.from(long.class, OP_TIME), 602 ClassParameter.from(long.class, REJECT_TIME), 603 ClassParameter.from(int.class, DURATION), 604 ClassParameter.from(int.class, PROXY_UID), 605 ClassParameter.from(String.class, PROXY_PACKAGE)); 606 } else if (RuntimeEnvironment.getApiLevel() < Build.VERSION_CODES.R) { 607 final long key = 608 AppOpsManager.makeKey(AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF); 609 610 final LongSparseLongArray accessTimes = new LongSparseLongArray(); 611 accessTimes.put(key, OP_TIME); 612 613 final LongSparseLongArray rejectTimes = new LongSparseLongArray(); 614 rejectTimes.put(key, REJECT_TIME); 615 616 final LongSparseLongArray durations = new LongSparseLongArray(); 617 durations.put(key, DURATION); 618 619 final LongSparseLongArray proxyUids = new LongSparseLongArray(); 620 proxyUids.put(key, PROXY_UID); 621 622 final LongSparseArray<String> proxyPackages = new LongSparseArray<>(); 623 proxyPackages.put(key, PROXY_PACKAGE); 624 625 return ReflectionHelpers.callConstructor( 626 OpEntry.class, 627 ClassParameter.from(int.class, op), 628 ClassParameter.from(boolean.class, false), 629 ClassParameter.from(int.class, mode), 630 ClassParameter.from(LongSparseLongArray.class, accessTimes), 631 ClassParameter.from(LongSparseLongArray.class, rejectTimes), 632 ClassParameter.from(LongSparseLongArray.class, durations), 633 ClassParameter.from(LongSparseLongArray.class, proxyUids), 634 ClassParameter.from(LongSparseArray.class, proxyPackages)); 635 } else { 636 final long key = 637 AppOpsManager.makeKey(AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF); 638 639 LongSparseArray<NoteOpEvent> accessEvents = new LongSparseArray<>(); 640 LongSparseArray<NoteOpEvent> rejectEvents = new LongSparseArray<>(); 641 642 accessEvents.put( 643 key, 644 new NoteOpEvent(OP_TIME, DURATION, new OpEventProxyInfo(PROXY_UID, PROXY_PACKAGE, null))); 645 rejectEvents.put(key, new NoteOpEvent(REJECT_TIME, -1, null)); 646 647 return new OpEntry( 648 op, 649 mode, 650 Collections.singletonMap( 651 null, new AttributedOpEntry(op, false, accessEvents, rejectEvents))); 652 } 653 } 654 getAudioRestrictionKey(int code, @AttributeUsage int usage)655 private static int getAudioRestrictionKey(int code, @AttributeUsage int usage) { 656 return code | (usage << 8); 657 } 658 659 @AutoValue 660 abstract static class Key { 661 @Nullable getUid()662 abstract Integer getUid(); 663 664 @Nullable getPackageName()665 abstract String getPackageName(); 666 667 @Nullable getOpCode()668 abstract Integer getOpCode(); 669 create(Integer uid, String packageName, Integer opCode)670 static Key create(Integer uid, String packageName, Integer opCode) { 671 return new AutoValue_ShadowAppOpsManager_Key(uid, packageName, opCode); 672 } 673 } 674 675 /** Class holding usage mode and exception packages. */ 676 public static class ModeAndException { 677 public final int mode; 678 public final List<String> exceptionPackages; 679 ModeAndException(int mode, String[] exceptionPackages)680 public ModeAndException(int mode, String[] exceptionPackages) { 681 this.mode = mode; 682 this.exceptionPackages = 683 exceptionPackages == null 684 ? Collections.emptyList() 685 : Collections.unmodifiableList(Arrays.asList(exceptionPackages)); 686 } 687 } 688 689 @Resetter reset()690 public static void reset() { 691 // The callback passed in AppOpsManager#setOnOpNotedCallback is stored statically. 692 // The check for staticallyInitialized is to make it so that we don't load AppOpsManager if it 693 // hadn't already been loaded (both to save time and to also avoid any errors that might 694 // happen if we tried to lazy load the class during reset) 695 if (RuntimeEnvironment.getApiLevel() >= R && staticallyInitialized) { 696 ReflectionHelpers.setStaticField(AppOpsManager.class, "sOnOpNotedCallback", null); 697 } 698 storedOps.clear(); 699 appModeMap.clear(); 700 longRunningOp.clear(); 701 appOpListeners.clear(); 702 audioRestrictions.clear(); 703 } 704 705 @ForType(className = "android.app.AppOpInfo") 706 interface AppOpInfoReflector { 707 @Accessor("name") getName()708 String getName(); 709 } 710 } 711