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())) { 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 if (mask != 0) { 413 mPackageManager.updatePermissionFlags(permission.getName(), 414 mPackageInfo.packageName, mask, 0, mUserHandle); 415 } 416 417 if (killUid != -1) { 418 mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE); 419 } 420 } 421 } 422 423 return true; 424 } 425 426 public boolean revokeRuntimePermissions(boolean fixedByTheUser) { 427 return revokeRuntimePermissions(fixedByTheUser, null); 428 } 429 430 public boolean revokeRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) { 431 final int uid = mPackageInfo.applicationInfo.uid; 432 433 // We toggle permissions only to apps that support runtime 434 // permissions, otherwise we toggle the app op corresponding 435 // to the permission if the permission is granted to the app. 436 for (Permission permission : mPermissions.values()) { 437 if (filterPermissions != null 438 && !ArrayUtils.contains(filterPermissions, permission.getName())) { 439 continue; 440 } 441 442 if (mAppSupportsRuntimePermissions) { 443 // Do not touch permissions fixed by the system. 444 if (permission.isSystemFixed()) { 445 return false; 446 } 447 448 // Revoke the permission if needed. 449 if (permission.isGranted()) { 450 permission.setGranted(false); 451 mPackageManager.revokeRuntimePermission(mPackageInfo.packageName, 452 permission.getName(), mUserHandle); 453 } 454 455 // Update the permission flags. 456 if (fixedByTheUser) { 457 // Take a note that the user fixed the permission. 458 if (permission.isUserSet() || !permission.isUserFixed()) { 459 permission.setUserSet(false); 460 permission.setUserFixed(true); 461 mPackageManager.updatePermissionFlags(permission.getName(), 462 mPackageInfo.packageName, 463 PackageManager.FLAG_PERMISSION_USER_SET 464 | PackageManager.FLAG_PERMISSION_USER_FIXED, 465 PackageManager.FLAG_PERMISSION_USER_FIXED, 466 mUserHandle); 467 } 468 } else { 469 if (!permission.isUserSet() || permission.isUserFixed()) { 470 permission.setUserSet(true); 471 permission.setUserFixed(false); 472 // Take a note that the user already chose once. 473 mPackageManager.updatePermissionFlags(permission.getName(), 474 mPackageInfo.packageName, 475 PackageManager.FLAG_PERMISSION_USER_SET 476 | PackageManager.FLAG_PERMISSION_USER_FIXED, 477 PackageManager.FLAG_PERMISSION_USER_SET, 478 mUserHandle); 479 } 480 } 481 } else { 482 // Legacy apps cannot have a non-granted permission but just in case. 483 if (!permission.isGranted()) { 484 continue; 485 } 486 487 int mask = 0; 488 int flags = 0; 489 int killUid = -1; 490 491 // If the permission has no corresponding app op, then it is a 492 // third-party one and we do not offer toggling of such permissions. 493 if (permission.hasAppOp()) { 494 if (permission.isAppOpAllowed()) { 495 permission.setAppOpAllowed(false); 496 // Disable the app op. 497 mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_IGNORED); 498 499 // Disabling an app op may put the app in a situation in which it 500 // has a handle to state it shouldn't have, so we have to kill the 501 // app. This matches the revoke runtime permission behavior. 502 killUid = uid; 503 } 504 505 // Mark that the permission should not be granted on upgrade 506 // when the app begins supporting runtime permissions. 507 if (!permission.shouldRevokeOnUpgrade()) { 508 permission.setRevokeOnUpgrade(true); 509 mask |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE; 510 flags |= PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE; 511 } 512 } 513 514 if (mask != 0) { 515 mPackageManager.updatePermissionFlags(permission.getName(), 516 mPackageInfo.packageName, mask, flags, mUserHandle); 517 } 518 519 if (killUid != -1) { 520 mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE); 521 } 522 } 523 } 524 525 return true; 526 } 527 528 public void setPolicyFixed() { 529 final int permissionCount = mPermissions.size(); 530 for (int i = 0; i < permissionCount; i++) { 531 Permission permission = mPermissions.valueAt(i); 532 permission.setPolicyFixed(true); 533 mPackageManager.updatePermissionFlags(permission.getName(), 534 mPackageInfo.packageName, 535 PackageManager.FLAG_PERMISSION_POLICY_FIXED, 536 PackageManager.FLAG_PERMISSION_POLICY_FIXED, 537 mUserHandle); 538 } 539 } 540 541 public List<Permission> getPermissions() { 542 return new ArrayList<>(mPermissions.values()); 543 } 544 545 public int getFlags() { 546 int flags = 0; 547 final int permissionCount = mPermissions.size(); 548 for (int i = 0; i < permissionCount; i++) { 549 Permission permission = mPermissions.valueAt(i); 550 flags |= permission.getFlags(); 551 } 552 return flags; 553 } 554 555 public boolean isUserFixed() { 556 final int permissionCount = mPermissions.size(); 557 for (int i = 0; i < permissionCount; i++) { 558 Permission permission = mPermissions.valueAt(i); 559 if (permission.isUserFixed()) { 560 return true; 561 } 562 } 563 return false; 564 } 565 566 public boolean isPolicyFixed() { 567 final int permissionCount = mPermissions.size(); 568 for (int i = 0; i < permissionCount; i++) { 569 Permission permission = mPermissions.valueAt(i); 570 if (permission.isPolicyFixed()) { 571 return true; 572 } 573 } 574 return false; 575 } 576 577 public boolean isUserSet() { 578 final int permissionCount = mPermissions.size(); 579 for (int i = 0; i < permissionCount; i++) { 580 Permission permission = mPermissions.valueAt(i); 581 if (permission.isUserSet()) { 582 return true; 583 } 584 } 585 return false; 586 } 587 588 public boolean isSystemFixed() { 589 final int permissionCount = mPermissions.size(); 590 for (int i = 0; i < permissionCount; i++) { 591 Permission permission = mPermissions.valueAt(i); 592 if (permission.isSystemFixed()) { 593 return true; 594 } 595 } 596 return false; 597 } 598 599 @Override 600 public int compareTo(AppPermissionGroup another) { 601 final int result = mLabel.toString().compareTo(another.mLabel.toString()); 602 if (result == 0) { 603 // Unbadged before badged. 604 return mPackageInfo.applicationInfo.uid 605 - another.mPackageInfo.applicationInfo.uid; 606 } 607 return result; 608 } 609 610 @Override 611 public boolean equals(Object obj) { 612 if (this == obj) { 613 return true; 614 } 615 616 if (obj == null) { 617 return false; 618 } 619 620 if (getClass() != obj.getClass()) { 621 return false; 622 } 623 624 AppPermissionGroup other = (AppPermissionGroup) obj; 625 626 if (mName == null) { 627 if (other.mName != null) { 628 return false; 629 } 630 } else if (!mName.equals(other.mName)) { 631 return false; 632 } 633 634 return true; 635 } 636 637 @Override 638 public int hashCode() { 639 return mName != null ? mName.hashCode() : 0; 640 } 641 642 @Override 643 public String toString() { 644 StringBuilder builder = new StringBuilder(); 645 builder.append(getClass().getSimpleName()); 646 builder.append("{name=").append(mName); 647 if (!mPermissions.isEmpty()) { 648 builder.append(", <has permissions>}"); 649 } else { 650 builder.append('}'); 651 } 652 return builder.toString(); 653 } 654 655 private void addPermission(Permission permission) { 656 mPermissions.put(permission.getName(), permission); 657 if (permission.isEphemeral()) { 658 mContainsEphemeralPermission = true; 659 } 660 if (!permission.isRuntimeOnly()) { 661 mContainsPreRuntimePermission = true; 662 } 663 } 664 } 665