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