• 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 android.annotation.NonNull;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.content.pm.ResolveInfo;
24 import android.health.connect.Constants;
25 import android.health.connect.HealthConnectManager;
26 import android.os.UserHandle;
27 import android.os.UserManager;
28 import android.util.ArraySet;
29 import android.util.Log;
30 
31 import com.android.internal.annotations.GuardedBy;
32 
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37 
38 /**
39  * Tracks apps which support {@link android.content.Intent#ACTION_VIEW_PERMISSION_USAGE} with {@link
40  * HealthConnectManager#CATEGORY_HEALTH_PERMISSIONS}.
41  *
42  * @hide
43  */
44 public class HealthPermissionIntentAppsTracker {
45     private static final String TAG = "HealthPermIntentTracker";
46     private static final Intent HEALTH_PERMISSIONS_USAGE_INTENT = getHealthPermissionsUsageIntent();
47 
48     private final PackageManager mPackageManager;
49     private final Object mLock = new Object();
50 
51     @GuardedBy("mLock")
52     private Map<UserHandle, Set<String>> mUserToHealthPackageNamesMap;
53 
HealthPermissionIntentAppsTracker(@onNull Context context)54     public HealthPermissionIntentAppsTracker(@NonNull Context context) {
55         mPackageManager = context.getPackageManager();
56         initPerUserMapping(context);
57     }
58 
59     /**
60      * Checks if the given app supports {@link android.content.Intent#ACTION_VIEW_PERMISSION_USAGE}
61      * with {@link HealthConnectManager#CATEGORY_HEALTH_PERMISSIONS}
62      *
63      * @param packageName: name of the package to check
64      * @param userHandle: the user to query
65      */
supportsPermissionUsageIntent( @onNull String packageName, @NonNull UserHandle userHandle)66     boolean supportsPermissionUsageIntent(
67             @NonNull String packageName, @NonNull UserHandle userHandle) {
68         // Consider readWrite lock if this is performance bottleneck.
69         synchronized (mLock) {
70             if (!mUserToHealthPackageNamesMap.containsKey(userHandle)) {
71                 Log.w(
72                         TAG,
73                         "Requested user handle: "
74                                 + userHandle.toString()
75                                 + " is not present in the state.");
76                 return false;
77             }
78 
79             if (!mUserToHealthPackageNamesMap.get(userHandle).contains(packageName)) {
80                 updateStateAndGetIfIntentWasRemoved(packageName, userHandle);
81             }
82 
83             return mUserToHealthPackageNamesMap.get(userHandle).contains(packageName);
84         }
85     }
86 
87     /**
88      * Updates package state if needed, returns whether activity for {@link
89      * android.content.Intent#ACTION_VIEW_PERMISSION_USAGE} with {@link
90      * HealthConnectManager#CATEGORY_HEALTH_PERMISSIONS} support has been disabled/removed.
91      */
updateStateAndGetIfIntentWasRemoved( @onNull String packageNameToUpdate, @NonNull UserHandle userHandle)92     boolean updateStateAndGetIfIntentWasRemoved(
93             @NonNull String packageNameToUpdate, @NonNull UserHandle userHandle) {
94         synchronized (mLock) {
95             if (!mUserToHealthPackageNamesMap.containsKey(userHandle)) {
96                 mUserToHealthPackageNamesMap.put(userHandle, new ArraySet<>());
97             }
98         }
99 
100         Intent permissionPackageUsageIntent = getHealthPermissionsUsageIntent();
101         permissionPackageUsageIntent.setPackage(packageNameToUpdate);
102         boolean removedIntent = false;
103         if (!mPackageManager
104                 .queryIntentActivitiesAsUser(
105                         permissionPackageUsageIntent,
106                         PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL),
107                         userHandle)
108                 .isEmpty()) {
109             synchronized (mLock) {
110                 mUserToHealthPackageNamesMap.get(userHandle).add(packageNameToUpdate);
111             }
112         } else {
113             synchronized (mLock) {
114                 removedIntent =
115                         mUserToHealthPackageNamesMap.get(userHandle).remove(packageNameToUpdate);
116             }
117         }
118         logStateIfDebugMode(userHandle);
119         return removedIntent;
120     }
121 
getHealthPermissionsUsageIntent()122     private static Intent getHealthPermissionsUsageIntent() {
123         Intent healthIntent = new Intent(Intent.ACTION_VIEW_PERMISSION_USAGE);
124         healthIntent.addCategory(HealthConnectManager.CATEGORY_HEALTH_PERMISSIONS);
125         return healthIntent;
126     }
127 
initPerUserMapping(Context context)128     private void initPerUserMapping(Context context) {
129         synchronized (mLock) {
130             mUserToHealthPackageNamesMap = new HashMap<>();
131         }
132         List<UserHandle> userHandles =
133                 context.getSystemService(UserManager.class)
134                         .getUserHandles(/* excludeDying= */ true);
135         for (UserHandle userHandle : userHandles) {
136             initPackageSetForUser(userHandle);
137         }
138     }
139 
140     /** Update list of health apps for given user. */
initPackageSetForUser(@onNull UserHandle userHandle)141     private void initPackageSetForUser(@NonNull UserHandle userHandle) {
142         List<ResolveInfo> healthAppInfos = getHealthIntentSupportiveAppsForUser(userHandle);
143         Set<String> healthApps = new ArraySet<String>(healthAppInfos.size());
144         for (ResolveInfo info : healthAppInfos) {
145             String packageName = extractPackageName(info);
146             if (packageName != null) {
147                 healthApps.add(packageName);
148             }
149         }
150         synchronized (mLock) {
151             mUserToHealthPackageNamesMap.put(userHandle, healthApps);
152         }
153         logStateIfDebugMode(userHandle);
154     }
155 
getHealthIntentSupportiveAppsForUser(@onNull UserHandle userHandle)156     private List<ResolveInfo> getHealthIntentSupportiveAppsForUser(@NonNull UserHandle userHandle) {
157         return mPackageManager.queryIntentActivitiesAsUser(
158                 HEALTH_PERMISSIONS_USAGE_INTENT,
159                 PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL),
160                 userHandle);
161     }
162 
extractPackageName(ResolveInfo info)163     private String extractPackageName(ResolveInfo info) {
164         if (info == null
165                 || info.activityInfo == null
166                 || info.activityInfo.applicationInfo == null) {
167             Log.w(TAG, "Can't fetch application info from resolve info.");
168             return null;
169         }
170         return info.activityInfo.applicationInfo.packageName;
171     }
172 
logStateIfDebugMode(@onNull UserHandle userHandle)173     private void logStateIfDebugMode(@NonNull UserHandle userHandle) {
174         if (Constants.DEBUG) {
175             Log.d(TAG, "State for user: " + userHandle.getIdentifier());
176             synchronized (mLock) {
177                 Log.d(TAG, mUserToHealthPackageNamesMap.toString());
178             }
179         }
180     }
181 }
182