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.packageinstaller.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.COUNT_ONLY_WHEN_GRANTED; 24 import static android.permission.PermissionControllerManager.COUNT_WHEN_SYSTEM; 25 import static android.permission.PermissionControllerManager.REASON_INSTALLER_POLICY_VIOLATION; 26 import static android.permission.PermissionControllerManager.REASON_MALWARE; 27 import static android.util.Xml.newSerializer; 28 29 import static com.android.packageinstaller.permission.utils.Utils.shouldShowPermission; 30 31 import static java.nio.charset.StandardCharsets.UTF_8; 32 33 import android.content.Context; 34 import android.content.pm.PackageInfo; 35 import android.content.pm.PackageManager; 36 import android.os.AsyncTask; 37 import android.os.UserHandle; 38 import android.permission.PermissionControllerService; 39 import android.permission.PermissionManager; 40 import android.permission.RuntimePermissionPresentationInfo; 41 import android.permission.RuntimePermissionUsageInfo; 42 import android.util.ArrayMap; 43 import android.util.Log; 44 import android.util.Xml; 45 46 import androidx.annotation.NonNull; 47 import androidx.annotation.Nullable; 48 49 import com.android.packageinstaller.permission.model.AppPermissionGroup; 50 import com.android.packageinstaller.permission.model.AppPermissions; 51 import com.android.packageinstaller.permission.model.Permission; 52 import com.android.packageinstaller.permission.utils.Utils; 53 54 import org.xmlpull.v1.XmlPullParser; 55 import org.xmlpull.v1.XmlSerializer; 56 57 import java.io.InputStream; 58 import java.io.OutputStream; 59 import java.nio.charset.StandardCharsets; 60 import java.util.ArrayList; 61 import java.util.Collections; 62 import java.util.List; 63 import java.util.Map; 64 import java.util.function.Consumer; 65 import java.util.function.IntConsumer; 66 67 /** 68 * Calls from the system into the permission controller. 69 * 70 * All methods are called async beside the backup related method. For these we force to use the 71 * async-task single thread executor so that multiple parallel backups don't override the delayed 72 * the backup state racily. 73 */ 74 public final class PermissionControllerServiceImpl extends PermissionControllerService { 75 private static final String LOG_TAG = PermissionControllerServiceImpl.class.getSimpleName(); 76 77 /** 78 * Expand {@code perms} by split permissions for an app with the given targetSDK. 79 * 80 * @param perms The permissions that should be expanded 81 * @param targetSDK The target SDK to expand for 82 * 83 * @return The expanded permissions 84 */ addSplitPermissions(@onNull List<String> perms, int targetSDK)85 private @NonNull ArrayList<String> addSplitPermissions(@NonNull List<String> perms, 86 int targetSDK) { 87 List<PermissionManager.SplitPermissionInfo> splitPerms = 88 getSystemService(PermissionManager.class).getSplitPermissions(); 89 90 // Add split permissions to the request 91 ArrayList<String> expandedPerms = new ArrayList<>(perms); 92 int numReqPerms = perms.size(); 93 for (int reqPermNum = 0; reqPermNum < numReqPerms; reqPermNum++) { 94 String reqPerm = perms.get(reqPermNum); 95 96 int numSplitPerms = splitPerms.size(); 97 for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) { 98 PermissionManager.SplitPermissionInfo splitPerm = splitPerms.get(splitPermNum); 99 100 if (targetSDK < splitPerm.getTargetSdk() 101 && splitPerm.getSplitPermission().equals(reqPerm)) { 102 expandedPerms.addAll(splitPerm.getNewPermissions()); 103 } 104 } 105 } 106 107 return expandedPerms; 108 } 109 110 /** 111 * Get the package info for a package. 112 * 113 * @param pkg The package name 114 * 115 * @return the package info or {@code null} if the package could not be found 116 */ getPkgInfo(@onNull String pkg)117 private @Nullable PackageInfo getPkgInfo(@NonNull String pkg) { 118 try { 119 return getPackageManager().getPackageInfo(pkg, GET_PERMISSIONS); 120 } catch (PackageManager.NameNotFoundException e) { 121 Log.w(LOG_TAG, pkg + " not found", e); 122 return null; 123 } 124 } 125 126 /** 127 * Given a set of permissions, find all permission groups of an app that can be revoked and that 128 * contain any of the permissions. 129 * 130 * @param permissions The permissions to revoke 131 * @param appPerms The {@link AppPermissions} for the app that is currently investigated 132 * 133 * @return The groups to revoke 134 */ getRevocableGroupsForPermissions( @onNull ArrayList<String> permissions, @NonNull AppPermissions appPerms)135 private @NonNull ArrayList<AppPermissionGroup> getRevocableGroupsForPermissions( 136 @NonNull ArrayList<String> permissions, @NonNull AppPermissions appPerms) { 137 ArrayList<AppPermissionGroup> groupsToRevoke = new ArrayList<>(); 138 int numGroups = appPerms.getPermissionGroups().size(); 139 for (int groupNum = 0; groupNum < numGroups; groupNum++) { 140 AppPermissionGroup group = appPerms.getPermissionGroups().get(groupNum); 141 142 // Do not override fixed permissions 143 if (group.isPolicyFixed() || group.isSystemFixed()) { 144 continue; 145 } 146 147 int numPerms = permissions.size(); 148 for (int permNum = 0; permNum < numPerms; permNum++) { 149 String reqPerm = permissions.get(permNum); 150 151 if (group.hasPermission(reqPerm)) { 152 groupsToRevoke.add(group); 153 154 // If fg permissions get revoked also revoke bg permissions as bg 155 // permissions require fg permissions. 156 AppPermissionGroup bgPerms = group.getBackgroundPermissions(); 157 if (bgPerms != null) { 158 groupsToRevoke.add(bgPerms); 159 } 160 } else { 161 AppPermissionGroup bgPerms = group.getBackgroundPermissions(); 162 if (bgPerms != null && bgPerms.hasPermission(reqPerm)) { 163 groupsToRevoke.add(bgPerms); 164 } 165 } 166 } 167 } 168 169 return groupsToRevoke; 170 } 171 172 /** 173 * Revoke all permissions of some groups. 174 * 175 * @param groupsToRevoke The groups 176 * 177 * @return The permissions that were revoked 178 */ revokePermissionGroups( @onNull ArrayList<AppPermissionGroup> groupsToRevoke)179 private @NonNull ArrayList<String> revokePermissionGroups( 180 @NonNull ArrayList<AppPermissionGroup> groupsToRevoke) { 181 ArrayList<String> revokedPerms = new ArrayList<>(); 182 183 int numGroupsToRevoke = groupsToRevoke.size(); 184 for (int groupsToRevokeNum = 0; groupsToRevokeNum < numGroupsToRevoke; 185 groupsToRevokeNum++) { 186 AppPermissionGroup group = groupsToRevoke.get(groupsToRevokeNum); 187 ArrayList<Permission> perms = group.getPermissions(); 188 189 // Mark the permissions as reviewed as we don't want to use to accidentally grant 190 // the permission during review 191 group.unsetReviewRequired(); 192 193 int numPerms = perms.size(); 194 for (int permNum = 0; permNum < numPerms; permNum++) { 195 Permission perm = perms.get(permNum); 196 197 // Only count individual permissions that are actually revoked 198 if (perm.isGrantedIncludingAppOp()) { 199 revokedPerms.add(perm.getName()); 200 } 201 } 202 203 group.revokeRuntimePermissions(false); 204 } 205 206 return revokedPerms; 207 } 208 209 @Override onRevokeRuntimePermissions(@onNull Map<String, List<String>> request, boolean doDryRun, int reason, @NonNull String callerPackageName, @NonNull Consumer<Map<String, List<String>>> callback)210 public void onRevokeRuntimePermissions(@NonNull Map<String, List<String>> request, 211 boolean doDryRun, int reason, @NonNull String callerPackageName, 212 @NonNull Consumer<Map<String, List<String>>> callback) { 213 AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> callback.accept( 214 onRevokeRuntimePermissions(request, doDryRun, reason, callerPackageName))); 215 } 216 onRevokeRuntimePermissions( @onNull Map<String, List<String>> request, boolean doDryRun, int reason, @NonNull String callerPackageName)217 private @NonNull Map<String, List<String>> onRevokeRuntimePermissions( 218 @NonNull Map<String, List<String>> request, boolean doDryRun, 219 int reason, @NonNull String callerPackageName) { 220 // The reason parameter is not checked by platform code as this might need to be updated 221 // async to platform releases. 222 if (reason != REASON_MALWARE && reason != REASON_INSTALLER_POLICY_VIOLATION) { 223 Log.e(LOG_TAG, "Invalid reason " + reason); 224 return Collections.emptyMap(); 225 } 226 227 PackageManager pm = getPackageManager(); 228 229 PackageInfo callerPkgInfo = getPkgInfo(callerPackageName); 230 if (callerPkgInfo == null) { 231 return Collections.emptyMap(); 232 } 233 int callerTargetSdk = callerPkgInfo.applicationInfo.targetSdkVersion; 234 235 Map<String, List<String>> actuallyRevokedPerms = new ArrayMap<>(); 236 ArrayList<AppPermissions> appsWithRevokedPerms = new ArrayList<>(); 237 238 for (Map.Entry<String, List<String>> appRequest : request.entrySet()) { 239 PackageInfo requestedPkgInfo = getPkgInfo(appRequest.getKey()); 240 if (requestedPkgInfo == null) { 241 continue; 242 } 243 244 // Permissions are per UID. Hence permissions will be removed from all apps sharing an 245 // UID. 246 String[] pkgNames = pm.getPackagesForUid(requestedPkgInfo.applicationInfo.uid); 247 if (pkgNames == null) { 248 continue; 249 } 250 251 int numPkgNames = pkgNames.length; 252 for (int pkgNum = 0; pkgNum < numPkgNames; pkgNum++) { 253 String pkgName = pkgNames[pkgNum]; 254 255 PackageInfo pkgInfo = getPkgInfo(pkgName); 256 if (pkgInfo == null) { 257 continue; 258 } 259 260 // If the revocation is because of a market policy violation only the installer can 261 // revoke the permissions. 262 if (reason == REASON_INSTALLER_POLICY_VIOLATION 263 && !callerPackageName.equals(pm.getInstallerPackageName(pkgName))) { 264 Log.i(LOG_TAG, "Ignoring " + pkgName + " as it is not installed by " 265 + callerPackageName); 266 continue; 267 } 268 269 // In rare cases the caller does not know about the permissions that have been added 270 // due to splits. Hence add them now. 271 ArrayList<String> expandedPerms = addSplitPermissions(appRequest.getValue(), 272 callerTargetSdk); 273 274 AppPermissions appPerms = new AppPermissions(this, pkgInfo, false, true, null); 275 276 // First find the groups that should be revoked and then revoke all permissions of 277 // these groups. This is needed as soon as a single permission in the group is 278 // granted, all other permissions get auto-granted on request. 279 ArrayList<AppPermissionGroup> groupsToRevoke = getRevocableGroupsForPermissions( 280 expandedPerms, appPerms); 281 ArrayList<String> revokedPerms = revokePermissionGroups(groupsToRevoke); 282 283 // In racy conditions the group might not have had granted permissions anymore 284 if (!revokedPerms.isEmpty()) { 285 actuallyRevokedPerms.put(pkgName, revokedPerms); 286 appsWithRevokedPerms.add(appPerms); 287 } 288 } 289 } 290 291 // Persist changes after we computed everything to remove 292 // This is necessary as we would otherwise only look at the first app of a shared UID. 293 if (!doDryRun) { 294 int numChangedApps = appsWithRevokedPerms.size(); 295 for (int i = 0; i < numChangedApps; i++) { 296 appsWithRevokedPerms.get(i).persistChanges(true); 297 } 298 } 299 300 return actuallyRevokedPerms; 301 } 302 303 @Override onGetRuntimePermissionsBackup(@onNull UserHandle user, @NonNull OutputStream backup, @NonNull Runnable callback)304 public void onGetRuntimePermissionsBackup(@NonNull UserHandle user, 305 @NonNull OutputStream backup, @NonNull Runnable callback) { 306 AsyncTask.execute(() -> { 307 onGetRuntimePermissionsBackup(user, backup); 308 callback.run(); 309 }); 310 } 311 onGetRuntimePermissionsBackup(@onNull UserHandle user, @NonNull OutputStream backup)312 private void onGetRuntimePermissionsBackup(@NonNull UserHandle user, 313 @NonNull OutputStream backup) { 314 BackupHelper backupHelper = new BackupHelper(this, user); 315 316 try { 317 XmlSerializer serializer = newSerializer(); 318 serializer.setOutput(backup, UTF_8.name()); 319 320 backupHelper.writeState(serializer); 321 serializer.flush(); 322 } catch (Exception e) { 323 Log.e(LOG_TAG, "Unable to write permissions backup", e); 324 } 325 } 326 327 @Override onRestoreRuntimePermissionsBackup(@onNull UserHandle user, @NonNull InputStream backup, Runnable callback)328 public void onRestoreRuntimePermissionsBackup(@NonNull UserHandle user, 329 @NonNull InputStream backup, Runnable callback) { 330 AsyncTask.execute(() -> { 331 onRestoreRuntimePermissionsBackup(user, backup); 332 callback.run(); 333 }); 334 } 335 onRestoreRuntimePermissionsBackup(@onNull UserHandle user, @NonNull InputStream backup)336 private void onRestoreRuntimePermissionsBackup(@NonNull UserHandle user, 337 @NonNull InputStream backup) { 338 try { 339 XmlPullParser parser = Xml.newPullParser(); 340 parser.setInput(backup, StandardCharsets.UTF_8.name()); 341 342 new BackupHelper(this, user).restoreState(parser); 343 } catch (Exception e) { 344 Log.e(LOG_TAG, "Exception restoring permissions: " + e.getMessage()); 345 } 346 } 347 348 @Override onRestoreDelayedRuntimePermissionsBackup(@onNull String packageName, @NonNull UserHandle user, @NonNull Consumer<Boolean> callback)349 public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String packageName, 350 @NonNull UserHandle user, @NonNull Consumer<Boolean> callback) { 351 AsyncTask.execute(() -> callback.accept( 352 onRestoreDelayedRuntimePermissionsBackup(packageName, user))); 353 } 354 onRestoreDelayedRuntimePermissionsBackup(@onNull String packageName, @NonNull UserHandle user)355 private boolean onRestoreDelayedRuntimePermissionsBackup(@NonNull String packageName, 356 @NonNull UserHandle user) { 357 try { 358 return new BackupHelper(this, user).restoreDelayedState(packageName); 359 } catch (Exception e) { 360 Log.e(LOG_TAG, "Exception restoring delayed permissions: " + e.getMessage()); 361 return false; 362 } 363 } 364 365 @Override onGetAppPermissions(@onNull String packageName, @NonNull Consumer<List<RuntimePermissionPresentationInfo>> callback)366 public void onGetAppPermissions(@NonNull String packageName, 367 @NonNull Consumer<List<RuntimePermissionPresentationInfo>> callback) { 368 AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> callback.accept( 369 onGetAppPermissions(this, packageName))); 370 } 371 372 /** 373 * Implementation of {@link PermissionControllerService#onGetAppPermissions(String)}}. 374 * Called by this class and the legacy implementation. 375 */ onGetAppPermissions( @onNull Context context, @NonNull String packageName)376 static @NonNull List<RuntimePermissionPresentationInfo> onGetAppPermissions( 377 @NonNull Context context, @NonNull String packageName) { 378 final PackageInfo packageInfo; 379 try { 380 packageInfo = context.getPackageManager().getPackageInfo(packageName, GET_PERMISSIONS); 381 } catch (PackageManager.NameNotFoundException e) { 382 Log.e(LOG_TAG, "Error getting package:" + packageName, e); 383 return Collections.emptyList(); 384 } 385 386 List<RuntimePermissionPresentationInfo> permissions = new ArrayList<>(); 387 388 AppPermissions appPermissions = new AppPermissions(context, packageInfo, false, null); 389 for (AppPermissionGroup group : appPermissions.getPermissionGroups()) { 390 if (shouldShowPermission(context, group)) { 391 final boolean granted = group.areRuntimePermissionsGranted(); 392 final boolean standard = Utils.OS_PKG.equals(group.getDeclaringPackage()); 393 RuntimePermissionPresentationInfo permission = 394 new RuntimePermissionPresentationInfo(group.getLabel(), 395 granted, standard); 396 permissions.add(permission); 397 } 398 } 399 400 return permissions; 401 } 402 403 @Override onRevokeRuntimePermission(@onNull String packageName, @NonNull String permissionName, @NonNull Runnable callback)404 public void onRevokeRuntimePermission(@NonNull String packageName, 405 @NonNull String permissionName, @NonNull Runnable callback) { 406 AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { 407 onRevokeRuntimePermission(packageName, permissionName); 408 callback.run(); 409 }); 410 } 411 onRevokeRuntimePermission(@onNull String packageName, @NonNull String permissionName)412 private void onRevokeRuntimePermission(@NonNull String packageName, 413 @NonNull String permissionName) { 414 try { 415 final PackageInfo packageInfo = getPackageManager().getPackageInfo(packageName, 416 GET_PERMISSIONS); 417 final AppPermissions appPermissions = new AppPermissions(this, packageInfo, false, 418 null); 419 420 final AppPermissionGroup appPermissionGroup = appPermissions.getGroupForPermission( 421 permissionName); 422 423 if (appPermissionGroup != null) { 424 appPermissionGroup.revokeRuntimePermissions(false); 425 } 426 } catch (PackageManager.NameNotFoundException e) { 427 Log.e(LOG_TAG, "Error getting package:" + packageName, e); 428 } 429 } 430 431 @Override onCountPermissionApps(@onNull List<String> permissionNames, int flags, @NonNull IntConsumer callback)432 public void onCountPermissionApps(@NonNull List<String> permissionNames, int flags, 433 @NonNull IntConsumer callback) { 434 AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> callback.accept( 435 onCountPermissionApps(permissionNames, flags))); 436 } 437 onCountPermissionApps(@onNull List<String> permissionNames, int flags)438 private int onCountPermissionApps(@NonNull List<String> permissionNames, int flags) { 439 boolean countSystem = (flags & COUNT_WHEN_SYSTEM) != 0; 440 boolean countOnlyGranted = (flags & COUNT_ONLY_WHEN_GRANTED) != 0; 441 442 List<PackageInfo> pkgs = getPackageManager().getInstalledPackages(GET_PERMISSIONS); 443 444 int numApps = 0; 445 446 int numPkgs = pkgs.size(); 447 for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) { 448 PackageInfo pkg = pkgs.get(pkgNum); 449 450 int numPerms = permissionNames.size(); 451 for (int permNum = 0; permNum < numPerms; permNum++) { 452 String perm = permissionNames.get(permNum); 453 454 AppPermissionGroup group = AppPermissionGroup.create(this, pkg, 455 permissionNames.get(permNum), true); 456 if (group == null || !shouldShowPermission(this, group)) { 457 continue; 458 } 459 460 AppPermissionGroup subGroup = null; 461 if (group.hasPermission(perm)) { 462 subGroup = group; 463 } else { 464 AppPermissionGroup bgGroup = group.getBackgroundPermissions(); 465 if (bgGroup != null && bgGroup.hasPermission(perm)) { 466 subGroup = bgGroup; 467 } 468 } 469 470 if (subGroup != null) { 471 if (!countSystem && !subGroup.isUserSensitive()) { 472 continue; 473 } 474 475 if (!countOnlyGranted || subGroup.areRuntimePermissionsGranted()) { 476 // The permission might not be granted, but some permissions of the group 477 // are granted. In this case the permission is granted silently when the app 478 // asks for it. 479 // Hence this is as-good-as-granted and we count it. 480 numApps++; 481 break; 482 } 483 } 484 } 485 } 486 487 return numApps; 488 } 489 490 @Override onGetPermissionUsages(boolean countSystem, long numMillis, @NonNull Consumer<List<RuntimePermissionUsageInfo>> callback)491 public void onGetPermissionUsages(boolean countSystem, long numMillis, 492 @NonNull Consumer<List<RuntimePermissionUsageInfo>> callback) { 493 AsyncTask.THREAD_POOL_EXECUTOR.execute( 494 () -> callback.accept(onGetPermissionUsages(countSystem, numMillis))); 495 } 496 onGetPermissionUsages( boolean countSystem, long numMillis)497 private @NonNull List<RuntimePermissionUsageInfo> onGetPermissionUsages( 498 boolean countSystem, long numMillis) { 499 return Collections.emptyList(); 500 } 501 502 @Override onSetRuntimePermissionGrantStateByDeviceAdmin(@onNull String callerPackageName, @NonNull String packageName, @NonNull String unexpandedPermission, int grantState, @NonNull Consumer<Boolean> callback)503 public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName, 504 @NonNull String packageName, @NonNull String unexpandedPermission, int grantState, 505 @NonNull Consumer<Boolean> callback) { 506 AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> callback.accept( 507 onSetRuntimePermissionGrantStateByDeviceAdmin(callerPackageName, packageName, 508 unexpandedPermission, grantState))); 509 } 510 onSetRuntimePermissionGrantStateByDeviceAdmin(@onNull String callerPackageName, @NonNull String packageName, @NonNull String unexpandedPermission, int grantState)511 private boolean onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String callerPackageName, 512 @NonNull String packageName, @NonNull String unexpandedPermission, int grantState) { 513 PackageInfo callerPkgInfo = getPkgInfo(callerPackageName); 514 if (callerPkgInfo == null) { 515 Log.w(LOG_TAG, "Cannot fix " + unexpandedPermission + " as admin " 516 + callerPackageName + " cannot be found"); 517 return false; 518 } 519 520 PackageInfo pkgInfo = getPkgInfo(packageName); 521 if (pkgInfo == null) { 522 Log.w(LOG_TAG, "Cannot fix " + unexpandedPermission + " as " + packageName 523 + " cannot be found"); 524 return false; 525 } 526 527 ArrayList<String> expandedPermissions = addSplitPermissions( 528 Collections.singletonList(unexpandedPermission), 529 callerPkgInfo.applicationInfo.targetSdkVersion); 530 531 AppPermissions app = new AppPermissions(this, pkgInfo, false, true, null); 532 533 int numPerms = expandedPermissions.size(); 534 for (int i = 0; i < numPerms; i++) { 535 String permName = expandedPermissions.get(i); 536 AppPermissionGroup group = app.getGroupForPermission(permName); 537 if (group == null || group.isSystemFixed()) { 538 continue; 539 } 540 541 Permission perm = group.getPermission(permName); 542 if (perm == null) { 543 continue; 544 } 545 546 switch (grantState) { 547 case PERMISSION_GRANT_STATE_GRANTED: 548 perm.setPolicyFixed(true); 549 group.grantRuntimePermissions(false, new String[]{permName}); 550 break; 551 case PERMISSION_GRANT_STATE_DENIED: 552 perm.setPolicyFixed(true); 553 group.revokeRuntimePermissions(false, new String[]{permName}); 554 break; 555 case PERMISSION_GRANT_STATE_DEFAULT: 556 perm.setPolicyFixed(false); 557 break; 558 default: 559 return false; 560 } 561 } 562 563 app.persistChanges(grantState == PERMISSION_GRANT_STATE_DENIED 564 || !callerPackageName.equals(packageName)); 565 566 return true; 567 } 568 569 @Override onGrantOrUpgradeDefaultRuntimePermissions(@onNull Runnable callback)570 public void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable callback) { 571 AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { 572 onGrantOrUpgradeDefaultRuntimePermissions(); 573 callback.run(); 574 }); 575 } 576 onGrantOrUpgradeDefaultRuntimePermissions()577 private void onGrantOrUpgradeDefaultRuntimePermissions() { 578 // TODO: Default permission grants should go here 579 RuntimePermissionsUpgradeController.upgradeIfNeeded(this); 580 } 581 } 582