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.content.pm.PackageManager.GET_PERMISSIONS;
20 
21 import static java.util.Objects.requireNonNull;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.content.Context;
26 import android.content.pm.PackageInfo;
27 import android.content.pm.PackageManager;
28 import android.health.connect.HealthConnectManager;
29 import android.os.UserHandle;
30 import android.util.ArrayMap;
31 import android.util.ArraySet;
32 import android.util.Log;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Optional;
40 import java.util.Set;
41 
42 /**
43  * Utility class with PackageInfo-related methods for {@link FirstGrantTimeManager}
44  *
45  * @hide
46  */
47 public final class PackageInfoUtils {
48     private static final String TAG = "HealthConnectPackageInfoUtils";
49 
50     @Nullable private static volatile PackageInfoUtils sPackageInfoUtils;
51 
52     /**
53      * Store PackageManager for each user. Keys are users, values are PackageManagers which get from
54      * each user.
55      */
56     private final Map<UserHandle, PackageManager> mUsersPackageManager = new ArrayMap<>();
57 
PackageInfoUtils()58     private PackageInfoUtils() {}
59 
60     @NonNull
getInstance()61     public static synchronized PackageInfoUtils getInstance() {
62         if (sPackageInfoUtils == null) {
63             sPackageInfoUtils = new PackageInfoUtils();
64         }
65 
66         return requireNonNull(sPackageInfoUtils);
67     }
68 
69     /** Reset singleton instance {@link PackageInfoUtils}. */
clearInstance()70     public static void clearInstance() {
71         sPackageInfoUtils = null;
72     }
73 
74     /** Set an instance of {@link PackageInfoUtils} for testing purposes, such as a mock. */
75     @VisibleForTesting
setInstanceForTest(@onNull PackageInfoUtils instance)76     static synchronized void setInstanceForTest(@NonNull PackageInfoUtils instance) {
77         sPackageInfoUtils = instance;
78     }
79 
80     @NonNull
collectSharedUserNameToUidsMappingForUser( @onNull List<PackageInfo> packageInfos, UserHandle user)81     Map<String, Set<Integer>> collectSharedUserNameToUidsMappingForUser(
82             @NonNull List<PackageInfo> packageInfos, UserHandle user) {
83         Map<String, Set<Integer>> sharedUserNameToUids = new ArrayMap<>();
84         for (PackageInfo info : packageInfos) {
85             if (info.sharedUserId != null) {
86                 if (sharedUserNameToUids.get(info.sharedUserId) == null) {
87                     sharedUserNameToUids.put(info.sharedUserId, new ArraySet<>());
88                 }
89                 sharedUserNameToUids.get(info.sharedUserId).add(info.applicationInfo.uid);
90             }
91         }
92         return sharedUserNameToUids;
93     }
94 
95     @NonNull
getPackagesHoldingHealthPermissions(UserHandle user, Context context)96     public List<PackageInfo> getPackagesHoldingHealthPermissions(UserHandle user, Context context) {
97         // TODO(b/260707328): replace with getPackagesHoldingPermissions
98         List<PackageInfo> allInfos =
99                 getPackageManagerAsUser(user, context)
100                         .getInstalledPackages(PackageManager.PackageInfoFlags.of(GET_PERMISSIONS));
101         List<PackageInfo> healthAppsInfos = new ArrayList<>();
102 
103         for (PackageInfo info : allInfos) {
104             if (anyRequestedHealthPermissionGranted(context, info)) {
105                 healthAppsInfos.add(info);
106             }
107         }
108         return healthAppsInfos;
109     }
110 
111     @SuppressWarnings("NullAway")
112     // TODO(b/317029272): fix this suppression
hasGrantedHealthPermissions( @onNull String[] packageNames, @NonNull UserHandle user, @NonNull Context context)113     boolean hasGrantedHealthPermissions(
114             @NonNull String[] packageNames, @NonNull UserHandle user, @NonNull Context context) {
115         for (String packageName : packageNames) {
116             PackageInfo info = getPackageInfoWithPermissionsAsUser(packageName, user, context);
117             if (anyRequestedHealthPermissionGranted(context, info)) {
118                 return true;
119             }
120         }
121         return false;
122     }
123 
124     @Nullable
getPackagesForUid(int packageUid, @NonNull UserHandle user, @NonNull Context context)125     String[] getPackagesForUid(int packageUid, @NonNull UserHandle user, @NonNull Context context) {
126         return getPackageManagerAsUser(user, context).getPackagesForUid(packageUid);
127     }
128 
129     /**
130      * Checks if the given package had any read/write permissions to Health Connect.
131      *
132      * @param context Context
133      * @param packageInfo Package to check
134      * @return If the given package is connected to Health Connect.
135      */
anyRequestedHealthPermissionGranted( @ullable Context context, @Nullable PackageInfo packageInfo)136     public static boolean anyRequestedHealthPermissionGranted(
137             @Nullable Context context, @Nullable PackageInfo packageInfo) {
138         if (context == null || packageInfo == null || packageInfo.requestedPermissions == null) {
139             Log.w(TAG, "Can't extract requested permissions from the package info.");
140             return false;
141         }
142 
143         for (int i = 0; i < packageInfo.requestedPermissions.length; i++) {
144             String currPerm = packageInfo.requestedPermissions[i];
145             if (HealthConnectManager.isHealthPermission(context, currPerm)
146                     && ((packageInfo.requestedPermissionsFlags[i]
147                                     & PackageInfo.REQUESTED_PERMISSION_GRANTED)
148                             != 0)) {
149                 return true;
150             }
151         }
152         return false;
153     }
154 
155     @Nullable
getPackageInfoWithPermissionsAsUser( @onNull String packageName, @NonNull UserHandle user, @NonNull Context context)156     public PackageInfo getPackageInfoWithPermissionsAsUser(
157             @NonNull String packageName, @NonNull UserHandle user, @NonNull Context context) {
158         try {
159             return getPackageManagerAsUser(user, context)
160                     .getPackageInfo(
161                             packageName, PackageManager.PackageInfoFlags.of(GET_PERMISSIONS));
162         } catch (PackageManager.NameNotFoundException e) {
163             // App not found.
164             Log.e(TAG, "NameNotFoundException for " + packageName);
165             return null;
166         }
167     }
168 
169     @Nullable
getSharedUserNameFromUid(int uid, Context context)170     String getSharedUserNameFromUid(int uid, Context context) {
171         UserHandle user = UserHandle.getUserHandleForUid(uid);
172         PackageManager packageManager = getPackageManagerAsUser(user, context);
173         String[] packages = packageManager.getPackagesForUid(uid);
174         if (packages == null || packages.length == 0) {
175             Log.e(TAG, "Can't get package names for UID: " + uid);
176             return null;
177         }
178         try {
179             PackageInfo info =
180                     packageManager.getPackageInfo(
181                             packages[0], PackageManager.PackageInfoFlags.of(0));
182             return info.sharedUserId;
183         } catch (PackageManager.NameNotFoundException e) {
184             Log.e(TAG, "Package " + packages[0] + " not found.");
185             return null;
186         }
187     }
188 
getPackageNameFromUid(int uid)189     Optional<String> getPackageNameFromUid(int uid) {
190         String[] packages = getPackageNamesForUid(uid);
191         if (packages.length != 1) {
192             Log.w(TAG, "Can't get one package name for UID: " + uid);
193             return Optional.empty();
194         }
195         return Optional.of(packages[0]);
196     }
197 
getPackageNamesForUid(int uid)198     String[] getPackageNamesForUid(int uid) {
199         PackageManager packageManager =
200                 mUsersPackageManager.get(UserHandle.getUserHandleForUid(uid));
201         if (packageManager == null) {
202             return new String[] {};
203         }
204         String[] packages = packageManager.getPackagesForUid(uid);
205         return packages != null ? packages : new String[] {};
206     }
207 
208     @Nullable
getPackageUid( @onNull String packageName, @NonNull UserHandle user, @NonNull Context context)209     Integer getPackageUid(
210             @NonNull String packageName, @NonNull UserHandle user, @NonNull Context context) {
211         Integer uid = null;
212         try {
213             uid =
214                     getPackageManagerAsUser(user, context)
215                             .getPackageUid(
216                                     packageName,
217                                     PackageManager.PackageInfoFlags.of(/* flags= */ 0));
218         } catch (PackageManager.NameNotFoundException e) {
219             Log.e(TAG, "NameNotFound exception for " + packageName);
220         }
221         return uid;
222     }
223 
224     @NonNull
getPackageManagerAsUser( @onNull UserHandle user, @NonNull Context context)225     private PackageManager getPackageManagerAsUser(
226             @NonNull UserHandle user, @NonNull Context context) {
227         PackageManager packageManager = mUsersPackageManager.get(user);
228         if (packageManager == null) {
229             packageManager = context.getPackageManager();
230             mUsersPackageManager.put(user, packageManager);
231         }
232         return packageManager;
233     }
234 }
235