1 /*
2  * Copyright (C) 2021 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.car.watchdog;
18 
19 import android.annotation.Nullable;
20 import android.automotive.watchdog.internal.ApplicationCategoryType;
21 import android.automotive.watchdog.internal.ComponentType;
22 import android.automotive.watchdog.internal.PackageIdentifier;
23 import android.automotive.watchdog.internal.PackageInfo;
24 import android.automotive.watchdog.internal.UidType;
25 import android.car.builtin.content.pm.PackageManagerHelper;
26 import android.car.builtin.util.Slogf;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageManager;
29 import android.os.Process;
30 import android.os.UserHandle;
31 import android.util.ArrayMap;
32 import android.util.ArraySet;
33 import android.util.SparseArray;
34 import android.util.SparseBooleanArray;
35 
36 import com.android.car.CarLog;
37 import com.android.car.internal.util.IntArray;
38 import com.android.internal.annotations.GuardedBy;
39 
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.List;
43 
44 /** Handles package info resolving */
45 public final class PackageInfoHandler {
46     public static final String SHARED_PACKAGE_PREFIX = "shared:";
47 
48     private static final String TAG = CarLog.tagFor(PackageInfoHandler.class);
49 
50     private final PackageManager mPackageManager;
51     private final Object mLock = new Object();
52     @GuardedBy("mLock")
53     private final SparseArray<String> mGenericPackageNameByUid = new SparseArray<>();
54     @GuardedBy("mLock")
55     private final SparseArray<List<String>> mPackagesBySharedUid = new SparseArray<>();
56     @GuardedBy("mLock")
57     private final ArrayMap<String, String> mGenericPackageNameByPackage = new ArrayMap<>();
58     @GuardedBy("mLock")
59     private final SparseArray<ArraySet<String>> mGenericPackageNamesByComponentType =
60             new SparseArray<>();
61     @GuardedBy("mLock")
62     private List<String> mVendorPackagePrefixes = new ArrayList<>();
63 
PackageInfoHandler(PackageManager packageManager)64     public PackageInfoHandler(PackageManager packageManager) {
65         mPackageManager = packageManager;
66     }
67 
68     /**
69      * Returns the generic package names for the given UIDs.
70      *
71      * <p>Some UIDs may not have names. This may occur when a UID is being removed and the
72      * internal data structures are not up-to-date. The caller should handle it.
73      */
getNamesForUids(int[] uids)74     public SparseArray<String> getNamesForUids(int[] uids) {
75         IntArray unmappedUids = new IntArray(uids.length);
76         SparseArray<String> genericPackageNameByUid = new SparseArray<>();
77         synchronized (mLock) {
78             for (int uid : uids) {
79                 String genericPackageName = mGenericPackageNameByUid.get(uid, null);
80                 if (genericPackageName != null) {
81                     genericPackageNameByUid.append(uid, genericPackageName);
82                 } else {
83                     unmappedUids.add(uid);
84                 }
85             }
86         }
87         if (unmappedUids.size() == 0) {
88             return genericPackageNameByUid;
89         }
90         String[] genericPackageNames = PackageManagerHelper.getNamesForUids(mPackageManager,
91                 unmappedUids.toArray());
92         synchronized (mLock) {
93             for (int i = 0; i < unmappedUids.size(); ++i) {
94                 if (genericPackageNames[i] == null || genericPackageNames[i].isEmpty()) {
95                     continue;
96                 }
97                 int uid = unmappedUids.get(i);
98                 String genericPackageName = genericPackageNames[i];
99                 mGenericPackageNameByUid.append(uid, genericPackageName);
100                 genericPackageNameByUid.append(uid, genericPackageName);
101                 mGenericPackageNameByPackage.put(genericPackageName, genericPackageName);
102                 if (!genericPackageName.startsWith(SHARED_PACKAGE_PREFIX)) {
103                     continue;
104                 }
105                 populateSharedPackagesLocked(uid, genericPackageName);
106             }
107         }
108         return genericPackageNameByUid;
109     }
110 
111     /**
112      * Returns the generic package name for the user package.
113      *
114      * <p>Returns null when no generic package name is found.
115      */
116     @Nullable
getNameForUserPackage(String packageName, int userId)117     public String getNameForUserPackage(String packageName, int userId) {
118         synchronized (mLock) {
119             String genericPackageName = mGenericPackageNameByPackage.get(packageName);
120             if (genericPackageName != null) {
121                 return genericPackageName;
122             }
123         }
124         try {
125             return getNameForPackage(PackageManagerHelper.getPackageInfoAsUser(
126                     mPackageManager, packageName, /* packageInfoFlags= */ 0, userId));
127         } catch (PackageManager.NameNotFoundException e) {
128             Slogf.e(TAG, "Package '%s' not found for user %d: %s", packageName, userId, e);
129         }
130         return null;
131     }
132 
133     /** Returns the packages owned by the shared UID */
getPackagesForUid(int uid, String genericPackageName)134     public List<String> getPackagesForUid(int uid, String genericPackageName) {
135         synchronized (mLock) {
136             /* When fetching the packages under a shared UID update the internal DS. This will help
137              * capture any recently installed packages.
138              */
139             populateSharedPackagesLocked(uid, genericPackageName);
140             return mPackagesBySharedUid.get(uid);
141         }
142     }
143 
144     /** Returns the generic package name for the given package info. */
getNameForPackage(android.content.pm.PackageInfo packageInfo)145     public String getNameForPackage(android.content.pm.PackageInfo packageInfo) {
146         synchronized (mLock) {
147             String genericPackageName = mGenericPackageNameByPackage.get(packageInfo.packageName);
148             if (genericPackageName != null) {
149                 return genericPackageName;
150             }
151             if (packageInfo.sharedUserId != null) {
152                 populateSharedPackagesLocked(packageInfo.applicationInfo.uid,
153                         SHARED_PACKAGE_PREFIX + packageInfo.sharedUserId);
154                 return SHARED_PACKAGE_PREFIX + packageInfo.sharedUserId;
155             }
156             mGenericPackageNameByPackage.put(packageInfo.packageName, packageInfo.packageName);
157             return packageInfo.packageName;
158         }
159     }
160 
161     /**
162      * Returns the internal package infos for the given UIDs.
163      *
164      * <p>Some UIDs may not have package infos. This may occur when a UID is being removed and the
165      * internal data structures are not up-to-date. The caller should handle it.
166      */
getPackageInfosForUids(int[] uids, List<String> vendorPackagePrefixes)167     public List<PackageInfo> getPackageInfosForUids(int[] uids,
168             List<String> vendorPackagePrefixes) {
169         setVendorPackagePrefixes(vendorPackagePrefixes);
170         SparseArray<String> genericPackageNameByUid = getNamesForUids(uids);
171         ArrayList<PackageInfo> packageInfos = new ArrayList<>(genericPackageNameByUid.size());
172         for (int i = 0; i < genericPackageNameByUid.size(); ++i) {
173             packageInfos.add(getPackageInfo(genericPackageNameByUid.keyAt(i),
174                     genericPackageNameByUid.valueAt(i)));
175         }
176         return packageInfos;
177     }
178 
179     /** Returns component type for the given uid and package name. */
getComponentType(int uid, String genericPackageName)180     public @ComponentType int getComponentType(int uid, String genericPackageName) {
181         synchronized (mLock) {
182             for (int i = 0; i < mGenericPackageNamesByComponentType.size(); ++i) {
183                 if (mGenericPackageNamesByComponentType.valueAt(i).contains(genericPackageName)) {
184                     return mGenericPackageNamesByComponentType.keyAt(i);
185                 }
186             }
187         }
188         int componentType = ComponentType.UNKNOWN;
189         if (genericPackageName.startsWith(SHARED_PACKAGE_PREFIX)) {
190             synchronized (mLock) {
191                 if (!mPackagesBySharedUid.contains(uid)) {
192                     populateSharedPackagesLocked(uid, genericPackageName);
193                 }
194                 List<String> packages = mPackagesBySharedUid.get(uid);
195                 if (packages != null) {
196                     componentType = getSharedComponentTypeInternal(
197                             UserHandle.getUserHandleForUid(uid), packages, genericPackageName);
198                 }
199             }
200         } else {
201             componentType = getUserPackageComponentType(
202                     UserHandle.getUserHandleForUid(uid), genericPackageName);
203         }
204         if (componentType != ComponentType.UNKNOWN) {
205             cachePackageComponentType(genericPackageName, componentType);
206         }
207         return componentType;
208     }
209 
210     @GuardedBy("mLock")
populateSharedPackagesLocked(int uid, String genericPackageName)211     private void populateSharedPackagesLocked(int uid, String genericPackageName) {
212         String[] packages = mPackageManager.getPackagesForUid(uid);
213         for (String pkg : packages) {
214             mGenericPackageNameByPackage.put(pkg, genericPackageName);
215         }
216         mPackagesBySharedUid.put(uid, Arrays.asList(packages));
217     }
218 
getPackageInfo(int uid, String genericPackageName)219     private PackageInfo getPackageInfo(int uid, String genericPackageName) {
220         PackageInfo packageInfo = new PackageInfo();
221         packageInfo.packageIdentifier = new PackageIdentifier();
222         packageInfo.packageIdentifier.uid = uid;
223         packageInfo.packageIdentifier.name = genericPackageName;
224         packageInfo.sharedUidPackages = new ArrayList<>();
225         packageInfo.componentType = getComponentType(uid, genericPackageName);
226         /* Application category type mapping is handled on the daemon side. */
227         packageInfo.appCategoryType = ApplicationCategoryType.OTHERS;
228         int appId = UserHandle.getAppId(uid);
229         packageInfo.uidType = appId >= Process.FIRST_APPLICATION_UID ? UidType.APPLICATION :
230                 UidType.NATIVE;
231 
232         if (genericPackageName.startsWith(SHARED_PACKAGE_PREFIX)) {
233             synchronized (mLock) {
234                 List<String> packages = mPackagesBySharedUid.get(uid);
235                 if (packages != null) {
236                     packageInfo.sharedUidPackages = new ArrayList<>(packages);
237                 }
238             }
239         }
240         return packageInfo;
241     }
242 
243     /**
244      * Returns the most restrictive component type shared by the given application infos.
245      *
246      * A shared UID has multiple packages associated with it and these packages may be
247      * mapped to different component types. Thus map the shared UID to the most restrictive
248      * component type.
249      */
getSharedComponentType( List<ApplicationInfo> applicationInfos, String genericPackageName)250     public @ComponentType int getSharedComponentType(
251             List<ApplicationInfo> applicationInfos, String genericPackageName) {
252         SparseBooleanArray seenComponents = new SparseBooleanArray();
253         for (int i = 0; i < applicationInfos.size(); ++i) {
254             int type = getComponentType(applicationInfos.get(i));
255             seenComponents.put(type, true);
256         }
257         if (seenComponents.get(ComponentType.VENDOR)) {
258             return ComponentType.VENDOR;
259         } else if (seenComponents.get(ComponentType.SYSTEM)) {
260             synchronized (mLock) {
261                 for (int i = 0; i < mVendorPackagePrefixes.size(); ++i) {
262                     if (genericPackageName.startsWith(mVendorPackagePrefixes.get(i))) {
263                         return ComponentType.VENDOR;
264                     }
265                 }
266             }
267             return ComponentType.SYSTEM;
268         } else if (seenComponents.get(ComponentType.THIRD_PARTY)) {
269             return ComponentType.THIRD_PARTY;
270         }
271         return ComponentType.UNKNOWN;
272     }
273 
getUserPackageComponentType( UserHandle userHandle, String packageName)274     private @ComponentType int getUserPackageComponentType(
275             UserHandle userHandle, String packageName) {
276         try {
277             ApplicationInfo info = mPackageManager.getApplicationInfoAsUser(
278                     packageName, /* flags= */ 0, userHandle);
279             return getComponentType(info);
280         } catch (PackageManager.NameNotFoundException e) {
281             Slogf.e(TAG, e, "Package '%s' not found for user %d", packageName,
282                     userHandle.getIdentifier());
283         }
284         return ComponentType.UNKNOWN;
285     }
286 
getSharedComponentTypeInternal( UserHandle userHandle, List<String> packages, String genericPackageName)287     private @ComponentType int getSharedComponentTypeInternal(
288             UserHandle userHandle, List<String> packages, String genericPackageName) {
289         List<ApplicationInfo> applicationInfos = new ArrayList<>();
290         for (int i = 0; i < packages.size(); ++i) {
291             try {
292                 applicationInfos.add(mPackageManager.getApplicationInfoAsUser(
293                         packages.get(i), /* flags= */ 0, userHandle));
294             } catch (PackageManager.NameNotFoundException e) {
295                 Slogf.w(TAG, "Package '%s' not found for user %d", packages.get(i),
296                         userHandle.getIdentifier());
297             }
298         }
299         return getSharedComponentType(applicationInfos, genericPackageName);
300     }
301 
302     /** Returns the component type for the given application info. */
getComponentType(ApplicationInfo applicationInfo)303     public int getComponentType(ApplicationInfo applicationInfo) {
304         if (PackageManagerHelper.isOemApp(applicationInfo)
305                 || PackageManagerHelper.isVendorApp(applicationInfo)
306                 || PackageManagerHelper.isOdmApp(applicationInfo)) {
307             return ComponentType.VENDOR;
308         }
309         if (PackageManagerHelper.isSystemApp(applicationInfo)
310                 || PackageManagerHelper.isUpdatedSystemApp(applicationInfo)
311                 || PackageManagerHelper.isProductApp(applicationInfo)
312                 || PackageManagerHelper.isSystemExtApp(applicationInfo)) {
313             synchronized (mLock) {
314                 for (int i = 0; i < mVendorPackagePrefixes.size(); ++i) {
315                     if (applicationInfo.packageName.startsWith(mVendorPackagePrefixes.get(i))) {
316                         return ComponentType.VENDOR;
317                     }
318                 }
319             }
320             return ComponentType.SYSTEM;
321         }
322         return ComponentType.THIRD_PARTY;
323     }
324 
setVendorPackagePrefixes(List<String> vendorPackagePrefixes)325     void setVendorPackagePrefixes(List<String> vendorPackagePrefixes) {
326         synchronized (mLock) {
327             if (mVendorPackagePrefixes.equals(vendorPackagePrefixes)) {
328                 return;
329             }
330             mVendorPackagePrefixes = vendorPackagePrefixes;
331             // When the vendor package prefixes change due to config update, the component types
332             // for these packages also change. Ergo, clear the component type cache, so the
333             // component types can be inferred again.
334             mGenericPackageNamesByComponentType.clear();
335         }
336     }
337 
cachePackageComponentType(String genericPackageName, @ComponentType int componentType)338     private void cachePackageComponentType(String genericPackageName,
339             @ComponentType int componentType) {
340         synchronized (mLock) {
341             ArraySet<String> packages = mGenericPackageNamesByComponentType.get(componentType);
342             if (packages == null) {
343                 packages = new ArraySet<>();
344             }
345             packages.add(genericPackageName);
346             mGenericPackageNamesByComponentType.append(componentType, packages);
347         }
348     }
349 }
350