1 /* 2 * Copyright (C) 2016 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.permissioncontroller.permission.service; 18 19 import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT; 20 import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED; 21 import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED; 22 import static android.content.pm.PackageManager.GET_PERMISSIONS; 23 import static android.permission.PermissionControllerManager.REASON_INSTALLER_POLICY_VIOLATION; 24 import static android.permission.PermissionControllerManager.REASON_MALWARE; 25 import static android.util.Xml.newSerializer; 26 27 import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_ONE_TIME_PERMISSION_REVOKED; 28 29 import static java.nio.charset.StandardCharsets.UTF_8; 30 31 import android.content.Intent; 32 import android.content.pm.PackageInfo; 33 import android.content.pm.PackageManager; 34 import android.os.AsyncTask; 35 import android.os.Handler; 36 import android.os.Looper; 37 import android.os.Process; 38 import android.os.UserHandle; 39 import android.os.UserManager; 40 import android.permission.AdminPermissionControlParams; 41 import android.permission.PermissionManager; 42 import android.permission.RuntimePermissionPresentationInfo; 43 import android.permission.RuntimePermissionUsageInfo; 44 import android.util.ArrayMap; 45 import android.util.ArraySet; 46 import android.util.Log; 47 import android.util.Xml; 48 49 import androidx.annotation.NonNull; 50 import androidx.annotation.Nullable; 51 52 import com.android.permissioncontroller.PermissionControllerProto.PermissionControllerDumpProto; 53 import com.android.permissioncontroller.PermissionControllerStatsLog; 54 import com.android.permissioncontroller.permission.model.AppPermissionGroup; 55 import com.android.permissioncontroller.permission.model.AppPermissions; 56 import com.android.permissioncontroller.permission.model.Permission; 57 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo; 58 import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState; 59 import com.android.permissioncontroller.permission.ui.AutoGrantPermissionsNotifier; 60 import com.android.permissioncontroller.permission.utils.AdminRestrictedPermissionsUtils; 61 import com.android.permissioncontroller.permission.utils.ArrayUtils; 62 import com.android.permissioncontroller.permission.utils.KotlinUtils; 63 import com.android.permissioncontroller.permission.utils.UserSensitiveFlagsUtils; 64 import com.android.permissioncontroller.permission.utils.Utils; 65 import com.android.permissioncontroller.role.model.Role; 66 import com.android.permissioncontroller.role.model.Roles; 67 68 import org.xmlpull.v1.XmlPullParser; 69 import org.xmlpull.v1.XmlSerializer; 70 71 import java.io.FileDescriptor; 72 import java.io.FileOutputStream; 73 import java.io.IOException; 74 import java.io.InputStream; 75 import java.io.OutputStream; 76 import java.io.PrintWriter; 77 import java.nio.charset.StandardCharsets; 78 import java.util.ArrayList; 79 import java.util.Collections; 80 import java.util.List; 81 import java.util.Map; 82 import java.util.Set; 83 import java.util.concurrent.Executor; 84 import java.util.function.Consumer; 85 import java.util.function.IntConsumer; 86 import java.util.stream.Collectors; 87 88 import kotlin.Pair; 89 import kotlinx.coroutines.BuildersKt; 90 import kotlinx.coroutines.GlobalScope; 91 92 /** 93 * Calls from the system into the permission controller. 94 * 95 * All reading methods are called async, and all writing method are called on the AsyncTask single 96 * thread executor so that multiple writes won't override each other concurrently. 97 */ 98 public final class PermissionControllerServiceImpl extends PermissionControllerLifecycleService { 99 private static final String LOG_TAG = PermissionControllerServiceImpl.class.getSimpleName(); 100 public static final String ONE_TIME_PERMISSION_REVOKED_REASON = "one-time permission revoked"; 101 private static final int MAX_RETRY_ATTEMPTS = 3; 102 private static final long RETRY_DELAY_MS = 500; 103 104 105 private final PermissionControllerServiceModel mServiceModel = new 106 PermissionControllerServiceModel(this); 107 108 @Override onUnbind(@ullable Intent intent)109 public boolean onUnbind(@Nullable Intent intent) { 110 mServiceModel.removeObservers(); 111 return super.onUnbind(intent); 112 } 113 114 @Override dump(FileDescriptor fd, PrintWriter writer, String[] args)115 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 116 PermissionControllerDumpProto dump; 117 try { 118 dump = BuildersKt.runBlocking( 119 GlobalScope.INSTANCE.getCoroutineContext(), 120 (coroutineScope, continuation) -> mServiceModel.onDump(continuation)); 121 } catch (Exception e) { 122 Log.e(LOG_TAG, "Cannot produce dump", e); 123 return; 124 } 125 126 if (ArrayUtils.contains(args, "--proto")) { 127 try (OutputStream out = new FileOutputStream(fd)) { 128 dump.writeTo(out); 129 } catch (IOException e) { 130 Log.e(LOG_TAG, "Cannot write dump", e); 131 } 132 } else { 133 writer.println(dump.toString()); 134 writer.flush(); 135 } 136 } 137 138 /** 139 * Expand {@code perms} by split permissions for an app with the given targetSDK. 140 * 141 * @param perms The permissions that should be expanded 142 * @param targetSDK The target SDK to expand for 143 * 144 * @return The expanded permissions 145 */ addSplitPermissions(@onNull List<String> perms, int targetSDK)146 private @NonNull ArrayList<String> addSplitPermissions(@NonNull List<String> perms, 147 int targetSDK) { 148 List<PermissionManager.SplitPermissionInfo> splitPerms = 149 getSystemService(PermissionManager.class).getSplitPermissions(); 150 151 // Add split permissions to the request 152 ArrayList<String> expandedPerms = new ArrayList<>(perms); 153 int numReqPerms = perms.size(); 154 for (int reqPermNum = 0; reqPermNum < numReqPerms; reqPermNum++) { 155 String reqPerm = perms.get(reqPermNum); 156 157 int numSplitPerms = splitPerms.size(); 158 for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) { 159 PermissionManager.SplitPermissionInfo splitPerm = splitPerms.get(splitPermNum); 160 161 if (targetSDK < splitPerm.getTargetSdk() 162 && splitPerm.getSplitPermission().equals(reqPerm)) { 163 expandedPerms.addAll(splitPerm.getNewPermissions()); 164 } 165 } 166 } 167 168 return expandedPerms; 169 } 170 171 /** 172 * Get the package info for a package. 173 * 174 * @param pkg The package name 175 * 176 * @return the package info or {@code null} if the package could not be found 177 */ getPkgInfo(@onNull String pkg)178 private @Nullable PackageInfo getPkgInfo(@NonNull String pkg) { 179 try { 180 return getPackageManager().getPackageInfo(pkg, GET_PERMISSIONS); 181 } catch (PackageManager.NameNotFoundException e) { 182 Log.w(LOG_TAG, pkg + " not found", e); 183 return null; 184 } 185 } 186 187 /** 188 * Given a set of permissions, find all permission groups of an app that can be revoked and that 189 * contain any of the permissions. 190 * 191 * @param permissions The permissions to revoke 192 * @param appPerms The {@link AppPermissions} for the app that is currently investigated 193 * 194 * @return The groups to revoke 195 */ getRevocableGroupsForPermissions( @onNull ArrayList<String> permissions, @NonNull AppPermissions appPerms)196 private @NonNull ArrayList<AppPermissionGroup> getRevocableGroupsForPermissions( 197 @NonNull ArrayList<String> permissions, @NonNull AppPermissions appPerms) { 198 ArrayList<AppPermissionGroup> groupsToRevoke = new ArrayList<>(); 199 int numGroups = appPerms.getPermissionGroups().size(); 200 for (int groupNum = 0; groupNum < numGroups; groupNum++) { 201 AppPermissionGroup group = appPerms.getPermissionGroups().get(groupNum); 202 203 // Do not override fixed permissions 204 if (group.isPolicyFixed() || group.isSystemFixed()) { 205 continue; 206 } 207 208 int numPerms = permissions.size(); 209 for (int permNum = 0; permNum < numPerms; permNum++) { 210 String reqPerm = permissions.get(permNum); 211 212 if (group.hasPermission(reqPerm)) { 213 groupsToRevoke.add(group); 214 215 // If fg permissions get revoked also revoke bg permissions as bg 216 // permissions require fg permissions. 217 AppPermissionGroup bgPerms = group.getBackgroundPermissions(); 218 if (bgPerms != null) { 219 groupsToRevoke.add(bgPerms); 220 } 221 } else { 222 AppPermissionGroup bgPerms = group.getBackgroundPermissions(); 223 if (bgPerms != null && bgPerms.hasPermission(reqPerm)) { 224 groupsToRevoke.add(bgPerms); 225 } 226 } 227 } 228 } 229 230 return groupsToRevoke; 231 } 232 233 /** 234 * Revoke all permissions of some groups. 235 * 236 * @param groupsToRevoke The groups 237 * 238 * @return The permissions that were revoked 239 */ revokePermissionGroups( @onNull ArrayList<AppPermissionGroup> groupsToRevoke)240 private @NonNull ArrayList<String> revokePermissionGroups( 241 @NonNull ArrayList<AppPermissionGroup> groupsToRevoke) { 242 ArrayList<String> revokedPerms = new ArrayList<>(); 243 244 int numGroupsToRevoke = groupsToRevoke.size(); 245 for (int groupsToRevokeNum = 0; groupsToRevokeNum < numGroupsToRevoke; 246 groupsToRevokeNum++) { 247 AppPermissionGroup group = groupsToRevoke.get(groupsToRevokeNum); 248 ArrayList<Permission> perms = group.getPermissions(); 249 250 // Mark the permissions as reviewed as we don't want to use to accidentally grant 251 // the permission during review 252 group.unsetReviewRequired(); 253 254 int numPerms = perms.size(); 255 for (int permNum = 0; permNum < numPerms; permNum++) { 256 Permission perm = perms.get(permNum); 257 258 // Only count individual permissions that are actually revoked 259 if (perm.isGrantedIncludingAppOp()) { 260 revokedPerms.add(perm.getName()); 261 } 262 } 263 264 group.revokeRuntimePermissions(false); 265 } 266 267 return revokedPerms; 268 } 269 270 @Override onRevokeRuntimePermissions(@onNull Map<String, List<String>> request, boolean doDryRun, int reason, @NonNull String callerPackageName, @NonNull Consumer<Map<String, List<String>>> callback)271 public void onRevokeRuntimePermissions(@NonNull Map<String, List<String>> request, 272 boolean doDryRun, int reason, @NonNull String callerPackageName, 273 @NonNull Consumer<Map<String, List<String>>> callback) { 274 AsyncTask.execute(() -> callback.accept(onRevokeRuntimePermissions(request, doDryRun, 275 reason, callerPackageName))); 276 } 277 onRevokeRuntimePermissions( @onNull Map<String, List<String>> request, boolean doDryRun, int reason, @NonNull String callerPackageName)278 private @NonNull Map<String, List<String>> onRevokeRuntimePermissions( 279 @NonNull Map<String, List<String>> request, boolean doDryRun, 280 int reason, @NonNull String callerPackageName) { 281 // The reason parameter is not checked by platform code as this might need to be updated 282 // async to platform releases. 283 if (reason != REASON_MALWARE && reason != REASON_INSTALLER_POLICY_VIOLATION) { 284 Log.e(LOG_TAG, "Invalid reason " + reason); 285 return Collections.emptyMap(); 286 } 287 288 PackageManager pm = getPackageManager(); 289 290 PackageInfo callerPkgInfo = getPkgInfo(callerPackageName); 291 if (callerPkgInfo == null) { 292 return Collections.emptyMap(); 293 } 294 int callerTargetSdk = callerPkgInfo.applicationInfo.targetSdkVersion; 295 296 Map<String, List<String>> actuallyRevokedPerms = new ArrayMap<>(); 297 ArrayList<AppPermissions> appsWithRevokedPerms = new ArrayList<>(); 298 299 for (Map.Entry<String, List<String>> appRequest : request.entrySet()) { 300 PackageInfo requestedPkgInfo = getPkgInfo(appRequest.getKey()); 301 if (requestedPkgInfo == null) { 302 continue; 303 } 304 305 // Permissions are per UID. Hence permissions will be removed from all apps sharing an 306 // UID. 307 String[] pkgNames = pm.getPackagesForUid(requestedPkgInfo.applicationInfo.uid); 308 if (pkgNames == null) { 309 continue; 310 } 311 312 int numPkgNames = pkgNames.length; 313 for (int pkgNum = 0; pkgNum < numPkgNames; pkgNum++) { 314 String pkgName = pkgNames[pkgNum]; 315 316 PackageInfo pkgInfo = getPkgInfo(pkgName); 317 if (pkgInfo == null) { 318 continue; 319 } 320 321 // If the revocation is because of a market policy violation only the installer can 322 // revoke the permissions. 323 if (reason == REASON_INSTALLER_POLICY_VIOLATION 324 && !callerPackageName.equals(pm.getInstallerPackageName(pkgName))) { 325 Log.i(LOG_TAG, "Ignoring " + pkgName + " as it is not installed by " 326 + callerPackageName); 327 continue; 328 } 329 330 // In rare cases the caller does not know about the permissions that have been added 331 // due to splits. Hence add them now. 332 ArrayList<String> expandedPerms = addSplitPermissions(appRequest.getValue(), 333 callerTargetSdk); 334 335 AppPermissions appPerms = new AppPermissions(this, pkgInfo, false, true, null); 336 337 // First find the groups that should be revoked and then revoke all permissions of 338 // these groups. This is needed as soon as a single permission in the group is 339 // granted, all other permissions get auto-granted on request. 340 ArrayList<AppPermissionGroup> groupsToRevoke = getRevocableGroupsForPermissions( 341 expandedPerms, appPerms); 342 ArrayList<String> revokedPerms = revokePermissionGroups(groupsToRevoke); 343 344 // In racy conditions the group might not have had granted permissions anymore 345 if (!revokedPerms.isEmpty()) { 346 actuallyRevokedPerms.put(pkgName, revokedPerms); 347 appsWithRevokedPerms.add(appPerms); 348 } 349 } 350 } 351 352 // Persist changes after we computed everything to remove 353 // This is necessary as we would otherwise only look at the first app of a shared UID. 354 if (!doDryRun) { 355 int numChangedApps = appsWithRevokedPerms.size(); 356 for (int i = 0; i < numChangedApps; i++) { 357 appsWithRevokedPerms.get(i).persistChanges(true); 358 } 359 } 360 361 return actuallyRevokedPerms; 362 } 363 364 @Override onGetRuntimePermissionsBackup(@onNull UserHandle user, @NonNull OutputStream backup, @NonNull Runnable callback)365 public void onGetRuntimePermissionsBackup(@NonNull UserHandle user, 366 @NonNull OutputStream backup, @NonNull Runnable callback) { 367 AsyncTask.execute(() -> { 368 onGetRuntimePermissionsBackup(user, backup); 369 callback.run(); 370 }); 371 } 372 onGetRuntimePermissionsBackup(@onNull UserHandle user, @NonNull OutputStream backup)373 private void onGetRuntimePermissionsBackup(@NonNull UserHandle user, 374 @NonNull OutputStream backup) { 375 BackupHelper backupHelper = new BackupHelper(this, user); 376 377 try { 378 XmlSerializer serializer = newSerializer(); 379 serializer.setOutput(backup, UTF_8.name()); 380 381 backupHelper.writeState(serializer); 382 serializer.flush(); 383 } catch (Exception e) { 384 Log.e(LOG_TAG, "Unable to write permissions backup", e); 385 } 386 } 387 388 @Override onStageAndApplyRuntimePermissionsBackup(@onNull UserHandle user, @NonNull InputStream backup, @NonNull Runnable callback)389 public void onStageAndApplyRuntimePermissionsBackup(@NonNull UserHandle user, 390 @NonNull InputStream backup, @NonNull Runnable callback) { 391 AsyncTask.execute(() -> { 392 onRestoreRuntimePermissionsBackup(user, backup); 393 callback.run(); 394 }); 395 } 396 onRestoreRuntimePermissionsBackup(@onNull UserHandle user, @NonNull InputStream backup)397 private void onRestoreRuntimePermissionsBackup(@NonNull UserHandle user, 398 @NonNull InputStream backup) { 399 try { 400 XmlPullParser parser = Xml.newPullParser(); 401 parser.setInput(backup, StandardCharsets.UTF_8.name()); 402 403 new BackupHelper(this, user).restoreState(parser); 404 } catch (Exception e) { 405 Log.e(LOG_TAG, "Exception restoring permissions: " + e.getMessage()); 406 } 407 } 408 409 @Override onApplyStagedRuntimePermissionBackup(@onNull String packageName, @NonNull UserHandle user, @NonNull Consumer<Boolean> callback)410 public void onApplyStagedRuntimePermissionBackup(@NonNull String packageName, 411 @NonNull UserHandle user, @NonNull Consumer<Boolean> callback) { 412 AsyncTask.execute(() -> callback.accept( 413 onRestoreDelayedRuntimePermissionsBackup(packageName, user))); 414 } 415 onRestoreDelayedRuntimePermissionsBackup(@onNull String packageName, @NonNull UserHandle user)416 private boolean onRestoreDelayedRuntimePermissionsBackup(@NonNull String packageName, 417 @NonNull UserHandle user) { 418 try { 419 return new BackupHelper(this, user).restoreDelayedState(packageName); 420 } catch (Exception e) { 421 Log.e(LOG_TAG, "Exception restoring delayed permissions: " + e.getMessage()); 422 return false; 423 } 424 } 425 426 @Override onGetAppPermissions(@onNull String packageName, @NonNull Consumer<List<RuntimePermissionPresentationInfo>> callback)427 public void onGetAppPermissions(@NonNull String packageName, 428 @NonNull Consumer<List<RuntimePermissionPresentationInfo>> callback) { 429 mServiceModel.onGetAppPermissions(packageName, (groupUiInfos) -> { 430 List<RuntimePermissionPresentationInfo> permissions = new ArrayList<>(); 431 432 for (Pair<String, AppPermGroupUiInfo> groupNameAndUiInfo : groupUiInfos) { 433 String groupName = groupNameAndUiInfo.getFirst(); 434 AppPermGroupUiInfo uiInfo = groupNameAndUiInfo.getSecond(); 435 boolean isPlatform = Utils.getPlatformPermissionGroups().contains(groupName); 436 CharSequence label = KotlinUtils.INSTANCE.getPermGroupLabel(this, groupName); 437 438 RuntimePermissionPresentationInfo permission = 439 new RuntimePermissionPresentationInfo(label, 440 uiInfo.getPermGrantState() != PermGrantState.PERMS_DENIED 441 && uiInfo.getPermGrantState() != PermGrantState.PERMS_ASK, 442 isPlatform); 443 permissions.add(permission); 444 } 445 callback.accept(permissions); 446 }); 447 } 448 449 @Override onRevokeRuntimePermission(@onNull String packageName, @NonNull String permissionName, @NonNull Runnable callback)450 public void onRevokeRuntimePermission(@NonNull String packageName, 451 @NonNull String permissionName, @NonNull Runnable callback) { 452 AsyncTask.execute(() -> { 453 onRevokeRuntimePermission(packageName, permissionName); 454 callback.run(); 455 }); 456 } 457 onRevokeRuntimePermission(@onNull String packageName, @NonNull String permissionName)458 private void onRevokeRuntimePermission(@NonNull String packageName, 459 @NonNull String permissionName) { 460 try { 461 final PackageInfo packageInfo = getPackageManager().getPackageInfo(packageName, 462 GET_PERMISSIONS); 463 final AppPermissions appPermissions = new AppPermissions(this, packageInfo, false, 464 null); 465 466 final AppPermissionGroup appPermissionGroup = appPermissions.getGroupForPermission( 467 permissionName); 468 469 if (appPermissionGroup != null) { 470 appPermissionGroup.revokeRuntimePermissions(false); 471 } 472 } catch (PackageManager.NameNotFoundException e) { 473 Log.e(LOG_TAG, "Error getting package:" + packageName, e); 474 } 475 } 476 477 @Override onCountPermissionApps(@onNull List<String> permissionNames, int flags, @NonNull IntConsumer callback)478 public void onCountPermissionApps(@NonNull List<String> permissionNames, int flags, 479 @NonNull IntConsumer callback) { 480 // There is no data processing needed, so we just directly pass the result onto the callback 481 mServiceModel.onCountPermissionAppsLiveData(permissionNames, flags, 482 callback); 483 } 484 485 /** 486 * Deprecated api call, only returns null. 487 */ 488 @Override 489 @Deprecated onGetPermissionUsages(boolean countSystem, long numMillis, @NonNull Consumer<List<RuntimePermissionUsageInfo>> callback)490 public void onGetPermissionUsages(boolean countSystem, long numMillis, 491 @NonNull Consumer<List<RuntimePermissionUsageInfo>> callback) { 492 callback.accept(null); 493 } 494 495 @Override onSetRuntimePermissionGrantStateByDeviceAdmin(@onNull String callerPackageName, @NonNull String packageName, @NonNull String unexpandedPermission, int grantState, @NonNull Consumer<Boolean> callback)496 public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName, 497 @NonNull String packageName, @NonNull String unexpandedPermission, int grantState, 498 @NonNull Consumer<Boolean> callback) { 499 AsyncTask.execute(() -> callback.accept(onSetRuntimePermissionGrantStateByDeviceAdmin( 500 callerPackageName, packageName, unexpandedPermission, grantState, true))); 501 } 502 503 /** 504 * Admin control based on params. 505 */ 506 @Override onSetRuntimePermissionGrantStateByDeviceAdmin( @onNull String callerPackageName, @NonNull AdminPermissionControlParams params, @NonNull Consumer<Boolean> callback)507 public void onSetRuntimePermissionGrantStateByDeviceAdmin( 508 @NonNull String callerPackageName, @NonNull AdminPermissionControlParams params, 509 @NonNull Consumer<Boolean> callback) { 510 AsyncTask.execute(() -> callback.accept(onSetRuntimePermissionGrantStateByDeviceAdmin( 511 callerPackageName, params.getGranteePackageName(), params.getPermission(), 512 params.getGrantState(), params.canAdminGrantSensorsPermissions()))); 513 } 514 onSetRuntimePermissionGrantStateByDeviceAdmin(@onNull String callerPackageName, @NonNull String packageName, @NonNull String unexpandedPermission, int grantState, boolean canAdminGrantSensorsPermissions)515 private boolean onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName, 516 @NonNull String packageName, @NonNull String unexpandedPermission, int grantState, 517 boolean canAdminGrantSensorsPermissions) { 518 PackageInfo callerPkgInfo = getPkgInfo(callerPackageName); 519 if (callerPkgInfo == null) { 520 Log.w(LOG_TAG, "Cannot fix " + unexpandedPermission + " as admin " 521 + callerPackageName + " cannot be found"); 522 return false; 523 } 524 525 PackageInfo pkgInfo = getPkgInfo(packageName); 526 if (pkgInfo == null) { 527 Log.w(LOG_TAG, "Cannot fix " + unexpandedPermission + " as " + packageName 528 + " cannot be found"); 529 return false; 530 } 531 532 ArrayList<String> expandedPermissions = addSplitPermissions( 533 Collections.singletonList(unexpandedPermission), 534 callerPkgInfo.applicationInfo.targetSdkVersion); 535 536 AppPermissions app = new AppPermissions(this, pkgInfo, false, true, null); 537 AutoGrantPermissionsNotifier autoGrantPermissionsNotifier = 538 new AutoGrantPermissionsNotifier(this, pkgInfo); 539 540 final boolean isManagedProfile = getSystemService(UserManager.class).isManagedProfile(); 541 542 int numPerms = expandedPermissions.size(); 543 for (int i = 0; i < numPerms; i++) { 544 String permName = expandedPermissions.get(i); 545 AppPermissionGroup group = app.getGroupForPermission(permName); 546 if (group == null || group.isSystemFixed()) { 547 continue; 548 } 549 550 Permission perm = group.getPermission(permName); 551 if (perm == null) { 552 continue; 553 } 554 555 switch (grantState) { 556 case PERMISSION_GRANT_STATE_GRANTED: 557 if (AdminRestrictedPermissionsUtils.mayAdminGrantPermission(perm.getName(), 558 canAdminGrantSensorsPermissions, isManagedProfile)) { 559 perm.setPolicyFixed(true); 560 group.grantRuntimePermissions(false, false, new String[]{permName}); 561 autoGrantPermissionsNotifier.onPermissionAutoGranted(permName); 562 } else { 563 // similar to PERMISSION_GRANT_STATE_DEFAULT 564 perm.setPolicyFixed(false); 565 } 566 break; 567 case PERMISSION_GRANT_STATE_DENIED: 568 perm.setPolicyFixed(true); 569 group.revokeRuntimePermissions(false, new String[]{permName}); 570 break; 571 case PERMISSION_GRANT_STATE_DEFAULT: 572 perm.setPolicyFixed(false); 573 break; 574 default: 575 return false; 576 } 577 } 578 579 app.persistChanges(grantState == PERMISSION_GRANT_STATE_DENIED 580 || !callerPackageName.equals(packageName)); 581 autoGrantPermissionsNotifier.notifyOfAutoGrantPermissions(false); 582 583 return true; 584 } 585 586 @Override onGrantOrUpgradeDefaultRuntimePermissions(@onNull Runnable callback)587 public void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable callback) { 588 performDefaultPermissionGrants(); 589 RuntimePermissionsUpgradeController.INSTANCE.upgradeIfNeeded(this, () -> { 590 callback.run(); 591 }); 592 } 593 performDefaultPermissionGrants()594 private void performDefaultPermissionGrants() { 595 // TODO: Default permission grants should go here 596 } 597 598 @Override onUpdateUserSensitivePermissionFlags(int uid, Executor executor, Runnable callback)599 public void onUpdateUserSensitivePermissionFlags(int uid, Executor executor, 600 Runnable callback) { 601 onUpdateUserSensistivePermissionFlagsWithRetry(uid, executor, callback, 0); 602 } 603 onUpdateUserSensistivePermissionFlagsWithRetry(int uid, Executor executor, Runnable callback, int numAttempts)604 private void onUpdateUserSensistivePermissionFlagsWithRetry(int uid, Executor executor, 605 Runnable callback, int numAttempts) { 606 String idString = uid == Process.INVALID_UID 607 ? "user " + Process.myUserHandle().getIdentifier() : "uid " + uid; 608 try { 609 Log.i(LOG_TAG, "Updating user sensitive for " + idString); 610 if (uid == Process.INVALID_UID) { 611 UserSensitiveFlagsUtils.updateUserSensitiveForUser(Process.myUserHandle(), 612 () -> executor.execute(callback)); 613 } else { 614 UserSensitiveFlagsUtils.updateUserSensitiveForUid(uid, 615 () -> executor.execute(callback)); 616 } 617 } catch (Exception e) { 618 // We specifically want to catch DeadSystemExceptions, but cannot explicitly request 619 // them, as it results in a compiler error 620 Log.w(LOG_TAG, "Failed to complete user sensitive update for " + idString 621 + ", attempt number " + (numAttempts + 1) + " of " + MAX_RETRY_ATTEMPTS, e); 622 if (numAttempts == MAX_RETRY_ATTEMPTS) { 623 throw e; 624 } else { 625 int attempts = numAttempts + 1; 626 Handler h = new Handler(Looper.getMainLooper()); 627 h.postDelayed(() -> onUpdateUserSensistivePermissionFlagsWithRetry(uid, 628 executor, callback, attempts), RETRY_DELAY_MS); 629 } 630 } 631 632 } 633 634 @Override onOneTimePermissionSessionTimeout(@onNull String packageName)635 public void onOneTimePermissionSessionTimeout(@NonNull String packageName) { 636 PackageManager pm = getPackageManager(); 637 PackageInfo packageInfo; 638 int uid; 639 try { 640 packageInfo = pm.getPackageInfo(packageName, GET_PERMISSIONS); 641 uid = pm.getPackageUid(packageName, 0); 642 } catch (PackageManager.NameNotFoundException e) { 643 throw new RuntimeException(e); 644 } 645 646 String[] permissions = packageInfo.requestedPermissions; 647 if (permissions == null) { 648 return; 649 } 650 651 Set<AppPermissionGroup> groups = new ArraySet<>(); 652 for (String permission : permissions) { 653 AppPermissionGroup group = AppPermissionGroup.create(this, packageInfo, permission, 654 true); 655 if (group != null && group.isOneTime()) { 656 groups.add(group); 657 } 658 } 659 long requestId = Utils.getValidSessionId(); 660 for (AppPermissionGroup group : groups) { 661 if (group.areRuntimePermissionsGranted()) { 662 logOneTimeSessionRevoke(packageName, uid, group, requestId); 663 // Revoke only one time granted permissions if not all 664 List<String> oneTimeGrantedPermissions = group.getPermissions().stream() 665 .filter(Permission::isOneTime).filter(Permission::isGranted) 666 .map(Permission::getName).collect(Collectors.toList()); 667 if (group.getPermissions().size() == oneTimeGrantedPermissions.size()) { 668 group.revokeRuntimePermissions(false); 669 } else { 670 group.revokeRuntimePermissions(false, 671 oneTimeGrantedPermissions.toArray(new String[0])); 672 } 673 } 674 group.setUserSet(false); 675 group.persistChanges(false, ONE_TIME_PERMISSION_REVOKED_REASON); 676 } 677 } 678 logOneTimeSessionRevoke(@onNull String packageName, int uid, AppPermissionGroup group, long requestId)679 private void logOneTimeSessionRevoke(@NonNull String packageName, int uid, 680 AppPermissionGroup group, long requestId) { 681 // used to keep lines below 100 chars 682 int r = PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__AUTO_ONE_TIME_PERMISSION_REVOKED; 683 684 for (Permission permission : group.getPermissions()) { 685 if (permission.isGranted()) { 686 String permName = permission.getName(); 687 Log.v(LOG_TAG, 688 "Permission grant result requestId=" + requestId + " callingUid=" 689 + uid + " callingPackage=" + packageName + " permission=" 690 + permName + " isImplicit=false" + " result=" + r); 691 692 PermissionControllerStatsLog.write( 693 PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED, 694 requestId, uid, packageName, permName, false, r); 695 } 696 } 697 } 698 699 @Override getPrivilegesDescriptionStringForProfile(@onNull String deviceProfileName)700 public String getPrivilegesDescriptionStringForProfile(@NonNull String deviceProfileName) { 701 Role role = Roles.get(this).get(deviceProfileName); 702 if (role == null) { 703 throw new IllegalArgumentException("No such role: " + deviceProfileName); 704 } 705 return getString(role.getDescriptionResource(), "APP_NAME"); 706 } 707 708 @Override onGetPlatformPermissionsForGroup(@onNull String permissionGroupName, @NonNull Consumer<List<String>> callback)709 public void onGetPlatformPermissionsForGroup(@NonNull String permissionGroupName, 710 @NonNull Consumer<List<String>> callback) { 711 callback.accept(Utils.getPlatformPermissionNamesOfGroup(permissionGroupName)); 712 } 713 714 @Override onGetGroupOfPlatformPermission(@onNull String permissionName, @NonNull Consumer<String> callback)715 public void onGetGroupOfPlatformPermission(@NonNull String permissionName, 716 @NonNull Consumer<String> callback) { 717 callback.accept(Utils.getGroupOfPlatformPermission(permissionName)); 718 } 719 } 720