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