/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.healthconnect.permission;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.health.connect.HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND;
import static android.health.connect.HealthPermissions.READ_HEART_RATE;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.health.connect.HealthConnectManager;
import android.health.connect.HealthPermissions;
import android.health.connect.internal.datatypes.utils.HealthConnectMappings;
import android.os.Binder;
import android.os.Build;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.healthfitness.flags.Flags;
import com.android.server.healthconnect.storage.datatypehelpers.AppInfoHelper;
import com.android.server.healthconnect.storage.datatypehelpers.HealthDataCategoryPriorityHelper;
import java.time.Instant;
import java.time.Period;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* A handler for HealthConnect permission-related logic.
*
* @hide
*/
public final class HealthConnectPermissionHelper {
private static final Period GRANT_TIME_TO_START_ACCESS_DATE_PERIOD = Period.ofDays(30);
private static final String TAG = "HealthConnectPermissionHelper";
private static final String UNKNOWN_REASON = "Unknown Reason";
private static final int MASK_PERMISSION_FLAGS =
PackageManager.FLAG_PERMISSION_USER_SET
| PackageManager.FLAG_PERMISSION_USER_FIXED
| PackageManager.FLAG_PERMISSION_AUTO_REVOKED;
private final Context mContext;
private final PackageManager mPackageManager;
private final HealthPermissionIntentAppsTracker mPermissionIntentAppsTracker;
private final FirstGrantTimeManager mFirstGrantTimeManager;
private final HealthDataCategoryPriorityHelper mHealthDataCategoryPriorityHelper;
private final AppInfoHelper mAppInfoHelper;
private final HealthConnectMappings mHealthConnectMappings;
/**
* Constructs a {@link HealthConnectPermissionHelper}.
*
* @param context the service context.
* @param packageManager a {@link PackageManager} instance.
* @param permissionIntentTracker a {@link
* com.android.server.healthconnect.permission.HealthPermissionIntentAppsTracker} instance
* that tracks apps allowed to request health permissions.
*/
public HealthConnectPermissionHelper(
Context context,
PackageManager packageManager,
HealthPermissionIntentAppsTracker permissionIntentTracker,
FirstGrantTimeManager firstGrantTimeManager,
HealthDataCategoryPriorityHelper healthDataCategoryPriorityHelper,
AppInfoHelper appInfoHelper,
HealthConnectMappings healthConnectMappings) {
mContext = context;
mPackageManager = packageManager;
mPermissionIntentAppsTracker = permissionIntentTracker;
mFirstGrantTimeManager = firstGrantTimeManager;
mHealthDataCategoryPriorityHelper = healthDataCategoryPriorityHelper;
mAppInfoHelper = appInfoHelper;
mHealthConnectMappings = healthConnectMappings;
}
/**
* See {@link HealthConnectManager#grantHealthPermission}.
*
*
NOTE: Once permission grant is successful, the package name will also be appended to the
* end of the priority list corresponding to {@code permissionName}'s health permission
* category.
*/
public void grantHealthPermission(String packageName, String permissionName, UserHandle user) {
enforceManageHealthPermissions(/* message= */ "grantHealthPermission");
enforceValidHealthPermission(permissionName);
UserHandle checkedUser = UserHandle.of(handleIncomingUser(user.getIdentifier()));
enforceValidPackage(packageName, checkedUser);
enforceSupportPermissionsUsageIntent(packageName, checkedUser, permissionName);
final long token = Binder.clearCallingIdentity();
try {
mPackageManager.grantRuntimePermission(packageName, permissionName, checkedUser);
mPackageManager.updatePermissionFlags(
permissionName,
packageName,
MASK_PERMISSION_FLAGS,
PackageManager.FLAG_PERMISSION_USER_SET,
checkedUser);
// If is split permission, automatically grant BODY_SENSORS or BACKGROUND.
if ((permissionName.equals(READ_HEART_RATE)
|| permissionName.equals(READ_HEALTH_DATA_IN_BACKGROUND))
&& isAppRequestingPermissionWithOutdatedTargetSdk(
packageName,
user,
toLegacyPermission(permissionName),
Build.VERSION_CODES.BAKLAVA)) {
grantRuntimePermissionAndUpdateFlags(
packageName,
user,
toLegacyPermission(permissionName),
PackageManager.FLAG_PERMISSION_USER_SET);
}
mAppInfoHelper.getOrInsertAppInfoId(packageName);
addToPriorityListIfRequired(packageName, permissionName, user);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/** See {@link HealthConnectManager#revokeHealthPermission}. */
public void revokeHealthPermission(
String packageName, String permissionName, @Nullable String reason, UserHandle user) {
enforceManageHealthPermissions(/* message= */ "revokeHealthPermission");
enforceValidHealthPermission(permissionName);
UserHandle checkedUser = UserHandle.of(handleIncomingUser(user.getIdentifier()));
enforceValidPackage(packageName, checkedUser);
final long token = Binder.clearCallingIdentity();
try {
// checkPermission doesn't have a variant that accepts user, get the packageManager for
// the user.
boolean isAlreadyDenied =
mContext.createContextAsUser(checkedUser, /* flags */ 0)
.getPackageManager()
.checkPermission(permissionName, packageName)
== PackageManager.PERMISSION_DENIED;
int permissionFlags =
mPackageManager.getPermissionFlags(permissionName, packageName, checkedUser);
if (!isAlreadyDenied) {
revokeRuntimePermission(packageName, checkedUser, permissionName, reason);
}
if (isAlreadyDenied
&& (permissionFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0) {
permissionFlags = permissionFlags | PackageManager.FLAG_PERMISSION_USER_FIXED;
} else {
permissionFlags = permissionFlags | PackageManager.FLAG_PERMISSION_USER_SET;
}
permissionFlags = permissionFlags & ~PackageManager.FLAG_PERMISSION_AUTO_REVOKED;
mPackageManager.updatePermissionFlags(
permissionName,
packageName,
MASK_PERMISSION_FLAGS,
permissionFlags,
checkedUser);
// If is from split permission, automatically revoke BODY_SENSORS or BACKGROUND.
if ((permissionName.equals(READ_HEART_RATE)
|| permissionName.equals(READ_HEALTH_DATA_IN_BACKGROUND))
&& isAppRequestingPermissionWithOutdatedTargetSdk(
packageName,
user,
toLegacyPermission(permissionName),
Build.VERSION_CODES.BAKLAVA)) {
revokeRuntimePermissionAndUpdateFlags(
packageName,
user,
toLegacyPermission(permissionName),
permissionFlags,
reason);
}
removeFromPriorityListIfRequired(packageName, permissionName, user);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* See {@link HealthConnectManager#revokeAllHealthPermissions}.
*
* @return {@code true} if any health permissions were revoked, {@code false} otherwise
*/
public boolean revokeAllHealthPermissions(
String packageName, @Nullable String reason, UserHandle user) {
enforceManageHealthPermissions(/* message= */ "revokeAllHealthPermissions");
UserHandle checkedUser = UserHandle.of(handleIncomingUser(user.getIdentifier()));
enforceValidPackage(packageName, checkedUser);
final long token = Binder.clearCallingIdentity();
try {
return revokeAllHealthPermissionsUnchecked(packageName, checkedUser, reason);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/** See {@link HealthConnectManager#getGrantedHealthPermissions}. */
public List getGrantedHealthPermissions(String packageName, UserHandle user) {
enforceManageHealthPermissions(/* message= */ "getGrantedHealthPermissions");
UserHandle checkedUser = UserHandle.of(handleIncomingUser(user.getIdentifier()));
enforceValidPackage(packageName, checkedUser);
final long token = Binder.clearCallingIdentity();
try {
return PackageInfoUtils.getGrantedHealthPermissions(mContext, packageName, checkedUser);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/** See {@link HealthConnectManager#getHealthPermissionsFlags(String, List)}. */
public Map getHealthPermissionsFlags(
String packageName, UserHandle user, List permissions) {
enforceManageHealthPermissions(/* message= */ "getHealthPermissionsFlags");
UserHandle checkedUser = UserHandle.of(handleIncomingUser(user.getIdentifier()));
enforceValidPackage(packageName, checkedUser);
final long token = Binder.clearCallingIdentity();
try {
return getHealthPermissionsFlagsUnchecked(packageName, checkedUser, permissions);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/** See {@link HealthConnectManager#setHealthPermissionsUserFixedFlagValue(String, List)}. */
public void setHealthPermissionsUserFixedFlagValue(
String packageName, UserHandle user, List permissions, boolean value) {
enforceManageHealthPermissions(/* message= */ "setHealthPermissionsUserFixedFlagValue");
UserHandle checkedUser = UserHandle.of(handleIncomingUser(user.getIdentifier()));
enforceValidPackage(packageName, checkedUser);
final long token = Binder.clearCallingIdentity();
try {
setHealthPermissionsUserFixedFlagValueUnchecked(
packageName, checkedUser, permissions, value);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Returns {@code true} if there is at least one granted permission for the provided {@code
* packageName}, {@code false} otherwise.
*/
public boolean hasGrantedHealthPermissions(String packageName, UserHandle user) {
return !getGrantedHealthPermissions(packageName, user).isEmpty();
}
/**
* Returns the date from which an app can read / write health data. See {@link
* HealthConnectManager#getHealthDataHistoricalAccessStartDate}
*/
public Optional getHealthDataStartDateAccess(String packageName, UserHandle user)
throws IllegalArgumentException {
enforceManageHealthPermissions(/* message= */ "getHealthDataStartDateAccess");
UserHandle checkedUser = UserHandle.of(handleIncomingUser(user.getIdentifier()));
enforceValidPackage(packageName, checkedUser);
return mFirstGrantTimeManager
.getFirstGrantTime(packageName, checkedUser)
.map(grantTime -> grantTime.minus(GRANT_TIME_TO_START_ACCESS_DATE_PERIOD))
.or(Optional::empty);
}
/**
* Same as {@link #getHealthDataStartDateAccess(String, UserHandle)} except this method also
* throws {@link IllegalAccessException} if health permission is in an incorrect state where
* first grant time can't be fetched.
*/
public Instant getHealthDataStartDateAccessOrThrow(String packageName, UserHandle user) {
Optional startDateAccess = getHealthDataStartDateAccess(packageName, user);
if (startDateAccess.isEmpty()) {
throwExceptionIncorrectPermissionState();
}
return startDateAccess.get();
}
/**
* Returns whether the given package is explicitly requesting health permissions (i.e. not as a
* result of a split permission platform migration).
*/
private boolean isPackageExplicitlyRequestingHealthPermission(
String packageName, UserHandle userHandle) {
PackageInfo packageInfo;
try {
packageInfo =
PackageInfoUtils.getPackageInfoUnchecked(
packageName,
userHandle,
PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS),
mContext);
} catch (IllegalArgumentException e) {
// If the package can't be found, be conservative and assume they
// are explicitly requesting a health permission.
return true;
}
Set requestedPermissions = new ArraySet<>(packageInfo.requestedPermissions);
Set healthPermissions = HealthConnectManager.getHealthPermissions(mContext);
List requestedHealthPermissions =
requestedPermissions.stream()
.filter(
requestedPermission ->
healthPermissions.contains(requestedPermission))
.collect(Collectors.toList());
if (requestedHealthPermissions.isEmpty()) {
return false;
}
if (!canPotentiallyBeSplitPermissions(requestedHealthPermissions)
|| packageInfo.applicationInfo == null) {
return true;
}
// Check the permission flags to see if these permissions are requested
// as a result of a split-permission due to a platform upgrade.
Map permissionFlags;
try {
permissionFlags =
getHealthPermissionsFlags(packageName, userHandle, requestedHealthPermissions);
} catch (IllegalArgumentException e) {
// If the package can't be found, assume it's asking for the health
// permissions explicitly.
return true;
}
// Permissions that aren't from split permission are explicitly
// requested by the app.
int targetSdkVersion = packageInfo.applicationInfo.targetSdkVersion;
return requestedHealthPermissions.stream()
.anyMatch(
requestedPermission ->
!isFromSplitPermission(
permissionFlags.getOrDefault(requestedPermission, 0),
targetSdkVersion));
}
/** Returns true if we should enforce permission usage intent for this package. */
public boolean shouldEnforcePermissionUsageIntent(String packageName, UserHandle userHandle) {
// When flag is disabled, always enforce permission usage intent.
if (!Flags.replaceBodySensorPermissionEnabled()) {
return true;
}
// The rationale intent is not currently required on Wear devices.
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
return false;
}
// We only need to enforce the rationale intent if the app is explicitly
// requesting at least one health permission. If the app isn't
// requesting any health permissions, or is only requesting them as a
// result of a split permission platform migration, then we don't need
// to enforce the rationale intent.
return isPackageExplicitlyRequestingHealthPermission(packageName, userHandle);
}
/**
* Returns true if we should enforce permission usage intent for the given package to be granted
* the given permission.
*/
private boolean shouldEnforcePermissionUsageIntent(
String packageName, UserHandle userHandle, String permissionName) {
// When flag is disabled, always enforce permission usage intent.
if (!Flags.replaceBodySensorPermissionEnabled()) {
return true;
}
// The rationale intent is not currently required on Wear devices.
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
return false;
}
// When flag is enabled, and is requesting split permission, do not enforce
// permission usage intent on Phone.
return !isRequestingSplitPermission(packageName, userHandle, permissionName);
}
/**
* Returns true if {@code permissionFlag} indicates the permission is implicit from permission
* split.
*/
public static boolean isFromSplitPermission(int permissionFlag, int targetSdk) {
return (targetSdk >= Build.VERSION_CODES.M)
? (permissionFlag & PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0
: (permissionFlag & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
}
private static boolean canPotentiallyBeSplitPermissions(List permissions) {
return (permissions.size() == 1 && permissions.contains(HealthPermissions.READ_HEART_RATE))
|| (permissions.size() == 2
&& permissions.contains(HealthPermissions.READ_HEART_RATE)
&& permissions.contains(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND));
}
/**
* Returns true if the app is requesting the given permission as a result of a split-permission
* platform migration.
*/
private boolean isRequestingSplitPermission(
String packageName, UserHandle userHandle, String permissionName) {
// BODY_SENSORS split permission.
if (!permissionName.equals(HealthPermissions.READ_HEART_RATE)
&& !permissionName.equals(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND)) {
return false;
}
PackageInfo packageInfo;
try {
packageInfo =
PackageInfoUtils.getPackageInfoUnchecked(
packageName,
userHandle,
PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS),
mContext);
} catch (IllegalArgumentException e) {
// If the package can't be found, default to consider as not containing split
// permission.
return false;
}
// BODY_SENSORS permission split only applies to apps targeting SDK < B.
int targetSdkVersion = packageInfo.applicationInfo.targetSdkVersion;
if (targetSdkVersion >= Build.VERSION_CODES.BAKLAVA) {
return false;
}
// Check the permission flags to see if these permissions are requested
// as a result of a split-permission due to a platform upgrade.
List permissionsToCheck = List.of(permissionName);
Map permissionFlags;
try {
permissionFlags =
getHealthPermissionsFlags(packageName, userHandle, permissionsToCheck);
} catch (IllegalArgumentException e) {
// If the package can't be found, default to consider as not containing split
// permission.
return false;
}
// Check if given permission is from a split-permission.
int permissionFlag = permissionFlags.getOrDefault(permissionName, 0);
return isFromSplitPermission(permissionFlag, targetSdkVersion);
}
/** Returns if the app is targeting SDK 35 and requesting the given permission. */
private boolean isAppRequestingPermissionWithOutdatedTargetSdk(
String packageName, UserHandle userHandle, String permission, int buildVersion) {
if (!Flags.replaceBodySensorPermissionEnabled()) {
return false;
}
// Only applies if current build is the same or newer than build version.
if (Build.VERSION.SDK_INT < buildVersion) {
return false;
}
PackageInfo packageInfo;
try {
packageInfo =
PackageInfoUtils.getPackageInfoUnchecked(
packageName,
userHandle,
PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS),
mContext);
} catch (IllegalArgumentException e) {
// If the package can't be found, default to consider as not containing split
// permission.
return false;
}
// If the app is targeting the given build version or newer, then they
// are not using an outdated target SDK.
if (packageInfo.applicationInfo.targetSdkVersion >= buildVersion) {
return false;
}
Set requestedPermissions = new ArraySet<>(packageInfo.requestedPermissions);
return requestedPermissions.contains(permission);
}
private void throwExceptionIncorrectPermissionState() {
throw new IllegalStateException(
"Incorrect health permission state, likely"
+ " because the calling application's manifest does not specify handling "
+ Intent.ACTION_VIEW_PERMISSION_USAGE
+ " with "
+ HealthConnectManager.CATEGORY_HEALTH_PERMISSIONS);
}
private void addToPriorityListIfRequired(
String packageName, String permissionName, UserHandle user) {
if (mHealthConnectMappings.isWritePermission(permissionName)) {
mHealthDataCategoryPriorityHelper.appendToPriorityList(
packageName,
mHealthConnectMappings.getHealthDataCategoryForWritePermission(permissionName),
user);
}
}
private void removeFromPriorityListIfRequired(
String packageName, String permissionName, UserHandle user) {
if (mHealthConnectMappings.isWritePermission(permissionName)) {
mHealthDataCategoryPriorityHelper.maybeRemoveAppFromPriorityList(
packageName,
mHealthConnectMappings.getHealthDataCategoryForWritePermission(permissionName),
user);
}
}
private Map getHealthPermissionsFlagsUnchecked(
String packageName, UserHandle user, List permissions) {
enforceValidHealthPermissions(packageName, user, permissions);
Map result = new ArrayMap<>();
for (String permission : permissions) {
result.put(
permission, mPackageManager.getPermissionFlags(permission, packageName, user));
}
return result;
}
private void setHealthPermissionsUserFixedFlagValueUnchecked(
String packageName, UserHandle user, List permissions, boolean value) {
enforceValidHealthPermissions(packageName, user, permissions);
int flagMask = PackageManager.FLAG_PERMISSION_USER_FIXED;
int flagValues = value ? PackageManager.FLAG_PERMISSION_USER_FIXED : 0;
for (String permission : permissions) {
mPackageManager.updatePermissionFlags(
permission, packageName, flagMask, flagValues, user);
}
}
private boolean revokeAllHealthPermissionsUnchecked(
String packageName, UserHandle user, @Nullable String reason) {
List grantedHealthPermissions =
PackageInfoUtils.getGrantedHealthPermissions(mContext, packageName, user);
for (String perm : grantedHealthPermissions) {
revokeRuntimePermission(packageName, user, perm, reason);
mPackageManager.updatePermissionFlags(
perm,
packageName,
MASK_PERMISSION_FLAGS,
PackageManager.FLAG_PERMISSION_USER_SET,
user);
removeFromPriorityListIfRequired(packageName, perm, user);
}
boolean revoked = !grantedHealthPermissions.isEmpty();
// If for legacy app, automatically deny BODY_SENSORS and BACKGROUND.
if (isAppRequestingPermissionWithOutdatedTargetSdk(
packageName,
user,
android.Manifest.permission.BODY_SENSORS,
Build.VERSION_CODES.BAKLAVA)) {
revoked |=
revokeRuntimePermissionAndUpdateFlags(
packageName,
user,
android.Manifest.permission.BODY_SENSORS,
PackageManager.FLAG_PERMISSION_USER_SET,
reason);
}
if (isAppRequestingPermissionWithOutdatedTargetSdk(
packageName,
user,
android.Manifest.permission.BODY_SENSORS_BACKGROUND,
Build.VERSION_CODES.BAKLAVA)) {
revoked |=
revokeRuntimePermissionAndUpdateFlags(
packageName,
user,
android.Manifest.permission.BODY_SENSORS_BACKGROUND,
PackageManager.FLAG_PERMISSION_USER_SET,
reason);
}
return revoked;
}
private void revokeRuntimePermission(
String packageName, UserHandle user, String permission, @Nullable String reason) {
mPackageManager.revokeRuntimePermission(
packageName, permission, user, reason == null ? UNKNOWN_REASON : reason);
}
private void enforceValidHealthPermission(String permissionName) {
if (!HealthConnectManager.getHealthPermissions(mContext).contains(permissionName)) {
throw new IllegalArgumentException("invalid health permission");
}
}
private void enforceValidPackage(String packageName, UserHandle user) {
PackageInfoUtils.getPackageInfoUnchecked(
packageName, user, PackageManager.PackageInfoFlags.of(0), mContext);
}
private void enforceManageHealthPermissions(String message) {
mContext.enforceCallingOrSelfPermission(
HealthPermissions.MANAGE_HEALTH_PERMISSIONS, message);
}
private void enforceSupportPermissionsUsageIntent(
String packageName, UserHandle userHandle, String permission) {
if (!shouldEnforcePermissionUsageIntent(packageName, userHandle, permission)) {
return;
}
if (!mPermissionIntentAppsTracker.supportsPermissionUsageIntent(packageName, userHandle)) {
throw new SecurityException(
"Package "
+ packageName
+ " for "
+ userHandle.toString()
+ " doesn't support health permissions usage intent.");
}
}
/**
* Checks input user id and converts it to positive id if needed, returns converted user id.
*
* @throws java.lang.SecurityException if the caller is affecting different users without
* holding the {@link INTERACT_ACROSS_USERS_FULL} permission.
*/
private int handleIncomingUser(int userId) {
int callingUserId = UserHandle.getUserHandleForUid(Binder.getCallingUid()).getIdentifier();
if (userId == callingUserId) {
return userId;
}
boolean canInteractAcrossUsersFull =
mContext.checkCallingOrSelfPermission(INTERACT_ACROSS_USERS_FULL)
== PERMISSION_GRANTED;
if (canInteractAcrossUsersFull) {
// If the UserHandle.CURRENT has been passed (negative value),
// convert it to positive userId.
if (userId == UserHandle.CURRENT.getIdentifier()) {
return ActivityManager.getCurrentUser();
}
return userId;
}
throw new SecurityException(
"Permission denied. Need to run as either the calling user id ("
+ callingUserId
+ "), or with "
+ INTERACT_ACROSS_USERS_FULL
+ " permission");
}
private void enforceValidHealthPermissions(
String packageName, UserHandle user, List permissions) {
PackageInfo packageInfo =
PackageInfoUtils.getPackageInfoUnchecked(
packageName,
user,
PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS),
mContext);
Set requestedPermissions = new ArraySet<>(packageInfo.requestedPermissions);
for (String permission : permissions) {
if (!requestedPermissions.contains(permission)) {
throw new IllegalArgumentException(
"undeclared permission " + permission + " for package " + packageName);
}
enforceValidHealthPermission(permission);
}
}
/** Sync permission granted status and flag between BODY_SENSORS and READ_HEART_RATE. */
private void grantRuntimePermissionAndUpdateFlags(
String packageName, UserHandle user, String legacyPermission, int permissionFlags) {
mPackageManager.grantRuntimePermission(packageName, legacyPermission, user);
mPackageManager.updatePermissionFlags(
legacyPermission, packageName, MASK_PERMISSION_FLAGS, permissionFlags, user);
}
/** Sync permission denied status and flag between BODY_SENSORS and READ_HEART_RATE. */
private boolean revokeRuntimePermissionAndUpdateFlags(
String packageName,
UserHandle user,
String legacyPermission,
int permissionFlags,
@Nullable String reason) {
boolean revoked = false;
if (mPackageManager.checkPermission(legacyPermission, packageName)
!= PackageManager.PERMISSION_DENIED) {
mPackageManager.revokeRuntimePermission(packageName, legacyPermission, user, reason);
revoked = true;
}
mPackageManager.updatePermissionFlags(
legacyPermission, packageName, MASK_PERMISSION_FLAGS, permissionFlags, user);
return revoked;
}
/**
* Returns legacy body sensor permission for split heart rate permission.
*
* @throws IllegalArgumentException if {@code permissionName} is neither READ_HEART_RATE nor
* READ_HEALTH_DATE_IN_BACKGROUND
*/
private static String toLegacyPermission(String permissionName)
throws IllegalArgumentException {
if (permissionName.equals(READ_HEART_RATE)) {
return android.Manifest.permission.BODY_SENSORS;
}
if (permissionName.equals(READ_HEALTH_DATA_IN_BACKGROUND)) {
return android.Manifest.permission.BODY_SENSORS_BACKGROUND;
}
throw new IllegalArgumentException(
"toLegacyPermission() encounters unexpected permission "
+ permissionName
+ ", should be one of READ_HEART_RATE and READ_HEALTH_DATA_IN_BACKGROUND");
}
}