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 create(Context context, PackageInfo packageInfo, String permissionName)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 create(Context context, PackageInfo packageInfo, PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos, UserHandle userHandle)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 loadGroupDescription(Context context, PackageItemInfo group)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 AppPermissionGroup(Context context, PackageInfo packageInfo, String name, String declaringPackage, CharSequence label, CharSequence description, String iconPkg, int iconResId, UserHandle userHandle)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 hasRuntimePermission()208 public boolean hasRuntimePermission() { 209 return mAppSupportsRuntimePermissions; 210 } 211 212 hasGrantedByDefaultPermission()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 hasAppOpPermission()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 getApp()235 public PackageInfo getApp() { 236 return mPackageInfo; 237 } 238 getName()239 public String getName() { 240 return mName; 241 } 242 getDeclaringPackage()243 public String getDeclaringPackage() { 244 return mDeclaringPackage; 245 } 246 getIconPkg()247 public String getIconPkg() { 248 return mIconPkg; 249 } 250 getIconResId()251 public int getIconResId() { 252 return mIconResId; 253 } 254 getLabel()255 public CharSequence getLabel() { 256 return mLabel; 257 } 258 getDescription()259 public CharSequence getDescription() { 260 return mDescription; 261 } 262 hasPermission(String permission)263 public boolean hasPermission(String permission) { 264 return mPermissions.get(permission) != null; 265 } 266 areRuntimePermissionsGranted()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 grantRuntimePermissions(boolean fixedByTheUser)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 revokeRuntimePermissions(boolean fixedByTheUser)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 setPolicyFixed()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 getPermissions()480 public List<Permission> getPermissions() { 481 return new ArrayList<>(mPermissions.values()); 482 } 483 getFlags()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 isUserFixed()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 isPolicyFixed()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 isUserSet()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 isSystemFixed()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 compareTo(AppPermissionGroup another)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 equals(Object obj)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 hashCode()577 public int hashCode() { 578 return mName != null ? mName.hashCode() : 0; 579 } 580 581 @Override toString()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 addPermission(Permission permission)594 private void addPermission(Permission permission) { 595 mPermissions.put(permission.getName(), permission); 596 } 597 } 598