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