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