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