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