• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.appsearch.appsindexer;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.appsearch.util.LogUtil;
22 import android.content.ContentResolver;
23 import android.content.Intent;
24 import android.content.pm.ActivityInfo;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.PackageInfo;
27 import android.content.pm.PackageManager;
28 import android.content.pm.ResolveInfo;
29 import android.content.pm.Signature;
30 import android.content.res.Resources;
31 import android.net.Uri;
32 import android.text.TextUtils;
33 import android.util.ArrayMap;
34 import android.util.Log;
35 
36 import com.android.server.appsearch.appsindexer.appsearchtypes.MobileApplication;
37 
38 import java.security.MessageDigest;
39 import java.security.NoSuchAlgorithmException;
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Objects;
44 
45 /** Utility class for pulling apps details from package manager. */
46 public final class AppsUtil {
47     public static final String TAG = "AppSearchAppsUtil";
48 
AppsUtil()49     private AppsUtil() {}
50 
51     /** Gets the resource Uri given a resource id. */
52     @NonNull
getResourceUri( @onNull PackageManager packageManager, @NonNull ApplicationInfo appInfo, int resourceId)53     private static Uri getResourceUri(
54             @NonNull PackageManager packageManager,
55             @NonNull ApplicationInfo appInfo,
56             int resourceId)
57             throws PackageManager.NameNotFoundException {
58         Objects.requireNonNull(packageManager);
59         Objects.requireNonNull(appInfo);
60         Resources resources = packageManager.getResourcesForApplication(appInfo);
61         String resPkg = resources.getResourcePackageName(resourceId);
62         String type = resources.getResourceTypeName(resourceId);
63         return makeResourceUri(appInfo.packageName, resPkg, type, resourceId);
64     }
65 
66     /**
67      * Appends the resource id instead of name to make the resource uri due to b/161564466. The
68      * resource names for some apps (e.g. Chrome) are obfuscated due to resource name collapsing, so
69      * we need to use resource id instead.
70      *
71      * @see Uri
72      */
73     @NonNull
makeResourceUri( @onNull String appPkg, @NonNull String resPkg, @NonNull String type, int resourceId)74     private static Uri makeResourceUri(
75             @NonNull String appPkg, @NonNull String resPkg, @NonNull String type, int resourceId) {
76         Objects.requireNonNull(appPkg);
77         Objects.requireNonNull(resPkg);
78         Objects.requireNonNull(type);
79 
80         // For more details on Android URIs, see the official Android documentation:
81         // https://developer.android.com/guide/topics/providers/content-provider-basics#ContentURIs
82         Uri.Builder uriBuilder = new Uri.Builder();
83         uriBuilder.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE);
84         uriBuilder.encodedAuthority(appPkg);
85         uriBuilder.appendEncodedPath(type);
86         if (!appPkg.equals(resPkg)) {
87             uriBuilder.appendEncodedPath(resPkg + ":" + resourceId);
88         } else {
89             uriBuilder.appendEncodedPath(String.valueOf(resourceId));
90         }
91         return uriBuilder.build();
92     }
93 
94     /**
95      * Gets the icon uri for the activity.
96      *
97      * @return the icon Uri string, or null if there is no icon resource.
98      */
99     @Nullable
getActivityIconUriString( @onNull PackageManager packageManager, @NonNull ActivityInfo activityInfo)100     private static String getActivityIconUriString(
101             @NonNull PackageManager packageManager, @NonNull ActivityInfo activityInfo) {
102         Objects.requireNonNull(packageManager);
103         Objects.requireNonNull(activityInfo);
104         int iconResourceId = activityInfo.getIconResource();
105         if (iconResourceId == 0) {
106             return null;
107         }
108 
109         try {
110             return getResourceUri(packageManager, activityInfo.applicationInfo, iconResourceId)
111                     .toString();
112         } catch (PackageManager.NameNotFoundException e) {
113             // If resources aren't found for the application, that is fine. We return null and
114             // handle it with getActivityIconUriString
115             return null;
116         }
117     }
118 
119     /**
120      * Gets {@link PackageInfo}s only for packages that have a launch activity, along with their
121      * corresponding {@link ResolveInfo}. This is useful for building schemas as well as determining
122      * which packages to set schemas for.
123      *
124      * @return a mapping of {@link PackageInfo}s with their corresponding {@link ResolveInfo} for
125      *     the packages launch activity.
126      * @see PackageManager#getInstalledPackages
127      * @see PackageManager#queryIntentActivities
128      */
129     @NonNull
getLaunchablePackages( @onNull PackageManager packageManager)130     public static Map<PackageInfo, ResolveInfo> getLaunchablePackages(
131             @NonNull PackageManager packageManager) {
132         Objects.requireNonNull(packageManager);
133         List<PackageInfo> packageInfos =
134                 packageManager.getInstalledPackages(
135                         PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES);
136         Map<PackageInfo, ResolveInfo> launchablePackages = new ArrayMap<>();
137         Intent intent = new Intent(Intent.ACTION_MAIN, null);
138         intent.addCategory(Intent.CATEGORY_LAUNCHER);
139         intent.setPackage(null);
140         List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
141         Map<String, ResolveInfo> packageNameToLauncher = new ArrayMap<>();
142         for (int i = 0; i < activities.size(); i++) {
143             ResolveInfo ri = activities.get(i);
144             packageNameToLauncher.put(ri.activityInfo.packageName, ri);
145         }
146 
147         for (int i = 0; i < packageInfos.size(); i++) {
148             PackageInfo packageInfo = packageInfos.get(i);
149             ResolveInfo resolveInfo = packageNameToLauncher.get(packageInfo.packageName);
150             if (resolveInfo != null) {
151                 // Include the resolve info as we might need it later to build the MobileApplication
152                 launchablePackages.put(packageInfo, resolveInfo);
153             }
154         }
155 
156         return launchablePackages;
157     }
158 
159     /**
160      * Uses {@link PackageManager} and a Map of {@link PackageInfo}s to {@link ResolveInfo}s to
161      * build AppSearch {@link MobileApplication} documents. Info from both are required to build app
162      * documents.
163      *
164      * @param packageInfos a mapping of {@link PackageInfo}s and their corresponding {@link
165      *     ResolveInfo} for the packages launch activity.
166      */
167     @NonNull
buildAppsFromPackageInfos( @onNull PackageManager packageManager, @NonNull Map<PackageInfo, ResolveInfo> packageInfos)168     public static List<MobileApplication> buildAppsFromPackageInfos(
169             @NonNull PackageManager packageManager,
170             @NonNull Map<PackageInfo, ResolveInfo> packageInfos) {
171         Objects.requireNonNull(packageManager);
172         Objects.requireNonNull(packageInfos);
173 
174         List<MobileApplication> mobileApplications = new ArrayList<>();
175         for (Map.Entry<PackageInfo, ResolveInfo> entry : packageInfos.entrySet()) {
176             MobileApplication mobileApplication =
177                     createMobileApplication(packageManager, entry.getKey(), entry.getValue());
178             if (mobileApplication != null && !mobileApplication.getDisplayName().isEmpty()) {
179                 mobileApplications.add(mobileApplication);
180             }
181         }
182         return mobileApplications;
183     }
184 
185     /** Gets the SHA-256 certificate from a {@link PackageManager}, or null if it is not found */
186     @Nullable
getCertificate(@onNull PackageInfo packageInfo)187     public static byte[] getCertificate(@NonNull PackageInfo packageInfo) {
188         Objects.requireNonNull(packageInfo);
189         if (packageInfo.signingInfo == null) {
190             if (LogUtil.DEBUG) {
191                 Log.d(TAG, "Signing info not found for package: " + packageInfo.packageName);
192             }
193             return null;
194         }
195         MessageDigest md;
196         try {
197             md = MessageDigest.getInstance("SHA256");
198         } catch (NoSuchAlgorithmException e) {
199             return null;
200         }
201         Signature[] signatures = packageInfo.signingInfo.getSigningCertificateHistory();
202         if (signatures == null || signatures.length == 0) {
203             return null;
204         }
205         md.update(signatures[0].toByteArray());
206         return md.digest();
207     }
208 
209     /**
210      * Uses PackageManager to supplement packageInfos with an application display name and icon uri.
211      *
212      * @return a MobileApplication representing the packageInfo, null if finding the signing
213      *     certificate fails.
214      */
215     @Nullable
createMobileApplication( @onNull PackageManager packageManager, @NonNull PackageInfo packageInfo, @NonNull ResolveInfo resolveInfo)216     private static MobileApplication createMobileApplication(
217             @NonNull PackageManager packageManager,
218             @NonNull PackageInfo packageInfo,
219             @NonNull ResolveInfo resolveInfo) {
220         Objects.requireNonNull(packageManager);
221         Objects.requireNonNull(packageInfo);
222         Objects.requireNonNull(resolveInfo);
223 
224         String applicationDisplayName = resolveInfo.loadLabel(packageManager).toString();
225         if (TextUtils.isEmpty(applicationDisplayName)) {
226             applicationDisplayName = packageInfo.applicationInfo.className;
227         }
228 
229         String iconUri = getActivityIconUriString(packageManager, resolveInfo.activityInfo);
230 
231         byte[] certificate = getCertificate(packageInfo);
232         if (certificate == null) {
233             return null;
234         }
235 
236         MobileApplication.Builder builder =
237                 new MobileApplication.Builder(packageInfo.packageName, certificate)
238                         .setDisplayName(applicationDisplayName)
239                         // TODO(b/275592563): Populate with nicknames from various sources
240                         .setCreationTimestampMillis(packageInfo.firstInstallTime)
241                         .setUpdatedTimestampMs(packageInfo.lastUpdateTime);
242 
243         String applicationLabel =
244                 packageManager.getApplicationLabel(packageInfo.applicationInfo).toString();
245         if (!applicationDisplayName.equals(applicationLabel)) {
246             // This can be different from applicationDisplayName, and should be indexed
247             builder.setAlternateNames(applicationLabel);
248         }
249 
250         if (iconUri != null) {
251             builder.setIconUri(iconUri);
252         }
253 
254         if (resolveInfo.activityInfo.name != null) {
255             builder.setClassName(resolveInfo.activityInfo.name);
256         }
257         return builder.build();
258     }
259 }
260 
261