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 17 package com.android.internal.content; 18 19 import static android.net.TrafficStats.MB_IN_BYTES; 20 import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL; 21 22 import android.content.Context; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.PackageInfo; 25 import android.content.pm.PackageInstaller.SessionParams; 26 import android.content.pm.PackageManager; 27 import android.content.pm.PackageManager.NameNotFoundException; 28 import android.content.pm.PackageParser.PackageLite; 29 import android.os.Environment; 30 import android.os.FileUtils; 31 import android.os.IBinder; 32 import android.os.RemoteException; 33 import android.os.ServiceManager; 34 import android.os.storage.IStorageManager; 35 import android.os.storage.StorageManager; 36 import android.os.storage.StorageResultCode; 37 import android.os.storage.StorageVolume; 38 import android.os.storage.VolumeInfo; 39 import android.provider.Settings; 40 import android.util.ArraySet; 41 import android.util.Log; 42 43 import com.android.internal.annotations.VisibleForTesting; 44 45 import libcore.io.IoUtils; 46 47 import java.io.File; 48 import java.io.FileOutputStream; 49 import java.io.IOException; 50 import java.io.InputStream; 51 import java.util.Collections; 52 import java.util.Objects; 53 import java.util.UUID; 54 import java.util.zip.ZipEntry; 55 import java.util.zip.ZipFile; 56 import java.util.zip.ZipOutputStream; 57 58 /** 59 * Constants used internally between the PackageManager 60 * and media container service transports. 61 * Some utility methods to invoke StorageManagerService api. 62 */ 63 public class PackageHelper { 64 public static final int RECOMMEND_INSTALL_INTERNAL = 1; 65 public static final int RECOMMEND_INSTALL_EXTERNAL = 2; 66 public static final int RECOMMEND_INSTALL_EPHEMERAL = 3; 67 public static final int RECOMMEND_FAILED_INSUFFICIENT_STORAGE = -1; 68 public static final int RECOMMEND_FAILED_INVALID_APK = -2; 69 public static final int RECOMMEND_FAILED_INVALID_LOCATION = -3; 70 public static final int RECOMMEND_FAILED_ALREADY_EXISTS = -4; 71 public static final int RECOMMEND_MEDIA_UNAVAILABLE = -5; 72 public static final int RECOMMEND_FAILED_INVALID_URI = -6; 73 public static final int RECOMMEND_FAILED_VERSION_DOWNGRADE = -7; 74 75 private static final boolean localLOGV = false; 76 private static final String TAG = "PackageHelper"; 77 // App installation location settings values 78 public static final int APP_INSTALL_AUTO = 0; 79 public static final int APP_INSTALL_INTERNAL = 1; 80 public static final int APP_INSTALL_EXTERNAL = 2; 81 82 private static TestableInterface sDefaultTestableInterface = null; 83 84 public static IStorageManager getStorageManager() throws RemoteException { 85 IBinder service = ServiceManager.getService("mount"); 86 if (service != null) { 87 return IStorageManager.Stub.asInterface(service); 88 } else { 89 Log.e(TAG, "Can't get storagemanager service"); 90 throw new RemoteException("Could not contact storagemanager service"); 91 } 92 } 93 94 public static String createSdDir(long sizeBytes, String cid, String sdEncKey, int uid, 95 boolean isExternal) { 96 // Round up to nearest MB, plus another MB for filesystem overhead 97 final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1; 98 try { 99 IStorageManager storageManager = getStorageManager(); 100 101 if (localLOGV) 102 Log.i(TAG, "Size of container " + sizeMb + " MB"); 103 104 int rc = storageManager.createSecureContainer(cid, sizeMb, "ext4", sdEncKey, uid, 105 isExternal); 106 if (rc != StorageResultCode.OperationSucceeded) { 107 Log.e(TAG, "Failed to create secure container " + cid); 108 return null; 109 } 110 String cachePath = storageManager.getSecureContainerPath(cid); 111 if (localLOGV) Log.i(TAG, "Created secure container " + cid + 112 " at " + cachePath); 113 return cachePath; 114 } catch (RemoteException e) { 115 Log.e(TAG, "StorageManagerService running?"); 116 } 117 return null; 118 } 119 120 public static boolean resizeSdDir(long sizeBytes, String cid, String sdEncKey) { 121 // Round up to nearest MB, plus another MB for filesystem overhead 122 final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1; 123 try { 124 IStorageManager storageManager = getStorageManager(); 125 int rc = storageManager.resizeSecureContainer(cid, sizeMb, sdEncKey); 126 if (rc == StorageResultCode.OperationSucceeded) { 127 return true; 128 } 129 } catch (RemoteException e) { 130 Log.e(TAG, "StorageManagerService running?"); 131 } 132 Log.e(TAG, "Failed to create secure container " + cid); 133 return false; 134 } 135 136 public static String mountSdDir(String cid, String key, int ownerUid) { 137 return mountSdDir(cid, key, ownerUid, true); 138 } 139 140 public static String mountSdDir(String cid, String key, int ownerUid, boolean readOnly) { 141 try { 142 int rc = getStorageManager().mountSecureContainer(cid, key, ownerUid, readOnly); 143 if (rc != StorageResultCode.OperationSucceeded) { 144 Log.i(TAG, "Failed to mount container " + cid + " rc : " + rc); 145 return null; 146 } 147 return getStorageManager().getSecureContainerPath(cid); 148 } catch (RemoteException e) { 149 Log.e(TAG, "StorageManagerService running?"); 150 } 151 return null; 152 } 153 154 public static boolean unMountSdDir(String cid) { 155 try { 156 int rc = getStorageManager().unmountSecureContainer(cid, true); 157 if (rc != StorageResultCode.OperationSucceeded) { 158 Log.e(TAG, "Failed to unmount " + cid + " with rc " + rc); 159 return false; 160 } 161 return true; 162 } catch (RemoteException e) { 163 Log.e(TAG, "StorageManagerService running?"); 164 } 165 return false; 166 } 167 168 public static boolean renameSdDir(String oldId, String newId) { 169 try { 170 int rc = getStorageManager().renameSecureContainer(oldId, newId); 171 if (rc != StorageResultCode.OperationSucceeded) { 172 Log.e(TAG, "Failed to rename " + oldId + " to " + 173 newId + "with rc " + rc); 174 return false; 175 } 176 return true; 177 } catch (RemoteException e) { 178 Log.i(TAG, "Failed ot rename " + oldId + " to " + newId + 179 " with exception : " + e); 180 } 181 return false; 182 } 183 184 public static String getSdDir(String cid) { 185 try { 186 return getStorageManager().getSecureContainerPath(cid); 187 } catch (RemoteException e) { 188 Log.e(TAG, "Failed to get container path for " + cid + 189 " with exception " + e); 190 } 191 return null; 192 } 193 194 public static String getSdFilesystem(String cid) { 195 try { 196 return getStorageManager().getSecureContainerFilesystemPath(cid); 197 } catch (RemoteException e) { 198 Log.e(TAG, "Failed to get container path for " + cid + 199 " with exception " + e); 200 } 201 return null; 202 } 203 204 public static boolean finalizeSdDir(String cid) { 205 try { 206 int rc = getStorageManager().finalizeSecureContainer(cid); 207 if (rc != StorageResultCode.OperationSucceeded) { 208 Log.i(TAG, "Failed to finalize container " + cid); 209 return false; 210 } 211 return true; 212 } catch (RemoteException e) { 213 Log.e(TAG, "Failed to finalize container " + cid + 214 " with exception " + e); 215 } 216 return false; 217 } 218 219 public static boolean destroySdDir(String cid) { 220 try { 221 if (localLOGV) Log.i(TAG, "Forcibly destroying container " + cid); 222 int rc = getStorageManager().destroySecureContainer(cid, true); 223 if (rc != StorageResultCode.OperationSucceeded) { 224 Log.i(TAG, "Failed to destroy container " + cid); 225 return false; 226 } 227 return true; 228 } catch (RemoteException e) { 229 Log.e(TAG, "Failed to destroy container " + cid + 230 " with exception " + e); 231 } 232 return false; 233 } 234 235 public static String[] getSecureContainerList() { 236 try { 237 return getStorageManager().getSecureContainerList(); 238 } catch (RemoteException e) { 239 Log.e(TAG, "Failed to get secure container list with exception" + 240 e); 241 } 242 return null; 243 } 244 245 public static boolean isContainerMounted(String cid) { 246 try { 247 return getStorageManager().isSecureContainerMounted(cid); 248 } catch (RemoteException e) { 249 Log.e(TAG, "Failed to find out if container " + cid + " mounted"); 250 } 251 return false; 252 } 253 254 /** 255 * Extract public files for the single given APK. 256 */ 257 public static long extractPublicFiles(File apkFile, File publicZipFile) 258 throws IOException { 259 final FileOutputStream fstr; 260 final ZipOutputStream publicZipOutStream; 261 262 if (publicZipFile == null) { 263 fstr = null; 264 publicZipOutStream = null; 265 } else { 266 fstr = new FileOutputStream(publicZipFile); 267 publicZipOutStream = new ZipOutputStream(fstr); 268 Log.d(TAG, "Extracting " + apkFile + " to " + publicZipFile); 269 } 270 271 long size = 0L; 272 273 try { 274 final ZipFile privateZip = new ZipFile(apkFile.getAbsolutePath()); 275 try { 276 // Copy manifest, resources.arsc and res directory to public zip 277 for (final ZipEntry zipEntry : Collections.list(privateZip.entries())) { 278 final String zipEntryName = zipEntry.getName(); 279 if ("AndroidManifest.xml".equals(zipEntryName) 280 || "resources.arsc".equals(zipEntryName) 281 || zipEntryName.startsWith("res/")) { 282 size += zipEntry.getSize(); 283 if (publicZipFile != null) { 284 copyZipEntry(zipEntry, privateZip, publicZipOutStream); 285 } 286 } 287 } 288 } finally { 289 try { privateZip.close(); } catch (IOException e) {} 290 } 291 292 if (publicZipFile != null) { 293 publicZipOutStream.finish(); 294 publicZipOutStream.flush(); 295 FileUtils.sync(fstr); 296 publicZipOutStream.close(); 297 FileUtils.setPermissions(publicZipFile.getAbsolutePath(), FileUtils.S_IRUSR 298 | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IROTH, -1, -1); 299 } 300 } finally { 301 IoUtils.closeQuietly(publicZipOutStream); 302 } 303 304 return size; 305 } 306 307 private static void copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile, 308 ZipOutputStream outZipStream) throws IOException { 309 byte[] buffer = new byte[4096]; 310 int num; 311 312 ZipEntry newEntry; 313 if (zipEntry.getMethod() == ZipEntry.STORED) { 314 // Preserve the STORED method of the input entry. 315 newEntry = new ZipEntry(zipEntry); 316 } else { 317 // Create a new entry so that the compressed len is recomputed. 318 newEntry = new ZipEntry(zipEntry.getName()); 319 } 320 outZipStream.putNextEntry(newEntry); 321 322 final InputStream data = inZipFile.getInputStream(zipEntry); 323 try { 324 while ((num = data.read(buffer)) > 0) { 325 outZipStream.write(buffer, 0, num); 326 } 327 outZipStream.flush(); 328 } finally { 329 IoUtils.closeQuietly(data); 330 } 331 } 332 333 public static boolean fixSdPermissions(String cid, int gid, String filename) { 334 try { 335 int rc = getStorageManager().fixPermissionsSecureContainer(cid, gid, filename); 336 if (rc != StorageResultCode.OperationSucceeded) { 337 Log.i(TAG, "Failed to fixperms container " + cid); 338 return false; 339 } 340 return true; 341 } catch (RemoteException e) { 342 Log.e(TAG, "Failed to fixperms container " + cid + " with exception " + e); 343 } 344 return false; 345 } 346 347 /** 348 * A group of external dependencies used in 349 * {@link #resolveInstallVolume(Context, String, int, long)}. It can be backed by real values 350 * from the system or mocked ones for testing purposes. 351 */ 352 public static abstract class TestableInterface { 353 abstract public StorageManager getStorageManager(Context context); 354 abstract public boolean getForceAllowOnExternalSetting(Context context); 355 abstract public boolean getAllow3rdPartyOnInternalConfig(Context context); 356 abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName); 357 abstract public File getDataDirectory(); 358 359 public boolean fitsOnInternalStorage(Context context, SessionParams params) 360 throws IOException { 361 StorageManager storage = getStorageManager(context); 362 final UUID target = storage.getUuidForPath(getDataDirectory()); 363 return (params.sizeBytes <= storage.getAllocatableBytes(target, 364 translateAllocateFlags(params.installFlags))); 365 } 366 } 367 368 private synchronized static TestableInterface getDefaultTestableInterface() { 369 if (sDefaultTestableInterface == null) { 370 sDefaultTestableInterface = new TestableInterface() { 371 @Override 372 public StorageManager getStorageManager(Context context) { 373 return context.getSystemService(StorageManager.class); 374 } 375 376 @Override 377 public boolean getForceAllowOnExternalSetting(Context context) { 378 return Settings.Global.getInt(context.getContentResolver(), 379 Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0; 380 } 381 382 @Override 383 public boolean getAllow3rdPartyOnInternalConfig(Context context) { 384 return context.getResources().getBoolean( 385 com.android.internal.R.bool.config_allow3rdPartyAppOnInternal); 386 } 387 388 @Override 389 public ApplicationInfo getExistingAppInfo(Context context, String packageName) { 390 ApplicationInfo existingInfo = null; 391 try { 392 existingInfo = context.getPackageManager().getApplicationInfo(packageName, 393 PackageManager.MATCH_ANY_USER); 394 } catch (NameNotFoundException ignored) { 395 } 396 return existingInfo; 397 } 398 399 @Override 400 public File getDataDirectory() { 401 return Environment.getDataDirectory(); 402 } 403 }; 404 } 405 return sDefaultTestableInterface; 406 } 407 408 @VisibleForTesting 409 @Deprecated 410 public static String resolveInstallVolume(Context context, String packageName, 411 int installLocation, long sizeBytes, TestableInterface testInterface) 412 throws IOException { 413 final SessionParams params = new SessionParams(SessionParams.MODE_INVALID); 414 params.appPackageName = packageName; 415 params.installLocation = installLocation; 416 params.sizeBytes = sizeBytes; 417 return resolveInstallVolume(context, params, testInterface); 418 } 419 420 /** 421 * Given a requested {@link PackageInfo#installLocation} and calculated 422 * install size, pick the actual volume to install the app. Only considers 423 * internal and private volumes, and prefers to keep an existing package on 424 * its current volume. 425 * 426 * @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null} 427 * for internal storage. 428 */ 429 public static String resolveInstallVolume(Context context, SessionParams params) 430 throws IOException { 431 TestableInterface testableInterface = getDefaultTestableInterface(); 432 return resolveInstallVolume(context, params.appPackageName, params.installLocation, 433 params.sizeBytes, testableInterface); 434 } 435 436 @VisibleForTesting 437 public static String resolveInstallVolume(Context context, SessionParams params, 438 TestableInterface testInterface) throws IOException { 439 final boolean forceAllowOnExternal = testInterface.getForceAllowOnExternalSetting(context); 440 final boolean allow3rdPartyOnInternal = 441 testInterface.getAllow3rdPartyOnInternalConfig(context); 442 // TODO: handle existing apps installed in ASEC; currently assumes 443 // they'll end up back on internal storage 444 ApplicationInfo existingInfo = testInterface.getExistingAppInfo(context, 445 params.appPackageName); 446 447 final boolean fitsOnInternal = testInterface.fitsOnInternalStorage(context, params); 448 final StorageManager storageManager = 449 testInterface.getStorageManager(context); 450 451 // System apps always forced to internal storage 452 if (existingInfo != null && existingInfo.isSystemApp()) { 453 if (fitsOnInternal) { 454 return StorageManager.UUID_PRIVATE_INTERNAL; 455 } else { 456 throw new IOException("Not enough space on existing volume " 457 + existingInfo.volumeUuid + " for system app " + params.appPackageName 458 + " upgrade"); 459 } 460 } 461 462 // Now deal with non-system apps. 463 final ArraySet<String> allCandidates = new ArraySet<>(); 464 VolumeInfo bestCandidate = null; 465 long bestCandidateAvailBytes = Long.MIN_VALUE; 466 for (VolumeInfo vol : storageManager.getVolumes()) { 467 boolean isInternalStorage = ID_PRIVATE_INTERNAL.equals(vol.id); 468 if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable() 469 && (!isInternalStorage || allow3rdPartyOnInternal)) { 470 final UUID target = storageManager.getUuidForPath(new File(vol.path)); 471 final long availBytes = storageManager.getAllocatableBytes(target, 472 translateAllocateFlags(params.installFlags)); 473 if (availBytes >= params.sizeBytes) { 474 allCandidates.add(vol.fsUuid); 475 } 476 if (availBytes >= bestCandidateAvailBytes) { 477 bestCandidate = vol; 478 bestCandidateAvailBytes = availBytes; 479 } 480 } 481 } 482 483 // If app expresses strong desire for internal storage, honor it 484 if (!forceAllowOnExternal 485 && params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { 486 if (existingInfo != null && !Objects.equals(existingInfo.volumeUuid, 487 StorageManager.UUID_PRIVATE_INTERNAL)) { 488 throw new IOException("Cannot automatically move " + params.appPackageName 489 + " from " + existingInfo.volumeUuid + " to internal storage"); 490 } 491 492 if (!allow3rdPartyOnInternal) { 493 throw new IOException("Not allowed to install non-system apps on internal storage"); 494 } 495 496 if (fitsOnInternal) { 497 return StorageManager.UUID_PRIVATE_INTERNAL; 498 } else { 499 throw new IOException("Requested internal only, but not enough space"); 500 } 501 } 502 503 // If app already exists somewhere, we must stay on that volume 504 if (existingInfo != null) { 505 if (Objects.equals(existingInfo.volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL) 506 && fitsOnInternal) { 507 return StorageManager.UUID_PRIVATE_INTERNAL; 508 } else if (allCandidates.contains(existingInfo.volumeUuid)) { 509 return existingInfo.volumeUuid; 510 } else { 511 throw new IOException("Not enough space on existing volume " 512 + existingInfo.volumeUuid + " for " + params.appPackageName + " upgrade"); 513 } 514 } 515 516 // We're left with new installations with either preferring external or auto, so just pick 517 // volume with most space 518 if (bestCandidate != null) { 519 return bestCandidate.fsUuid; 520 } else { 521 throw new IOException("No special requests, but no room on allowed volumes. " 522 + " allow3rdPartyOnInternal? " + allow3rdPartyOnInternal); 523 } 524 } 525 526 public static boolean fitsOnInternal(Context context, SessionParams params) throws IOException { 527 final StorageManager storage = context.getSystemService(StorageManager.class); 528 final UUID target = storage.getUuidForPath(Environment.getDataDirectory()); 529 return (params.sizeBytes <= storage.getAllocatableBytes(target, 530 translateAllocateFlags(params.installFlags))); 531 } 532 533 public static boolean fitsOnExternal(Context context, SessionParams params) { 534 final StorageManager storage = context.getSystemService(StorageManager.class); 535 final StorageVolume primary = storage.getPrimaryVolume(); 536 return (params.sizeBytes > 0) && !primary.isEmulated() 537 && Environment.MEDIA_MOUNTED.equals(primary.getState()) 538 && params.sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile()); 539 } 540 541 @Deprecated 542 public static int resolveInstallLocation(Context context, String packageName, 543 int installLocation, long sizeBytes, int installFlags) { 544 final SessionParams params = new SessionParams(SessionParams.MODE_INVALID); 545 params.appPackageName = packageName; 546 params.installLocation = installLocation; 547 params.sizeBytes = sizeBytes; 548 params.installFlags = installFlags; 549 try { 550 return resolveInstallLocation(context, params); 551 } catch (IOException e) { 552 throw new IllegalStateException(e); 553 } 554 } 555 556 /** 557 * Given a requested {@link PackageInfo#installLocation} and calculated 558 * install size, pick the actual location to install the app. 559 */ 560 public static int resolveInstallLocation(Context context, SessionParams params) 561 throws IOException { 562 ApplicationInfo existingInfo = null; 563 try { 564 existingInfo = context.getPackageManager().getApplicationInfo(params.appPackageName, 565 PackageManager.MATCH_ANY_USER); 566 } catch (NameNotFoundException ignored) { 567 } 568 569 final int prefer; 570 final boolean checkBoth; 571 boolean ephemeral = false; 572 if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) { 573 prefer = RECOMMEND_INSTALL_INTERNAL; 574 ephemeral = true; 575 checkBoth = false; 576 } else if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { 577 prefer = RECOMMEND_INSTALL_INTERNAL; 578 checkBoth = false; 579 } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) { 580 prefer = RECOMMEND_INSTALL_EXTERNAL; 581 checkBoth = false; 582 } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { 583 prefer = RECOMMEND_INSTALL_INTERNAL; 584 checkBoth = false; 585 } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { 586 prefer = RECOMMEND_INSTALL_EXTERNAL; 587 checkBoth = true; 588 } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { 589 // When app is already installed, prefer same medium 590 if (existingInfo != null) { 591 // TODO: distinguish if this is external ASEC 592 if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { 593 prefer = RECOMMEND_INSTALL_EXTERNAL; 594 } else { 595 prefer = RECOMMEND_INSTALL_INTERNAL; 596 } 597 } else { 598 prefer = RECOMMEND_INSTALL_INTERNAL; 599 } 600 checkBoth = true; 601 } else { 602 prefer = RECOMMEND_INSTALL_INTERNAL; 603 checkBoth = false; 604 } 605 606 boolean fitsOnInternal = false; 607 if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) { 608 fitsOnInternal = fitsOnInternal(context, params); 609 } 610 611 boolean fitsOnExternal = false; 612 if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) { 613 fitsOnExternal = fitsOnExternal(context, params); 614 } 615 616 if (prefer == RECOMMEND_INSTALL_INTERNAL) { 617 // The ephemeral case will either fit and return EPHEMERAL, or will not fit 618 // and will fall through to return INSUFFICIENT_STORAGE 619 if (fitsOnInternal) { 620 return (ephemeral) 621 ? PackageHelper.RECOMMEND_INSTALL_EPHEMERAL 622 : PackageHelper.RECOMMEND_INSTALL_INTERNAL; 623 } 624 } else if (prefer == RECOMMEND_INSTALL_EXTERNAL) { 625 if (fitsOnExternal) { 626 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 627 } 628 } 629 630 if (checkBoth) { 631 if (fitsOnInternal) { 632 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 633 } else if (fitsOnExternal) { 634 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 635 } 636 } 637 638 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; 639 } 640 641 public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked, 642 String abiOverride) throws IOException { 643 NativeLibraryHelper.Handle handle = null; 644 try { 645 handle = NativeLibraryHelper.Handle.create(pkg); 646 return calculateInstalledSize(pkg, handle, isForwardLocked, abiOverride); 647 } finally { 648 IoUtils.closeQuietly(handle); 649 } 650 } 651 652 public static long calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle, 653 boolean isForwardLocked, String abiOverride) throws IOException { 654 long sizeBytes = 0; 655 656 // Include raw APKs, and possibly unpacked resources 657 for (String codePath : pkg.getAllCodePaths()) { 658 final File codeFile = new File(codePath); 659 sizeBytes += codeFile.length(); 660 661 if (isForwardLocked) { 662 sizeBytes += PackageHelper.extractPublicFiles(codeFile, null); 663 } 664 } 665 666 // Include all relevant native code 667 sizeBytes += NativeLibraryHelper.sumNativeBinariesWithOverride(handle, abiOverride); 668 669 return sizeBytes; 670 } 671 672 public static String replaceEnd(String str, String before, String after) { 673 if (!str.endsWith(before)) { 674 throw new IllegalArgumentException( 675 "Expected " + str + " to end with " + before); 676 } 677 return str.substring(0, str.length() - before.length()) + after; 678 } 679 680 public static int translateAllocateFlags(int installFlags) { 681 if ((installFlags & PackageManager.INSTALL_ALLOCATE_AGGRESSIVE) != 0) { 682 return StorageManager.FLAG_ALLOCATE_AGGRESSIVE; 683 } else { 684 return 0; 685 } 686 } 687 } 688