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);