Merge "Clear cache space when allocating bytes."
diff --git a/api/current.txt b/api/current.txt
index 85e153a..1b37fb4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -31255,10 +31255,8 @@
     method public void allocateBytes(java.io.File, long, int) throws java.io.IOException;
     method public void allocateBytes(java.io.FileDescriptor, long, int) throws java.io.IOException;
     method public long getAllocatableBytes(java.io.File, int) throws java.io.IOException;
-    method public long getCacheQuotaBytes();
-    method public long getCacheSizeBytes();
-    method public long getExternalCacheQuotaBytes();
-    method public long getExternalCacheSizeBytes();
+    method public long getCacheQuotaBytes(java.io.File);
+    method public long getCacheSizeBytes(java.io.File);
     method public java.lang.String getMountedObbPath(java.lang.String);
     method public android.os.storage.StorageVolume getPrimaryStorageVolume();
     method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
diff --git a/api/removed.txt b/api/removed.txt
index ab22b6e..e467811 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -180,6 +180,10 @@
 package android.os.storage {
 
   public class StorageManager {
+    method public deprecated long getCacheQuotaBytes();
+    method public deprecated long getCacheSizeBytes();
+    method public deprecated long getExternalCacheQuotaBytes();
+    method public deprecated long getExternalCacheSizeBytes();
     method public android.os.storage.StorageVolume getPrimaryVolume();
     method public android.os.storage.StorageVolume[] getVolumeList();
   }
diff --git a/api/system-current.txt b/api/system-current.txt
index 1d3ff9a..8abf661 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -34176,10 +34176,8 @@
     method public void allocateBytes(java.io.File, long, int) throws java.io.IOException;
     method public void allocateBytes(java.io.FileDescriptor, long, int) throws java.io.IOException;
     method public long getAllocatableBytes(java.io.File, int) throws java.io.IOException;
-    method public long getCacheQuotaBytes();
-    method public long getCacheSizeBytes();
-    method public long getExternalCacheQuotaBytes();
-    method public long getExternalCacheSizeBytes();
+    method public long getCacheQuotaBytes(java.io.File);
+    method public long getCacheSizeBytes(java.io.File);
     method public java.lang.String getMountedObbPath(java.lang.String);
     method public android.os.storage.StorageVolume getPrimaryStorageVolume();
     method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
diff --git a/api/system-removed.txt b/api/system-removed.txt
index 1ba26f5..6773112 100644
--- a/api/system-removed.txt
+++ b/api/system-removed.txt
@@ -174,6 +174,10 @@
 package android.os.storage {
 
   public class StorageManager {
+    method public deprecated long getCacheQuotaBytes();
+    method public deprecated long getCacheSizeBytes();
+    method public deprecated long getExternalCacheQuotaBytes();
+    method public deprecated long getExternalCacheSizeBytes();
     method public android.os.storage.StorageVolume getPrimaryVolume();
     method public android.os.storage.StorageVolume[] getVolumeList();
   }
diff --git a/api/test-current.txt b/api/test-current.txt
index 5e08f1a..3269554 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -31374,10 +31374,8 @@
     method public void allocateBytes(java.io.File, long, int) throws java.io.IOException;
     method public void allocateBytes(java.io.FileDescriptor, long, int) throws java.io.IOException;
     method public long getAllocatableBytes(java.io.File, int) throws java.io.IOException;
-    method public long getCacheQuotaBytes();
-    method public long getCacheSizeBytes();
-    method public long getExternalCacheQuotaBytes();
-    method public long getExternalCacheSizeBytes();
+    method public long getCacheQuotaBytes(java.io.File);
+    method public long getCacheSizeBytes(java.io.File);
     method public java.lang.String getMountedObbPath(java.lang.String);
     method public android.os.storage.StorageVolume getPrimaryStorageVolume();
     method public android.os.storage.StorageVolume getStorageVolume(java.io.File);
diff --git a/api/test-removed.txt b/api/test-removed.txt
index ab22b6e..e467811 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -180,6 +180,10 @@
 package android.os.storage {
 
   public class StorageManager {
+    method public deprecated long getCacheQuotaBytes();
+    method public deprecated long getCacheSizeBytes();
+    method public deprecated long getExternalCacheQuotaBytes();
+    method public deprecated long getExternalCacheSizeBytes();
     method public android.os.storage.StorageVolume getPrimaryVolume();
     method public android.os.storage.StorageVolume[] getVolumeList();
   }
diff --git a/core/java/android/app/usage/IStorageStatsManager.aidl b/core/java/android/app/usage/IStorageStatsManager.aidl
index 62ebf60..f4c18dd 100644
--- a/core/java/android/app/usage/IStorageStatsManager.aidl
+++ b/core/java/android/app/usage/IStorageStatsManager.aidl
@@ -21,6 +21,7 @@
 
 /** {@hide} */
 interface IStorageStatsManager {
+    boolean isQuotaSupported(String volumeUuid, String callingPackage);
     long getTotalBytes(String volumeUuid, String callingPackage);
     long getFreeBytes(String volumeUuid, String callingPackage);
     StorageStats queryStatsForUid(String volumeUuid, int uid, String callingPackage);
diff --git a/core/java/android/app/usage/StorageStatsManager.java b/core/java/android/app/usage/StorageStatsManager.java
index 9d30771..7d4efb9 100644
--- a/core/java/android/app/usage/StorageStatsManager.java
+++ b/core/java/android/app/usage/StorageStatsManager.java
@@ -46,6 +46,15 @@
         mService = Preconditions.checkNotNull(service);
     }
 
+    /** {@hide} */
+    public boolean isQuotaSupported(String volumeUuid) {
+        try {
+            return mService.isQuotaSupported(volumeUuid, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Return the total space on the requested storage volume.
      * <p>
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 7c015de..280fa2c 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -432,14 +432,13 @@
      */
     public static boolean contains(File dir, File file) {
         if (dir == null || file == null) return false;
+        return contains(dir.getAbsolutePath(), file.getAbsolutePath());
+    }
 
-        String dirPath = dir.getAbsolutePath();
-        String filePath = file.getAbsolutePath();
-
+    public static boolean contains(String dirPath, String filePath) {
         if (dirPath.equals(filePath)) {
             return true;
         }
-
         if (!dirPath.endsWith("/")) {
             dirPath += "/";
         }
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 7702c17..8882672 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -546,6 +546,25 @@
     }
 
     /**
+     * Return the filesystem path of the real file on disk that is represented
+     * by the given {@link FileDescriptor}.
+     *
+     * @hide
+     */
+    public static File getFile(FileDescriptor fd) throws IOException {
+        try {
+            final String path = Os.readlink("/proc/self/fd/" + fd.getInt$());
+            if (OsConstants.S_ISREG(Os.stat(path).st_mode)) {
+                return new File(path);
+            } else {
+                throw new IOException("Not a regular file: " + path);
+            }
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        }
+    }
+
+    /**
      * Retrieve the actual FileDescriptor associated with this object.
      *
      * @return Returns the FileDescriptor associated with this object.
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 9e35bf6..2cc4b71 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -293,6 +293,6 @@
     ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode) = 74;
     long getCacheQuotaBytes(String volumeUuid, int uid) = 75;
     long getCacheSizeBytes(String volumeUuid, int uid) = 76;
-    long getAllocatableBytes(String path, int flags) = 77;
-    void allocateBytes(String path, long bytes, int flags) = 78;
+    long getAllocatableBytes(String volumeUuid, int flags) = 77;
+    void allocateBytes(String volumeUuid, long bytes, int flags) = 78;
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 2a3c03d..e070c6e 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -16,6 +16,7 @@
 
 package android.os.storage;
 
+import static android.net.TrafficStats.GB_IN_BYTES;
 import static android.net.TrafficStats.MB_IN_BYTES;
 
 import android.annotation.IntDef;
@@ -675,6 +676,36 @@
     }
 
     /** {@hide} */
+    public @Nullable String findUuidForPath(File path) {
+        Preconditions.checkNotNull(path);
+        final String pathString = path.getAbsolutePath();
+        if (FileUtils.contains(Environment.getDataDirectory().getAbsolutePath(), pathString)) {
+            return StorageManager.UUID_PRIVATE_INTERNAL;
+        }
+        try {
+            for (VolumeInfo vol : mStorageManager.getVolumes(0)) {
+                if (vol.path != null && FileUtils.contains(vol.path, pathString)) {
+                    // TODO: verify that emulated adopted devices have UUID of
+                    // underlying volume
+                    return vol.fsUuid;
+                }
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        throw new IllegalStateException("Failed to find a storage device for " + path);
+    }
+
+    /** {@hide} */
+    public @Nullable File findPathForUuid(String volumeUuid) {
+        final VolumeInfo vol = findVolumeByQualifiedUuid(volumeUuid);
+        if (vol != null) {
+            return vol.getPath();
+        }
+        throw new IllegalStateException("Failed to find a storage device for " + volumeUuid);
+    }
+
+    /** {@hide} */
     public @NonNull List<VolumeInfo> getVolumes() {
         try {
             return Arrays.asList(mStorageManager.getVolumes(0));
@@ -1069,9 +1100,12 @@
         throw new IllegalStateException("Missing primary storage");
     }
 
-    /** {@hide} */
-    private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
+    private static final int DEFAULT_THRESHOLD_PERCENTAGE = 5;
     private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
+
+    private static final int DEFAULT_CACHE_PERCENTAGE = 10;
+    private static final long DEFAULT_CACHE_MAX_BYTES = 5 * GB_IN_BYTES;
+
     private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
 
     /**
@@ -1102,6 +1136,23 @@
     }
 
     /**
+     * Return the minimum number of bytes of storage on the device that should
+     * be reserved for cached data.
+     *
+     * @hide
+     */
+    public long getStorageCacheBytes(File path) {
+        final long cachePercent = Settings.Global.getInt(mResolver,
+                Settings.Global.SYS_STORAGE_CACHE_PERCENTAGE, DEFAULT_CACHE_PERCENTAGE);
+        final long cacheBytes = (path.getTotalSpace() * cachePercent) / 100;
+
+        final long maxCacheBytes = Settings.Global.getLong(mResolver,
+                Settings.Global.SYS_STORAGE_CACHE_MAX_BYTES, DEFAULT_CACHE_MAX_BYTES);
+
+        return Math.min(cacheBytes, maxCacheBytes);
+    }
+
+    /**
      * Return the number of available bytes at which the given path is
      * considered full.
      *
@@ -1409,40 +1460,37 @@
     }
 
     /**
-     * Return quota size in bytes for cached data belonging to the calling app.
+     * Return quota size in bytes for all cached data belonging to the calling
+     * app on the filesystem that hosts the given path.
      * <p>
      * If your app goes above this quota, your cached files will be some of the
      * first to be deleted when additional disk space is needed. Conversely, if
      * your app stays under this quota, your cached files will be some of the
      * last to be deleted when additional disk space is needed.
      * <p>
-     * This quota may change over time depending on how frequently the user
+     * This quota will change over time depending on how frequently the user
      * interacts with your app, and depending on how much disk space is used.
-     * <p>
-     * Cached data tracked by this method always includes
-     * {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}, and
-     * it also includes {@link Context#getExternalCacheDir()} if the primary
-     * shared/external storage is hosted on the same storage device as your
-     * private data.
      * <p class="note">
      * Note: if your app uses the {@code android:sharedUserId} manifest feature,
      * then cached data for all packages in your shared UID is tracked together
      * as a single unit.
      * </p>
      *
-     * @see #getCacheSizeBytes()
+     * @see #getCacheSizeBytes(File)
      */
-    public long getCacheQuotaBytes() {
+    public long getCacheQuotaBytes(File path) {
         try {
+            final String volumeUuid = findUuidForPath(path);
             final ApplicationInfo app = mContext.getApplicationInfo();
-            return mStorageManager.getCacheQuotaBytes(app.volumeUuid, app.uid);
+            return mStorageManager.getCacheQuotaBytes(volumeUuid, app.uid);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Return total size in bytes of cached data belonging to the calling app.
+     * Return total size in bytes of all cached data belonging to the calling
+     * app on the filesystem that hosts the given path.
      * <p>
      * Cached data tracked by this method always includes
      * {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}, and
@@ -1457,67 +1505,38 @@
      *
      * @see #getCacheQuotaBytes()
      */
-    public long getCacheSizeBytes() {
+    public long getCacheSizeBytes(File path) {
         try {
+            final String volumeUuid = findUuidForPath(path);
             final ApplicationInfo app = mContext.getApplicationInfo();
-            return mStorageManager.getCacheSizeBytes(app.volumeUuid, app.uid);
+            return mStorageManager.getCacheSizeBytes(volumeUuid, app.uid);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
-    /**
-     * Return quota size in bytes for cached data on primary shared/external
-     * storage belonging to the calling app.
-     * <p>
-     * If primary shared/external storage is hosted on the same storage device
-     * as your private data, this method will return -1, since all data stored
-     * under {@link Context#getExternalCacheDir()} will be counted under
-     * {@link #getCacheQuotaBytes()}.
-     * <p class="note">
-     * Note: if your app uses the {@code android:sharedUserId} manifest feature,
-     * then cached data for all packages in your shared UID is tracked together
-     * as a single unit.
-     * </p>
-     */
+    /** @removed */
+    @Deprecated
+    public long getCacheQuotaBytes() {
+        return getCacheQuotaBytes(mContext.getCacheDir());
+    }
+
+    /** @removed */
+    @Deprecated
+    public long getCacheSizeBytes() {
+        return getCacheSizeBytes(mContext.getCacheDir());
+    }
+
+    /** @removed */
+    @Deprecated
     public long getExternalCacheQuotaBytes() {
-        final ApplicationInfo app = mContext.getApplicationInfo();
-        final String primaryUuid = getPrimaryStorageUuid();
-        if (Objects.equals(app.volumeUuid, primaryUuid)) {
-            return -1;
-        }
-        try {
-            return mStorageManager.getCacheQuotaBytes(primaryUuid, app.uid);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return getCacheQuotaBytes(mContext.getExternalCacheDir());
     }
 
-    /**
-     * Return total size in bytes of cached data on primary shared/external
-     * storage belonging to the calling app.
-     * <p>
-     * If primary shared/external storage is hosted on the same storage device
-     * as your private data, this method will return -1, since all data stored
-     * under {@link Context#getExternalCacheDir()} will be counted under
-     * {@link #getCacheQuotaBytes()}.
-     * <p class="note">
-     * Note: if your app uses the {@code android:sharedUserId} manifest feature,
-     * then cached data for all packages in your shared UID is tracked together
-     * as a single unit.
-     * </p>
-     */
+    /** @removed */
+    @Deprecated
     public long getExternalCacheSizeBytes() {
-        final ApplicationInfo app = mContext.getApplicationInfo();
-        final String primaryUuid = getPrimaryStorageUuid();
-        if (Objects.equals(app.volumeUuid, primaryUuid)) {
-            return -1;
-        }
-        try {
-            return mStorageManager.getCacheSizeBytes(primaryUuid, app.uid);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return getCacheSizeBytes(mContext.getExternalCacheDir());
     }
 
     /**
@@ -1551,28 +1570,30 @@
      * Return the maximum number of new bytes that your app can allocate for
      * itself using {@link #allocateBytes(File, long, int)} at the given path.
      * This value is typically larger than {@link File#getUsableSpace()}, since
-     * the system may automatically delete cached files to satisfy your request.
+     * the system may be willing to delete cached files to satisfy an allocation
+     * request.
      * <p>
      * This method is best used as a pre-flight check, such as deciding if there
      * is enough space to store an entire music album before you allocate space
      * for each audio file in the album. Attempts to allocate disk space beyond
-     * this value will fail.
+     * the returned value will fail.
      * <p class="note">
      * Note: if your app uses the {@code android:sharedUserId} manifest feature,
      * then allocatable space for all packages in your shared UID is tracked
      * together as a single unit.
      * </p>
      *
-     * @param file the directory where you're considering allocating disk space,
+     * @param path the path where you're considering allocating disk space,
      *            since allocatable space can vary widely depending on the
      *            underlying storage device.
      * @param flags to apply to the request.
      * @return the maximum number of new bytes that the calling app can allocate
      *         using {@link #allocateBytes(File, long, int)}.
      */
-    public long getAllocatableBytes(File file, @AllocateFlags int flags) throws IOException {
+    public long getAllocatableBytes(File path, @AllocateFlags int flags) throws IOException {
         try {
-            return mStorageManager.getAllocatableBytes(file.getAbsolutePath(), flags);
+            final String volumeUuid = findUuidForPath(path);
+            return mStorageManager.getAllocatableBytes(volumeUuid, flags);
         } catch (ParcelableException e) {
             e.maybeRethrow(IOException.class);
             throw new RuntimeException(e);
@@ -1594,14 +1615,15 @@
      * {@link #allocateBytes(FileDescriptor, long, int)} which will guarantee
      * that bytes are allocated to an opened file.
      *
-     * @param file the directory where you'd like to allocate disk space.
+     * @param path the path where you'd like to allocate disk space.
      * @param bytes the number of bytes to allocate.
      * @param flags to apply to the request.
      * @see #getAllocatableBytes(File, int)
      */
-    public void allocateBytes(File file, long bytes, @AllocateFlags int flags) throws IOException {
+    public void allocateBytes(File path, long bytes, @AllocateFlags int flags) throws IOException {
         try {
-            mStorageManager.allocateBytes(file.getAbsolutePath(), bytes, flags);
+            final String volumeUuid = findUuidForPath(path);
+            mStorageManager.allocateBytes(volumeUuid, bytes, flags);
         } catch (ParcelableException e) {
             e.maybeRethrow(IOException.class);
         } catch (RemoteException e) {
@@ -1610,37 +1632,39 @@
     }
 
     /**
-     * Allocate the requested number of bytes for your application to use at the
-     * given path. This will cause the system to delete any cached files
+     * Allocate the requested number of bytes for your application to use in the
+     * given open file. This will cause the system to delete any cached files
      * necessary to satisfy your request.
      * <p>
      * Attempts to allocate disk space beyond the value returned by
      * {@link #getAllocatableBytes(File, int)} will fail.
      * <p>
-     * This method guarantees that bytes are allocated to the opened file,
-     * otherwise it will throw if fast allocation not possible. Fast allocation
-     * is typically only supported in private app data directories, and on
-     * shared/external storage devices which are emulated.
+     * This method guarantees that bytes have been allocated to the opened file,
+     * otherwise it will throw if fast allocation is not possible. Fast
+     * allocation is typically only supported in private app data directories,
+     * and on shared/external storage devices which are emulated.
      *
-     * @param fd the directory where you'd like to allocate disk space.
-     * @param bytes the number of bytes to allocate.
+     * @param fd the open file that you'd like to allocate disk space for.
+     * @param bytes the number of bytes to allocate. This is the desired final
+     *            size of the open file.
      * @param flags to apply to the request.
      * @see #getAllocatableBytes(File, int)
      * @see Environment#isExternalStorageEmulated(File)
      */
     public void allocateBytes(FileDescriptor fd, long bytes, @AllocateFlags int flags)
             throws IOException {
-        final File file;
-        try {
-            file = new File(Os.readlink("/proc/self/fd/" + fd.getInt$()));
-        } catch (ErrnoException e) {
-            throw e.rethrowAsIOException();
-        }
+        final File file = ParcelFileDescriptor.getFile(fd);
         for (int i = 0; i < 3; i++) {
-            allocateBytes(file, bytes, flags);
-
             try {
+                final long haveBytes = Os.fstat(fd).st_blocks * 512;
+                final long needBytes = bytes - haveBytes;
+
+                if (needBytes > 0) {
+                    allocateBytes(file, needBytes, flags);
+                }
+
                 Os.posix_fallocate(fd, 0, bytes);
+                return;
             } catch (ErrnoException e) {
                 if (e.errno == OsConstants.ENOSPC) {
                     Log.w(TAG, "Odd, not enough space; let's try again?");
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c9b1c9c..ebc5b88 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8578,6 +8578,24 @@
                 SYS_STORAGE_FULL_THRESHOLD_BYTES = "sys_storage_full_threshold_bytes";
 
         /**
+         * Minimum percentage of storage on the device that is reserved for
+         * cached data.
+         *
+         * @hide
+         */
+        public static final String
+                SYS_STORAGE_CACHE_PERCENTAGE = "sys_storage_cache_percentage";
+
+        /**
+         * Maximum bytes of storage on the device that is reserved for cached
+         * data.
+         *
+         * @hide
+         */
+        public static final String
+                SYS_STORAGE_CACHE_MAX_BYTES = "sys_storage_cache_max_bytes";
+
+        /**
          * The maximum reconnect delay for short network outages or when the
          * network is suspended due to phone use.
          *
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 0415971..32136bb 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -70,13 +70,13 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.DiskInfo;
-import android.os.storage.IStorageEventListener;
-import android.os.storage.IStorageShutdownObserver;
 import android.os.storage.IObbActionListener;
+import android.os.storage.IStorageEventListener;
 import android.os.storage.IStorageManager;
-import android.os.storage.StorageManagerInternal;
+import android.os.storage.IStorageShutdownObserver;
 import android.os.storage.OnObbStateChangeListener;
 import android.os.storage.StorageManager;
+import android.os.storage.StorageManagerInternal;
 import android.os.storage.StorageResultCode;
 import android.os.storage.StorageVolume;
 import android.os.storage.VolumeInfo;
@@ -109,6 +109,7 @@
 import com.android.server.NativeDaemonConnector.SensitiveArg;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.storage.AppFuseBridge;
+
 import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
 
@@ -3293,14 +3294,69 @@
     }
 
     @Override
-    public long getAllocatableBytes(String path, int flags) {
-        return new File(path).getUsableSpace();
+    public long getAllocatableBytes(String volumeUuid, int flags) {
+        final StorageManager storage = mContext.getSystemService(StorageManager.class);
+        final StorageStatsManager stats = mContext.getSystemService(StorageStatsManager.class);
+
+        final boolean aggressive = (flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0;
+        if (aggressive) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.ALLOCATE_AGGRESSIVE, TAG);
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            // In general, apps can allocate as much space as they want, except
+            // we never let them eat into either the minimum cache space or into
+            // the low disk warning space.
+            final File path = storage.findPathForUuid(volumeUuid);
+            if (stats.isQuotaSupported(volumeUuid)) {
+                if (aggressive) {
+                    return Math.max(0,
+                            stats.getFreeBytes(volumeUuid) - storage.getStorageFullBytes(path));
+                } else {
+                    return Math.max(0,
+                            stats.getFreeBytes(volumeUuid) - storage.getStorageLowBytes(path)
+                                    - storage.getStorageCacheBytes(path));
+                }
+            } else {
+                // When we don't have fast quota information, we ignore cached
+                // data and only consider unused bytes.
+                if (aggressive) {
+                    return Math.max(0, path.getUsableSpace() - storage.getStorageFullBytes(path));
+                } else {
+                    return Math.max(0, path.getUsableSpace() - storage.getStorageLowBytes(path));
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     @Override
-    public void allocateBytes(String path, long bytes, int flags) {
-        if (bytes > new File(path).getUsableSpace()) {
-            throw new ParcelableException(new IOException("Not enough usable space"));
+    public void allocateBytes(String volumeUuid, long bytes, int flags) {
+        final StorageManager storage = mContext.getSystemService(StorageManager.class);
+
+        // This method call will enforce FLAG_ALLOCATE_AGGRESSIVE permissions so
+        // we don't have to enforce them locally
+        final long allocatableBytes = getAllocatableBytes(volumeUuid, flags);
+        if (bytes > allocatableBytes) {
+            throw new ParcelableException(new IOException("Failed to allocate " + bytes
+                    + " because only " + allocatableBytes + " allocatable"));
+        }
+
+        // Free up enough disk space to satisfy both the requested allocation
+        // and our low disk warning space.
+        final File path = storage.findPathForUuid(volumeUuid);
+        bytes += storage.getStorageLowBytes(path);
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mPms.freeStorage(volumeUuid, bytes, flags);
+        } catch (IOException e) {
+            throw new ParcelableException(e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 449d808..db04515 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -465,6 +465,15 @@
         }
     }
 
+    public boolean isQuotaSupported(String volumeUuid) throws InstallerException {
+        if (!checkBeforeRemote()) return false;
+        try {
+            return mInstalld.isQuotaSupported(volumeUuid);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
     private static void assertValidInstructionSet(String instructionSet)
             throws InstallerException {
         for (String abi : Build.SUPPORTED_ABIS) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 463cfac..1c5675a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -24,6 +24,7 @@
 import static android.system.OsConstants.O_CREAT;
 import static android.system.OsConstants.O_RDONLY;
 import static android.system.OsConstants.O_WRONLY;
+
 import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid;
 import static com.android.server.pm.PackageInstallerService.prepareStageDir;
 
@@ -54,8 +55,8 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.SELinux;
 import android.os.UserHandle;
+import android.os.storage.StorageManager;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
@@ -66,9 +67,6 @@
 import android.util.MathUtils;
 import android.util.Slog;
 
-import libcore.io.IoUtils;
-import libcore.io.Libcore;
-
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.content.PackageHelper;
@@ -78,6 +76,9 @@
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
 
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileFilter;
@@ -455,14 +456,9 @@
 
             // If caller specified a total length, allocate it for them. Free up
             // cache space to grow, if needed.
-            if (lengthBytes > 0) {
-                final StructStat stat = Libcore.os.fstat(targetFd);
-                final long deltaBytes = lengthBytes - stat.st_size;
-                // Only need to free up space when writing to internal stage
-                if (stageDir != null && deltaBytes > 0) {
-                    mPm.freeStorage(params.volumeUuid, deltaBytes);
-                }
-                Libcore.os.posix_fallocate(targetFd, 0, lengthBytes);
+            if (stageDir != null && lengthBytes > 0) {
+                mContext.getSystemService(StorageManager.class).allocateBytes(targetFd,
+                        lengthBytes, 0);
             }
 
             if (offsetBytes > 0) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f95ee8f..371a062 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3821,7 +3821,8 @@
         });
     }
 
-    void freeStorage(String volumeUuid, long freeStorageSize) throws IOException {
+    public void freeStorage(String volumeUuid, long freeStorageSize, int storageFlags)
+            throws IOException {
         synchronized (mInstallLock) {
             try {
                 mInstaller.freeCache(volumeUuid, freeStorageSize, 0);
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 96907c3..a44860e 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -130,6 +130,17 @@
     }
 
     @Override
+    public boolean isQuotaSupported(String volumeUuid, String callingPackage) {
+        enforcePermission(Binder.getCallingUid(), callingPackage);
+
+        try {
+            return mInstaller.isQuotaSupported(volumeUuid);
+        } catch (InstallerException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Override
     public long getTotalBytes(String volumeUuid, String callingPackage) {
         enforcePermission(Binder.getCallingUid(), callingPackage);