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