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