diff options
author | Kenny Root <kroot@google.com> | 2011-03-15 14:48:56 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2011-03-15 14:48:56 -0700 |
commit | d022c23aed0d022057c0074a158b522c83e906e4 (patch) | |
tree | f3ea5813b838aca632dfbaa4cf627f72625b25de | |
parent | 0e5e2e2b2bb3d6c3a0216181df29e6bfdc71256f (diff) | |
parent | 62e1b4e9d41a01db423b5e4684ecf529ed46106d (diff) | |
download | frameworks_base-d022c23aed0d022057c0074a158b522c83e906e4.zip frameworks_base-d022c23aed0d022057c0074a158b522c83e906e4.tar.gz frameworks_base-d022c23aed0d022057c0074a158b522c83e906e4.tar.bz2 |
Merge "Revise free space checks for package installs"
5 files changed, 220 insertions, 159 deletions
diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl index aee1626..dd22e25 100755 --- a/core/java/com/android/internal/app/IMediaContainerService.aidl +++ b/core/java/com/android/internal/app/IMediaContainerService.aidl @@ -27,8 +27,9 @@ interface IMediaContainerService { String key, String resFileName); boolean copyResource(in Uri packageURI, in ParcelFileDescriptor outStream); - PackageInfoLite getMinimalPackageInfo(in Uri fileUri, int flags); - boolean checkFreeStorage(boolean external, in Uri fileUri); + PackageInfoLite getMinimalPackageInfo(in Uri fileUri, in int flags, in long threshold); + boolean checkInternalFreeStorage(in Uri fileUri, in long threshold); + boolean checkExternalFreeStorage(in Uri fileUri); ObbInfo getObbInfo(in String filename); long calculateDirectorySize(in String directory); } diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java index d6c43f9..b57046c 100644 --- a/core/java/com/android/internal/content/PackageHelper.java +++ b/core/java/com/android/internal/content/PackageHelper.java @@ -56,18 +56,13 @@ public class PackageHelper { return null; } - public static String createSdDir(long sizeBytes, String cid, + public static String createSdDir(int sizeMb, String cid, String sdEncKey, int uid) { // Create mount point via MountService IMountService mountService = getMountService(); - int sizeMb = (int) (sizeBytes >> 20); - if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) { - sizeMb++; - } - // Add buffer size - sizeMb++; + if (localLOGV) - Log.i(TAG, "Size of container " + sizeMb + " MB " + sizeBytes + " bytes"); + Log.i(TAG, "Size of container " + sizeMb + " MB"); try { int rc = mountService.createSecureContainer( diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index 21f77e3..15c1653 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -47,7 +47,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipException; @@ -117,7 +117,7 @@ public class DefaultContainerService extends IntentService { * @return Returns PackageInfoLite object containing * the package info and recommended app location. */ - public PackageInfoLite getMinimalPackageInfo(final Uri fileUri, int flags) { + public PackageInfoLite getMinimalPackageInfo(final Uri fileUri, int flags, long threshold) { PackageInfoLite ret = new PackageInfoLite(); if (fileUri == null) { Log.i(TAG, "Invalid package uri " + fileUri); @@ -131,15 +131,9 @@ public class DefaultContainerService extends IntentService { return ret; } String archiveFilePath = fileUri.getPath(); - PackageParser packageParser = new PackageParser(archiveFilePath); - File sourceFile = new File(archiveFilePath); DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); - PackageParser.PackageLite pkg = packageParser.parsePackageLite( - archiveFilePath, 0); - // Nuke the parser reference right away and force a gc - packageParser = null; - Runtime.getRuntime().gc(); + PackageParser.PackageLite pkg = PackageParser.parsePackageLite(archiveFilePath, 0); if (pkg == null) { Log.w(TAG, "Failed to parse package"); ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; @@ -147,12 +141,22 @@ public class DefaultContainerService extends IntentService { } ret.packageName = pkg.packageName; ret.installLocation = pkg.installLocation; - ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, archiveFilePath, flags); + ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, + archiveFilePath, flags, threshold); return ret; } - public boolean checkFreeStorage(boolean external, Uri fileUri) { - return checkFreeStorageInner(external, fileUri); + @Override + public boolean checkInternalFreeStorage(Uri packageUri, long threshold) + throws RemoteException { + final File apkFile = new File(packageUri.getPath()); + return isUnderInternalThreshold(apkFile, threshold); + } + + @Override + public boolean checkExternalFreeStorage(Uri packageUri) throws RemoteException { + final File apkFile = new File(packageUri.getPath()); + return isUnderExternalThreshold(apkFile); } public ObbInfo getObbInfo(String filename) { @@ -225,41 +229,15 @@ public class DefaultContainerService extends IntentService { String codePath = packageURI.getPath(); File codeFile = new File(codePath); - // Calculate size of container needed to hold base APK. - long sizeBytes = codeFile.length(); - - // Check all the native files that need to be copied and add that to the container size. - ZipFile zipFile; - List<Pair<ZipEntry, String>> nativeFiles; - try { - zipFile = new ZipFile(codeFile); - - nativeFiles = new LinkedList<Pair<ZipEntry, String>>(); - - NativeLibraryHelper.listPackageNativeBinariesLI(zipFile, nativeFiles); + // Native files we need to copy to the container. + List<Pair<ZipEntry, String>> nativeFiles = new ArrayList<Pair<ZipEntry, String>>(); - final int N = nativeFiles.size(); - for (int i = 0; i < N; i++) { - final Pair<ZipEntry, String> entry = nativeFiles.get(i); - - /* - * Note that PackageHelper.createSdDir adds a 1MB padding on - * our claimed size, so we don't have to worry about block - * alignment here. - */ - sizeBytes += entry.first.getSize(); - } - } catch (ZipException e) { - Log.w(TAG, "Failed to extract data from package file", e); - return null; - } catch (IOException e) { - Log.w(TAG, "Failed to cache package shared libs", e); - return null; - } + // Calculate size of container needed to hold base APK. + final int sizeMb = calculateContainerSize(codeFile, nativeFiles); // Create new container String newCachePath = null; - if ((newCachePath = PackageHelper.createSdDir(sizeBytes, newCid, key, Process.myUid())) == null) { + if ((newCachePath = PackageHelper.createSdDir(sizeMb, newCid, key, Process.myUid())) == null) { Log.e(TAG, "Failed to create container " + newCid); return null; } @@ -274,6 +252,8 @@ public class DefaultContainerService extends IntentService { } try { + ZipFile zipFile = new ZipFile(codeFile); + File sharedLibraryDir = new File(newCachePath, LIB_DIR_NAME); sharedLibraryDir.mkdir(); @@ -386,159 +366,196 @@ public class DefaultContainerService extends IntentService { return true; } - // Constants related to app heuristics - // No-installation limit for internal flash: 10% or less space available - private static final double LOW_NAND_FLASH_TRESHOLD = 0.1; + private static final int PREFER_INTERNAL = 1; + private static final int PREFER_EXTERNAL = 2; - // SD-to-internal app size threshold: currently set to 1 MB - private static final long INSTALL_ON_SD_THRESHOLD = (1024 * 1024); - private static final int ERR_LOC = -1; - - private int recommendAppInstallLocation(int installLocation, - String archiveFilePath, int flags) { - boolean checkInt = false; - boolean checkExt = false; + private int recommendAppInstallLocation(int installLocation, String archiveFilePath, int flags, + long threshold) { + int prefer; boolean checkBoth = false; + check_inner : { - // Check flags. + /* + * Explicit install flags should override the manifest settings. + */ if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) { - // Check for forward locked app - checkInt = true; + /* + * Forward-locked applications cannot be installed on SD card, + * so only allow checking internal storage. + */ + prefer = PREFER_INTERNAL; break check_inner; } else if ((flags & PackageManager.INSTALL_INTERNAL) != 0) { - // Explicit flag to install internally. - // Check internal storage and return - checkInt = true; + prefer = PREFER_INTERNAL; break check_inner; } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) { - // Explicit flag to install externally. - // Check external storage and return - checkExt = true; + prefer = PREFER_EXTERNAL; break check_inner; } - // Check for manifest option + + /* No install flags. Check for manifest option. */ if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) { - checkInt = true; + prefer = PREFER_INTERNAL; break check_inner; } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { - checkExt = true; + prefer = PREFER_EXTERNAL; checkBoth = true; break check_inner; } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { - checkInt = true; + // We default to preferring internal storage. + prefer = PREFER_INTERNAL; checkBoth = true; break check_inner; } + // Pick user preference int installPreference = Settings.System.getInt(getApplicationContext() .getContentResolver(), Settings.Secure.DEFAULT_INSTALL_LOCATION, PackageHelper.APP_INSTALL_AUTO); if (installPreference == PackageHelper.APP_INSTALL_INTERNAL) { - checkInt = true; + prefer = PREFER_INTERNAL; break check_inner; } else if (installPreference == PackageHelper.APP_INSTALL_EXTERNAL) { - checkExt = true; + prefer = PREFER_EXTERNAL; break check_inner; } - // Fall back to default policy if nothing else is specified. - checkInt = true; + + /* + * Fall back to default policy of internal-only if nothing else is + * specified. + */ + prefer = PREFER_INTERNAL; } - // Package size = code size + cache size + data size - // If code size > 1 MB, install on SD card. - // Else install on internal NAND flash, unless space on NAND is less than 10% - String status = Environment.getExternalStorageState(); - long availSDSize = -1; - boolean mediaAvailable = false; - if (!Environment.isExternalStorageEmulated() && status.equals(Environment.MEDIA_MOUNTED)) { - StatFs sdStats = new StatFs( - Environment.getExternalStorageDirectory().getPath()); - availSDSize = (long)sdStats.getAvailableBlocks() * - (long)sdStats.getBlockSize(); - mediaAvailable = true; + final boolean emulated = Environment.isExternalStorageEmulated(); + + final File apkFile = new File(archiveFilePath); + + boolean fitsOnInternal = false; + if (checkBoth || prefer == PREFER_INTERNAL) { + fitsOnInternal = isUnderInternalThreshold(apkFile, threshold); } - StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath()); - long totalInternalSize = (long)internalStats.getBlockCount() * - (long)internalStats.getBlockSize(); - long availInternalSize = (long)internalStats.getAvailableBlocks() * - (long)internalStats.getBlockSize(); - - double pctNandFree = (double)availInternalSize / (double)totalInternalSize; - - File apkFile = new File(archiveFilePath); - long pkgLen = apkFile.length(); - - // To make final copy - long reqInstallSize = pkgLen; - // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now. - long reqInternalSize = 0; - boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD); - boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize); + boolean fitsOnSd = false; - if (mediaAvailable && (reqInstallSize < availSDSize)) { - // If we do not have an internal size requirement - // don't do a threshold check. - if (reqInternalSize == 0) { - fitsOnSd = true; - } else if ((reqInternalSize < availInternalSize) && intThresholdOk) { - fitsOnSd = true; - } + if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) { + fitsOnSd = isUnderExternalThreshold(apkFile); } - boolean fitsOnInt = intThresholdOk && intAvailOk; - if (checkInt) { - // Check for internal memory availability - if (fitsOnInt) { + + if (prefer == PREFER_INTERNAL) { + if (fitsOnInternal) { return PackageHelper.RECOMMEND_INSTALL_INTERNAL; } - } else if (checkExt) { + } else if (!emulated && prefer == PREFER_EXTERNAL) { if (fitsOnSd) { return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; } } + if (checkBoth) { - // Check for internal first - if (fitsOnInt) { + if (fitsOnInternal) { return PackageHelper.RECOMMEND_INSTALL_INTERNAL; - } - // Check for external next - if (fitsOnSd) { + } else if (!emulated && fitsOnSd) { return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; } } - if ((checkExt || checkBoth) && !mediaAvailable) { + + /* + * If they requested to be on the external media by default, return that + * the media was unavailable. Otherwise, indicate there was insufficient + * storage space available. + */ + if (!emulated && (checkBoth || prefer == PREFER_EXTERNAL) + && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE; + } else { + return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; } - return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; } - private boolean checkFreeStorageInner(boolean external, Uri packageURI) { - File apkFile = new File(packageURI.getPath()); - long size = apkFile.length(); - if (external) { - String status = Environment.getExternalStorageState(); - long availSDSize = -1; - if (status.equals(Environment.MEDIA_MOUNTED)) { - StatFs sdStats = new StatFs( - Environment.getExternalStorageDirectory().getPath()); - availSDSize = (long)sdStats.getAvailableBlocks() * - (long)sdStats.getBlockSize(); + private boolean isUnderInternalThreshold(File apkFile, long threshold) { + final long size = apkFile.length(); + + final StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath()); + final long availInternalSize = (long) internalStats.getAvailableBlocks() + * (long) internalStats.getBlockSize(); + + return (availInternalSize - size) > threshold; + } + + + private boolean isUnderExternalThreshold(File apkFile) { + if (Environment.isExternalStorageEmulated()) { + return false; + } + + final int sizeMb = calculateContainerSize(apkFile, null); + + final int availSdMb; + if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { + StatFs sdStats = new StatFs(Environment.getExternalStorageDirectory().getPath()); + long availSdSize = (long) (sdStats.getAvailableBlocks() * sdStats.getBlockSize()); + availSdMb = (int) (availSdSize >> 20); + } else { + availSdMb = -1; + } + + return availSdMb > sizeMb; + } + + /** + * Calculate the container size for an APK. Takes into account the + * + * @param apkFile file from which to calculate size + * @return size in megabytes (2^20 bytes) + */ + private int calculateContainerSize(File apkFile, List<Pair<ZipEntry, String>> outFiles) { + // Calculate size of container needed to hold base APK. + long sizeBytes = apkFile.length(); + + // Check all the native files that need to be copied and add that to the + // container size. + ZipFile zipFile; + final List<Pair<ZipEntry, String>> nativeFiles; + try { + zipFile = new ZipFile(apkFile); + + if (outFiles != null) { + nativeFiles = outFiles; + } else { + nativeFiles = new ArrayList<Pair<ZipEntry, String>>(); + } + + NativeLibraryHelper.listPackageNativeBinariesLI(zipFile, nativeFiles); + + final int N = nativeFiles.size(); + for (int i = 0; i < N; i++) { + final Pair<ZipEntry, String> entry = nativeFiles.get(i); + + /* + * Note a 1MB padding is added to the claimed size, so we don't + * have to worry about block alignment here. + */ + sizeBytes += entry.first.getSize(); } - return availSDSize > size; + } catch (ZipException e) { + Log.w(TAG, "Failed to extract data from package file", e); + } catch (IOException e) { + Log.w(TAG, "Failed to cache package shared libs", e); } - StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath()); - long totalInternalSize = (long)internalStats.getBlockCount() * - (long)internalStats.getBlockSize(); - long availInternalSize = (long)internalStats.getAvailableBlocks() * - (long)internalStats.getBlockSize(); - - double pctNandFree = (double)availInternalSize / (double)totalInternalSize; - // To make final copy - long reqInstallSize = size; - // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now. - long reqInternalSize = 0; - boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD); - boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize); - return intThresholdOk && intAvailOk; + + int sizeMb = (int) (sizeBytes >> 20); + if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) { + sizeMb++; + } + + /* + * Add buffer size because we don't have a good way to determine the + * real FAT size. Your FAT size varies with how many directory entries + * you need, how big the whole filesystem is, and other such headaches. + */ + sizeMb++; + + return sizeMb; } } diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java index 0fba7c3..b0d2158 100644 --- a/services/java/com/android/server/DeviceStorageMonitorService.java +++ b/services/java/com/android/server/DeviceStorageMonitorService.java @@ -398,4 +398,24 @@ class DeviceStorageMonitorService extends Binder { // force an early check postCheckMemoryMsg(true, 0); } + + /** + * Callable from other things in the system service to obtain the low memory + * threshold. + * + * @return low memory threshold in bytes + */ + public long getMemoryLowThreshold() { + return mMemLowThreshold; + } + + /** + * Callable from other things in the system process to check whether memory + * is low. + * + * @return true is memory is low + */ + public boolean isMemoryLow() { + return mLowMemFlag; + } } diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index 04f271d..3aebb5c 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -4919,12 +4919,24 @@ class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Cannot install fwd locked apps on sdcard"); ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; } else { + final long lowThreshold; + + final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager + .getService(DeviceStorageMonitorService.SERVICE); + if (dsm == null) { + Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed"); + lowThreshold = 0L; + } else { + lowThreshold = dsm.getMemoryLowThreshold(); + } + // Remote call to find out default install location final PackageInfoLite pkgLite; try { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); - pkgLite = mContainerService.getMinimalPackageInfo(packageURI, flags); + pkgLite = mContainerService.getMinimalPackageInfo(packageURI, flags, + lowThreshold); } finally { mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); } @@ -5144,10 +5156,26 @@ class PackageManagerService extends IPackageManager.Stub { } boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException { + final long lowThreshold; + + final DeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager + .getService(DeviceStorageMonitorService.SERVICE); + if (dsm == null) { + Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed"); + lowThreshold = 0L; + } else { + if (dsm.isMemoryLow()) { + Log.w(TAG, "Memory is reported as being too low; aborting package install"); + return false; + } + + lowThreshold = dsm.getMemoryLowThreshold(); + } + try { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); - return imcs.checkFreeStorage(false, packageURI); + return imcs.checkInternalFreeStorage(packageURI, lowThreshold); } finally { mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); } @@ -5373,7 +5401,7 @@ class PackageManagerService extends IPackageManager.Stub { try { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); - return imcs.checkFreeStorage(true, packageURI); + return imcs.checkExternalFreeStorage(packageURI); } finally { mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION); } |