• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 com.android.bedstead.permissions;
18 
19 import static android.content.pm.PackageManager.PERMISSION_DENIED;
20 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
21 import static android.cts.testapisreflection.TestApisReflectionKt.addOverridePermissionState;
22 import static android.cts.testapisreflection.TestApisReflectionKt.clearOverridePermissionStates;
23 import static android.cts.testapisreflection.TestApisReflectionKt.removeOverridePermissionState;
24 
25 import android.app.AppOpsManager;
26 import android.content.Context;
27 import android.content.pm.PackageManager;
28 import android.content.pm.PermissionInfo;
29 import android.cts.testapisreflection.TestApisReflectionKt;
30 import android.os.Build;
31 import android.util.Log;
32 
33 import com.android.bedstead.nene.TestApis;
34 import com.android.bedstead.nene.annotations.Experimental;
35 import com.android.bedstead.nene.appops.AppOpsMode;
36 import com.android.bedstead.nene.exceptions.NeneException;
37 import com.android.bedstead.nene.packages.Package;
38 import com.android.bedstead.nene.users.UserReference;
39 import com.android.bedstead.nene.utils.FailureDumper;
40 import com.android.bedstead.nene.utils.ShellCommand;
41 import com.android.bedstead.nene.utils.ShellCommandUtils;
42 import com.android.bedstead.nene.utils.Tags;
43 import com.android.bedstead.nene.utils.UndoableContext;
44 import com.android.bedstead.nene.utils.Versions;
45 
46 import com.google.common.collect.ImmutableSet;
47 
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.Collection;
51 import java.util.Collections;
52 import java.util.HashSet;
53 import java.util.List;
54 import java.util.Set;
55 import java.util.concurrent.atomic.AtomicBoolean;
56 import java.util.stream.Collectors;
57 
58 /** Permission manager for tests. */
59 public final class Permissions {
60 
61     public static final AtomicBoolean sIgnorePermissions = new AtomicBoolean(false);
62     private static final String LOG_TAG = "Permissions";
63     private static final Context sContext = TestApis.context().instrumentedContext();
64     private static final PackageManager sPackageManager = sContext.getPackageManager();
65     private static final AppOpsManager sAppOpsManager =
66             TestApis.context().instrumentedContext().getSystemService(AppOpsManager.class);
67     private static final Package sInstrumentedPackage =
68             TestApis.packages().instrumented();
69     private static final UserReference sUser = TestApis.users().instrumented();
70     private static final Package sShellPackage =
71             TestApis.packages().find("com.android.shell");
72     private static final boolean SUPPORTS_ADOPT_SHELL_PERMISSIONS =
73             Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
74 
75     /**
76      * Permissions which cannot be given to shell.
77      *
78      * <p>Each entry must include a comment with the reason it cannot be added.
79      */
80     private static final ImmutableSet EXEMPT_SHELL_PERMISSIONS = ImmutableSet.of(
81 
82     );
83 
84     public static final Permissions sInstance = new Permissions();
85 
86     private final List<PermissionContextImpl> mPermissionContexts =
87             Collections.synchronizedList(new ArrayList<>());
88     private final Set<String> mShellPermissions;
89     private final Set<String> mInstrumentedRequestedPermissions;
90 
ignoringPermissions()91     public static UndoableContext ignoringPermissions() {
92         boolean original = Permissions.sIgnorePermissions.get();
93         Permissions.sIgnorePermissions.set(true);
94 
95         if (SUPPORTS_ADOPT_SHELL_PERMISSIONS) {
96             adoptShellPermissionIdentity();
97         }
98 
99         return new UndoableContext(() -> {
100             if (SUPPORTS_ADOPT_SHELL_PERMISSIONS) {
101                 dropShellPermissionIdentity();
102             }
103             Permissions.sIgnorePermissions.set(original);
104         });
105     }
106 
107     private Permissions() {
108         // Packages requires using INTERACT_ACROSS_USERS_FULL but we don't want it to rely on
109         // Permissions or it'll recurse forever - so we disable permission checks and just use
110         // shell permission adoption directly while initialising
111         try (UndoableContext c = ignoringPermissions()) {
112             if (SUPPORTS_ADOPT_SHELL_PERMISSIONS) {
113                 mShellPermissions = sShellPackage.requestedPermissions();
114             } else {
115                 mShellPermissions = new HashSet<>();
116             }
117             mInstrumentedRequestedPermissions = sInstrumentedPackage.requestedPermissions();
118         }
119     }
120 
121     /**
122      * Enter a {@link PermissionContext} where the given permissions are granted.
123      *
124      * <p>If the permissions cannot be granted, and are not already granted, an exception will be
125      * thrown.
126      *
127      * <p>Recommended usage:
128      * {@code
129      *
130      * try (PermissionContext p = mTestApis.permissions().withPermission(PERMISSION1, PERMISSION2) {
131      * // Code which needs the permissions goes here
132      * }
133      * }
134      */
135     public PermissionContextImpl withPermission(String... permissions) {
136         PermissionContextImpl permissionContext = PermissionContextImpl.create(this);
137         mPermissionContexts.add(permissionContext);
138 
139         PermissionContextImpl unused = permissionContext.withPermission(permissions);
140 
141         return permissionContext;
142     }
143 
144     /**
145      * Enter a {@link PermissionContext} where the given permissions are granted only when running
146      * on the given version or above.
147      *
148      * <p>If the permissions cannot be granted, and are not already granted, an exception will be
149      * thrown.
150      *
151      * <p>If the version does not match, the permission context will not change.
152      */
153     public PermissionContextImpl withPermissionOnVersionAtLeast(
154             int minSdkVersion, String... permissions) {
155         PermissionContextImpl permissionContext = PermissionContextImpl.create(this);
156         mPermissionContexts.add(permissionContext);
157 
158         PermissionContextImpl unused =
159                 permissionContext.withPermissionOnVersionAtLeast(minSdkVersion, permissions);
160 
161         return permissionContext;
162     }
163 
164     /**
165      * Enter a {@link PermissionContext} where the given permissions are granted only when running
166      * on the given version or below.
167      *
168      * <p>If the permissions cannot be granted, and are not already granted, an exception will be
169      * thrown.
170      *
171      * <p>If the version does not match, the permission context will not change.
172      */
173     public PermissionContextImpl withPermissionOnVersionAtMost(
174             int maxSdkVersion, String... permissions) {
175         PermissionContextImpl permissionContext = PermissionContextImpl.create(this);
176         mPermissionContexts.add(permissionContext);
177 
178         PermissionContextImpl unused =
179                 permissionContext.withPermissionOnVersionAtMost(maxSdkVersion, permissions);
180 
181         return permissionContext;
182     }
183 
184     /**
185      * Enter a {@link PermissionContext} where the given permissions are granted only when running
186      * on the range of versions given (inclusive).
187      *
188      * <p>If the permissions cannot be granted, and are not already granted, an exception will be
189      * thrown.
190      *
191      * <p>If the version does not match, the permission context will not change.
192      */
193     public PermissionContextImpl withPermissionOnVersionBetween(
194             int minSdkVersion, int maxSdkVersion, String... permissions) {
195         PermissionContextImpl permissionContext = PermissionContextImpl.create(this);
196         mPermissionContexts.add(permissionContext);
197 
198         PermissionContextImpl unused =
199                 permissionContext.withPermissionOnVersionBetween(minSdkVersion, maxSdkVersion,
200                         permissions);
201 
202         return permissionContext;
203     }
204 
205     /**
206      * Enter a {@link PermissionContext} where the given permissions are granted only when running
207      * on the given version.
208      *
209      * <p>If the permissions cannot be granted, and are not already granted, an exception will be
210      * thrown.
211      *
212      * <p>If the version does not match, the permission context will not change.
213      */
214     public PermissionContextImpl withPermissionOnVersion(int sdkVersion, String... permissions) {
215         PermissionContextImpl permissionContext = PermissionContextImpl.create(this);
216         mPermissionContexts.add(permissionContext);
217 
218         PermissionContextImpl unused =
219                 permissionContext.withPermissionOnVersion(sdkVersion, permissions);
220 
221         return permissionContext;
222     }
223 
224     /**
225      * Enter a {@link PermissionContext} where the given appOps are granted.
226      *
227      * <p>If the appOps cannot be granted, and are not already granted, an exception will be
228      * thrown.
229      *
230      * <p>Recommended usage:
231      * {@code
232      *
233      * try (PermissionContext p = mTestApis.permissions().withAppOps(APP_OP1, APP_OP2) {
234      * // Code which needs the app ops goes here
235      * }
236      * }
237      */
238     public PermissionContextImpl withAppOp(String... appOps) {
239         PermissionContextImpl permissionContext = PermissionContextImpl.create(this);
240         mPermissionContexts.add(permissionContext);
241 
242         PermissionContextImpl unused = permissionContext.withAppOp(appOps);
243 
244         return permissionContext;
245     }
246 
247     /**
248      * Enter a {@link PermissionContext} where the given appOps are granted.
249      *
250      * <p>If the appOps cannot be granted, and are not already granted, an exception will be
251      * thrown.
252      *
253      * <p>Recommended usage:
254      * {@code
255      *
256      * try (PermissionContext p = mTestApis.permissions().withAppOps(APP_OP1, APP_OP2) {
257      * // Code which needs the app ops goes here
258      * }
259      * }
260      *
261      * <p>If the version does not match the appOp will not be granted.
262      */
263     public PermissionContextImpl withAppOpOnVersion(int sdkVersion, String... appOps) {
264         PermissionContextImpl permissionContext = PermissionContextImpl.create(this);
265         mPermissionContexts.add(permissionContext);
266 
267         PermissionContextImpl unused = permissionContext.withAppOpOnVersion(sdkVersion, appOps);
268 
269         return permissionContext;
270     }
271 
272     /**
273      * Enter a {@link PermissionContext} where the given appOps are granted.
274      *
275      * <p>If the appOps cannot be granted, and are not already granted, an exception will be
276      * thrown.
277      *
278      * <p>Recommended usage:
279      * {@code
280      *
281      * try (PermissionContext p = mTestApis.permissions().withAppOps(APP_OP1, APP_OP2) {
282      * // Code which needs the app ops goes here
283      * }
284      * }
285      *
286      * <p>If the version does not match the appOp will not be granted.
287      */
288     public PermissionContextImpl withAppOpOnVersionAtLeast(int sdkVersion, String... appOps) {
289         PermissionContextImpl permissionContext = PermissionContextImpl.create(this);
290         mPermissionContexts.add(permissionContext);
291 
292         PermissionContextImpl unused = permissionContext.withAppOpOnVersionAtLeast(sdkVersion,
293                 appOps);
294 
295         return permissionContext;
296     }
297 
298     /**
299      * Enter a {@link PermissionContext} where the given appOps are granted.
300      *
301      * <p>If the appOps cannot be granted, and are not already granted, an exception will be
302      * thrown.
303      *
304      * <p>Recommended usage:
305      * {@code
306      *
307      * try (PermissionContext p = mTestApis.permissions().withAppOps(APP_OP1, APP_OP2) {
308      * // Code which needs the app ops goes here
309      * }
310      * }
311      *
312      * <p>If the version does not match the appOp will not be granted.
313      */
314     public PermissionContextImpl withAppOpOnVersionAtMost(int sdkVersion, String... appOps) {
315         PermissionContextImpl permissionContext = PermissionContextImpl.create(this);
316         mPermissionContexts.add(permissionContext);
317 
318         PermissionContextImpl unused = permissionContext.withAppOpOnVersionAtMost(sdkVersion,
319                 appOps);
320 
321         return permissionContext;
322     }
323 
324     /**
325      * Enter a {@link PermissionContext} where the given appOps are granted.
326      *
327      * <p>If the appOps cannot be granted, and are not already granted, an exception will be
328      * thrown.
329      *
330      * <p>Recommended usage:
331      * {@code
332      *
333      * try (PermissionContext p = mTestApis.permissions().withAppOps(APP_OP1, APP_OP2) {
334      * // Code which needs the app ops goes here
335      * }
336      * }
337      *
338      * <p>If the version does not match the appOp will not be granted.
339      */
340     public PermissionContextImpl withAppOpOnVersionBetween(
341             int minSdkVersion, int maxSdkVersion, String... appOps) {
342         PermissionContextImpl permissionContext = PermissionContextImpl.create(this);
343         mPermissionContexts.add(permissionContext);
344 
345         PermissionContextImpl unused = permissionContext.withAppOpOnVersionBetween(minSdkVersion,
346                 maxSdkVersion, appOps);
347 
348         return permissionContext;
349     }
350 
351     /**
352      * Enter a {@link PermissionContext} where the given permissions are not granted.
353      *
354      * <p>If the permissions cannot be denied, and are not already denied, an exception will be
355      * thrown.
356      *
357      * <p>Recommended usage:
358      * {@code
359      *
360      * try (PermissionContext p =
361      * mTestApis.permissions().withoutPermission(PERMISSION1, PERMISSION2) {
362      * // Code which needs the permissions to be denied goes here
363      * }
364      */
365     public PermissionContextImpl withoutPermission(String... permissions) {
366         PermissionContextImpl permissionContext = PermissionContextImpl.create(this);
367         mPermissionContexts.add(permissionContext);
368 
369         PermissionContextImpl unused = permissionContext.withoutPermission(permissions);
370 
371         return permissionContext;
372     }
373 
374     /**
375      * Enter a {@link PermissionContext} where the given appOps are not granted.
376      *
377      * <p>If the appOps cannot be denied, and are not already denied, an exception will be
378      * thrown.
379      *
380      * <p>Recommended usage:
381      * {@code
382      *
383      * try (PermissionContext p =
384      * mTestApis.permissions().withoutappOp(APP_OP1, APP_OP2) {
385      * // Code which needs the appOp to be denied goes here
386      * }
387      * }
388      */
389     public PermissionContextImpl withoutAppOp(String... appOps) {
390         PermissionContextImpl permissionContext = PermissionContextImpl.create(this);
391         mPermissionContexts.add(permissionContext);
392 
393         PermissionContextImpl unused = permissionContext.withoutAppOp(appOps);
394 
395         return permissionContext;
396     }
397 
398     void undoPermission(PermissionContextImpl permissionContext) {
399         boolean unused = mPermissionContexts.remove(permissionContext);
400         applyPermissions(/* removedPermissionContext = */ permissionContext);
401     }
402 
403     void applyPermissions() {
404         applyPermissions(null);
405     }
406 
407     private void applyPermissions(PermissionContextImpl removedPermissionContext) {
408         if (sIgnorePermissions.get()) {
409             return;
410         }
411 
412         Set<String> grantedPermissions = new HashSet<>();
413         Set<String> deniedPermissions = new HashSet<>();
414         Set<String> grantedAppOps = new HashSet<>();
415         Set<String> deniedAppOps = new HashSet<>();
416 
417         synchronized (mPermissionContexts) {
418             for (PermissionContextImpl permissionContext : mPermissionContexts) {
419                 for (String permission : permissionContext.grantedPermissions()) {
420                     grantedPermissions.add(permission);
421                     deniedPermissions.remove(permission);
422                 }
423 
424                 for (String permission : permissionContext.deniedPermissions()) {
425                     grantedPermissions.remove(permission);
426                     deniedPermissions.add(permission);
427                 }
428 
429                 for (String appOp : permissionContext.grantedAppOps()) {
430                     grantedAppOps.add(appOp);
431                     deniedAppOps.remove(appOp);
432                 }
433 
434                 for (String appOp : permissionContext.deniedAppOps()) {
435                     grantedAppOps.remove(appOp);
436                     deniedAppOps.add(appOp);
437                 }
438             }
439         }
440 
441         setPermissionState(
442                 TestApis.packages().instrumented(),
443                 TestApis.users().instrumented(),
444                 grantedPermissions,
445                 deniedPermissions);
446         Package appOpPackage =
447                 hasAdoptedShellPermissionIdentity ? sShellPackage : sInstrumentedPackage;
448         setAppOpState(
449                 appOpPackage,
450                 TestApis.users().instrumented(),
451                 grantedAppOps,
452                 deniedAppOps
453         );
454 
455         if (removedPermissionContext != null) {
456             removedPermissionContext.grantedAppOps().stream().filter(
457                             (i) -> !grantedAppOps.contains(i) && !deniedAppOps.contains(i))
458                     .forEach(i -> appOpPackage.appOps().set(i, AppOpsMode.DEFAULT));
459             removedPermissionContext.deniedAppOps().stream().filter(
460                             (i) -> !grantedAppOps.contains(i) && !deniedAppOps.contains(i))
461                     .forEach(i -> appOpPackage.appOps().set(i, AppOpsMode.DEFAULT));
462         }
463 
464     }
465 
466     /**
467      * Throw an exception including permission contextual information.
468      */
469     public void throwPermissionException(
470             String message, String permission) {
471         String protectionLevel = "Permission not found";
472         try {
473             protectionLevel = Integer.toString(sPackageManager.getPermissionInfo(
474                     permission, /* flags= */ 0).protectionLevel);
475         } catch (PackageManager.NameNotFoundException e) {
476             Log.e(LOG_TAG, "Permission not found", e);
477         }
478 
479 
480 
481         try (UndoableContext c = ignoringPermissions()){
482             throw new NeneException(
483                     message
484                             + "\n\n"
485                             + "If this is a new test. Consider moving it to a root-enabled test"
486                             + " suite and adding @RequireRootInstrumentation to the method. This"
487                             + " enables arbitrary use of permissions.\n\n"
488                             + "Running On User: "
489                             + sUser
490                             + "\nPermission: "
491                             + permission
492                             + "\nPermission protection level: "
493                             + protectionLevel
494                             + "\nPermission state: "
495                             + sContext.checkSelfPermission(permission)
496                             + "\nInstrumented Package: "
497                             + sInstrumentedPackage.packageName()
498                             + "\n\nRequested Permissions:\n"
499                             + sInstrumentedPackage.requestedPermissions()
500                             + "\n\nCan adopt shell permissions: "
501                             + SUPPORTS_ADOPT_SHELL_PERMISSIONS
502                             + "\nShell permissions:"
503                             + mShellPermissions
504                             + "\nExempt Shell permissions: "
505                             + EXEMPT_SHELL_PERMISSIONS);
506         }
507     }
508 
509     void clearPermissions() {
510         mPermissionContexts.clear();
511         applyPermissions();
512     }
513 
514     /**
515      * Returns all of the permissions which can be adopted.
516      */
517     public Set<String> adoptablePermissions() {
518         return mShellPermissions;
519     }
520 
521     /**
522      * Returns all of the permissions which are currently able to be used.
523      */
524     public Set<String> usablePermissions() {
525         Set<String> usablePermissions = new HashSet<>();
526         usablePermissions.addAll(mShellPermissions);
527         usablePermissions.addAll(mInstrumentedRequestedPermissions);
528         return usablePermissions;
529     }
530 
531     private void removePermissionContextsUntilCanApplyPermissions() {
532         boolean appliedPermissions = false;
533         while (!mPermissionContexts.isEmpty() && !appliedPermissions) {
534             try {
535                 mPermissionContexts.remove(mPermissionContexts.size() - 1);
536                 applyPermissions();
537                 appliedPermissions = true;
538             } catch (NeneException e) {
539                 // Suppress NeneException here as we may get a few as we pop through the stack
540             }
541         }
542     }
543 
544     private boolean canGrantPermission(String permission) {
545         try {
546             PermissionInfo p = sPackageManager.getPermissionInfo(permission, /* flags= */ 0);
547             if ((p.protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) > 0) {
548                 return true;
549             }
550             return (p.protectionLevel & PermissionInfo.PROTECTION_DANGEROUS) > 0;
551         } catch (PackageManager.NameNotFoundException e) {
552             return false;
553         }
554     }
555 
556     /** True if the current process has the given permission. */
557     public boolean hasPermission(String permission) {
558         return sContext.checkSelfPermission(permission) == PERMISSION_GRANTED;
559     }
560 
561     /**
562      * True if the current process has the given appOp set to ALLOWED.
563      *
564      * <p>This accounts for shell identity being adopted (in which case it will check the appop
565      * status of the shell identity).
566      */
567     public boolean hasAppOpAllowed(String appOp) {
568         Package appOpPackage = sInstrumentedPackage;
569         if (hasAdoptedShellPermissionIdentity) {
570             // We care about the shell package
571             appOpPackage = sShellPackage;
572         }
573 
574         return appOpPackage.appOps().get(appOp) == AppOpsMode.ALLOWED;
575     }
576 
577     /**
578      * Sets a permission state for a given package on a given user.
579      *
580      * <p>Generally tests should not use this method directly. They should instead used the {@link
581      * #withPermission} and {@link #withoutPermission} methods.
582      *
583      * <p>When this is used while executing a test which uses the RequireRootInstrumentation
584      * annotation, and using Android 15+, it will have access to all permissions for all apps.
585      *
586      * <p>Otherwise, when applying to the instrumented package, shell permission adoption will be
587      * used.
588      *
589      * <p>Otherwise, if the permission is able to be granted/denied by ADB then that will be done.
590      *
591      * <p>Otherwise an error will be thrown.
592      */
593     public void setPermissionState(
594             Package pkg,
595             UserReference user,
596             Collection<String> permissionsToGrant,
597             Collection<String> permissionsToDeny) {
598         FailureDumper.Companion.getFailureDumpers().add(
599                 "com.android.bedstead.permissions.PermissionsAnnotationExecutor");
600         // TODO: replace with dependency on bedstead-root when properly modularised
601         if (Tags.hasTag("root-instrumentation")
602                 && Versions.meetsMinimumSdkVersionRequirement(Versions.V)) {
603             // We must reset as it may have been set previously
604             resetRootPermissionState(pkg, user);
605 
606             for (String grantedPermission : permissionsToGrant) {
607                 forceRootPermissionState(pkg, user, grantedPermission, true);
608             }
609             for (String deniedPermission : permissionsToDeny) {
610                 forceRootPermissionState(pkg, user, deniedPermission, false);
611             }
612 
613             return;
614         }
615 
616         setPermissionStateToPackageWithoutRoot(pkg, user, permissionsToGrant, permissionsToDeny);
617     }
618 
619     /**
620      * Sets an appOp state for a given package on a given user.
621      *
622      * Generally tests should not use this method directly. They should instead used the
623      * {@link #withAppOp} and {@link #withoutAppOp} methods.
624      *
625      * Note that if shell permission identity is adopted, then the app op state will not be queried
626      * for the package - and the shell package should have its app op state set instead.
627      */
628     public void setAppOpState(Package pkg, UserReference user, Collection<String> grantedAppOps,
629             Collection<String> deniedAppOps) {
630         FailureDumper.Companion.getFailureDumpers().add(
631                 "com.android.bedstead.permissions.PermissionsAnnotationExecutor");
632         // Filter so we get just the appOps which require a state that they are not currently in
633         Set<String> filteredGrantedAppOps = grantedAppOps.stream()
634                 .filter(o -> pkg.appOps().get(o) != AppOpsMode.ALLOWED)
635                 .collect(Collectors.toSet());
636         Set<String> filteredDeniedAppOps = deniedAppOps.stream()
637                 .filter(o -> pkg.appOps().get(o) != AppOpsMode.IGNORED)
638                 .collect(Collectors.toSet());
639 
640         if (!filteredGrantedAppOps.isEmpty() || !filteredDeniedAppOps.isEmpty()) {
641             // We need MANAGE_APP_OPS_MODES to change app op permissions - but don't want to
642             // infinite loop so won't use .appOps().set()
643             Set<String> previousAdoptedShellPermissions = getAdoptedShellPermissions();
644             adoptShellPermissionIdentity(CommonPermissions.MANAGE_APP_OPS_MODES);
645             for (String appOp : filteredGrantedAppOps) {
646                 sAppOpsManager.setMode(appOp, pkg.uid(sUser),
647                         pkg.packageName(), AppOpsMode.ALLOWED.value());
648             }
649             for (String appOp : filteredDeniedAppOps) {
650                 sAppOpsManager.setMode(appOp, pkg.uid(sUser),
651                         pkg.packageName(), AppOpsMode.IGNORED.value());
652             }
653 
654             adoptShellPermissionIdentity(previousAdoptedShellPermissions);
655         }
656     }
657 
658     private void setPermissionStateToPackageWithoutAdoption(Package pkg, UserReference user,
659             Collection<String> permissionsToGrant, Collection<String> permissionsToDeny) {
660         for (String permission : permissionsToGrant) {
661             if (canGrantPermission(permission)) {
662                 pkg.grantPermission(user, permission);
663             } else {
664                 removePermissionContextsUntilCanApplyPermissions();
665                 throwPermissionException(
666                         "Requires granting permission " + permission + " but cannot.", permission);
667             }
668         }
669 
670         for (String permission : permissionsToDeny) {
671             if (pkg.equals(TestApis.packages().instrumented()) && user.equals(
672                     TestApis.users().instrumented())) {
673                 // We can't deny permissions from ourselves or it'll kill the process
674                 removePermissionContextsUntilCanApplyPermissions();
675                 throwPermissionException(
676                         "Requires denying permission " + permission + " but cannot.", permission);
677             } else {
678                 pkg.denyPermission(user, permission);
679             }
680         }
681     }
682 
683     private void setPermissionStateToPackageWithoutRoot(Package pkg, UserReference user,
684             Collection<String> permissionsToGrant, Collection<String> permissionsToDeny) {
685         if (!pkg.equals(TestApis.packages().instrumented()) || !SUPPORTS_ADOPT_SHELL_PERMISSIONS) {
686             // We can't adopt...
687             setPermissionStateToPackageWithoutAdoption(pkg, user, permissionsToGrant,
688                     permissionsToDeny);
689             return;
690         }
691 
692         if (TestApis.packages().instrumented().isInstantApp()) {
693             // Instant Apps aren't able to know the permissions of shell so we can't know if we can
694             // adopt it - we'll assume we can adopt and log
695             Log.i(LOG_TAG,
696                     "Adopting all shell permissions as can't check shell: " + mPermissionContexts);
697             adoptShellPermissionIdentity();
698             return;
699         }
700 
701         dropShellPermissionIdentity();
702         // We first try to use shell permissions, because they can be revoked/etc. much more easily
703 
704         Set<String> adoptedShellPermissions = new HashSet<>();
705         Set<String> grantedPermissions = new HashSet<>();
706         Set<String> deniedPermissions = new HashSet<>();
707         for (String permission : permissionsToGrant) {
708             Log.d(LOG_TAG, "Trying to grant " + permission);
709             if (sInstrumentedPackage.hasPermission(user, permission)) {
710                 // Already granted, can skip
711                 Log.d(LOG_TAG, permission + " already granted at runtime");
712             } else if (mInstrumentedRequestedPermissions.contains(permission)
713                     && sContext.checkSelfPermission(permission) == PERMISSION_GRANTED) {
714                 // Already granted, can skip
715                 Log.d(LOG_TAG, permission + " already granted from manifest");
716             } else if (mShellPermissions.contains(permission)) {
717                 adoptedShellPermissions.add(permission);
718             } else {
719                 grantedPermissions.add(permission);
720             }
721         }
722 
723         for (String permission : permissionsToDeny) {
724             if (!sInstrumentedPackage.hasPermission(sUser, permission)) {
725                 // Already denied, can skip
726             } else if (!mShellPermissions.contains(permission)) {
727                 adoptedShellPermissions.add(permission);
728             } else {
729                 deniedPermissions.add(permission);
730             }
731         }
732 
733         if (!adoptedShellPermissions.isEmpty()) {
734             adoptShellPermissionIdentity(adoptedShellPermissions);
735         }
736         if (!grantedPermissions.isEmpty() || !deniedPermissions.isEmpty()) {
737             setPermissionStateToPackageWithoutAdoption(pkg, user, grantedPermissions,
738                     deniedPermissions);
739         }
740     }
741 
742     /** Ensure that permissions are not being overridden for any packages. */
743     @Experimental
744     public void clearAllOverridePermissionStates() {
745         if (Versions.meetsMinimumSdkVersionRequirement(Versions.V)) {
746             try {
747                 TestApisReflectionKt.clearAllOverridePermissionStates(
748                         ShellCommandUtils.uiAutomation());
749             } catch (Exception e) {
750                 Log.e(LOG_TAG, "Error clearing all override permission states", e);
751             }
752         }
753     }
754 
755     private void resetRootPermissionState(Package pkg, UserReference user) {
756         clearOverridePermissionStates(ShellCommandUtils.uiAutomation(), pkg.uid(user));
757     }
758 
759     private void forceRootPermissionState(Package pkg, UserReference user, String permission,
760             boolean granted) {
761         addOverridePermissionState(
762                 ShellCommandUtils.uiAutomation(), pkg.uid(user), permission,
763                 granted ? PERMISSION_GRANTED : PERMISSION_DENIED);
764     }
765 
766     public void removeRootPermissionState(Package pkg, UserReference user, String permission) {
767         removeOverridePermissionState(
768                 ShellCommandUtils.uiAutomation(), pkg.uid(user), permission);
769     }
770 
771     private static boolean hasAdoptedShellPermissionIdentity = false;
772 
773     private static void adoptShellPermissionIdentity(String... permissions) {
774         adoptShellPermissionIdentity(new HashSet<>(Arrays.asList(permissions)));
775     }
776 
777     private static void adoptShellPermissionIdentity(Collection<String> permissions) {
778         if (permissions.isEmpty()) {
779             dropShellPermissionIdentity();
780             return;
781         }
782 
783         Log.d(LOG_TAG, "Adopting " + permissions);
784         hasAdoptedShellPermissionIdentity = true;
785         ShellCommandUtils.uiAutomation().adoptShellPermissionIdentity(
786                 permissions.toArray(new String[0]));
787 
788         if (Versions.meetsMinimumSdkVersionRequirement(Versions.S)) {
789             // b/365494315: verify if the adoption was successful - for S+ only as
790             // UiAutomation#getAdoptedShellPermissions was added in S.
791             Set<String> adoptedPermissions = getAdoptedShellPermissions();
792             if (!adoptedPermissions.containsAll(permissions)) {
793                 String message = "Expected all of the following permissions to be adopted but "
794                         + "were not: " + permissions + ". Actual adopted permissions: " +
795                         adoptedPermissions + ". See the stacktrace to find the caller.";
796                 throw new NeneException(message);
797             }
798         }
799     }
800 
801     private static void adoptShellPermissionIdentity() {
802         Log.d(LOG_TAG, "Adopting all shell permissions");
803         hasAdoptedShellPermissionIdentity = true;
804         ShellCommandUtils.uiAutomation().adoptShellPermissionIdentity();
805     }
806 
807     private static void dropShellPermissionIdentity() {
808         Log.d(LOG_TAG, "Dropping shell permissions");
809         hasAdoptedShellPermissionIdentity = false;
810         ShellCommandUtils.uiAutomation().dropShellPermissionIdentity();
811     }
812 
813     /** Get string dump of permissions state. */
814     public String dump() {
815         if (!Versions.meetsMinimumSdkVersionRequirement(Versions.V)) {
816             Log.i(LOG_TAG, "Cannot dump permission before V so dumping packages");
817             return TestApis.packages().dump();
818         }
819 
820         return ShellCommand.builder("dumpsys permissionmgr").validate((s) -> !s.isEmpty())
821                 .executeOrThrowNeneException("Error dumping permission state");
822     }
823 
824     /**
825      * Get adopted shell permissions.
826      *
827      * <p>See {@code UiAutomation#getAdoptedShellPermissions}.
828      *
829      * <p>Note: Does not compute anything for Versions < S, returns an empty set instead.
830      */
831     private static Set<String> getAdoptedShellPermissions() {
832         if (Versions.meetsMinimumSdkVersionRequirement(Versions.S)) {
833             return TestApisReflectionKt.getAdoptedShellPermissions(
834                     ShellCommandUtils.uiAutomation());
835         }
836 
837         return Collections.emptySet();
838     }
839 
840     /**
841      * create a PermissionContextImpl that doesn't block others threads to create other contexts
842      * don't use it in the tests directly
843      */
844     public PermissionContextImpl createNonBlockingPermissionContext() {
845         var context = PermissionContextImpl.createNonBlocking(this);
846         mPermissionContexts.add(context);
847         return context;
848     }
849 }
850 
851