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