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.AlertDialog;
22 import android.app.Dialog;
23 import android.app.DialogFragment;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.pm.ApplicationInfo;
27 import android.content.pm.PackageInfo;
28 import android.content.pm.PackageManager;
29 import android.content.res.Resources;
30 import android.graphics.Bitmap;
31 import android.graphics.Canvas;
32 import android.graphics.drawable.BitmapDrawable;
33 import android.graphics.drawable.Drawable;
34 import android.os.Bundle;
35 import android.os.Parcel;
36 import android.os.Parcelable;
37 import android.os.UserHandle;
38 import android.util.Log;
39 import android.view.View;
40 import android.widget.ImageView;
41 import android.widget.TextView;
42 
43 import androidx.annotation.NonNull;
44 import androidx.annotation.Nullable;
45 import androidx.annotation.StringRes;
46 
47 import java.io.Closeable;
48 import java.io.File;
49 import java.io.IOException;
50 
51 /**
52  * This is a utility class for defining some utility methods and constants
53  * used in the package installer application.
54  */
55 public class PackageUtil {
56     private static final String LOG_TAG = PackageUtil.class.getSimpleName();
57 
58     public static final String PREFIX="com.android.packageinstaller.";
59     public static final String INTENT_ATTR_INSTALL_STATUS = PREFIX+"installStatus";
60     public static final String INTENT_ATTR_APPLICATION_INFO=PREFIX+"applicationInfo";
61     public static final String INTENT_ATTR_PERMISSIONS_LIST=PREFIX+"PermissionsList";
62     //intent attribute strings related to uninstall
63     public static final String INTENT_ATTR_PACKAGE_NAME=PREFIX+"PackageName";
64 
65     /**
66      * Utility method to get package information for a given {@link File}
67      */
68     @Nullable
getPackageInfo(Context context, File sourceFile, int flags)69     public static PackageInfo getPackageInfo(Context context, File sourceFile, int flags) {
70         try {
71             return context.getPackageManager().getPackageArchiveInfo(sourceFile.getAbsolutePath(),
72                     flags);
73         } catch (Exception ignored) {
74             return null;
75         }
76     }
77 
initSnippet(View snippetView, CharSequence label, Drawable icon)78     public static View initSnippet(View snippetView, CharSequence label, Drawable icon) {
79         ((ImageView)snippetView.findViewById(R.id.app_icon)).setImageDrawable(icon);
80         ((TextView)snippetView.findViewById(R.id.app_name)).setText(label);
81         return snippetView;
82     }
83 
84     /**
85      * Utility method to display a snippet of an installed application.
86      * The content view should have been set on context before invoking this method.
87      * appSnippet view should include R.id.app_icon and R.id.app_name
88      * defined on it.
89      *
90      * @param pContext context of package that can load the resources
91      * @param componentInfo ComponentInfo object whose resources are to be loaded
92      * @param snippetView the snippet view
93      */
initSnippetForInstalledApp(Context pContext, ApplicationInfo appInfo, View snippetView)94     public static View initSnippetForInstalledApp(Context pContext,
95             ApplicationInfo appInfo, View snippetView) {
96         return initSnippetForInstalledApp(pContext, appInfo, snippetView, null);
97     }
98 
99     /**
100      * Utility method to display a snippet of an installed application.
101      * The content view should have been set on context before invoking this method.
102      * appSnippet view should include R.id.app_icon and R.id.app_name
103      * defined on it.
104      *
105      * @param pContext context of package that can load the resources
106      * @param componentInfo ComponentInfo object whose resources are to be loaded
107      * @param snippetView the snippet view
108      * @param UserHandle user that the app si installed for.
109      */
initSnippetForInstalledApp(Context pContext, ApplicationInfo appInfo, View snippetView, UserHandle user)110     public static View initSnippetForInstalledApp(Context pContext,
111             ApplicationInfo appInfo, View snippetView, UserHandle user) {
112         final PackageManager pm = pContext.getPackageManager();
113         Drawable icon = appInfo.loadIcon(pm);
114         if (user != null) {
115             icon = pContext.getPackageManager().getUserBadgedIcon(icon, user);
116         }
117         return initSnippet(
118                 snippetView,
119                 appInfo.loadLabel(pm),
120                 icon);
121     }
122 
123     static final class AppSnippet implements Parcelable {
124         @NonNull public CharSequence label;
125         @Nullable public Drawable icon;
AppSnippet(@onNull CharSequence label, @Nullable Drawable icon)126         public AppSnippet(@NonNull CharSequence label, @Nullable Drawable icon) {
127             this.label = label;
128             this.icon = icon;
129         }
130 
AppSnippet(Parcel in)131         private AppSnippet(Parcel in) {
132             label = in.readString();
133             Bitmap bmp = in.readParcelable(getClass().getClassLoader(), Bitmap.class);
134             icon = new BitmapDrawable(Resources.getSystem(), bmp);
135         }
136 
137         @Override
toString()138         public String toString() {
139             return "AppSnippet[" + label + (icon != null ? "(has" : "(no ") + " icon)]";
140         }
141 
142         @Override
describeContents()143         public int describeContents() {
144             return 0;
145         }
146 
147         @Override
writeToParcel(@onNull Parcel dest, int flags)148         public void writeToParcel(@NonNull Parcel dest, int flags) {
149             dest.writeString(label.toString());
150             Bitmap bmp = getBitmapFromDrawable(icon);
151             dest.writeParcelable(bmp, 0);
152         }
153 
getBitmapFromDrawable(Drawable drawable)154         private Bitmap getBitmapFromDrawable(Drawable drawable) {
155             // Create an empty bitmap with the dimensions of our drawable
156             final Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
157                     drawable.getIntrinsicHeight(),
158                     Bitmap.Config.ARGB_8888);
159             // Associate it with a canvas. This canvas will draw the icon on the bitmap
160             final Canvas canvas = new Canvas(bmp);
161             // Draw the drawable in the canvas. The canvas will ultimately paint the drawable in the
162             // bitmap held within
163             drawable.draw(canvas);
164 
165             return bmp;
166         }
167 
168         public static final Parcelable.Creator<AppSnippet> CREATOR = new Parcelable.Creator<>() {
169             public AppSnippet createFromParcel(Parcel in) {
170                 return new AppSnippet(in);
171             }
172 
173             public AppSnippet[] newArray(int size) {
174                 return new AppSnippet[size];
175             }
176         };
177     }
178 
179     /**
180      * Utility method to load application label
181      *
182      * @param pContext context of package that can load the resources
183      * @param appInfo ApplicationInfo object of package whose resources are to be loaded
184      * @param sourceFile File the package is in
185      */
getAppSnippet( Activity pContext, ApplicationInfo appInfo, File sourceFile)186     public static AppSnippet getAppSnippet(
187             Activity pContext, ApplicationInfo appInfo, File sourceFile) {
188         final String archiveFilePath = sourceFile.getAbsolutePath();
189         PackageManager pm = pContext.getPackageManager();
190         appInfo.publicSourceDir = archiveFilePath;
191 
192         CharSequence label = null;
193         // Try to load the label from the package's resources. If an app has not explicitly
194         // specified any label, just use the package name.
195         if (appInfo.labelRes != 0) {
196             try {
197                 label = appInfo.loadLabel(pm);
198             } catch (Resources.NotFoundException e) {
199             }
200         }
201         if (label == null) {
202             label = (appInfo.nonLocalizedLabel != null) ?
203                     appInfo.nonLocalizedLabel : appInfo.packageName;
204         }
205         Drawable icon = null;
206         // Try to load the icon from the package's resources. If an app has not explicitly
207         // specified any resource, just use the default icon for now.
208         try {
209             if (appInfo.icon != 0) {
210                 try {
211                     icon = appInfo.loadIcon(pm);
212                 } catch (Resources.NotFoundException e) {
213                 }
214             }
215             if (icon == null) {
216                 icon = pContext.getPackageManager().getDefaultActivityIcon();
217             }
218         } catch (OutOfMemoryError e) {
219             Log.i(LOG_TAG, "Could not load app icon", e);
220         }
221         return new PackageUtil.AppSnippet(label, icon);
222     }
223 
224     /**
225      * Get the maximum target sdk for a UID.
226      *
227      * @param context The context to use
228      * @param uid The UID requesting the install/uninstall
229      *
230      * @return The maximum target SDK or -1 if the uid does not match any packages.
231      */
getMaxTargetSdkVersionForUid(@onNull Context context, int uid)232     static int getMaxTargetSdkVersionForUid(@NonNull Context context, int uid) {
233         PackageManager pm = context.getPackageManager();
234         final String[] packages = pm.getPackagesForUid(uid);
235         int targetSdkVersion = -1;
236         if (packages != null) {
237             for (String packageName : packages) {
238                 try {
239                     ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
240                     targetSdkVersion = Math.max(targetSdkVersion, info.targetSdkVersion);
241                 } catch (PackageManager.NameNotFoundException e) {
242                     // Ignore and try the next package
243                 }
244             }
245         }
246         return targetSdkVersion;
247     }
248 
249 
250     /**
251      * Quietly close a closeable resource (e.g. a stream or file). The input may already
252      * be closed and it may even be null.
253      */
safeClose(Closeable resource)254     static void safeClose(Closeable resource) {
255         if (resource != null) {
256             try {
257                 resource.close();
258             } catch (IOException ioe) {
259                 // Catch and discard the error
260             }
261         }
262     }
263 
264     /**
265      * A simple error dialog showing a message
266      */
267     public static class SimpleErrorDialog extends DialogFragment {
268         private static final String MESSAGE_KEY =
269                 SimpleErrorDialog.class.getName() + "MESSAGE_KEY";
270 
newInstance(@tringRes int message)271         static SimpleErrorDialog newInstance(@StringRes int message) {
272             SimpleErrorDialog dialog = new SimpleErrorDialog();
273 
274             Bundle args = new Bundle();
275             args.putInt(MESSAGE_KEY, message);
276             dialog.setArguments(args);
277 
278             return dialog;
279         }
280 
281         @Override
onCreateDialog(Bundle savedInstanceState)282         public Dialog onCreateDialog(Bundle savedInstanceState) {
283             return new AlertDialog.Builder(getActivity())
284                     .setMessage(getArguments().getInt(MESSAGE_KEY))
285                     .setPositiveButton(R.string.ok, (dialog, which) -> getActivity().finish())
286                     .create();
287         }
288 
289         @Override
onCancel(DialogInterface dialog)290         public void onCancel(DialogInterface dialog) {
291             getActivity().setResult(Activity.RESULT_CANCELED);
292             getActivity().finish();
293         }
294     }
295 }
296