DefaultContainerService.java revision 57dcf5b177b56195421535938544f32d8b591b42
1/* 2 * Copyright (C) 2010 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.defcontainer; 18 19import android.app.IntentService; 20import android.content.Intent; 21import android.content.pm.ContainerEncryptionParams; 22import android.content.pm.IPackageManager; 23import android.content.pm.LimitedLengthInputStream; 24import android.content.pm.MacAuthenticatedInputStream; 25import android.content.pm.PackageCleanItem; 26import android.content.pm.PackageInfo; 27import android.content.pm.PackageInfoLite; 28import android.content.pm.PackageManager; 29import android.content.pm.PackageParser; 30import android.content.pm.PackageParser.PackageParserException; 31import android.content.res.ObbInfo; 32import android.content.res.ObbScanner; 33import android.os.Build; 34import android.os.Environment; 35import android.os.Environment.UserEnvironment; 36import android.os.FileUtils; 37import android.os.IBinder; 38import android.os.ParcelFileDescriptor; 39import android.os.Process; 40import android.os.RemoteException; 41import android.os.ServiceManager; 42import android.os.StatFs; 43import android.provider.Settings; 44import android.system.ErrnoException; 45import android.system.Os; 46import android.system.StructStatVfs; 47import android.util.Slog; 48 49import com.android.internal.app.IMediaContainerService; 50import com.android.internal.content.NativeLibraryHelper; 51import com.android.internal.content.NativeLibraryHelper.ApkHandle; 52import com.android.internal.content.PackageHelper; 53 54import libcore.io.IoUtils; 55import libcore.io.Streams; 56 57import java.io.BufferedInputStream; 58import java.io.File; 59import java.io.FileInputStream; 60import java.io.FileNotFoundException; 61import java.io.IOException; 62import java.io.InputStream; 63import java.io.OutputStream; 64import java.security.DigestException; 65import java.security.GeneralSecurityException; 66import java.security.InvalidAlgorithmParameterException; 67import java.security.InvalidKeyException; 68import java.security.NoSuchAlgorithmException; 69 70import javax.crypto.Cipher; 71import javax.crypto.CipherInputStream; 72import javax.crypto.Mac; 73import javax.crypto.NoSuchPaddingException; 74 75/* 76 * This service copies a downloaded apk to a file passed in as 77 * a ParcelFileDescriptor or to a newly created container specified 78 * by parameters. The DownloadManager gives access to this process 79 * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER 80 * permission to access apks downloaded via the download manager. 81 */ 82public class DefaultContainerService extends IntentService { 83 private static final String TAG = "DefContainer"; 84 private static final boolean localLOGV = false; 85 86 private static final String LIB_DIR_NAME = "lib"; 87 88 private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() { 89 /** 90 * Creates a new container and copies resource there. 91 * @param packageURI the uri of resource to be copied. Can be either 92 * a content uri or a file uri 93 * @param cid the id of the secure container that should 94 * be used for creating a secure container into which the resource 95 * will be copied. 96 * @param key Refers to key used for encrypting the secure container 97 * @param resFileName Name of the target resource file(relative to newly 98 * created secure container) 99 * @return Returns the new cache path where the resource has been copied into 100 * 101 */ 102 @Override 103 public String copyResourceToContainer(final String packagePath, final String cid, 104 final String key, final String resFileName, final String publicResFileName, 105 boolean isExternal, boolean isForwardLocked, String abiOverride) { 106 if (packagePath == null || cid == null) { 107 return null; 108 } 109 110 if (isExternal) { 111 // Make sure the sdcard is mounted. 112 String status = Environment.getExternalStorageState(); 113 if (!status.equals(Environment.MEDIA_MOUNTED)) { 114 Slog.w(TAG, "Make sure sdcard is mounted."); 115 return null; 116 } 117 } 118 119 ApkHandle handle = null; 120 try { 121 handle = ApkHandle.create(packagePath); 122 return copyResourceInner(packagePath, cid, key, resFileName, publicResFileName, 123 isExternal, isForwardLocked, handle, abiOverride); 124 } catch (IOException ioe) { 125 Slog.w(TAG, "Problem opening APK: " + packagePath); 126 return null; 127 } finally { 128 IoUtils.closeQuietly(handle); 129 } 130 } 131 132 /** 133 * Copy specified resource to output stream 134 * 135 * @param packageURI the uri of resource to be copied. Should be a file 136 * uri 137 * @param encryptionParams parameters describing the encryption used for 138 * this file 139 * @param outStream Remote file descriptor to be used for copying 140 * @return returns status code according to those in 141 * {@link PackageManager} 142 */ 143 @Override 144 public int copyResource(final String packagePath, 145 ContainerEncryptionParams encryptionParams, ParcelFileDescriptor outStream) { 146 if (packagePath == null || outStream == null) { 147 return PackageManager.INSTALL_FAILED_INVALID_URI; 148 } 149 150 ParcelFileDescriptor.AutoCloseOutputStream autoOut 151 = new ParcelFileDescriptor.AutoCloseOutputStream(outStream); 152 153 try { 154 copyFile(packagePath, autoOut, encryptionParams); 155 return PackageManager.INSTALL_SUCCEEDED; 156 } catch (FileNotFoundException e) { 157 Slog.e(TAG, "Could not copy URI " + packagePath.toString() + " FNF: " 158 + e.getMessage()); 159 return PackageManager.INSTALL_FAILED_INVALID_URI; 160 } catch (IOException e) { 161 Slog.e(TAG, "Could not copy URI " + packagePath.toString() + " IO: " 162 + e.getMessage()); 163 return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; 164 } catch (DigestException e) { 165 Slog.e(TAG, "Could not copy URI " + packagePath.toString() + " Security: " 166 + e.getMessage()); 167 return PackageManager.INSTALL_FAILED_INVALID_APK; 168 } finally { 169 IoUtils.closeQuietly(autoOut); 170 } 171 } 172 173 /** 174 * Determine the recommended install location for package 175 * specified by file uri location. 176 * 177 * @return Returns PackageInfoLite object containing 178 * the package info and recommended app location. 179 */ 180 @Override 181 public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags, 182 long threshold, String abiOverride) { 183 PackageInfoLite ret = new PackageInfoLite(); 184 185 if (packagePath == null) { 186 Slog.i(TAG, "Invalid package file " + packagePath); 187 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 188 return ret; 189 } 190 191 final File apkFile = new File(packagePath); 192 final PackageParser.ApkLite pkg; 193 try { 194 pkg = PackageParser.parseApkLite(apkFile, 0); 195 } catch (PackageParserException e) { 196 Slog.w(TAG, "Failed to parse package"); 197 198 if (!apkFile.exists()) { 199 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI; 200 } else { 201 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 202 } 203 204 return ret; 205 } 206 207 ret.packageName = pkg.packageName; 208 ret.versionCode = pkg.versionCode; 209 ret.installLocation = pkg.installLocation; 210 ret.verifiers = pkg.verifiers; 211 212 ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, 213 packagePath, flags, threshold, abiOverride); 214 215 return ret; 216 } 217 218 @Override 219 public boolean checkInternalFreeStorage(String packagePath, boolean isForwardLocked, 220 long threshold) throws RemoteException { 221 final File apkFile = new File(packagePath); 222 try { 223 return isUnderInternalThreshold(apkFile, isForwardLocked, threshold); 224 } catch (IOException e) { 225 return true; 226 } 227 } 228 229 @Override 230 public boolean checkExternalFreeStorage(String packagePath, boolean isForwardLocked, 231 String abiOverride) throws RemoteException { 232 final File apkFile = new File(packagePath); 233 try { 234 return isUnderExternalThreshold(apkFile, isForwardLocked, abiOverride); 235 } catch (IOException e) { 236 return true; 237 } 238 } 239 240 @Override 241 public ObbInfo getObbInfo(String filename) { 242 try { 243 return ObbScanner.getObbInfo(filename); 244 } catch (IOException e) { 245 Slog.d(TAG, "Couldn't get OBB info for " + filename); 246 return null; 247 } 248 } 249 250 @Override 251 public long calculateDirectorySize(String path) throws RemoteException { 252 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 253 254 final File dir = Environment.maybeTranslateEmulatedPathToInternal(new File(path)); 255 if (dir.exists() && dir.isDirectory()) { 256 final String targetPath = dir.getAbsolutePath(); 257 return MeasurementUtils.measureDirectory(targetPath); 258 } else { 259 return 0L; 260 } 261 } 262 263 @Override 264 public long[] getFileSystemStats(String path) { 265 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 266 267 try { 268 final StructStatVfs stat = Os.statvfs(path); 269 final long totalSize = stat.f_blocks * stat.f_bsize; 270 final long availSize = stat.f_bavail * stat.f_bsize; 271 return new long[] { totalSize, availSize }; 272 } catch (ErrnoException e) { 273 throw new IllegalStateException(e); 274 } 275 } 276 277 @Override 278 public void clearDirectory(String path) throws RemoteException { 279 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 280 281 final File directory = new File(path); 282 if (directory.exists() && directory.isDirectory()) { 283 eraseFiles(directory); 284 } 285 } 286 287 @Override 288 public long calculateInstalledSize(String packagePath, boolean isForwardLocked, 289 String abiOverride) throws RemoteException { 290 final File packageFile = new File(packagePath); 291 try { 292 return calculateContainerSize(packageFile, isForwardLocked, abiOverride) * 1024 * 1024; 293 } catch (IOException e) { 294 /* 295 * Okay, something failed, so let's just estimate it to be 2x 296 * the file size. Note this will be 0 if the file doesn't exist. 297 */ 298 return packageFile.length() * 2; 299 } 300 } 301 }; 302 303 public DefaultContainerService() { 304 super("DefaultContainerService"); 305 setIntentRedelivery(true); 306 } 307 308 @Override 309 protected void onHandleIntent(Intent intent) { 310 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) { 311 final IPackageManager pm = IPackageManager.Stub.asInterface( 312 ServiceManager.getService("package")); 313 PackageCleanItem item = null; 314 try { 315 while ((item = pm.nextPackageToClean(item)) != null) { 316 final UserEnvironment userEnv = new UserEnvironment(item.userId); 317 eraseFiles(userEnv.buildExternalStorageAppDataDirs(item.packageName)); 318 eraseFiles(userEnv.buildExternalStorageAppMediaDirs(item.packageName)); 319 if (item.andCode) { 320 eraseFiles(userEnv.buildExternalStorageAppObbDirs(item.packageName)); 321 } 322 } 323 } catch (RemoteException e) { 324 } 325 } 326 } 327 328 void eraseFiles(File[] paths) { 329 for (File path : paths) { 330 eraseFiles(path); 331 } 332 } 333 334 void eraseFiles(File path) { 335 if (path.isDirectory()) { 336 String[] files = path.list(); 337 if (files != null) { 338 for (String file : files) { 339 eraseFiles(new File(path, file)); 340 } 341 } 342 } 343 path.delete(); 344 } 345 346 public IBinder onBind(Intent intent) { 347 return mBinder; 348 } 349 350 private String copyResourceInner(String packagePath, String newCid, String key, String resFileName, 351 String publicResFileName, boolean isExternal, boolean isForwardLocked, 352 ApkHandle handle, String abiOverride) { 353 // The .apk file 354 String codePath = packagePath; 355 File codeFile = new File(codePath); 356 357 String[] abiList = Build.SUPPORTED_ABIS; 358 if (abiOverride != null) { 359 abiList = new String[] { abiOverride }; 360 } else { 361 try { 362 if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && 363 NativeLibraryHelper.hasRenderscriptBitcode(handle)) { 364 abiList = Build.SUPPORTED_32_BIT_ABIS; 365 } 366 } catch (IOException ioe) { 367 Slog.w(TAG, "Problem determining ABI for: " + codeFile.getPath()); 368 return null; 369 } 370 } 371 372 final int abi = NativeLibraryHelper.findSupportedAbi(handle, abiList); 373 374 // Calculate size of container needed to hold base APK. 375 final int sizeMb; 376 try { 377 sizeMb = calculateContainerSize(handle, codeFile, abi, isForwardLocked); 378 } catch (IOException e) { 379 Slog.w(TAG, "Problem when trying to copy " + codeFile.getPath()); 380 return null; 381 } 382 383 // Create new container 384 final String newCachePath = PackageHelper.createSdDir(sizeMb, newCid, key, Process.myUid(), 385 isExternal); 386 if (newCachePath == null) { 387 Slog.e(TAG, "Failed to create container " + newCid); 388 return null; 389 } 390 391 if (localLOGV) { 392 Slog.i(TAG, "Created container for " + newCid + " at path : " + newCachePath); 393 } 394 395 final File resFile = new File(newCachePath, resFileName); 396 if (FileUtils.copyFile(new File(codePath), resFile)) { 397 if (localLOGV) { 398 Slog.i(TAG, "Copied " + codePath + " to " + resFile); 399 } 400 } else { 401 Slog.e(TAG, "Failed to copy " + codePath + " to " + resFile); 402 // Clean up container 403 PackageHelper.destroySdDir(newCid); 404 return null; 405 } 406 407 try { 408 Os.chmod(resFile.getAbsolutePath(), 0640); 409 } catch (ErrnoException e) { 410 Slog.e(TAG, "Could not chown APK: " + e.getMessage()); 411 PackageHelper.destroySdDir(newCid); 412 return null; 413 } 414 415 if (isForwardLocked) { 416 File publicZipFile = new File(newCachePath, publicResFileName); 417 try { 418 PackageHelper.extractPublicFiles(resFile.getAbsolutePath(), publicZipFile); 419 if (localLOGV) { 420 Slog.i(TAG, "Copied resources to " + publicZipFile); 421 } 422 } catch (IOException e) { 423 Slog.e(TAG, "Could not chown public APK " + publicZipFile.getAbsolutePath() + ": " 424 + e.getMessage()); 425 PackageHelper.destroySdDir(newCid); 426 return null; 427 } 428 429 try { 430 Os.chmod(publicZipFile.getAbsolutePath(), 0644); 431 } catch (ErrnoException e) { 432 Slog.e(TAG, "Could not chown public resource file: " + e.getMessage()); 433 PackageHelper.destroySdDir(newCid); 434 return null; 435 } 436 } 437 438 final File sharedLibraryDir = new File(newCachePath, LIB_DIR_NAME); 439 if (sharedLibraryDir.mkdir()) { 440 int ret = PackageManager.INSTALL_SUCCEEDED; 441 if (abi >= 0) { 442 ret = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, 443 sharedLibraryDir, abiList[abi]); 444 } else if (abi != PackageManager.NO_NATIVE_LIBRARIES) { 445 ret = abi; 446 } 447 448 if (ret != PackageManager.INSTALL_SUCCEEDED) { 449 Slog.e(TAG, "Could not copy native libraries to " + sharedLibraryDir.getPath()); 450 PackageHelper.destroySdDir(newCid); 451 return null; 452 } 453 } else { 454 Slog.e(TAG, "Could not create native lib directory: " + sharedLibraryDir.getPath()); 455 PackageHelper.destroySdDir(newCid); 456 return null; 457 } 458 459 if (!PackageHelper.finalizeSdDir(newCid)) { 460 Slog.e(TAG, "Failed to finalize " + newCid + " at path " + newCachePath); 461 // Clean up container 462 PackageHelper.destroySdDir(newCid); 463 return null; 464 } 465 466 if (localLOGV) { 467 Slog.i(TAG, "Finalized container " + newCid); 468 } 469 470 if (PackageHelper.isContainerMounted(newCid)) { 471 if (localLOGV) { 472 Slog.i(TAG, "Unmounting " + newCid + " at path " + newCachePath); 473 } 474 475 // Force a gc to avoid being killed. 476 Runtime.getRuntime().gc(); 477 PackageHelper.unMountSdDir(newCid); 478 } else { 479 if (localLOGV) { 480 Slog.i(TAG, "Container " + newCid + " not mounted"); 481 } 482 } 483 484 return newCachePath; 485 } 486 487 private static void copyToFile(InputStream inputStream, OutputStream out) throws IOException { 488 byte[] buffer = new byte[16384]; 489 int bytesRead; 490 while ((bytesRead = inputStream.read(buffer)) >= 0) { 491 out.write(buffer, 0, bytesRead); 492 } 493 } 494 495 private void copyFile(String packagePath, OutputStream outStream, 496 ContainerEncryptionParams encryptionParams) throws FileNotFoundException, IOException, 497 DigestException { 498 InputStream inStream = null; 499 try { 500 final InputStream is = new FileInputStream(new File(packagePath)); 501 inStream = new BufferedInputStream(is); 502 503 /* 504 * If this resource is encrypted, get the decrypted stream version 505 * of it. 506 */ 507 ApkContainer container = new ApkContainer(inStream, encryptionParams); 508 509 try { 510 /* 511 * We copy the source package file to a temp file and then 512 * rename it to the destination file in order to eliminate a 513 * window where the package directory scanner notices the new 514 * package file but it's not completely copied yet. 515 */ 516 copyToFile(container.getInputStream(), outStream); 517 518 if (!container.isAuthenticated()) { 519 throw new DigestException(); 520 } 521 } catch (GeneralSecurityException e) { 522 throw new DigestException("A problem occured copying the file."); 523 } 524 } finally { 525 IoUtils.closeQuietly(inStream); 526 } 527 } 528 529 private static class ApkContainer { 530 private static final int MAX_AUTHENTICATED_DATA_SIZE = 16384; 531 532 private final InputStream mInStream; 533 534 private MacAuthenticatedInputStream mAuthenticatedStream; 535 536 private byte[] mTag; 537 538 public ApkContainer(InputStream inStream, ContainerEncryptionParams encryptionParams) 539 throws IOException { 540 if (encryptionParams == null) { 541 mInStream = inStream; 542 } else { 543 mInStream = getDecryptedStream(inStream, encryptionParams); 544 mTag = encryptionParams.getMacTag(); 545 } 546 } 547 548 public boolean isAuthenticated() { 549 if (mAuthenticatedStream == null) { 550 return true; 551 } 552 553 return mAuthenticatedStream.isTagEqual(mTag); 554 } 555 556 private Mac getMacInstance(ContainerEncryptionParams encryptionParams) throws IOException { 557 final Mac m; 558 try { 559 final String macAlgo = encryptionParams.getMacAlgorithm(); 560 561 if (macAlgo != null) { 562 m = Mac.getInstance(macAlgo); 563 m.init(encryptionParams.getMacKey(), encryptionParams.getMacSpec()); 564 } else { 565 m = null; 566 } 567 568 return m; 569 } catch (NoSuchAlgorithmException e) { 570 throw new IOException(e); 571 } catch (InvalidKeyException e) { 572 throw new IOException(e); 573 } catch (InvalidAlgorithmParameterException e) { 574 throw new IOException(e); 575 } 576 } 577 578 public InputStream getInputStream() { 579 return mInStream; 580 } 581 582 private InputStream getDecryptedStream(InputStream inStream, 583 ContainerEncryptionParams encryptionParams) throws IOException { 584 final Cipher c; 585 try { 586 c = Cipher.getInstance(encryptionParams.getEncryptionAlgorithm()); 587 c.init(Cipher.DECRYPT_MODE, encryptionParams.getEncryptionKey(), 588 encryptionParams.getEncryptionSpec()); 589 } catch (NoSuchAlgorithmException e) { 590 throw new IOException(e); 591 } catch (NoSuchPaddingException e) { 592 throw new IOException(e); 593 } catch (InvalidKeyException e) { 594 throw new IOException(e); 595 } catch (InvalidAlgorithmParameterException e) { 596 throw new IOException(e); 597 } 598 599 final long encStart = encryptionParams.getEncryptedDataStart(); 600 final long end = encryptionParams.getDataEnd(); 601 if (end < encStart) { 602 throw new IOException("end <= encStart"); 603 } 604 605 final Mac mac = getMacInstance(encryptionParams); 606 if (mac != null) { 607 final long macStart = encryptionParams.getAuthenticatedDataStart(); 608 if (macStart >= Integer.MAX_VALUE) { 609 throw new IOException("macStart >= Integer.MAX_VALUE"); 610 } 611 612 final long furtherOffset; 613 if (macStart >= 0 && encStart >= 0 && macStart < encStart) { 614 /* 615 * If there is authenticated data at the beginning, read 616 * that into our MAC first. 617 */ 618 final long authenticatedLengthLong = encStart - macStart; 619 if (authenticatedLengthLong > MAX_AUTHENTICATED_DATA_SIZE) { 620 throw new IOException("authenticated data is too long"); 621 } 622 final int authenticatedLength = (int) authenticatedLengthLong; 623 624 final byte[] authenticatedData = new byte[(int) authenticatedLength]; 625 626 Streams.readFully(inStream, authenticatedData, (int) macStart, 627 authenticatedLength); 628 mac.update(authenticatedData, 0, authenticatedLength); 629 630 furtherOffset = 0; 631 } else { 632 /* 633 * No authenticated data at the beginning. Just skip the 634 * required number of bytes to the beginning of the stream. 635 */ 636 if (encStart > 0) { 637 furtherOffset = encStart; 638 } else { 639 furtherOffset = 0; 640 } 641 } 642 643 /* 644 * If there is data at the end of the stream we want to ignore, 645 * wrap this in a LimitedLengthInputStream. 646 */ 647 if (furtherOffset >= 0 && end > furtherOffset) { 648 inStream = new LimitedLengthInputStream(inStream, furtherOffset, end - encStart); 649 } else if (furtherOffset > 0) { 650 inStream.skip(furtherOffset); 651 } 652 653 mAuthenticatedStream = new MacAuthenticatedInputStream(inStream, mac); 654 655 inStream = mAuthenticatedStream; 656 } else { 657 if (encStart >= 0) { 658 if (end > encStart) { 659 inStream = new LimitedLengthInputStream(inStream, encStart, end - encStart); 660 } else { 661 inStream.skip(encStart); 662 } 663 } 664 } 665 666 return new CipherInputStream(inStream, c); 667 } 668 669 } 670 671 private static final int PREFER_INTERNAL = 1; 672 private static final int PREFER_EXTERNAL = 2; 673 674 private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags, 675 long threshold, String abiOverride) { 676 int prefer; 677 boolean checkBoth = false; 678 679 final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0; 680 681 check_inner : { 682 /* 683 * Explicit install flags should override the manifest settings. 684 */ 685 if ((flags & PackageManager.INSTALL_INTERNAL) != 0) { 686 prefer = PREFER_INTERNAL; 687 break check_inner; 688 } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) { 689 prefer = PREFER_EXTERNAL; 690 break check_inner; 691 } 692 693 /* No install flags. Check for manifest option. */ 694 if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { 695 prefer = PREFER_INTERNAL; 696 break check_inner; 697 } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { 698 prefer = PREFER_EXTERNAL; 699 checkBoth = true; 700 break check_inner; 701 } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { 702 // We default to preferring internal storage. 703 prefer = PREFER_INTERNAL; 704 checkBoth = true; 705 break check_inner; 706 } 707 708 // Pick user preference 709 int installPreference = Settings.Global.getInt(getApplicationContext() 710 .getContentResolver(), 711 Settings.Global.DEFAULT_INSTALL_LOCATION, 712 PackageHelper.APP_INSTALL_AUTO); 713 if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) { 714 prefer = PREFER_INTERNAL; 715 break check_inner; 716 } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) { 717 prefer = PREFER_EXTERNAL; 718 break check_inner; 719 } 720 721 /* 722 * Fall back to default policy of internal-only if nothing else is 723 * specified. 724 */ 725 prefer = PREFER_INTERNAL; 726 } 727 728 final boolean emulated = Environment.isExternalStorageEmulated(); 729 730 final File apkFile = new File(archiveFilePath); 731 732 boolean fitsOnInternal = false; 733 if (checkBoth || prefer == PREFER_INTERNAL) { 734 try { 735 fitsOnInternal = isUnderInternalThreshold(apkFile, isForwardLocked, threshold); 736 } catch (IOException e) { 737 return PackageHelper.RECOMMEND_FAILED_INVALID_URI; 738 } 739 } 740 741 boolean fitsOnSd = false; 742 if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) { 743 try { 744 fitsOnSd = isUnderExternalThreshold(apkFile, isForwardLocked, abiOverride); 745 } catch (IOException e) { 746 return PackageHelper.RECOMMEND_FAILED_INVALID_URI; 747 } 748 } 749 750 if (prefer == PREFER_INTERNAL) { 751 if (fitsOnInternal) { 752 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 753 } 754 } else if (!emulated && prefer == PREFER_EXTERNAL) { 755 if (fitsOnSd) { 756 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 757 } 758 } 759 760 if (checkBoth) { 761 if (fitsOnInternal) { 762 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; 763 } else if (!emulated && fitsOnSd) { 764 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; 765 } 766 } 767 768 /* 769 * If they requested to be on the external media by default, return that 770 * the media was unavailable. Otherwise, indicate there was insufficient 771 * storage space available. 772 */ 773 if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL) 774 && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { 775 return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE; 776 } else { 777 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; 778 } 779 } 780 781 /** 782 * Measure a file to see if it fits within the free space threshold. 783 * 784 * @param apkFile file to check 785 * @param threshold byte threshold to compare against 786 * @return true if file fits under threshold 787 * @throws FileNotFoundException when APK does not exist 788 */ 789 private boolean isUnderInternalThreshold(File apkFile, boolean isForwardLocked, long threshold) 790 throws IOException { 791 long size = apkFile.length(); 792 if (size == 0 && !apkFile.exists()) { 793 throw new FileNotFoundException(); 794 } 795 796 if (isForwardLocked) { 797 size += PackageHelper.extractPublicFiles(apkFile.getAbsolutePath(), null); 798 } 799 800 final StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath()); 801 final long availInternalSize = (long) internalStats.getAvailableBlocks() 802 * (long) internalStats.getBlockSize(); 803 804 return (availInternalSize - size) > threshold; 805 } 806 807 808 /** 809 * Measure a file to see if it fits in the external free space. 810 * 811 * @param apkFile file to check 812 * @return true if file fits 813 * @throws IOException when file does not exist 814 */ 815 private boolean isUnderExternalThreshold(File apkFile, boolean isForwardLocked, String abiOverride) 816 throws IOException { 817 if (Environment.isExternalStorageEmulated()) { 818 return false; 819 } 820 821 final int sizeMb = calculateContainerSize(apkFile, isForwardLocked, abiOverride); 822 823 final int availSdMb; 824 if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { 825 final StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath()); 826 final int blocksToMb = (1 << 20) / sdStats.getBlockSize(); 827 availSdMb = sdStats.getAvailableBlocks() * blocksToMb; 828 } else { 829 availSdMb = -1; 830 } 831 832 return availSdMb > sizeMb; 833 } 834 835 private int calculateContainerSize(File apkFile, boolean forwardLocked, 836 String abiOverride) throws IOException { 837 ApkHandle handle = null; 838 try { 839 handle = ApkHandle.create(apkFile); 840 final int abi = NativeLibraryHelper.findSupportedAbi(handle, 841 (abiOverride != null) ? new String[] { abiOverride } : Build.SUPPORTED_ABIS); 842 return calculateContainerSize(handle, apkFile, abi, forwardLocked); 843 } finally { 844 IoUtils.closeQuietly(handle); 845 } 846 } 847 848 /** 849 * Calculate the container size for an APK. Takes into account the 850 * 851 * @param apkFile file from which to calculate size 852 * @return size in megabytes (2^20 bytes) 853 * @throws IOException when there is a problem reading the file 854 */ 855 private int calculateContainerSize(NativeLibraryHelper.ApkHandle apkHandle, 856 File apkFile, int abiIndex, boolean forwardLocked) throws IOException { 857 // Calculate size of container needed to hold base APK. 858 long sizeBytes = apkFile.length(); 859 if (sizeBytes == 0 && !apkFile.exists()) { 860 throw new FileNotFoundException(); 861 } 862 863 // Check all the native files that need to be copied and add that to the 864 // container size. 865 if (abiIndex >= 0) { 866 sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(apkHandle, 867 Build.SUPPORTED_ABIS[abiIndex]); 868 } 869 870 if (forwardLocked) { 871 sizeBytes += PackageHelper.extractPublicFiles(apkFile.getPath(), null); 872 } 873 874 int sizeMb = (int) (sizeBytes >> 20); 875 if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) { 876 sizeMb++; 877 } 878 879 /* 880 * Add buffer size because we don't have a good way to determine the 881 * real FAT size. Your FAT size varies with how many directory entries 882 * you need, how big the whole filesystem is, and other such headaches. 883 */ 884 sizeMb++; 885 886 return sizeMb; 887 } 888} 889