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