• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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