1 /* 2 * Copyright (C) 2022 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.server.healthconnect.permission; 18 19 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; 20 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 21 import static android.health.connect.HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND; 22 import static android.health.connect.HealthPermissions.READ_HEART_RATE; 23 24 import android.annotation.Nullable; 25 import android.app.ActivityManager; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.pm.PackageInfo; 29 import android.content.pm.PackageManager; 30 import android.health.connect.HealthConnectManager; 31 import android.health.connect.HealthPermissions; 32 import android.health.connect.internal.datatypes.utils.HealthConnectMappings; 33 import android.os.Binder; 34 import android.os.Build; 35 import android.os.UserHandle; 36 import android.util.ArrayMap; 37 import android.util.ArraySet; 38 39 import com.android.healthfitness.flags.Flags; 40 import com.android.server.healthconnect.storage.datatypehelpers.AppInfoHelper; 41 import com.android.server.healthconnect.storage.datatypehelpers.HealthDataCategoryPriorityHelper; 42 43 import java.time.Instant; 44 import java.time.Period; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Optional; 48 import java.util.Set; 49 import java.util.stream.Collectors; 50 51 /** 52 * A handler for HealthConnect permission-related logic. 53 * 54 * @hide 55 */ 56 public final class HealthConnectPermissionHelper { 57 private static final Period GRANT_TIME_TO_START_ACCESS_DATE_PERIOD = Period.ofDays(30); 58 private static final String TAG = "HealthConnectPermissionHelper"; 59 private static final String UNKNOWN_REASON = "Unknown Reason"; 60 61 private static final int MASK_PERMISSION_FLAGS = 62 PackageManager.FLAG_PERMISSION_USER_SET 63 | PackageManager.FLAG_PERMISSION_USER_FIXED 64 | PackageManager.FLAG_PERMISSION_AUTO_REVOKED; 65 66 private final Context mContext; 67 private final PackageManager mPackageManager; 68 private final HealthPermissionIntentAppsTracker mPermissionIntentAppsTracker; 69 private final FirstGrantTimeManager mFirstGrantTimeManager; 70 private final HealthDataCategoryPriorityHelper mHealthDataCategoryPriorityHelper; 71 private final AppInfoHelper mAppInfoHelper; 72 private final HealthConnectMappings mHealthConnectMappings; 73 74 /** 75 * Constructs a {@link HealthConnectPermissionHelper}. 76 * 77 * @param context the service context. 78 * @param packageManager a {@link PackageManager} instance. 79 * @param permissionIntentTracker a {@link 80 * com.android.server.healthconnect.permission.HealthPermissionIntentAppsTracker} instance 81 * that tracks apps allowed to request health permissions. 82 */ HealthConnectPermissionHelper( Context context, PackageManager packageManager, HealthPermissionIntentAppsTracker permissionIntentTracker, FirstGrantTimeManager firstGrantTimeManager, HealthDataCategoryPriorityHelper healthDataCategoryPriorityHelper, AppInfoHelper appInfoHelper, HealthConnectMappings healthConnectMappings)83 public HealthConnectPermissionHelper( 84 Context context, 85 PackageManager packageManager, 86 HealthPermissionIntentAppsTracker permissionIntentTracker, 87 FirstGrantTimeManager firstGrantTimeManager, 88 HealthDataCategoryPriorityHelper healthDataCategoryPriorityHelper, 89 AppInfoHelper appInfoHelper, 90 HealthConnectMappings healthConnectMappings) { 91 mContext = context; 92 mPackageManager = packageManager; 93 mPermissionIntentAppsTracker = permissionIntentTracker; 94 mFirstGrantTimeManager = firstGrantTimeManager; 95 mHealthDataCategoryPriorityHelper = healthDataCategoryPriorityHelper; 96 mAppInfoHelper = appInfoHelper; 97 mHealthConnectMappings = healthConnectMappings; 98 } 99 100 /** 101 * See {@link HealthConnectManager#grantHealthPermission}. 102 * 103 * <p>NOTE: Once permission grant is successful, the package name will also be appended to the 104 * end of the priority list corresponding to {@code permissionName}'s health permission 105 * category. 106 */ grantHealthPermission(String packageName, String permissionName, UserHandle user)107 public void grantHealthPermission(String packageName, String permissionName, UserHandle user) { 108 enforceManageHealthPermissions(/* message= */ "grantHealthPermission"); 109 enforceValidHealthPermission(permissionName); 110 UserHandle checkedUser = UserHandle.of(handleIncomingUser(user.getIdentifier())); 111 enforceValidPackage(packageName, checkedUser); 112 enforceSupportPermissionsUsageIntent(packageName, checkedUser, permissionName); 113 final long token = Binder.clearCallingIdentity(); 114 try { 115 mPackageManager.grantRuntimePermission(packageName, permissionName, checkedUser); 116 mPackageManager.updatePermissionFlags( 117 permissionName, 118 packageName, 119 MASK_PERMISSION_FLAGS, 120 PackageManager.FLAG_PERMISSION_USER_SET, 121 checkedUser); 122 123 // If is split permission, automatically grant BODY_SENSORS or BACKGROUND. 124 if ((permissionName.equals(READ_HEART_RATE) 125 || permissionName.equals(READ_HEALTH_DATA_IN_BACKGROUND)) 126 && isAppRequestingPermissionWithOutdatedTargetSdk( 127 packageName, 128 user, 129 toLegacyPermission(permissionName), 130 Build.VERSION_CODES.BAKLAVA)) { 131 grantRuntimePermissionAndUpdateFlags( 132 packageName, 133 user, 134 toLegacyPermission(permissionName), 135 PackageManager.FLAG_PERMISSION_USER_SET); 136 } 137 mAppInfoHelper.getOrInsertAppInfoId(packageName); 138 addToPriorityListIfRequired(packageName, permissionName, user); 139 } finally { 140 Binder.restoreCallingIdentity(token); 141 } 142 } 143 144 /** See {@link HealthConnectManager#revokeHealthPermission}. */ revokeHealthPermission( String packageName, String permissionName, @Nullable String reason, UserHandle user)145 public void revokeHealthPermission( 146 String packageName, String permissionName, @Nullable String reason, UserHandle user) { 147 enforceManageHealthPermissions(/* message= */ "revokeHealthPermission"); 148 enforceValidHealthPermission(permissionName); 149 UserHandle checkedUser = UserHandle.of(handleIncomingUser(user.getIdentifier())); 150 enforceValidPackage(packageName, checkedUser); 151 final long token = Binder.clearCallingIdentity(); 152 try { 153 // checkPermission doesn't have a variant that accepts user, get the packageManager for 154 // the user. 155 boolean isAlreadyDenied = 156 mContext.createContextAsUser(checkedUser, /* flags */ 0) 157 .getPackageManager() 158 .checkPermission(permissionName, packageName) 159 == PackageManager.PERMISSION_DENIED; 160 int permissionFlags = 161 mPackageManager.getPermissionFlags(permissionName, packageName, checkedUser); 162 if (!isAlreadyDenied) { 163 revokeRuntimePermission(packageName, checkedUser, permissionName, reason); 164 } 165 if (isAlreadyDenied 166 && (permissionFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0) { 167 permissionFlags = permissionFlags | PackageManager.FLAG_PERMISSION_USER_FIXED; 168 } else { 169 permissionFlags = permissionFlags | PackageManager.FLAG_PERMISSION_USER_SET; 170 } 171 permissionFlags = permissionFlags & ~PackageManager.FLAG_PERMISSION_AUTO_REVOKED; 172 mPackageManager.updatePermissionFlags( 173 permissionName, 174 packageName, 175 MASK_PERMISSION_FLAGS, 176 permissionFlags, 177 checkedUser); 178 // If is from split permission, automatically revoke BODY_SENSORS or BACKGROUND. 179 if ((permissionName.equals(READ_HEART_RATE) 180 || permissionName.equals(READ_HEALTH_DATA_IN_BACKGROUND)) 181 && isAppRequestingPermissionWithOutdatedTargetSdk( 182 packageName, 183 user, 184 toLegacyPermission(permissionName), 185 Build.VERSION_CODES.BAKLAVA)) { 186 revokeRuntimePermissionAndUpdateFlags( 187 packageName, 188 user, 189 toLegacyPermission(permissionName), 190 permissionFlags, 191 reason); 192 } 193 194 removeFromPriorityListIfRequired(packageName, permissionName, user); 195 196 } finally { 197 Binder.restoreCallingIdentity(token); 198 } 199 } 200 201 /** 202 * See {@link HealthConnectManager#revokeAllHealthPermissions}. 203 * 204 * @return {@code true} if any health permissions were revoked, {@code false} otherwise 205 */ revokeAllHealthPermissions( String packageName, @Nullable String reason, UserHandle user)206 public boolean revokeAllHealthPermissions( 207 String packageName, @Nullable String reason, UserHandle user) { 208 enforceManageHealthPermissions(/* message= */ "revokeAllHealthPermissions"); 209 UserHandle checkedUser = UserHandle.of(handleIncomingUser(user.getIdentifier())); 210 enforceValidPackage(packageName, checkedUser); 211 final long token = Binder.clearCallingIdentity(); 212 try { 213 return revokeAllHealthPermissionsUnchecked(packageName, checkedUser, reason); 214 } finally { 215 Binder.restoreCallingIdentity(token); 216 } 217 } 218 219 /** See {@link HealthConnectManager#getGrantedHealthPermissions}. */ getGrantedHealthPermissions(String packageName, UserHandle user)220 public List<String> getGrantedHealthPermissions(String packageName, UserHandle user) { 221 enforceManageHealthPermissions(/* message= */ "getGrantedHealthPermissions"); 222 UserHandle checkedUser = UserHandle.of(handleIncomingUser(user.getIdentifier())); 223 enforceValidPackage(packageName, checkedUser); 224 final long token = Binder.clearCallingIdentity(); 225 try { 226 return PackageInfoUtils.getGrantedHealthPermissions(mContext, packageName, checkedUser); 227 } finally { 228 Binder.restoreCallingIdentity(token); 229 } 230 } 231 232 /** See {@link HealthConnectManager#getHealthPermissionsFlags(String, List)}. */ getHealthPermissionsFlags( String packageName, UserHandle user, List<String> permissions)233 public Map<String, Integer> getHealthPermissionsFlags( 234 String packageName, UserHandle user, List<String> permissions) { 235 enforceManageHealthPermissions(/* message= */ "getHealthPermissionsFlags"); 236 UserHandle checkedUser = UserHandle.of(handleIncomingUser(user.getIdentifier())); 237 enforceValidPackage(packageName, checkedUser); 238 final long token = Binder.clearCallingIdentity(); 239 try { 240 return getHealthPermissionsFlagsUnchecked(packageName, checkedUser, permissions); 241 } finally { 242 Binder.restoreCallingIdentity(token); 243 } 244 } 245 246 /** See {@link HealthConnectManager#setHealthPermissionsUserFixedFlagValue(String, List)}. */ setHealthPermissionsUserFixedFlagValue( String packageName, UserHandle user, List<String> permissions, boolean value)247 public void setHealthPermissionsUserFixedFlagValue( 248 String packageName, UserHandle user, List<String> permissions, boolean value) { 249 enforceManageHealthPermissions(/* message= */ "setHealthPermissionsUserFixedFlagValue"); 250 UserHandle checkedUser = UserHandle.of(handleIncomingUser(user.getIdentifier())); 251 enforceValidPackage(packageName, checkedUser); 252 final long token = Binder.clearCallingIdentity(); 253 try { 254 setHealthPermissionsUserFixedFlagValueUnchecked( 255 packageName, checkedUser, permissions, value); 256 } finally { 257 Binder.restoreCallingIdentity(token); 258 } 259 } 260 261 /** 262 * Returns {@code true} if there is at least one granted permission for the provided {@code 263 * packageName}, {@code false} otherwise. 264 */ hasGrantedHealthPermissions(String packageName, UserHandle user)265 public boolean hasGrantedHealthPermissions(String packageName, UserHandle user) { 266 return !getGrantedHealthPermissions(packageName, user).isEmpty(); 267 } 268 269 /** 270 * Returns the date from which an app can read / write health data. See {@link 271 * HealthConnectManager#getHealthDataHistoricalAccessStartDate} 272 */ getHealthDataStartDateAccess(String packageName, UserHandle user)273 public Optional<Instant> getHealthDataStartDateAccess(String packageName, UserHandle user) 274 throws IllegalArgumentException { 275 enforceManageHealthPermissions(/* message= */ "getHealthDataStartDateAccess"); 276 UserHandle checkedUser = UserHandle.of(handleIncomingUser(user.getIdentifier())); 277 enforceValidPackage(packageName, checkedUser); 278 279 return mFirstGrantTimeManager 280 .getFirstGrantTime(packageName, checkedUser) 281 .map(grantTime -> grantTime.minus(GRANT_TIME_TO_START_ACCESS_DATE_PERIOD)) 282 .or(Optional::empty); 283 } 284 285 /** 286 * Same as {@link #getHealthDataStartDateAccess(String, UserHandle)} except this method also 287 * throws {@link IllegalAccessException} if health permission is in an incorrect state where 288 * first grant time can't be fetched. 289 */ getHealthDataStartDateAccessOrThrow(String packageName, UserHandle user)290 public Instant getHealthDataStartDateAccessOrThrow(String packageName, UserHandle user) { 291 Optional<Instant> startDateAccess = getHealthDataStartDateAccess(packageName, user); 292 if (startDateAccess.isEmpty()) { 293 throwExceptionIncorrectPermissionState(); 294 } 295 return startDateAccess.get(); 296 } 297 298 /** 299 * Returns whether the given package is explicitly requesting health permissions (i.e. not as a 300 * result of a split permission platform migration). 301 */ isPackageExplicitlyRequestingHealthPermission( String packageName, UserHandle userHandle)302 private boolean isPackageExplicitlyRequestingHealthPermission( 303 String packageName, UserHandle userHandle) { 304 PackageInfo packageInfo; 305 try { 306 packageInfo = 307 PackageInfoUtils.getPackageInfoUnchecked( 308 packageName, 309 userHandle, 310 PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS), 311 mContext); 312 } catch (IllegalArgumentException e) { 313 // If the package can't be found, be conservative and assume they 314 // are explicitly requesting a health permission. 315 return true; 316 } 317 Set<String> requestedPermissions = new ArraySet<>(packageInfo.requestedPermissions); 318 319 Set<String> healthPermissions = HealthConnectManager.getHealthPermissions(mContext); 320 List<String> requestedHealthPermissions = 321 requestedPermissions.stream() 322 .filter( 323 requestedPermission -> 324 healthPermissions.contains(requestedPermission)) 325 .collect(Collectors.toList()); 326 if (requestedHealthPermissions.isEmpty()) { 327 return false; 328 } 329 330 if (!canPotentiallyBeSplitPermissions(requestedHealthPermissions) 331 || packageInfo.applicationInfo == null) { 332 return true; 333 } 334 // Check the permission flags to see if these permissions are requested 335 // as a result of a split-permission due to a platform upgrade. 336 Map<String, Integer> permissionFlags; 337 try { 338 permissionFlags = 339 getHealthPermissionsFlags(packageName, userHandle, requestedHealthPermissions); 340 } catch (IllegalArgumentException e) { 341 // If the package can't be found, assume it's asking for the health 342 // permissions explicitly. 343 return true; 344 } 345 346 // Permissions that aren't from split permission are explicitly 347 // requested by the app. 348 int targetSdkVersion = packageInfo.applicationInfo.targetSdkVersion; 349 return requestedHealthPermissions.stream() 350 .anyMatch( 351 requestedPermission -> 352 !isFromSplitPermission( 353 permissionFlags.getOrDefault(requestedPermission, 0), 354 targetSdkVersion)); 355 } 356 357 /** Returns true if we should enforce permission usage intent for this package. */ shouldEnforcePermissionUsageIntent(String packageName, UserHandle userHandle)358 public boolean shouldEnforcePermissionUsageIntent(String packageName, UserHandle userHandle) { 359 // When flag is disabled, always enforce permission usage intent. 360 if (!Flags.replaceBodySensorPermissionEnabled()) { 361 return true; 362 } 363 364 // The rationale intent is not currently required on Wear devices. 365 if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) { 366 return false; 367 } 368 369 // We only need to enforce the rationale intent if the app is explicitly 370 // requesting at least one health permission. If the app isn't 371 // requesting any health permissions, or is only requesting them as a 372 // result of a split permission platform migration, then we don't need 373 // to enforce the rationale intent. 374 return isPackageExplicitlyRequestingHealthPermission(packageName, userHandle); 375 } 376 377 /** 378 * Returns true if we should enforce permission usage intent for the given package to be granted 379 * the given permission. 380 */ shouldEnforcePermissionUsageIntent( String packageName, UserHandle userHandle, String permissionName)381 private boolean shouldEnforcePermissionUsageIntent( 382 String packageName, UserHandle userHandle, String permissionName) { 383 // When flag is disabled, always enforce permission usage intent. 384 if (!Flags.replaceBodySensorPermissionEnabled()) { 385 return true; 386 } 387 388 // The rationale intent is not currently required on Wear devices. 389 if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) { 390 return false; 391 } 392 393 // When flag is enabled, and is requesting split permission, do not enforce 394 // permission usage intent on Phone. 395 return !isRequestingSplitPermission(packageName, userHandle, permissionName); 396 } 397 398 /** 399 * Returns true if {@code permissionFlag} indicates the permission is implicit from permission 400 * split. 401 */ isFromSplitPermission(int permissionFlag, int targetSdk)402 public static boolean isFromSplitPermission(int permissionFlag, int targetSdk) { 403 return (targetSdk >= Build.VERSION_CODES.M) 404 ? (permissionFlag & PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0 405 : (permissionFlag & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0; 406 } 407 canPotentiallyBeSplitPermissions(List<String> permissions)408 private static boolean canPotentiallyBeSplitPermissions(List<String> permissions) { 409 return (permissions.size() == 1 && permissions.contains(HealthPermissions.READ_HEART_RATE)) 410 || (permissions.size() == 2 411 && permissions.contains(HealthPermissions.READ_HEART_RATE) 412 && permissions.contains(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND)); 413 } 414 415 /** 416 * Returns true if the app is requesting the given permission as a result of a split-permission 417 * platform migration. 418 */ isRequestingSplitPermission( String packageName, UserHandle userHandle, String permissionName)419 private boolean isRequestingSplitPermission( 420 String packageName, UserHandle userHandle, String permissionName) { 421 // BODY_SENSORS split permission. 422 if (!permissionName.equals(HealthPermissions.READ_HEART_RATE) 423 && !permissionName.equals(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND)) { 424 return false; 425 } 426 427 PackageInfo packageInfo; 428 try { 429 packageInfo = 430 PackageInfoUtils.getPackageInfoUnchecked( 431 packageName, 432 userHandle, 433 PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS), 434 mContext); 435 } catch (IllegalArgumentException e) { 436 // If the package can't be found, default to consider as not containing split 437 // permission. 438 return false; 439 } 440 441 // BODY_SENSORS permission split only applies to apps targeting SDK < B. 442 int targetSdkVersion = packageInfo.applicationInfo.targetSdkVersion; 443 if (targetSdkVersion >= Build.VERSION_CODES.BAKLAVA) { 444 return false; 445 } 446 447 // Check the permission flags to see if these permissions are requested 448 // as a result of a split-permission due to a platform upgrade. 449 List<String> permissionsToCheck = List.of(permissionName); 450 Map<String, Integer> permissionFlags; 451 try { 452 permissionFlags = 453 getHealthPermissionsFlags(packageName, userHandle, permissionsToCheck); 454 } catch (IllegalArgumentException e) { 455 // If the package can't be found, default to consider as not containing split 456 // permission. 457 return false; 458 } 459 460 // Check if given permission is from a split-permission. 461 int permissionFlag = permissionFlags.getOrDefault(permissionName, 0); 462 return isFromSplitPermission(permissionFlag, targetSdkVersion); 463 } 464 465 /** Returns if the app is targeting SDK 35 and requesting the given permission. */ isAppRequestingPermissionWithOutdatedTargetSdk( String packageName, UserHandle userHandle, String permission, int buildVersion)466 private boolean isAppRequestingPermissionWithOutdatedTargetSdk( 467 String packageName, UserHandle userHandle, String permission, int buildVersion) { 468 if (!Flags.replaceBodySensorPermissionEnabled()) { 469 return false; 470 } 471 472 // Only applies if current build is the same or newer than build version. 473 if (Build.VERSION.SDK_INT < buildVersion) { 474 return false; 475 } 476 477 PackageInfo packageInfo; 478 try { 479 packageInfo = 480 PackageInfoUtils.getPackageInfoUnchecked( 481 packageName, 482 userHandle, 483 PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS), 484 mContext); 485 } catch (IllegalArgumentException e) { 486 // If the package can't be found, default to consider as not containing split 487 // permission. 488 return false; 489 } 490 491 // If the app is targeting the given build version or newer, then they 492 // are not using an outdated target SDK. 493 if (packageInfo.applicationInfo.targetSdkVersion >= buildVersion) { 494 return false; 495 } 496 497 Set<String> requestedPermissions = new ArraySet<>(packageInfo.requestedPermissions); 498 return requestedPermissions.contains(permission); 499 } 500 throwExceptionIncorrectPermissionState()501 private void throwExceptionIncorrectPermissionState() { 502 throw new IllegalStateException( 503 "Incorrect health permission state, likely" 504 + " because the calling application's manifest does not specify handling " 505 + Intent.ACTION_VIEW_PERMISSION_USAGE 506 + " with " 507 + HealthConnectManager.CATEGORY_HEALTH_PERMISSIONS); 508 } 509 addToPriorityListIfRequired( String packageName, String permissionName, UserHandle user)510 private void addToPriorityListIfRequired( 511 String packageName, String permissionName, UserHandle user) { 512 if (mHealthConnectMappings.isWritePermission(permissionName)) { 513 mHealthDataCategoryPriorityHelper.appendToPriorityList( 514 packageName, 515 mHealthConnectMappings.getHealthDataCategoryForWritePermission(permissionName), 516 user); 517 } 518 } 519 removeFromPriorityListIfRequired( String packageName, String permissionName, UserHandle user)520 private void removeFromPriorityListIfRequired( 521 String packageName, String permissionName, UserHandle user) { 522 if (mHealthConnectMappings.isWritePermission(permissionName)) { 523 mHealthDataCategoryPriorityHelper.maybeRemoveAppFromPriorityList( 524 packageName, 525 mHealthConnectMappings.getHealthDataCategoryForWritePermission(permissionName), 526 user); 527 } 528 } 529 getHealthPermissionsFlagsUnchecked( String packageName, UserHandle user, List<String> permissions)530 private Map<String, Integer> getHealthPermissionsFlagsUnchecked( 531 String packageName, UserHandle user, List<String> permissions) { 532 enforceValidHealthPermissions(packageName, user, permissions); 533 534 Map<String, Integer> result = new ArrayMap<>(); 535 536 for (String permission : permissions) { 537 result.put( 538 permission, mPackageManager.getPermissionFlags(permission, packageName, user)); 539 } 540 541 return result; 542 } 543 setHealthPermissionsUserFixedFlagValueUnchecked( String packageName, UserHandle user, List<String> permissions, boolean value)544 private void setHealthPermissionsUserFixedFlagValueUnchecked( 545 String packageName, UserHandle user, List<String> permissions, boolean value) { 546 enforceValidHealthPermissions(packageName, user, permissions); 547 548 int flagMask = PackageManager.FLAG_PERMISSION_USER_FIXED; 549 int flagValues = value ? PackageManager.FLAG_PERMISSION_USER_FIXED : 0; 550 551 for (String permission : permissions) { 552 mPackageManager.updatePermissionFlags( 553 permission, packageName, flagMask, flagValues, user); 554 } 555 } 556 revokeAllHealthPermissionsUnchecked( String packageName, UserHandle user, @Nullable String reason)557 private boolean revokeAllHealthPermissionsUnchecked( 558 String packageName, UserHandle user, @Nullable String reason) { 559 List<String> grantedHealthPermissions = 560 PackageInfoUtils.getGrantedHealthPermissions(mContext, packageName, user); 561 for (String perm : grantedHealthPermissions) { 562 revokeRuntimePermission(packageName, user, perm, reason); 563 mPackageManager.updatePermissionFlags( 564 perm, 565 packageName, 566 MASK_PERMISSION_FLAGS, 567 PackageManager.FLAG_PERMISSION_USER_SET, 568 user); 569 removeFromPriorityListIfRequired(packageName, perm, user); 570 } 571 boolean revoked = !grantedHealthPermissions.isEmpty(); 572 // If for legacy app, automatically deny BODY_SENSORS and BACKGROUND. 573 if (isAppRequestingPermissionWithOutdatedTargetSdk( 574 packageName, 575 user, 576 android.Manifest.permission.BODY_SENSORS, 577 Build.VERSION_CODES.BAKLAVA)) { 578 revoked |= 579 revokeRuntimePermissionAndUpdateFlags( 580 packageName, 581 user, 582 android.Manifest.permission.BODY_SENSORS, 583 PackageManager.FLAG_PERMISSION_USER_SET, 584 reason); 585 } 586 if (isAppRequestingPermissionWithOutdatedTargetSdk( 587 packageName, 588 user, 589 android.Manifest.permission.BODY_SENSORS_BACKGROUND, 590 Build.VERSION_CODES.BAKLAVA)) { 591 revoked |= 592 revokeRuntimePermissionAndUpdateFlags( 593 packageName, 594 user, 595 android.Manifest.permission.BODY_SENSORS_BACKGROUND, 596 PackageManager.FLAG_PERMISSION_USER_SET, 597 reason); 598 } 599 return revoked; 600 } 601 revokeRuntimePermission( String packageName, UserHandle user, String permission, @Nullable String reason)602 private void revokeRuntimePermission( 603 String packageName, UserHandle user, String permission, @Nullable String reason) { 604 mPackageManager.revokeRuntimePermission( 605 packageName, permission, user, reason == null ? UNKNOWN_REASON : reason); 606 } 607 enforceValidHealthPermission(String permissionName)608 private void enforceValidHealthPermission(String permissionName) { 609 if (!HealthConnectManager.getHealthPermissions(mContext).contains(permissionName)) { 610 throw new IllegalArgumentException("invalid health permission"); 611 } 612 } 613 enforceValidPackage(String packageName, UserHandle user)614 private void enforceValidPackage(String packageName, UserHandle user) { 615 PackageInfoUtils.getPackageInfoUnchecked( 616 packageName, user, PackageManager.PackageInfoFlags.of(0), mContext); 617 } 618 enforceManageHealthPermissions(String message)619 private void enforceManageHealthPermissions(String message) { 620 mContext.enforceCallingOrSelfPermission( 621 HealthPermissions.MANAGE_HEALTH_PERMISSIONS, message); 622 } 623 enforceSupportPermissionsUsageIntent( String packageName, UserHandle userHandle, String permission)624 private void enforceSupportPermissionsUsageIntent( 625 String packageName, UserHandle userHandle, String permission) { 626 if (!shouldEnforcePermissionUsageIntent(packageName, userHandle, permission)) { 627 return; 628 } 629 630 if (!mPermissionIntentAppsTracker.supportsPermissionUsageIntent(packageName, userHandle)) { 631 throw new SecurityException( 632 "Package " 633 + packageName 634 + " for " 635 + userHandle.toString() 636 + " doesn't support health permissions usage intent."); 637 } 638 } 639 640 /** 641 * Checks input user id and converts it to positive id if needed, returns converted user id. 642 * 643 * @throws java.lang.SecurityException if the caller is affecting different users without 644 * holding the {@link INTERACT_ACROSS_USERS_FULL} permission. 645 */ handleIncomingUser(int userId)646 private int handleIncomingUser(int userId) { 647 int callingUserId = UserHandle.getUserHandleForUid(Binder.getCallingUid()).getIdentifier(); 648 if (userId == callingUserId) { 649 return userId; 650 } 651 652 boolean canInteractAcrossUsersFull = 653 mContext.checkCallingOrSelfPermission(INTERACT_ACROSS_USERS_FULL) 654 == PERMISSION_GRANTED; 655 if (canInteractAcrossUsersFull) { 656 // If the UserHandle.CURRENT has been passed (negative value), 657 // convert it to positive userId. 658 if (userId == UserHandle.CURRENT.getIdentifier()) { 659 return ActivityManager.getCurrentUser(); 660 } 661 return userId; 662 } 663 664 throw new SecurityException( 665 "Permission denied. Need to run as either the calling user id (" 666 + callingUserId 667 + "), or with " 668 + INTERACT_ACROSS_USERS_FULL 669 + " permission"); 670 } 671 enforceValidHealthPermissions( String packageName, UserHandle user, List<String> permissions)672 private void enforceValidHealthPermissions( 673 String packageName, UserHandle user, List<String> permissions) { 674 PackageInfo packageInfo = 675 PackageInfoUtils.getPackageInfoUnchecked( 676 packageName, 677 user, 678 PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS), 679 mContext); 680 681 Set<String> requestedPermissions = new ArraySet<>(packageInfo.requestedPermissions); 682 683 for (String permission : permissions) { 684 if (!requestedPermissions.contains(permission)) { 685 throw new IllegalArgumentException( 686 "undeclared permission " + permission + " for package " + packageName); 687 } 688 689 enforceValidHealthPermission(permission); 690 } 691 } 692 693 /** Sync permission granted status and flag between BODY_SENSORS and READ_HEART_RATE. */ grantRuntimePermissionAndUpdateFlags( String packageName, UserHandle user, String legacyPermission, int permissionFlags)694 private void grantRuntimePermissionAndUpdateFlags( 695 String packageName, UserHandle user, String legacyPermission, int permissionFlags) { 696 mPackageManager.grantRuntimePermission(packageName, legacyPermission, user); 697 mPackageManager.updatePermissionFlags( 698 legacyPermission, packageName, MASK_PERMISSION_FLAGS, permissionFlags, user); 699 } 700 701 /** Sync permission denied status and flag between BODY_SENSORS and READ_HEART_RATE. */ revokeRuntimePermissionAndUpdateFlags( String packageName, UserHandle user, String legacyPermission, int permissionFlags, @Nullable String reason)702 private boolean revokeRuntimePermissionAndUpdateFlags( 703 String packageName, 704 UserHandle user, 705 String legacyPermission, 706 int permissionFlags, 707 @Nullable String reason) { 708 boolean revoked = false; 709 if (mPackageManager.checkPermission(legacyPermission, packageName) 710 != PackageManager.PERMISSION_DENIED) { 711 mPackageManager.revokeRuntimePermission(packageName, legacyPermission, user, reason); 712 revoked = true; 713 } 714 mPackageManager.updatePermissionFlags( 715 legacyPermission, packageName, MASK_PERMISSION_FLAGS, permissionFlags, user); 716 return revoked; 717 } 718 719 /** 720 * Returns legacy body sensor permission for split heart rate permission. 721 * 722 * @throws IllegalArgumentException if {@code permissionName} is neither READ_HEART_RATE nor 723 * READ_HEALTH_DATE_IN_BACKGROUND 724 */ toLegacyPermission(String permissionName)725 private static String toLegacyPermission(String permissionName) 726 throws IllegalArgumentException { 727 if (permissionName.equals(READ_HEART_RATE)) { 728 return android.Manifest.permission.BODY_SENSORS; 729 } 730 if (permissionName.equals(READ_HEALTH_DATA_IN_BACKGROUND)) { 731 return android.Manifest.permission.BODY_SENSORS_BACKGROUND; 732 } 733 throw new IllegalArgumentException( 734 "toLegacyPermission() encounters unexpected permission " 735 + permissionName 736 + ", should be one of READ_HEART_RATE and READ_HEALTH_DATA_IN_BACKGROUND"); 737 } 738 } 739