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