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