PackageHelper.java revision f39ca8f5e5f43f130b4590921087a63f88bb8286
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.internal.content; 18 19import static android.net.TrafficStats.MB_IN_BYTES; 20 21import android.content.Context; 22import android.content.pm.ApplicationInfo; 23import android.content.pm.PackageInfo; 24import android.content.pm.PackageManager; 25import android.content.pm.PackageManager.NameNotFoundException; 26import android.content.pm.PackageParser.PackageLite; 27import android.os.Environment; 28import android.os.FileUtils; 29import android.os.IBinder; 30import android.os.RemoteException; 31import android.os.ServiceManager; 32import android.os.storage.IMountService; 33import android.os.storage.StorageManager; 34import android.os.storage.StorageResultCode; 35import android.os.storage.StorageVolume; 36import android.os.storage.VolumeInfo; 37import android.provider.Settings; 38import android.util.ArraySet; 39import android.util.Log; 40import android.util.Slog; 41 42import libcore.io.IoUtils; 43 44import java.io.File; 45import java.io.FileOutputStream; 46import java.io.IOException; 47import java.io.InputStream; 48import java.util.Collections; 49import java.util.zip.ZipEntry; 50import java.util.zip.ZipFile; 51import java.util.zip.ZipOutputStream; 52 53/** 54 * Constants used internally between the PackageManager 55 * and media container service transports. 56 * Some utility methods to invoke MountService api. 57 */ 58public class PackageHelper { 59 public static final int RECOMMEND_INSTALL_INTERNAL = 1; 60 public static final int RECOMMEND_INSTALL_EXTERNAL = 2; 61 public static final int RECOMMEND_FAILED_INSUFFICIENT_STORAGE = -1; 62 public static final int RECOMMEND_FAILED_INVALID_APK = -2; 63 public static final int RECOMMEND_FAILED_INVALID_LOCATION = -3; 64 public static final int RECOMMEND_FAILED_ALREADY_EXISTS = -4; 65 public static final int RECOMMEND_MEDIA_UNAVAILABLE = -5; 66 public static final int RECOMMEND_FAILED_INVALID_URI = -6; 67 public static final int RECOMMEND_FAILED_VERSION_DOWNGRADE = -7; 68 69 private static final boolean localLOGV = false; 70 private static final String TAG = "PackageHelper"; 71 // App installation location settings values 72 public static final int APP_INSTALL_AUTO = 0; 73 public static final int APP_INSTALL_INTERNAL = 1; 74 public static final int APP_INSTALL_EXTERNAL = 2; 75 76 public static IMountService getMountService() throws RemoteException { 77 IBinder service = ServiceManager.getService("mount"); 78 if (service != null) { 79 return IMountService.Stub.asInterface(service); 80 } else { 81 Log.e(TAG, "Can't get mount service"); 82 throw new RemoteException("Could not contact mount service"); 83 } 84 } 85 86 public static String createSdDir(long sizeBytes, String cid, String sdEncKey, int uid, 87 boolean isExternal) { 88 // Round up to nearest MB, plus another MB for filesystem overhead 89 final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1; 90 try { 91 IMountService mountService = getMountService(); 92 93 if (localLOGV) 94 Log.i(TAG, "Size of container " + sizeMb + " MB"); 95 96 int rc = mountService.createSecureContainer(cid, sizeMb, "ext4", sdEncKey, uid, 97 isExternal); 98 if (rc != StorageResultCode.OperationSucceeded) { 99 Log.e(TAG, "Failed to create secure container " + cid); 100 return null; 101 } 102 String cachePath = mountService.getSecureContainerPath(cid); 103 if (localLOGV) Log.i(TAG, "Created secure container " + cid + 104 " at " + cachePath); 105 return cachePath; 106 } catch (RemoteException e) { 107 Log.e(TAG, "MountService running?"); 108 } 109 return null; 110 } 111 112 public static boolean resizeSdDir(long sizeBytes, String cid, String sdEncKey) { 113 // Round up to nearest MB, plus another MB for filesystem overhead 114 final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1; 115 try { 116 IMountService mountService = getMountService(); 117 int rc = mountService.resizeSecureContainer(cid, sizeMb, sdEncKey); 118 if (rc == StorageResultCode.OperationSucceeded) { 119 return true; 120 } 121 } catch (RemoteException e) { 122 Log.e(TAG, "MountService running?"); 123 } 124 Log.e(TAG, "Failed to create secure container " + cid); 125 return false; 126 } 127 128 public static String mountSdDir(String cid, String key, int ownerUid) { 129 return mountSdDir(cid, key, ownerUid, true); 130 } 131 132 public static String mountSdDir(String cid, String key, int ownerUid, boolean readOnly) { 133 try { 134 int rc = getMountService().mountSecureContainer(cid, key, ownerUid, readOnly); 135 if (rc != StorageResultCode.OperationSucceeded) { 136 Log.i(TAG, "Failed to mount container " + cid + " rc : " + rc); 137 return null; 138 } 139 return getMountService().getSecureContainerPath(cid); 140 } catch (RemoteException e) { 141 Log.e(TAG, "MountService running?"); 142 } 143 return null; 144 } 145 146 public static boolean unMountSdDir(String cid) { 147 try { 148 int rc = getMountService().unmountSecureContainer(cid, true); 149 if (rc != StorageResultCode.OperationSucceeded) { 150 Log.e(TAG, "Failed to unmount " + cid + " with rc " + rc); 151 return false; 152 } 153 return true; 154 } catch (RemoteException e) { 155 Log.e(TAG, "MountService running?"); 156 } 157 return false; 158 } 159 160 public static boolean renameSdDir(String oldId, String newId) { 161 try { 162 int rc = getMountService().renameSecureContainer(oldId, newId); 163 if (rc != StorageResultCode.OperationSucceeded) { 164 Log.e(TAG, "Failed to rename " + oldId + " to " + 165 newId + "with rc " + rc); 166 return false; 167 } 168 return true; 169 } catch (RemoteException e) { 170 Log.i(TAG, "Failed ot rename " + oldId + " to " + newId + 171 " with exception : " + e); 172 } 173 return false; 174 } 175 176 public static String getSdDir(String cid) { 177 try { 178 return getMountService().getSecureContainerPath(cid); 179 } catch (RemoteException e) { 180 Log.e(TAG, "Failed to get container path for " + cid + 181 " with exception " + e); 182 } 183 return null; 184 } 185 186 public static String getSdFilesystem(String cid) { 187 try { 188 return getMountService().getSecureContainerFilesystemPath(cid); 189 } catch (RemoteException e) { 190 Log.e(TAG, "Failed to get container path for " + cid + 191 " with exception " + e); 192 } 193 return null; 194 } 195 196 public static boolean finalizeSdDir(String cid) { 197 try { 198 int rc = getMountService().finalizeSecureContainer(cid); 199 if (rc != StorageResultCode.OperationSucceeded) { 200 Log.i(TAG, "Failed to finalize container " + cid); 201 return false; 202 } 203 return true; 204 } catch (RemoteException e) { 205 Log.e(TAG, "Failed to finalize container " + cid + 206 " with exception " + e); 207 } 208 return false; 209 } 210 211 public static boolean destroySdDir(String cid) { 212 try { 213 if (localLOGV) Log.i(TAG, "Forcibly destroying container " + cid); 214 int rc = getMountService().destroySecureContainer(cid, true); 215 if (rc != StorageResultCode.OperationSucceeded) { 216 Log.i(TAG, "Failed to destroy container " + cid); 217 return false; 218 } 219 return true; 220 } catch (RemoteException e) { 221 Log.e(TAG, "Failed to destroy container " + cid + 222 " with exception " + e); 223 } 224 return false; 225 } 226 227 public static String[] getSecureContainerList() { 228 try { 229 return getMountService().getSecureContainerList(); 230 } catch (RemoteException e) { 231 Log.e(TAG, "Failed to get secure container list with exception" + 232 e); 233 } 234 return null; 235 } 236 237 public static boolean isContainerMounted(String cid) { 238 try { 239 return getMountService().isSecureContainerMounted(cid); 240 } catch (RemoteException e) { 241 Log.e(TAG, "Failed to find out if container " + cid + " mounted"); 242 } 243 return false; 244 } 245 246 /** 247 * Extract public files for the single given APK. 248 */ 249 public static long extractPublicFiles(File apkFile, File publicZipFile) 250 throws IOException { 251 final FileOutputStream fstr; 252 final ZipOutputStream publicZipOutStream; 253 254 if (publicZipFile == null) { 255 fstr = null; 256 publicZipOutStream = null; 257 } else { 258 fstr = new FileOutputStream(publicZipFile); 259 publicZipOutStream = new ZipOutputStream(fstr); 260 Log.d(TAG, "Extracting " + apkFile + " to " + publicZipFile); 261 } 262 263 long size = 0L; 264 265 try { 266 final ZipFile privateZip = new ZipFile(apkFile.getAbsolutePath()); 267 try { 268 // Copy manifest, resources.arsc and res directory to public zip 269 for (final ZipEntry zipEntry : Collections.list(privateZip.entries())) { 270 final String zipEntryName = zipEntry.getName(); 271 if ("AndroidManifest.xml".equals(zipEntryName) 272 || "resources.arsc".equals(zipEntryName) 273 || zipEntryName.startsWith("res/")) { 274 size += zipEntry.getSize(); 275 if (publicZipFile != null) { 276 copyZipEntry(zipEntry, privateZip, publicZipOutStream); 277 } 278 } 279 } 280 } finally { 281 try { privateZip.close(); } catch (IOException e) {} 282 } 283 284 if (publicZipFile != null) { 285 publicZipOutStream.finish(); 286 publicZipOutStream.flush(); 287 FileUtils.sync(fstr); 288 publicZipOutStream.close(); 289 FileUtils.setPermissions(publicZipFile.getAbsolutePath(), FileUtils.S_IRUSR 290 | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IROTH, -1, -1); 291 } 292 } finally { 293 IoUtils.closeQuietly(publicZipOutStream); 294 } 295 296 return size; 297 } 298 299 private static void copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile, 300 ZipOutputStream outZipStream) throws IOException { 301 byte[] buffer = new byte[4096]; 302 int num; 303 304 ZipEntry newEntry; 305 if (zipEntry.getMethod() == ZipEntry.STORED) { 306 // Preserve the STORED method of the input entry. 307 newEntry = new ZipEntry(zipEntry); 308 } else { 309 // Create a new entry so that the compressed len is recomputed. 310 newEntry = new ZipEntry(zipEntry.getName()); 311 } 312 outZipStream.putNextEntry(newEntry); 313 314 final InputStream data = inZipFile.getInputStream(zipEntry); 315 try { 316 while ((num = data.read(buffer)) > 0) { 317 outZipStream.write(buffer, 0, num); 318 } 319 outZipStream.flush(); 320 } finally { 321 IoUtils.closeQuietly(data); 322 } 323 } 324 325 public static boolean fixSdPermissions(String cid, int gid, String filename) { 326 try { 327 int rc = getMountService().fixPermissionsSecureContainer(cid, gid, filename); 328 if (rc != StorageResultCode.OperationSucceeded) { 329 Log.i(TAG, "Failed to fixperms container " + cid); 330 return false; 331 } 332 return true; 333 } catch (RemoteException e) { 334 Log.e(TAG, "Failed to fixperms container " + cid + " with exception " + e); 335 } 336 return false; 337 } 338 339 /** 340 * Given a requested {@link PackageInfo#installLocation} and calculated 341 * install size, pick the actual volume to install the app. Only considers 342 * internal and private volumes, and prefers to keep an existing package on 343 * its current volume. 344 * 345 * @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null} 346 * for internal storage. 347 */ 348 public static String resolveInstallVolume(Context context, String packageName, 349 int installLocation, long sizeBytes) throws IOException { 350 final boolean forceAllowOnExternal = Settings.Global.getInt( 351 context.getContentResolver(), Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0; 352 // TODO: handle existing apps installed in ASEC; currently assumes 353 // they'll end up back on internal storage 354 ApplicationInfo existingInfo = null; 355 try { 356 existingInfo = context.getPackageManager().getApplicationInfo(packageName, 357 PackageManager.GET_UNINSTALLED_PACKAGES); 358 } catch (NameNotFoundException ignored) { 359 } 360 361 final StorageManager storageManager = context.getSystemService(StorageManager.class); 362 final boolean fitsOnInternal = fitsOnInternal(context, sizeBytes); 363 364 final ArraySet<String> allCandidates = new ArraySet<>(); 365 VolumeInfo bestCandidate = null; 366 long bestCandidateAvailBytes = Long.MIN_VALUE; 367 for (VolumeInfo vol : storageManager.getVolumes()) { 368 if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) { 369 final long availBytes = storageManager.getStorageBytesUntilLow(new File(vol.path)); 370 if (availBytes >= sizeBytes) { 371 allCandidates.add(vol.fsUuid); 372 } 373 if (availBytes >= bestCandidateAvailBytes) { 374 bestCandidate = vol; 375 bestCandidateAvailBytes = availBytes; 376 } 377 } 378 } 379 380 // System apps always forced to internal storage 381 if (existingInfo != null && existingInfo.isSystemApp()) { 382 installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY; 383 } 384 385 // If app expresses strong desire for internal space, honor it 386 if (!forceAllowOnExternal 387 && installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { 388 if (fitsOnInternal) { 389 return null; 390 } else { 391 throw new IOException("Requested internal only, but not enough space"); 392 } 393 } 394 395 // If app already exists somewhere, prefer to stay on that volume 396 if (existingInfo != null) { 397 if (existingInfo.volumeUuid == null && fitsOnInternal) { 398 return null; 399 } 400 if (allCandidates.contains(existingInfo.volumeUuid)) { 401 return existingInfo.volumeUuid; 402 } 403 } 404 405 // We're left with either preferring external or auto, so just pick 406 // volume with most space 407 if (bestCandidate != null) { 408 return bestCandidate.fsUuid; 409 } else if (fitsOnInternal) { 410 return null; 411 } else { 412 throw new IOException("No special requests, but no room anywhere"); 413 } 414 } 415 416 public static boolean fitsOnInternal(Context context, long sizeBytes) { 417 final StorageManager storage = context.getSystemService(StorageManager.class); 418 final File target = Environment.getDataDirectory(); 419 return (sizeBytes <= storage.getStorageBytesUntilLow(target)); 420 } 421 422 public static boolean fitsOnExternal(Context context, long sizeBytes) { 423 final StorageManager storage = context.getSystemService(StorageManager.class); 424 final StorageVolume primary = storage.getPrimaryVolume(); 425 return (sizeBytes > 0) && !primary.isEmulated() 426 && Environment.MEDIA_MOUNTED.equals(primary.getState()) 427 && sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile()); 428 } 429 430 /** 431 * Given a requested {@link PackageInfo#installLocation} and calculated 432 * install size, pick the actual location to install the app. 433 */ 434 public static int resolveInstallLocation(Context context, String packageName, 435 int installLocation, long sizeBytes, int installFlags) { 436 ApplicationInfo existingInfo = null; 437 try { 438 existingInfo = context.getPackageManager().getApplicationInfo(packageName, 439 PackageManager.GET_UNINSTALLED_PACKAGES); 440 } catch (NameNotFoundException ignored) { 441 } 442 443 final int prefer; 444 final boolean checkBoth; 445 if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) { 446 prefer = RECOMMEND_INSTALL_INTERNAL; 447 checkBoth = false; 448 } else if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) { 449 prefer = RECOMMEND_INSTALL_EXTERNAL; 450 checkBoth = false; 451 } else if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { 452 prefer = RECOMMEND_INSTALL_INTERNAL; 453 checkBoth = false; 454 } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { 455 prefer = RECOMMEND_INSTALL_EXTERNAL; 456 checkBoth = true; 457 } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { 458 // When app is already installed, prefer same medium 459 if (existingInfo != null) { 460 // TODO: distinguish if this is external ASEC 461 if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { 462 prefer = RECOMMEND_INSTALL_EXTERNAL; 463 } else { 464 prefer = RECOMMEND_INSTALL_INTERNAL; 465 } 466 } else { 467 prefer = RECOMMEND_INSTALL_INTERNAL; 468 } 469 checkBoth = true; 470 } else { 471 prefer = RECOMMEND_INSTALL_INTERNAL; 472 checkBoth = false; 473 } 474 475 boolean fitsOnInternal = false; 476 if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) { 477 fitsOnInternal = fitsOnInternal(context, sizeBytes); 478 } 479 480 boolean fitsOnExternal = false; 481 if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) { 482 fitsOnExternal = fitsOnExternal(context, sizeBytes); 483 } 484 485 if (prefer == RECOMMEND_INSTALL_INTERNAL) { 486 if (fitsOnInternal) { 487 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 488 } 489 } else if (prefer == RECOMMEND_INSTALL_EXTERNAL) { 490 if (fitsOnExternal) { 491 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 492 } 493 } 494 495 if (checkBoth) { 496 if (fitsOnInternal) { 497 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 498 } else if (fitsOnExternal) { 499 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 500 } 501 } 502 503 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; 504 } 505 506 public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked, 507 String abiOverride) throws IOException { 508 NativeLibraryHelper.Handle handle = null; 509 try { 510 handle = NativeLibraryHelper.Handle.create(pkg); 511 return calculateInstalledSize(pkg, handle, isForwardLocked, abiOverride); 512 } finally { 513 IoUtils.closeQuietly(handle); 514 } 515 } 516 517 public static long calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle, 518 boolean isForwardLocked, String abiOverride) throws IOException { 519 long sizeBytes = 0; 520 521 // Include raw APKs, and possibly unpacked resources 522 for (String codePath : pkg.getAllCodePaths()) { 523 final File codeFile = new File(codePath); 524 sizeBytes += codeFile.length(); 525 526 if (isForwardLocked) { 527 sizeBytes += PackageHelper.extractPublicFiles(codeFile, null); 528 } 529 } 530 531 // Include all relevant native code 532 sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride); 533 534 return sizeBytes; 535 } 536 537 public static String replaceEnd(String str, String before, String after) { 538 if (!str.endsWith(before)) { 539 throw new IllegalArgumentException( 540 "Expected " + str + " to end with " + before); 541 } 542 return str.substring(0, str.length() - before.length()) + after; 543 } 544} 545