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 create(Context context, PackageInfo packageInfo, String permissionName)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 create(Context context, PackageInfo packageInfo, PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos, UserHandle userHandle)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 loadGroupDescription(Context context, PackageItemInfo group)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 AppPermissionGroup(Context context, PackageInfo packageInfo, String name, String declaringPackage, CharSequence label, CharSequence description, String iconPkg, int iconResId, UserHandle userHandle)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 doesSupportRuntimePermissions()215 public boolean doesSupportRuntimePermissions() { 216 return mAppSupportsRuntimePermissions; 217 } 218 isGrantingAllowed()219 public boolean isGrantingAllowed() { 220 return (!mIsEphemeralApp || mContainsEphemeralPermission) 221 && (mAppSupportsRuntimePermissions || mContainsPreRuntimePermission); 222 } 223 isReviewRequired()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 resetReviewRequired()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 hasGrantedByDefaultPermission()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 getApp()263 public PackageInfo getApp() { 264 return mPackageInfo; 265 } 266 getName()267 public String getName() { 268 return mName; 269 } 270 getDeclaringPackage()271 public String getDeclaringPackage() { 272 return mDeclaringPackage; 273 } 274 getIconPkg()275 public String getIconPkg() { 276 return mIconPkg; 277 } 278 getIconResId()279 public int getIconResId() { 280 return mIconResId; 281 } 282 getLabel()283 public CharSequence getLabel() { 284 return mLabel; 285 } 286 getDescription()287 public CharSequence getDescription() { 288 return mDescription; 289 } 290 getUserId()291 public int getUserId() { 292 return mUserHandle.getIdentifier(); 293 } 294 hasPermission(String permission)295 public boolean hasPermission(String permission) { 296 return mPermissions.get(permission) != null; 297 } 298 areRuntimePermissionsGranted()299 public boolean areRuntimePermissionsGranted() { 300 return areRuntimePermissionsGranted(null); 301 } 302 areRuntimePermissionsGranted(String[] filterPermissions)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 grantRuntimePermissions(boolean fixedByTheUser)326 public boolean grantRuntimePermissions(boolean fixedByTheUser) { 327 return grantRuntimePermissions(fixedByTheUser, null); 328 } 329 grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions)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 revokeRuntimePermissions(boolean fixedByTheUser)426 public boolean revokeRuntimePermissions(boolean fixedByTheUser) { 427 return revokeRuntimePermissions(fixedByTheUser, null); 428 } 429 revokeRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions)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 setPolicyFixed()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 getPermissions()541 public List<Permission> getPermissions() { 542 return new ArrayList<>(mPermissions.values()); 543 } 544 getFlags()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 isUserFixed()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 isPolicyFixed()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 isUserSet()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 isSystemFixed()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 compareTo(AppPermissionGroup another)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 equals(Object obj)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 hashCode()638 public int hashCode() { 639 return mName != null ? mName.hashCode() : 0; 640 } 641 642 @Override toString()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 addPermission(Permission permission)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