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