1 /* 2 * Copyright (C) 2015 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.packageinstaller.permission.model; 18 19 import android.annotation.StringRes; 20 import android.annotation.SystemApi; 21 import android.app.ActivityManager; 22 import android.app.AppOpsManager; 23 import android.content.Context; 24 import android.content.pm.PackageInfo; 25 import android.content.pm.PackageItemInfo; 26 import android.content.pm.PackageManager; 27 import android.content.pm.PermissionGroupInfo; 28 import android.content.pm.PermissionInfo; 29 import android.os.Build; 30 import android.os.Process; 31 import android.os.UserHandle; 32 import android.util.ArrayMap; 33 34 import com.android.packageinstaller.R; 35 import com.android.packageinstaller.permission.utils.ArrayUtils; 36 import com.android.packageinstaller.permission.utils.LocationUtils; 37 38 import java.text.Collator; 39 import java.util.ArrayList; 40 import java.util.List; 41 42 public final class AppPermissionGroup implements Comparable<AppPermissionGroup> { 43 private static final String PLATFORM_PACKAGE_NAME = "android"; 44 45 private static final String KILL_REASON_APP_OP_CHANGE = "Permission related app op changed"; 46 47 private final Context mContext; 48 private final UserHandle mUserHandle; 49 private final PackageManager mPackageManager; 50 private final AppOpsManager mAppOps; 51 private final ActivityManager mActivityManager; 52 private final Collator mCollator; 53 54 private final PackageInfo mPackageInfo; 55 private final String mName; 56 private final String mDeclaringPackage; 57 private final CharSequence mLabel; 58 private final @StringRes int mRequest; 59 private final CharSequence mDescription; 60 private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>(); 61 private final String mIconPkg; 62 private final int mIconResId; 63 64 private final boolean mAppSupportsRuntimePermissions; 65 private final boolean mIsEphemeralApp; 66 private boolean mContainsEphemeralPermission; 67 private boolean mContainsPreRuntimePermission; 68 69 public static AppPermissionGroup create(Context context, PackageInfo packageInfo, 70 String permissionName) { 71 PermissionInfo permissionInfo; 72 try { 73 permissionInfo = context.getPackageManager().getPermissionInfo(permissionName, 0); 74 } catch (PackageManager.NameNotFoundException e) { 75 return null; 76 } 77 78 if ((permissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) 79 != PermissionInfo.PROTECTION_DANGEROUS 80 || (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) == 0 81 || (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) != 0) { 82 return null; 83 } 84 85 PackageItemInfo groupInfo = permissionInfo; 86 if (permissionInfo.group != null) { 87 try { 88 groupInfo = context.getPackageManager().getPermissionGroupInfo( 89 permissionInfo.group, 0); 90 } catch (PackageManager.NameNotFoundException e) { 91 /* ignore */ 92 } 93 } 94 95 List<PermissionInfo> permissionInfos = null; 96 if (groupInfo instanceof PermissionGroupInfo) { 97 try { 98 permissionInfos = context.getPackageManager().queryPermissionsByGroup( 99 groupInfo.name, 0); 100 } catch (PackageManager.NameNotFoundException e) { 101 /* ignore */ 102 } 103 } 104 105 return create(context, packageInfo, groupInfo, permissionInfos, 106 Process.myUserHandle()); 107 } 108 109 public static AppPermissionGroup create(Context context, PackageInfo packageInfo, 110 PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos, 111 UserHandle userHandle) { 112 113 AppPermissionGroup group = new AppPermissionGroup(context, packageInfo, groupInfo.name, 114 groupInfo.packageName, groupInfo.loadLabel(context.getPackageManager()), 115 loadGroupDescription(context, groupInfo), getRequest(groupInfo), 116 groupInfo.packageName, groupInfo.icon, userHandle); 117 118 if (groupInfo instanceof PermissionInfo) { 119 permissionInfos = new ArrayList<>(); 120 permissionInfos.add((PermissionInfo) groupInfo); 121 } 122 123 if (permissionInfos == null || permissionInfos.isEmpty()) { 124 return null; 125 } 126 127 final int permissionCount = packageInfo.requestedPermissions.length; 128 for (int i = 0; i < permissionCount; i++) { 129 String requestedPermission = packageInfo.requestedPermissions[i]; 130 131 PermissionInfo requestedPermissionInfo = null; 132 133 for (PermissionInfo permissionInfo : permissionInfos) { 134 if (requestedPermission.equals(permissionInfo.name)) { 135 requestedPermissionInfo = permissionInfo; 136 break; 137 } 138 } 139 140 if (requestedPermissionInfo == null) { 141 continue; 142 } 143 144 // Collect only runtime permissions. 145 if ((requestedPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) 146 != PermissionInfo.PROTECTION_DANGEROUS) { 147 continue; 148 } 149 150 // Don't allow toggling non-platform permission groups for legacy apps via app ops. 151 if (packageInfo.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1 152 && !PLATFORM_PACKAGE_NAME.equals(groupInfo.packageName)) { 153 continue; 154 } 155 156 final boolean granted = (packageInfo.requestedPermissionsFlags[i] 157 & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0; 158 159 final String appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName) 160 ? AppOpsManager.permissionToOp(requestedPermissionInfo.name) : null; 161 162 final boolean appOpAllowed = appOp != null 163 && context.getSystemService(AppOpsManager.class).checkOpNoThrow(appOp, 164 packageInfo.applicationInfo.uid, packageInfo.packageName) 165 == AppOpsManager.MODE_ALLOWED; 166 167 final int flags = context.getPackageManager().getPermissionFlags( 168 requestedPermission, packageInfo.packageName, userHandle); 169 170 Permission permission = new Permission(requestedPermission, granted, 171 appOp, appOpAllowed, flags, requestedPermissionInfo.protectionLevel); 172 group.addPermission(permission); 173 } 174 175 return group; 176 } 177 178 private static @StringRes int getRequest(PackageItemInfo group) { 179 if (group instanceof PermissionGroupInfo) { 180 return ((PermissionGroupInfo) group).requestRes; 181 } else if (group instanceof PermissionInfo) { 182 return ((PermissionInfo) group).requestRes; 183 } else { 184 return 0; 185 } 186 } 187 188 private static CharSequence loadGroupDescription(Context context, PackageItemInfo group) { 189 CharSequence description = null; 190 if (group instanceof PermissionGroupInfo) { 191 description = ((PermissionGroupInfo) group).loadDescription( 192 context.getPackageManager()); 193 } else if (group instanceof PermissionInfo) { 194 description = ((PermissionInfo) group).loadDescription( 195 context.getPackageManager()); 196 } 197 198 if (description == null || description.length() <= 0) { 199 description = context.getString(R.string.default_permission_description); 200 } 201 202 return description; 203 } 204 205 private AppPermissionGroup(Context context, PackageInfo packageInfo, String name, 206 String declaringPackage, CharSequence label, CharSequence description, 207 @StringRes int request, String iconPkg, int iconResId, UserHandle userHandle) { 208 mContext = context; 209 mUserHandle = userHandle; 210 mPackageManager = mContext.getPackageManager(); 211 mPackageInfo = packageInfo; 212 mAppSupportsRuntimePermissions = packageInfo.applicationInfo 213 .targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1; 214 mIsEphemeralApp = packageInfo.applicationInfo.isInstantApp(); 215 mAppOps = context.getSystemService(AppOpsManager.class); 216 mActivityManager = context.getSystemService(ActivityManager.class); 217 mDeclaringPackage = declaringPackage; 218 mName = name; 219 mLabel = label; 220 mDescription = description; 221 mCollator = Collator.getInstance( 222 context.getResources().getConfiguration().getLocales().get(0)); 223 mRequest = request; 224 if (iconResId != 0) { 225 mIconPkg = iconPkg; 226 mIconResId = iconResId; 227 } else { 228 mIconPkg = context.getPackageName(); 229 mIconResId = R.drawable.ic_perm_device_info; 230 } 231 } 232 233 public boolean doesSupportRuntimePermissions() { 234 return mAppSupportsRuntimePermissions; 235 } 236 237 public boolean isGrantingAllowed() { 238 return (!mIsEphemeralApp || mContainsEphemeralPermission) 239 && (mAppSupportsRuntimePermissions || mContainsPreRuntimePermission); 240 } 241 242 public boolean isReviewRequired() { 243 if (mAppSupportsRuntimePermissions) { 244 return false; 245 } 246 final int permissionCount = mPermissions.size(); 247 for (int i = 0; i < permissionCount; i++) { 248 Permission permission = mPermissions.valueAt(i); 249 if (permission.isReviewRequired()) { 250 return true; 251 } 252 } 253 return false; 254 } 255 256 public void resetReviewRequired() { 257 final int permissionCount = mPermissions.size(); 258 for (int i = 0; i < permissionCount; i++) { 259 Permission permission = mPermissions.valueAt(i); 260 if (permission.isReviewRequired()) { 261 permission.resetReviewRequired(); 262 mPackageManager.updatePermissionFlags(permission.getName(), 263 mPackageInfo.packageName, 264 PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 265 0, mUserHandle); 266 } 267 } 268 } 269 270 public boolean hasGrantedByDefaultPermission() { 271 final int permissionCount = mPermissions.size(); 272 for (int i = 0; i < permissionCount; i++) { 273 Permission permission = mPermissions.valueAt(i); 274 if (permission.isGrantedByDefault()) { 275 return true; 276 } 277 } 278 return false; 279 } 280 281 public PackageInfo getApp() { 282 return mPackageInfo; 283 } 284 285 public String getName() { 286 return mName; 287 } 288 289 public String getDeclaringPackage() { 290 return mDeclaringPackage; 291 } 292 293 public String getIconPkg() { 294 return mIconPkg; 295 } 296 297 public int getIconResId() { 298 return mIconResId; 299 } 300 301 public CharSequence getLabel() { 302 return mLabel; 303 } 304 305 /** 306 * @hide 307 * @return The resource Id of the request string. 308 */ 309 @SystemApi 310 public @StringRes int getRequest() { 311 return mRequest; 312 } 313 314 public CharSequence getDescription() { 315 return mDescription; 316 } 317 318 public int getUserId() { 319 return mUserHandle.getIdentifier(); 320 } 321 322 public boolean hasPermission(String permission) { 323 return mPermissions.get(permission) != null; 324 } 325 326 public boolean areRuntimePermissionsGranted() { 327 return areRuntimePermissionsGranted(null); 328 } 329 330 public boolean areRuntimePermissionsGranted(String[] filterPermissions) { 331 if (LocationUtils.isLocationGroupAndProvider(mName, mPackageInfo.packageName)) { 332 return LocationUtils.isLocationEnabled(mContext); 333 } 334 final int permissionCount = mPermissions.size(); 335 for (int i = 0; i < permissionCount; i++) { 336 Permission permission = mPermissions.valueAt(i); 337 if (filterPermissions != null 338 && !ArrayUtils.contains(filterPermissions, permission.getName())) { 339 continue; 340 } 341 if (mAppSupportsRuntimePermissions) { 342 if (permission.isGranted()) { 343 return true; 344 } 345 } else if (permission.isGranted() && (permission.getAppOp() == null 346 || permission.isAppOpAllowed()) && !permission.isReviewRequired()) { 347 return true; 348 } 349 } 350 return false; 351 } 352 353 public boolean grantRuntimePermissions(boolean fixedByTheUser) { 354 return grantRuntimePermissions(fixedByTheUser, null); 355 } 356 357 public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) { 358 final int uid = mPackageInfo.applicationInfo.uid; 359 360 // We toggle permissions only to apps that support runtime 361 // permissions, otherwise we toggle the app op corresponding 362 // to the permission if the permission is granted to the app. 363 for (Permission permission : mPermissions.values()) { 364 if (filterPermissions != null 365 && !ArrayUtils.contains(filterPermissions, permission.getName())) { 366 continue; 367 } 368 369 if (!permission.isGrantingAllowed(mIsEphemeralApp, mAppSupportsRuntimePermissions)) { 370 // Skip unallowed permissions. 371 continue; 372 } 373 374 if (mAppSupportsRuntimePermissions) { 375 // Do not touch permissions fixed by the system. 376 if (permission.isSystemFixed()) { 377 return false; 378 } 379 380 // Ensure the permission app op enabled before the permission grant. 381 if (permission.hasAppOp() && !permission.isAppOpAllowed()) { 382 permission.setAppOpAllowed(true); 383 mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED); 384 } 385 386 // Grant the permission if needed. 387 if (!permission.isGranted()) { 388 permission.setGranted(true); 389 mPackageManager.grantRuntimePermission(mPackageInfo.packageName, 390 permission.getName(), mUserHandle); 391 } 392 393 // Update the permission flags. 394 if (!fixedByTheUser) { 395 // Now the apps can ask for the permission as the user 396 // no longer has it fixed in a denied state. 397 if (permission.isUserFixed() || permission.isUserSet()) { 398 permission.setUserFixed(false); 399 permission.setUserSet(false); 400 mPackageManager.updatePermissionFlags(permission.getName(), 401 mPackageInfo.packageName, 402 PackageManager.FLAG_PERMISSION_USER_FIXED 403 | PackageManager.FLAG_PERMISSION_USER_SET, 404 0, mUserHandle); 405 } 406 } 407 } else { 408 // Legacy apps cannot have a not granted permission but just in case. 409 if (!permission.isGranted()) { 410 continue; 411 } 412 413 int killUid = -1; 414 int mask = 0; 415 416 // If the permissions has no corresponding app op, then it is a 417 // third-party one and we do not offer toggling of such permissions. 418 if (permission.hasAppOp()) { 419 if (!permission.isAppOpAllowed()) { 420 permission.setAppOpAllowed(true); 421 // Enable the app op. 422 mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED); 423 424 // Legacy apps do not know that they have to retry access to a 425 // resource due to changes in runtime permissions (app ops in this 426 // case). Therefore, we restart them on app op change, so they 427 // can pick up the change. 428 killUid = uid; 429 } 430 431 // Mark that the permission should not be be granted on upgrade 432 // when the app begins supporting runtime permissions. 433 if (permission.shouldRevokeOnUpgrade()) { 434 permission.setRevokeOnUpgrade(false); 435 mask |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE; 436 } 437 } 438 439 // Granting a permission explicitly means the user already 440 // reviewed it so clear the review flag on every grant. 441 if (permission.isReviewRequired()) { 442 permission.resetReviewRequired(); 443 mask |= PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED; 444 } 445 446 if (mask != 0) { 447 mPackageManager.updatePermissionFlags(permission.getName(), 448 mPackageInfo.packageName, mask, 0, mUserHandle); 449 } 450 451 if (killUid != -1) { 452 mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE); 453 } 454 } 455 } 456 457 return true; 458 } 459 460 public boolean revokeRuntimePermissions(boolean fixedByTheUser) { 461 return revokeRuntimePermissions(fixedByTheUser, null); 462 } 463 464 public boolean revokeRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) { 465 final int uid = mPackageInfo.applicationInfo.uid; 466 467 // We toggle permissions only to apps that support runtime 468 // permissions, otherwise we toggle the app op corresponding 469 // to the permission if the permission is granted to the app. 470 for (Permission permission : mPermissions.values()) { 471 if (filterPermissions != null 472 && !ArrayUtils.contains(filterPermissions, permission.getName())) { 473 continue; 474 } 475 476 if (mAppSupportsRuntimePermissions) { 477 // Do not touch permissions fixed by the system. 478 if (permission.isSystemFixed()) { 479 return false; 480 } 481 482 // Revoke the permission if needed. 483 if (permission.isGranted()) { 484 permission.setGranted(false); 485 mPackageManager.revokeRuntimePermission(mPackageInfo.packageName, 486 permission.getName(), mUserHandle); 487 } 488 489 // Update the permission flags. 490 if (fixedByTheUser) { 491 // Take a note that the user fixed the permission. 492 if (permission.isUserSet() || !permission.isUserFixed()) { 493 permission.setUserSet(false); 494 permission.setUserFixed(true); 495 mPackageManager.updatePermissionFlags(permission.getName(), 496 mPackageInfo.packageName, 497 PackageManager.FLAG_PERMISSION_USER_SET 498 | PackageManager.FLAG_PERMISSION_USER_FIXED, 499 PackageManager.FLAG_PERMISSION_USER_FIXED, 500 mUserHandle); 501 } 502 } else { 503 if (!permission.isUserSet() || permission.isUserFixed()) { 504 permission.setUserSet(true); 505 permission.setUserFixed(false); 506 // Take a note that the user already chose once. 507 mPackageManager.updatePermissionFlags(permission.getName(), 508 mPackageInfo.packageName, 509 PackageManager.FLAG_PERMISSION_USER_SET 510 | PackageManager.FLAG_PERMISSION_USER_FIXED, 511 PackageManager.FLAG_PERMISSION_USER_SET, 512 mUserHandle); 513 } 514 } 515 } else { 516 // Legacy apps cannot have a non-granted permission but just in case. 517 if (!permission.isGranted()) { 518 continue; 519 } 520 521 int mask = 0; 522 int flags = 0; 523 int killUid = -1; 524 525 // If the permission has no corresponding app op, then it is a 526 // third-party one and we do not offer toggling of such permissions. 527 if (permission.hasAppOp()) { 528 if (permission.isAppOpAllowed()) { 529 permission.setAppOpAllowed(false); 530 // Disable the app op. 531 mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_IGNORED); 532 533 // Disabling an app op may put the app in a situation in which it 534 // has a handle to state it shouldn't have, so we have to kill the 535 // app. This matches the revoke runtime permission behavior. 536 killUid = uid; 537 } 538 539 // Mark that the permission should not be granted on upgrade 540 // when the app begins supporting runtime permissions. 541 if (!permission.shouldRevokeOnUpgrade()) { 542 permission.setRevokeOnUpgrade(true); 543 mask |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE; 544 flags |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE; 545 } 546 } 547 548 if (mask != 0) { 549 mPackageManager.updatePermissionFlags(permission.getName(), 550 mPackageInfo.packageName, mask, flags, mUserHandle); 551 } 552 553 if (killUid != -1) { 554 mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE); 555 } 556 } 557 } 558 559 return true; 560 } 561 562 public void setPolicyFixed() { 563 final int permissionCount = mPermissions.size(); 564 for (int i = 0; i < permissionCount; i++) { 565 Permission permission = mPermissions.valueAt(i); 566 permission.setPolicyFixed(true); 567 mPackageManager.updatePermissionFlags(permission.getName(), 568 mPackageInfo.packageName, 569 PackageManager.FLAG_PERMISSION_POLICY_FIXED, 570 PackageManager.FLAG_PERMISSION_POLICY_FIXED, 571 mUserHandle); 572 } 573 } 574 575 public List<Permission> getPermissions() { 576 return new ArrayList<>(mPermissions.values()); 577 } 578 579 public int getFlags() { 580 int flags = 0; 581 final int permissionCount = mPermissions.size(); 582 for (int i = 0; i < permissionCount; i++) { 583 Permission permission = mPermissions.valueAt(i); 584 flags |= permission.getFlags(); 585 } 586 return flags; 587 } 588 589 public boolean isUserFixed() { 590 final int permissionCount = mPermissions.size(); 591 for (int i = 0; i < permissionCount; i++) { 592 Permission permission = mPermissions.valueAt(i); 593 if (permission.isUserFixed()) { 594 return true; 595 } 596 } 597 return false; 598 } 599 600 public boolean isPolicyFixed() { 601 final int permissionCount = mPermissions.size(); 602 for (int i = 0; i < permissionCount; i++) { 603 Permission permission = mPermissions.valueAt(i); 604 if (permission.isPolicyFixed()) { 605 return true; 606 } 607 } 608 return false; 609 } 610 611 public boolean isUserSet() { 612 final int permissionCount = mPermissions.size(); 613 for (int i = 0; i < permissionCount; i++) { 614 Permission permission = mPermissions.valueAt(i); 615 if (permission.isUserSet()) { 616 return true; 617 } 618 } 619 return false; 620 } 621 622 public boolean isSystemFixed() { 623 final int permissionCount = mPermissions.size(); 624 for (int i = 0; i < permissionCount; i++) { 625 Permission permission = mPermissions.valueAt(i); 626 if (permission.isSystemFixed()) { 627 return true; 628 } 629 } 630 return false; 631 } 632 633 @Override 634 public int compareTo(AppPermissionGroup another) { 635 final int result = mCollator.compare(mLabel.toString(), another.mLabel.toString()); 636 if (result == 0) { 637 // Unbadged before badged. 638 return mPackageInfo.applicationInfo.uid 639 - another.mPackageInfo.applicationInfo.uid; 640 } 641 return result; 642 } 643 644 @Override 645 public boolean equals(Object obj) { 646 if (this == obj) { 647 return true; 648 } 649 650 if (obj == null) { 651 return false; 652 } 653 654 if (getClass() != obj.getClass()) { 655 return false; 656 } 657 658 AppPermissionGroup other = (AppPermissionGroup) obj; 659 660 if (mName == null) { 661 if (other.mName != null) { 662 return false; 663 } 664 } else if (!mName.equals(other.mName)) { 665 return false; 666 } 667 668 return true; 669 } 670 671 @Override 672 public int hashCode() { 673 return mName != null ? mName.hashCode() : 0; 674 } 675 676 @Override 677 public String toString() { 678 StringBuilder builder = new StringBuilder(); 679 builder.append(getClass().getSimpleName()); 680 builder.append("{name=").append(mName); 681 if (!mPermissions.isEmpty()) { 682 builder.append(", <has permissions>}"); 683 } else { 684 builder.append('}'); 685 } 686 return builder.toString(); 687 } 688 689 private void addPermission(Permission permission) { 690 mPermissions.put(permission.getName(), permission); 691 if (permission.isEphemeral()) { 692 mContainsEphemeralPermission = true; 693 } 694 if (!permission.isRuntimeOnly()) { 695 mContainsPreRuntimePermission = true; 696 } 697 } 698 } 699