2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.defcontainer;
19 import android.app.IntentService;
20 import android.content.Intent;
21 import android.content.pm.ContainerEncryptionParams;
22 import android.content.pm.IPackageManager;
23 import android.content.pm.LimitedLengthInputStream;
24 import android.content.pm.MacAuthenticatedInputStream;
25 import android.content.pm.PackageCleanItem;
26 import android.content.pm.PackageInfo;
27 import android.content.pm.PackageInfoLite;
28 import android.content.pm.PackageManager;
29 import android.content.pm.PackageParser;
30 import android.content.res.ObbInfo;
31 import android.content.res.ObbScanner;
32 import android.net.Uri;
33 import android.os.Environment;
34 import android.os.Environment.UserEnvironment;
35 import android.os.FileUtils;
36 import android.os.IBinder;
37 import android.os.ParcelFileDescriptor;
38 import android.os.Process;
39 import android.os.RemoteException;
40 import android.os.ServiceManager;
41 import android.os.StatFs;
42 import android.os.SystemClock;
43 import android.provider.Settings;
44 import android.util.DisplayMetrics;
45 import android.util.Log;
46 import android.util.Slog;
48 import com.android.internal.app.IMediaContainerService;
49 import com.android.internal.content.NativeLibraryHelper;
50 import com.android.internal.content.PackageHelper;
52 import java.io.BufferedInputStream;
54 import java.io.FileInputStream;
55 import java.io.FileNotFoundException;
56 import java.io.IOException;
57 import java.io.InputStream;
58 import java.io.OutputStream;
59 import java.security.DigestException;
60 import java.security.GeneralSecurityException;
61 import java.security.InvalidAlgorithmParameterException;
62 import java.security.InvalidKeyException;
63 import java.security.NoSuchAlgorithmException;
65 import javax.crypto.Cipher;
66 import javax.crypto.CipherInputStream;
67 import javax.crypto.Mac;
68 import javax.crypto.NoSuchPaddingException;
70 import libcore.io.ErrnoException;
71 import libcore.io.IoUtils;
72 import libcore.io.Libcore;
73 import libcore.io.Streams;
74 import libcore.io.StructStatFs;
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.
83 public class DefaultContainerService extends IntentService {
84 private static final String TAG = "DefContainer";
85 private static final boolean localLOGV = false;
87 private static final String LIB_DIR_NAME = "lib";
89 private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
91 * Creates a new container and copies resource there.
92 * @param paackageURI 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
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
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) {
106 if (packageURI == null || cid == null) {
110 return copyResourceInner(packageURI, cid, key, resFileName, publicResFileName,
111 isExternal, isForwardLocked);
115 * Copy specified resource to output stream
117 * @param packageURI the uri of resource to be copied. Should be a file
119 * @param encryptionParams parameters describing the encryption used for
121 * @param outStream Remote file descriptor to be used for copying
122 * @return returns status code according to those in
123 * {@link PackageManager}
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;
131 ParcelFileDescriptor.AutoCloseOutputStream autoOut
132 = new ParcelFileDescriptor.AutoCloseOutputStream(outStream);
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: "
140 return PackageManager.INSTALL_FAILED_INVALID_URI;
141 } catch (IOException e) {
142 Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " IO: "
144 return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
145 } catch (DigestException e) {
146 Slog.e(TAG, "Could not copy URI " + packageURI.toString() + " Security: "
148 return PackageManager.INSTALL_FAILED_INVALID_APK;
153 * Determine the recommended install location for package
154 * specified by file uri location.
155 * @param fileUri the uri of resource to be copied. Should be a
157 * @return Returns PackageInfoLite object containing
158 * the package info and recommended app location.
160 public PackageInfoLite getMinimalPackageInfo(final String packagePath, int flags,
162 PackageInfoLite ret = new PackageInfoLite();
164 if (packagePath == null) {
165 Slog.i(TAG, "Invalid package file " + packagePath);
166 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
170 DisplayMetrics metrics = new DisplayMetrics();
171 metrics.setToDefaults();
173 PackageParser.PackageLite pkg = PackageParser.parsePackageLite(packagePath, 0);
175 Slog.w(TAG, "Failed to parse package");
177 final File apkFile = new File(packagePath);
178 if (!apkFile.exists()) {
179 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI;
181 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
187 ret.packageName = pkg.packageName;
188 ret.versionCode = pkg.versionCode;
189 ret.installLocation = pkg.installLocation;
190 ret.verifiers = pkg.verifiers;
192 ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,
193 packagePath, flags, threshold);
199 public boolean checkInternalFreeStorage(Uri packageUri, boolean isForwardLocked,
200 long threshold) throws RemoteException {
201 final File apkFile = new File(packageUri.getPath());
203 return isUnderInternalThreshold(apkFile, isForwardLocked, threshold);
204 } catch (IOException e) {
210 public boolean checkExternalFreeStorage(Uri packageUri, boolean isForwardLocked)
211 throws RemoteException {
212 final File apkFile = new File(packageUri.getPath());
214 return isUnderExternalThreshold(apkFile, isForwardLocked);
215 } catch (IOException e) {
220 public ObbInfo getObbInfo(String filename) {
222 return ObbScanner.getObbInfo(filename);
223 } catch (IOException e) {
224 Slog.d(TAG, "Couldn't get OBB info for " + filename);
230 public long calculateDirectorySize(String path) throws RemoteException {
231 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
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);
243 public long[] getFileSystemStats(String path) {
244 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
247 final StructStatFs stat = Libcore.os.statfs(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);
257 public void clearDirectory(String path) throws RemoteException {
258 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
260 final File directory = new File(path);
261 if (directory.exists() && directory.isDirectory()) {
262 eraseFiles(directory);
267 public long calculateInstalledSize(String packagePath, boolean isForwardLocked)
268 throws RemoteException {
269 final File packageFile = new File(packagePath);
271 return calculateContainerSize(packageFile, isForwardLocked) * 1024 * 1024;
272 } catch (IOException e) {
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.
277 return packageFile.length() * 2;
282 public DefaultContainerService() {
283 super("DefaultContainerService");
284 setIntentRedelivery(true);
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;
294 while ((item = pm.nextPackageToClean(item)) != null) {
295 final UserEnvironment userEnv = new UserEnvironment(item.userId);
296 eraseFiles(userEnv.getExternalStorageAppDataDirectory(item.packageName));
297 eraseFiles(userEnv.getExternalStorageAppMediaDirectory(item.packageName));
299 eraseFiles(userEnv.getExternalStorageAppObbDirectory(item.packageName));
302 } catch (RemoteException e) {
307 void eraseFiles(File path) {
308 if (path.isDirectory()) {
309 String[] files = path.list();
311 for (String file : files) {
312 eraseFiles(new File(path, file));
319 public IBinder onBind(Intent intent) {
323 private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName,
324 String publicResFileName, boolean isExternal, boolean isForwardLocked) {
327 // Make sure the sdcard is mounted.
328 String status = Environment.getExternalStorageState();
329 if (!status.equals(Environment.MEDIA_MOUNTED)) {
330 Slog.w(TAG, "Make sure sdcard is mounted.");
336 String codePath = packageURI.getPath();
337 File codeFile = new File(codePath);
339 // Calculate size of container needed to hold base APK.
342 sizeMb = calculateContainerSize(codeFile, isForwardLocked);
343 } catch (IOException e) {
344 Slog.w(TAG, "Problem when trying to copy " + codeFile.getPath());
348 // Create new container
349 final String newCachePath = PackageHelper.createSdDir(sizeMb, newCid, key, Process.myUid(),
351 if (newCachePath == null) {
352 Slog.e(TAG, "Failed to create container " + newCid);
357 Slog.i(TAG, "Created container for " + newCid + " at path : " + newCachePath);
360 final File resFile = new File(newCachePath, resFileName);
361 if (FileUtils.copyFile(new File(codePath), resFile)) {
363 Slog.i(TAG, "Copied " + codePath + " to " + resFile);
366 Slog.e(TAG, "Failed to copy " + codePath + " to " + resFile);
367 // Clean up container
368 PackageHelper.destroySdDir(newCid);
373 Libcore.os.chmod(resFile.getAbsolutePath(), 0640);
374 } catch (ErrnoException e) {
375 Slog.e(TAG, "Could not chown APK: " + e.getMessage());
376 PackageHelper.destroySdDir(newCid);
380 if (isForwardLocked) {
381 File publicZipFile = new File(newCachePath, publicResFileName);
383 PackageHelper.extractPublicFiles(resFile.getAbsolutePath(), publicZipFile);
385 Slog.i(TAG, "Copied resources to " + publicZipFile);
387 } catch (IOException e) {
388 Slog.e(TAG, "Could not chown public APK " + publicZipFile.getAbsolutePath() + ": "
390 PackageHelper.destroySdDir(newCid);
395 Libcore.os.chmod(publicZipFile.getAbsolutePath(), 0644);
396 } catch (ErrnoException e) {
397 Slog.e(TAG, "Could not chown public resource file: " + e.getMessage());
398 PackageHelper.destroySdDir(newCid);
403 final File sharedLibraryDir = new File(newCachePath, LIB_DIR_NAME);
404 if (sharedLibraryDir.mkdir()) {
405 int ret = NativeLibraryHelper.copyNativeBinariesIfNeededLI(codeFile, sharedLibraryDir);
406 if (ret != PackageManager.INSTALL_SUCCEEDED) {
407 Slog.e(TAG, "Could not copy native libraries to " + sharedLibraryDir.getPath());
408 PackageHelper.destroySdDir(newCid);
412 Slog.e(TAG, "Could not create native lib directory: " + sharedLibraryDir.getPath());
413 PackageHelper.destroySdDir(newCid);
417 if (!PackageHelper.finalizeSdDir(newCid)) {
418 Slog.e(TAG, "Failed to finalize " + newCid + " at path " + newCachePath);
419 // Clean up container
420 PackageHelper.destroySdDir(newCid);
425 Slog.i(TAG, "Finalized container " + newCid);
428 if (PackageHelper.isContainerMounted(newCid)) {
430 Slog.i(TAG, "Unmounting " + newCid + " at path " + newCachePath);
433 // Force a gc to avoid being killed.
434 Runtime.getRuntime().gc();
435 PackageHelper.unMountSdDir(newCid);
438 Slog.i(TAG, "Container " + newCid + " not mounted");
445 private static void copyToFile(InputStream inputStream, OutputStream out) throws IOException {
446 byte[] buffer = new byte[16384];
448 while ((bytesRead = inputStream.read(buffer)) >= 0) {
449 out.write(buffer, 0, bytesRead);
453 private void copyFile(Uri pPackageURI, OutputStream outStream,
454 ContainerEncryptionParams encryptionParams) throws FileNotFoundException, IOException,
456 String scheme = pPackageURI.getScheme();
457 InputStream inStream = null;
459 if (scheme == null || scheme.equals("file")) {
460 final InputStream is = new FileInputStream(new File(pPackageURI.getPath()));
461 inStream = new BufferedInputStream(is);
462 } else if (scheme.equals("content")) {
463 final ParcelFileDescriptor fd;
465 fd = getContentResolver().openFileDescriptor(pPackageURI, "r");
466 } catch (FileNotFoundException e) {
467 Slog.e(TAG, "Couldn't open file descriptor from download service. "
468 + "Failed with exception " + e);
473 Slog.e(TAG, "Provider returned no file descriptor for " +
474 pPackageURI.toString());
475 throw new FileNotFoundException("provider returned no file descriptor");
478 Slog.i(TAG, "Opened file descriptor from download service.");
480 inStream = new ParcelFileDescriptor.AutoCloseInputStream(fd);
483 Slog.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI);
484 throw new FileNotFoundException("Package URI is not 'file:' or 'content:'");
488 * If this resource is encrypted, get the decrypted stream version
491 ApkContainer container = new ApkContainer(inStream, encryptionParams);
495 * We copy the source package file to a temp file and then
496 * rename it to the destination file in order to eliminate a
497 * window where the package directory scanner notices the new
498 * package file but it's not completely copied yet.
500 copyToFile(container.getInputStream(), outStream);
502 if (!container.isAuthenticated()) {
503 throw new DigestException();
505 } catch (GeneralSecurityException e) {
506 throw new DigestException("A problem occured copying the file.");
509 IoUtils.closeQuietly(inStream);
513 private static class ApkContainer {
514 private static final int MAX_AUTHENTICATED_DATA_SIZE = 16384;
516 private final InputStream mInStream;
518 private MacAuthenticatedInputStream mAuthenticatedStream;
522 public ApkContainer(InputStream inStream, ContainerEncryptionParams encryptionParams)
524 if (encryptionParams == null) {
525 mInStream = inStream;
527 mInStream = getDecryptedStream(inStream, encryptionParams);
528 mTag = encryptionParams.getMacTag();
532 public boolean isAuthenticated() {
533 if (mAuthenticatedStream == null) {
537 return mAuthenticatedStream.isTagEqual(mTag);
540 private Mac getMacInstance(ContainerEncryptionParams encryptionParams) throws IOException {
543 final String macAlgo = encryptionParams.getMacAlgorithm();
545 if (macAlgo != null) {
546 m = Mac.getInstance(macAlgo);
547 m.init(encryptionParams.getMacKey(), encryptionParams.getMacSpec());
553 } catch (NoSuchAlgorithmException e) {
554 throw new IOException(e);
555 } catch (InvalidKeyException e) {
556 throw new IOException(e);
557 } catch (InvalidAlgorithmParameterException e) {
558 throw new IOException(e);
562 public InputStream getInputStream() {
566 private InputStream getDecryptedStream(InputStream inStream,
567 ContainerEncryptionParams encryptionParams) throws IOException {
570 c = Cipher.getInstance(encryptionParams.getEncryptionAlgorithm());
571 c.init(Cipher.DECRYPT_MODE, encryptionParams.getEncryptionKey(),
572 encryptionParams.getEncryptionSpec());
573 } catch (NoSuchAlgorithmException e) {
574 throw new IOException(e);
575 } catch (NoSuchPaddingException e) {
576 throw new IOException(e);
577 } catch (InvalidKeyException e) {
578 throw new IOException(e);
579 } catch (InvalidAlgorithmParameterException e) {
580 throw new IOException(e);
583 final long encStart = encryptionParams.getEncryptedDataStart();
584 final long end = encryptionParams.getDataEnd();
585 if (end < encStart) {
586 throw new IOException("end <= encStart");
589 final Mac mac = getMacInstance(encryptionParams);
591 final long macStart = encryptionParams.getAuthenticatedDataStart();
592 if (macStart >= Integer.MAX_VALUE) {
593 throw new IOException("macStart >= Integer.MAX_VALUE");
596 final long furtherOffset;
597 if (macStart >= 0 && encStart >= 0 && macStart < encStart) {
599 * If there is authenticated data at the beginning, read
600 * that into our MAC first.
602 final long authenticatedLengthLong = encStart - macStart;
603 if (authenticatedLengthLong > MAX_AUTHENTICATED_DATA_SIZE) {
604 throw new IOException("authenticated data is too long");
606 final int authenticatedLength = (int) authenticatedLengthLong;
608 final byte[] authenticatedData = new byte[(int) authenticatedLength];
610 Streams.readFully(inStream, authenticatedData, (int) macStart,
611 authenticatedLength);
612 mac.update(authenticatedData, 0, authenticatedLength);
617 * No authenticated data at the beginning. Just skip the
618 * required number of bytes to the beginning of the stream.
621 furtherOffset = encStart;
628 * If there is data at the end of the stream we want to ignore,
629 * wrap this in a LimitedLengthInputStream.
631 if (furtherOffset >= 0 && end > furtherOffset) {
632 inStream = new LimitedLengthInputStream(inStream, furtherOffset, end - encStart);
633 } else if (furtherOffset > 0) {
634 inStream.skip(furtherOffset);
637 mAuthenticatedStream = new MacAuthenticatedInputStream(inStream, mac);
639 inStream = mAuthenticatedStream;
642 if (end > encStart) {
643 inStream = new LimitedLengthInputStream(inStream, encStart, end - encStart);
645 inStream.skip(encStart);
650 return new CipherInputStream(inStream, c);
655 private static final int PREFER_INTERNAL = 1;
656 private static final int PREFER_EXTERNAL = 2;
658 private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags,
661 boolean checkBoth = false;
663 final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
667 * Explicit install flags should override the manifest settings.
669 if ((flags & PackageManager.INSTALL_INTERNAL) != 0) {
670 prefer = PREFER_INTERNAL;
672 } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) {
673 prefer = PREFER_EXTERNAL;
677 /* No install flags. Check for manifest option. */
678 if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
679 prefer = PREFER_INTERNAL;
681 } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
682 prefer = PREFER_EXTERNAL;
685 } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
686 // We default to preferring internal storage.
687 prefer = PREFER_INTERNAL;
692 // Pick user preference
693 int installPreference = Settings.Global.getInt(getApplicationContext()
694 .getContentResolver(),
695 Settings.Global.DEFAULT_INSTALL_LOCATION,
696 PackageHelper.APP_INSTALL_AUTO);
697 if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) {
698 prefer = PREFER_INTERNAL;
700 } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) {
701 prefer = PREFER_EXTERNAL;
706 * Fall back to default policy of internal-only if nothing else is
709 prefer = PREFER_INTERNAL;
712 final boolean emulated = Environment.isExternalStorageEmulated();
714 final File apkFile = new File(archiveFilePath);
716 boolean fitsOnInternal = false;
717 if (checkBoth || prefer == PREFER_INTERNAL) {
719 fitsOnInternal = isUnderInternalThreshold(apkFile, isForwardLocked, threshold);
720 } catch (IOException e) {
721 return PackageHelper.RECOMMEND_FAILED_INVALID_URI;
725 boolean fitsOnSd = false;
726 if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) {
728 fitsOnSd = isUnderExternalThreshold(apkFile, isForwardLocked);
729 } catch (IOException e) {
730 return PackageHelper.RECOMMEND_FAILED_INVALID_URI;
734 if (prefer == PREFER_INTERNAL) {
735 if (fitsOnInternal) {
736 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
738 } else if (!emulated && prefer == PREFER_EXTERNAL) {
740 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
745 if (fitsOnInternal) {
746 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
747 } else if (!emulated && fitsOnSd) {
748 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
753 * If they requested to be on the external media by default, return that
754 * the media was unavailable. Otherwise, indicate there was insufficient
755 * storage space available.
757 if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)
758 && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
759 return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;
761 return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
766 * Measure a file to see if it fits within the free space threshold.
768 * @param apkFile file to check
769 * @param threshold byte threshold to compare against
770 * @return true if file fits under threshold
771 * @throws FileNotFoundException when APK does not exist
773 private boolean isUnderInternalThreshold(File apkFile, boolean isForwardLocked, long threshold)
775 long size = apkFile.length();
776 if (size == 0 && !apkFile.exists()) {
777 throw new FileNotFoundException();
780 if (isForwardLocked) {
781 size += PackageHelper.extractPublicFiles(apkFile.getAbsolutePath(), null);
784 final StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());
785 final long availInternalSize = (long) internalStats.getAvailableBlocks()
786 * (long) internalStats.getBlockSize();
788 return (availInternalSize - size) > threshold;
793 * Measure a file to see if it fits in the external free space.
795 * @param apkFile file to check
796 * @return true if file fits
797 * @throws IOException when file does not exist
799 private boolean isUnderExternalThreshold(File apkFile, boolean isForwardLocked)
801 if (Environment.isExternalStorageEmulated()) {
805 final int sizeMb = calculateContainerSize(apkFile, isForwardLocked);
808 if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
809 final StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath());
810 final int blocksToMb = (1 << 20) / sdStats.getBlockSize();
811 availSdMb = sdStats.getAvailableBlocks() * blocksToMb;
816 return availSdMb > sizeMb;
820 * Calculate the container size for an APK. Takes into account the
822 * @param apkFile file from which to calculate size
823 * @return size in megabytes (2^20 bytes)
824 * @throws IOException when there is a problem reading the file
826 private int calculateContainerSize(File apkFile, boolean forwardLocked) throws IOException {
827 // Calculate size of container needed to hold base APK.
828 long sizeBytes = apkFile.length();
829 if (sizeBytes == 0 && !apkFile.exists()) {
830 throw new FileNotFoundException();
833 // Check all the native files that need to be copied and add that to the
835 sizeBytes += NativeLibraryHelper.sumNativeBinariesLI(apkFile);
838 sizeBytes += PackageHelper.extractPublicFiles(apkFile.getPath(), null);
841 int sizeMb = (int) (sizeBytes >> 20);
842 if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) {
847 * Add buffer size because we don't have a good way to determine the
848 * real FAT size. Your FAT size varies with how many directory entries
849 * you need, how big the whole filesystem is, and other such headaches.