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