• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 **
3 ** Copyright 2007, The Android Open Source Project
4 **
5 ** Licensed under the Apache License, Version 2.0 (the "License");
6 ** you may not use this file except in compliance with the License.
7 ** You may obtain a copy of the License at
8 **
9 **     http://www.apache.org/licenses/LICENSE-2.0
10 **
11 ** Unless required by applicable law or agreed to in writing, software
12 ** distributed under the License is distributed on an "AS IS" BASIS,
13 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ** See the License for the specific language governing permissions and
15 ** limitations under the License.
16 */
17 
18 package com.android.packageinstaller;
19 
20 import android.app.Activity;
21 import android.app.ActivityManager;
22 import android.app.AlertDialog;
23 import android.app.Dialog;
24 import android.app.DialogFragment;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageInfo;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ProviderInfo;
31 import android.content.res.Resources;
32 import android.graphics.Bitmap;
33 import android.graphics.Bitmap.CompressFormat;
34 import android.graphics.BitmapFactory;
35 import android.graphics.Canvas;
36 import android.graphics.drawable.BitmapDrawable;
37 import android.graphics.drawable.Drawable;
38 import android.os.Bundle;
39 import android.os.Parcel;
40 import android.os.Parcelable;
41 import android.os.UserHandle;
42 import android.util.Log;
43 import android.view.View;
44 import android.widget.ImageView;
45 import android.widget.TextView;
46 
47 import androidx.annotation.NonNull;
48 import androidx.annotation.Nullable;
49 import androidx.annotation.StringRes;
50 
51 import java.io.ByteArrayOutputStream;
52 import java.io.Closeable;
53 import java.io.File;
54 import java.io.IOException;
55 import java.nio.file.Files;
56 import java.nio.file.Path;
57 import java.util.Arrays;
58 import java.util.Objects;
59 import java.util.stream.Stream;
60 
61 /**
62  * This is a utility class for defining some utility methods and constants
63  * used in the package installer application.
64  */
65 public class PackageUtil {
66     private static final String LOG_TAG = "PackageInstaller";
67 
68     public static final String PREFIX="com.android.packageinstaller.";
69     public static final String INTENT_ATTR_INSTALL_STATUS = PREFIX+"installStatus";
70     public static final String INTENT_ATTR_APPLICATION_INFO=PREFIX+"applicationInfo";
71     public static final String INTENT_ATTR_PERMISSIONS_LIST=PREFIX+"PermissionsList";
72     //intent attribute strings related to uninstall
73     public static final String INTENT_ATTR_PACKAGE_NAME=PREFIX+"PackageName";
74     private static final String DOWNLOADS_AUTHORITY = "downloads";
75     private static final String SPLIT_BASE_APK_SUFFIX = "base.apk";
76     private static final String SPLIT_APK_SUFFIX = ".apk";
77 
78     /**
79      * Utility method to get package information for a given {@link File}
80      */
81     @Nullable
getPackageInfo(Context context, File sourceFile, int flags)82     public static PackageInfo getPackageInfo(Context context, File sourceFile, int flags) {
83         String filePath = sourceFile.getAbsolutePath();
84         if (filePath.endsWith(SPLIT_BASE_APK_SUFFIX)) {
85             File dir = sourceFile.getParentFile();
86             try (Stream<Path> list = Files.list(dir.toPath())) {
87                 long count = list
88                         .filter((name) -> name.endsWith(SPLIT_APK_SUFFIX))
89                         .limit(2)
90                         .count();
91                 if (count > 1) {
92                     // split apks, use file directory to get archive info
93                     filePath = dir.getPath();
94                 }
95             } catch (Exception ignored) {
96                 // No access to the parent directory, proceed to read app snippet
97                 // from the base apk only
98             }
99         }
100         try {
101             return context.getPackageManager().getPackageArchiveInfo(filePath, flags);
102         } catch (Exception ignored) {
103             return null;
104         }
105     }
106 
initSnippet(View snippetView, CharSequence label, Drawable icon)107     public static View initSnippet(View snippetView, CharSequence label, Drawable icon) {
108         ((ImageView)snippetView.findViewById(R.id.app_icon)).setImageDrawable(icon);
109         ((TextView)snippetView.findViewById(R.id.app_name)).setText(label);
110         return snippetView;
111     }
112 
113     /**
114      * Utility method to display a snippet of an installed application.
115      * The content view should have been set on context before invoking this method.
116      * appSnippet view should include R.id.app_icon and R.id.app_name
117      * defined on it.
118      *
119      * @param pContext context of package that can load the resources
120      * @param componentInfo ComponentInfo object whose resources are to be loaded
121      * @param snippetView the snippet view
122      */
initSnippetForInstalledApp(Context pContext, ApplicationInfo appInfo, View snippetView)123     public static View initSnippetForInstalledApp(Context pContext,
124             ApplicationInfo appInfo, View snippetView) {
125         return initSnippetForInstalledApp(pContext, appInfo, snippetView, null);
126     }
127 
128     /**
129      * Utility method to display a snippet of an installed application.
130      * The content view should have been set on context before invoking this method.
131      * appSnippet view should include R.id.app_icon and R.id.app_name
132      * defined on it.
133      *
134      * @param pContext context of package that can load the resources
135      * @param componentInfo ComponentInfo object whose resources are to be loaded
136      * @param snippetView the snippet view
137      * @param UserHandle user that the app si installed for.
138      */
initSnippetForInstalledApp(Context pContext, ApplicationInfo appInfo, View snippetView, UserHandle user)139     public static View initSnippetForInstalledApp(Context pContext,
140             ApplicationInfo appInfo, View snippetView, UserHandle user) {
141         final PackageManager pm = pContext.getPackageManager();
142         Drawable icon = appInfo.loadIcon(pm);
143         if (user != null) {
144             icon = pContext.getPackageManager().getUserBadgedIcon(icon, user);
145         }
146         return initSnippet(
147                 snippetView,
148                 appInfo.loadLabel(pm),
149                 icon);
150     }
151 
152     static final class AppSnippet implements Parcelable {
153         @NonNull public CharSequence label;
154         @NonNull public Drawable icon;
155         public int iconSize;
156 
AppSnippet(@onNull CharSequence label, @NonNull Drawable icon, Context context)157         AppSnippet(@NonNull CharSequence label, @NonNull Drawable icon, Context context) {
158             this.label = label;
159             this.icon = icon;
160             final ActivityManager am = context.getSystemService(ActivityManager.class);
161             this.iconSize = am.getLauncherLargeIconSize();
162         }
163 
AppSnippet(Parcel in)164         private AppSnippet(Parcel in) {
165             label = in.readString();
166             byte[] b = in.readBlob();
167             Bitmap bmp = BitmapFactory.decodeByteArray(b, 0, b.length);
168             icon = new BitmapDrawable(Resources.getSystem(), bmp);
169             iconSize = in.readInt();
170         }
171 
172         @Override
toString()173         public String toString() {
174             return "AppSnippet[" + label + " (has icon)]";
175         }
176 
177         @Override
describeContents()178         public int describeContents() {
179             return 0;
180         }
181 
182         @Override
writeToParcel(@onNull Parcel dest, int flags)183         public void writeToParcel(@NonNull Parcel dest, int flags) {
184             dest.writeString(label.toString());
185 
186             Bitmap bmp = getBitmapFromDrawable(icon);
187             dest.writeBlob(getBytesFromBitmap(bmp));
188             bmp.recycle();
189 
190             dest.writeInt(iconSize);
191         }
192 
getBitmapFromDrawable(Drawable drawable)193         private Bitmap getBitmapFromDrawable(Drawable drawable) {
194             // Create an empty bitmap with the dimensions of our drawable
195             final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
196                 drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
197             // Associate it with a canvas. This canvas will draw the icon on the bitmap
198             final Canvas canvas = new Canvas(bmp);
199             // Draw the drawable in the canvas. The canvas will ultimately paint the drawable in the
200             // bitmap held within
201             drawable.draw(canvas);
202 
203             // Scale it down if the icon is too large
204             if ((bmp.getWidth() > iconSize * 2) || (bmp.getHeight() > iconSize * 2)) {
205                 Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp, iconSize, iconSize, true);
206                 if (scaledBitmap != bmp) {
207                     bmp.recycle();
208                 }
209                 return scaledBitmap;
210             }
211             return bmp;
212         }
213 
getBytesFromBitmap(Bitmap bmp)214         private byte[] getBytesFromBitmap(Bitmap bmp) {
215             ByteArrayOutputStream baos = null;
216             try {
217                 baos = new ByteArrayOutputStream();
218                 bmp.compress(CompressFormat.PNG, 100, baos);
219             } finally {
220                 try {
221                     if (baos != null) {
222                         baos.close();
223                     }
224                 } catch (IOException e) {
225                     Log.e(LOG_TAG, "ByteArrayOutputStream was not closed");
226                 }
227             }
228             return baos.toByteArray();
229         }
230 
231         public static final Parcelable.Creator<AppSnippet> CREATOR = new Parcelable.Creator<>() {
232             public AppSnippet createFromParcel(Parcel in) {
233                 return new AppSnippet(in);
234             }
235 
236             public AppSnippet[] newArray(int size) {
237                 return new AppSnippet[size];
238             }
239         };
240     }
241 
242     /**
243      * Utility method to load application label
244      *
245      * @param pContext context of package that can load the resources
246      * @param appInfo ApplicationInfo object of package whose resources are to be loaded
247      * @param sourceFile File the package is in
248      */
getAppSnippet( Activity pContext, ApplicationInfo appInfo, File sourceFile)249     public static AppSnippet getAppSnippet(
250             Activity pContext, ApplicationInfo appInfo, File sourceFile) {
251         final String archiveFilePath = sourceFile.getAbsolutePath();
252         PackageManager pm = pContext.getPackageManager();
253         appInfo.publicSourceDir = archiveFilePath;
254 
255         if (appInfo.splitNames != null && appInfo.splitSourceDirs == null) {
256             final File[] files = sourceFile.getParentFile().listFiles(
257                     (dir, name) -> name.endsWith(SPLIT_APK_SUFFIX));
258             final String[] splits = Arrays.stream(appInfo.splitNames)
259                     .map(i -> findFilePath(files, i + SPLIT_APK_SUFFIX))
260                     .filter(Objects::nonNull)
261                     .toArray(String[]::new);
262 
263             appInfo.splitSourceDirs = splits;
264             appInfo.splitPublicSourceDirs = splits;
265         }
266 
267         CharSequence label = null;
268         // Try to load the label from the package's resources. If an app has not explicitly
269         // specified any label, just use the package name.
270         if (appInfo.labelRes != 0) {
271             try {
272                 label = appInfo.loadLabel(pm);
273             } catch (Resources.NotFoundException e) {
274             }
275         }
276         if (label == null) {
277             label = (appInfo.nonLocalizedLabel != null) ?
278                     appInfo.nonLocalizedLabel : appInfo.packageName;
279         }
280         Drawable icon = null;
281         // Try to load the icon from the package's resources. If an app has not explicitly
282         // specified any resource, just use the default icon for now.
283         try {
284             if (appInfo.icon != 0) {
285                 try {
286                     icon = appInfo.loadIcon(pm);
287                 } catch (Resources.NotFoundException e) {
288                 }
289             }
290             if (icon == null) {
291                 icon = pContext.getPackageManager().getDefaultActivityIcon();
292             }
293         } catch (OutOfMemoryError e) {
294             Log.i(LOG_TAG, "Could not load app icon", e);
295         }
296         return new PackageUtil.AppSnippet(label, icon, pContext);
297     }
298 
findFilePath(File[] files, String postfix)299     private static String findFilePath(File[] files, String postfix) {
300         final int length = files != null ? files.length : 0;
301         for (int i = 0; i < length; i++) {
302             File file = files[i];
303             final String path = file.getAbsolutePath();
304             if (path.endsWith(postfix)) {
305                 return path;
306             }
307         }
308         return null;
309     }
310 
311     /**
312      * Get the maximum target sdk for a UID.
313      *
314      * @param context The context to use
315      * @param uid The UID requesting the install/uninstall
316      *
317      * @return The maximum target SDK or -1 if the uid does not match any packages.
318      */
getMaxTargetSdkVersionForUid(@onNull Context context, int uid)319     static int getMaxTargetSdkVersionForUid(@NonNull Context context, int uid) {
320         PackageManager pm = context.getPackageManager();
321         final String[] packages = pm.getPackagesForUid(uid);
322         int targetSdkVersion = -1;
323         if (packages != null) {
324             for (String packageName : packages) {
325                 try {
326                     ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
327                     targetSdkVersion = Math.max(targetSdkVersion, info.targetSdkVersion);
328                 } catch (PackageManager.NameNotFoundException e) {
329                     // Ignore and try the next package
330                 }
331             }
332         }
333         return targetSdkVersion;
334     }
335 
336 
337     /**
338      * Quietly close a closeable resource (e.g. a stream or file). The input may already
339      * be closed and it may even be null.
340      */
safeClose(Closeable resource)341     static void safeClose(Closeable resource) {
342         if (resource != null) {
343             try {
344                 resource.close();
345             } catch (IOException ioe) {
346                 // Catch and discard the error
347             }
348         }
349     }
350 
351     /**
352      * A simple error dialog showing a message
353      */
354     public static class SimpleErrorDialog extends DialogFragment {
355         private static final String MESSAGE_KEY =
356                 SimpleErrorDialog.class.getName() + "MESSAGE_KEY";
357 
newInstance(@tringRes int message)358         static SimpleErrorDialog newInstance(@StringRes int message) {
359             SimpleErrorDialog dialog = new SimpleErrorDialog();
360 
361             Bundle args = new Bundle();
362             args.putInt(MESSAGE_KEY, message);
363             dialog.setArguments(args);
364 
365             return dialog;
366         }
367 
368         @Override
onCreateDialog(Bundle savedInstanceState)369         public Dialog onCreateDialog(Bundle savedInstanceState) {
370             return new AlertDialog.Builder(getActivity())
371                     .setMessage(getArguments().getInt(MESSAGE_KEY))
372                     .setPositiveButton(R.string.ok, (dialog, which) -> getActivity().finish())
373                     .create();
374         }
375 
376         @Override
onCancel(DialogInterface dialog)377         public void onCancel(DialogInterface dialog) {
378             getActivity().setResult(Activity.RESULT_CANCELED);
379             getActivity().finish();
380         }
381     }
382 
383     /**
384      * Determines if the UID belongs to the system downloads provider and returns the
385      * {@link ApplicationInfo} of the provider
386      *
387      * @param uid UID of the caller
388      * @return {@link ApplicationInfo} of the provider if a downloads provider exists,
389      *          it is a system app, and its UID matches with the passed UID, null otherwise.
390      */
getSystemDownloadsProviderInfo(PackageManager pm, int uid)391     public static ApplicationInfo getSystemDownloadsProviderInfo(PackageManager pm, int uid) {
392         final ProviderInfo providerInfo = pm.resolveContentProvider(
393                 DOWNLOADS_AUTHORITY, 0);
394         if (providerInfo == null) {
395             // There seems to be no currently enabled downloads provider on the system.
396             return null;
397         }
398         ApplicationInfo appInfo = providerInfo.applicationInfo;
399         if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 && uid == appInfo.uid) {
400             return appInfo;
401         }
402         return null;
403     }
404 }
405