Avoid calling installd where not needed
The method StorageStatsManager#getCacheBytes is called several times
during package installation. It calls
InstalldNativeService::getUserSize via a binder call.
The runtime of getUserSize is variable; I have traces where it takes
50ms and traces where it takes 500ms.
The return value of getUserSize used to take into account freeable cache
space when determining available space on a given volume. In most cases,
it is possible to first do a pessimistic check of the space available on
the volume - forgoing the installd call - and only consider cache space
if the pessimistic check fails.
This avoids a binder call + the time spend in getUserSize for most
installs.
Bug: None
Test: atest frameworks/base/core/tests/coretests/src/android/content/pm/PackageHelperTests.java
cts/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
Change-Id: Icaecee732ef330fee1b409d2dd76723822c25959
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 69c1295..5b9205d 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1987,11 +1987,31 @@
*/
public static final int FLAG_ALLOCATE_DEFY_HALF_RESERVED = 1 << 2;
+ /**
+ * Flag indicating that a disk space check should not take into account
+ * freeable cached space when determining allocatable space.
+ *
+ * Intended for use with {@link #getAllocatableBytes()}.
+ * @hide
+ */
+ public static final int FLAG_ALLOCATE_NON_CACHE_ONLY = 1 << 3;
+
+ /**
+ * Flag indicating that a disk space check should only return freeable
+ * cached space when determining allocatable space.
+ *
+ * Intended for use with {@link #getAllocatableBytes()}.
+ * @hide
+ */
+ public static final int FLAG_ALLOCATE_CACHE_ONLY = 1 << 4;
+
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_ALLOCATE_" }, value = {
FLAG_ALLOCATE_AGGRESSIVE,
FLAG_ALLOCATE_DEFY_ALL_RESERVED,
FLAG_ALLOCATE_DEFY_HALF_RESERVED,
+ FLAG_ALLOCATE_NON_CACHE_ONLY,
+ FLAG_ALLOCATE_CACHE_ONLY,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AllocateFlags {}
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index ad1ff90..1d81c59 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -261,8 +261,21 @@
public static boolean fitsOnInternal(Context context, SessionParams params) throws IOException {
final StorageManager storage = context.getSystemService(StorageManager.class);
final UUID target = storage.getUuidForPath(Environment.getDataDirectory());
- return (params.sizeBytes <= storage.getAllocatableBytes(target,
- translateAllocateFlags(params.installFlags)));
+ final int flags = translateAllocateFlags(params.installFlags);
+
+ final long allocateableBytes = storage.getAllocatableBytes(target,
+ flags | StorageManager.FLAG_ALLOCATE_NON_CACHE_ONLY);
+
+ // If we fit on internal storage without including freeable cache space, don't bother
+ // checking to determine how much space is taken up by the cache.
+ if (params.sizeBytes <= allocateableBytes) {
+ return true;
+ }
+
+ final long cacheClearable = storage.getAllocatableBytes(target,
+ flags | StorageManager.FLAG_ALLOCATE_CACHE_ONLY);
+
+ return params.sizeBytes <= allocateableBytes + cacheClearable;
}
public static boolean fitsOnExternal(Context context, SessionParams params) {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index deff7ef..ddde7fe 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3208,28 +3208,28 @@
// should be kept in sync with getFreeBytes().
final File path = storage.findPathForUuid(volumeUuid);
- final long usable = path.getUsableSpace();
- final long lowReserved = storage.getStorageLowBytes(path);
- final long fullReserved = storage.getStorageFullBytes(path);
+ long usable = 0;
+ long lowReserved = 0;
+ long fullReserved = 0;
+ long cacheClearable = 0;
- if (stats.isQuotaSupported(volumeUuid)) {
+ if ((flags & StorageManager.FLAG_ALLOCATE_CACHE_ONLY) == 0) {
+ usable = path.getUsableSpace();
+ lowReserved = storage.getStorageLowBytes(path);
+ fullReserved = storage.getStorageFullBytes(path);
+ }
+
+ if ((flags & StorageManager.FLAG_ALLOCATE_NON_CACHE_ONLY) == 0
+ && stats.isQuotaSupported(volumeUuid)) {
final long cacheTotal = stats.getCacheBytes(volumeUuid);
final long cacheReserved = storage.getStorageCacheBytes(path, flags);
- final long cacheClearable = Math.max(0, cacheTotal - cacheReserved);
+ cacheClearable = Math.max(0, cacheTotal - cacheReserved);
+ }
- if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) {
- return Math.max(0, (usable + cacheClearable) - fullReserved);
- } else {
- return Math.max(0, (usable + cacheClearable) - lowReserved);
- }
+ if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) {
+ return Math.max(0, (usable + cacheClearable) - fullReserved);
} else {
- // When we don't have fast quota information, we ignore cached
- // data and only consider unused bytes.
- if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) {
- return Math.max(0, usable - fullReserved);
- } else {
- return Math.max(0, usable - lowReserved);
- }
+ return Math.max(0, (usable + cacheClearable) - lowReserved);
}
} catch (IOException e) {
throw new ParcelableException(e);
@@ -3242,10 +3242,17 @@
public void allocateBytes(String volumeUuid, long bytes, int flags, String callingPackage) {
flags = adjustAllocateFlags(flags, Binder.getCallingUid(), callingPackage);
- final long allocatableBytes = getAllocatableBytes(volumeUuid, flags, callingPackage);
+ final long allocatableBytes = getAllocatableBytes(volumeUuid,
+ flags | StorageManager.FLAG_ALLOCATE_NON_CACHE_ONLY, callingPackage);
if (bytes > allocatableBytes) {
- throw new ParcelableException(new IOException("Failed to allocate " + bytes
- + " because only " + allocatableBytes + " allocatable"));
+ // If we don't have room without taking cache into account, check to see if we'd have
+ // room if we included freeable cache space.
+ final long cacheClearable = getAllocatableBytes(volumeUuid,
+ flags | StorageManager.FLAG_ALLOCATE_CACHE_ONLY, callingPackage);
+ if (bytes > allocatableBytes + cacheClearable) {
+ throw new ParcelableException(new IOException("Failed to allocate " + bytes
+ + " because only " + (allocatableBytes + cacheClearable) + " allocatable"));
+ }
}
final StorageManager storage = mContext.getSystemService(StorageManager.class);