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 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 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 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 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 209 public boolean hasRuntimePermission() { 210 return mAppSupportsRuntimePermissions; 211 } 212 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 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 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 252 public PackageInfo getApp() { 253 return mPackageInfo; 254 } 255 256 public String getName() { 257 return mName; 258 } 259 260 public String getDeclaringPackage() { 261 return mDeclaringPackage; 262 } 263 264 public String getIconPkg() { 265 return mIconPkg; 266 } 267 268 public int getIconResId() { 269 return mIconResId; 270 } 271 272 public CharSequence getLabel() { 273 return mLabel; 274 } 275 276 public CharSequence getDescription() { 277 return mDescription; 278 } 279 280 public int getUserId() { 281 return mUserHandle.getIdentifier(); 282 } 283 284 public boolean hasPermission(String permission) { 285 return mPermissions.get(permission) != null; 286 } 287 288 public boolean areRuntimePermissionsGranted() { 289 return areRuntimePermissionsGranted(null); 290 } 291 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 315 public boolean grantRuntimePermissions(boolean fixedByTheUser) { 316 return grantRuntimePermissions(fixedByTheUser, null); 317 } 318 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 410 public boolean revokeRuntimePermissions(boolean fixedByTheUser) { 411 return revokeRuntimePermissions(fixedByTheUser, null); 412 } 413 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 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 523 public List<Permission> getPermissions() { 524 return new ArrayList<>(mPermissions.values()); 525 } 526 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 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 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 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 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 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 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 620 public int hashCode() { 621 return mName != null ? mName.hashCode() : 0; 622 } 623 624 @Override 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 637 private void addPermission(Permission permission) { 638 mPermissions.put(permission.getName(), permission); 639 } 640 } 641