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