• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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