Merge "Split cache clearing into two phases."
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index e070c6e..a1f8dfb 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1677,8 +1677,8 @@
                 "Well this is embarassing; we can't allocate " + bytes + " for " + file);
     }
 
-    private static final String XATTR_ATOMIC = "user.atomic";
-    private static final String XATTR_TOMBSTONE = "user.tombstone";
+    private static final String XATTR_CACHE_ATOMIC = "user.cache_atomic";
+    private static final String XATTR_CACHE_TOMBSTONE = "user.cache_tombstone";
 
     /** {@hide} */
     private static void setCacheBehavior(File path, String name, boolean enabled)
@@ -1736,7 +1736,7 @@
      * to all contained files and directories.
      */
     public void setCacheBehaviorAtomic(File path, boolean atomic) throws IOException {
-        setCacheBehavior(path, XATTR_ATOMIC, atomic);
+        setCacheBehavior(path, XATTR_CACHE_ATOMIC, atomic);
     }
 
     /**
@@ -1744,7 +1744,7 @@
      * {@link #setCacheBehaviorAtomic(File, boolean)}.
      */
     public boolean isCacheBehaviorAtomic(File path) throws IOException {
-        return isCacheBehavior(path, XATTR_ATOMIC);
+        return isCacheBehavior(path, XATTR_CACHE_ATOMIC);
     }
 
     /**
@@ -1764,7 +1764,7 @@
      * </p>
      */
     public void setCacheBehaviorTombstone(File path, boolean tombstone) throws IOException {
-        setCacheBehavior(path, XATTR_TOMBSTONE, tombstone);
+        setCacheBehavior(path, XATTR_CACHE_TOMBSTONE, tombstone);
     }
 
     /**
@@ -1772,7 +1772,7 @@
      * {@link #setCacheBehaviorTombstone(File, boolean)}.
      */
     public boolean isCacheBehaviorTombstone(File path) throws IOException {
-        return isCacheBehavior(path, XATTR_TOMBSTONE);
+        return isCacheBehavior(path, XATTR_CACHE_TOMBSTONE);
     }
 
     private final Object mFuseAppLoopLock = new Object();
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index db04515..5abdb60 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -63,6 +63,9 @@
     public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
     public static final int FLAG_CLEAR_CODE_CACHE_ONLY = 1 << 9;
     public static final int FLAG_USE_QUOTA = 1 << 12;
+    public static final int FLAG_FREE_CACHE_V2 = 1 << 13;
+    public static final int FLAG_FREE_CACHE_V2_DEFY_QUOTA = 1 << 14;
+    public static final int FLAG_FREE_CACHE_NOOP = 1 << 15;
 
     private final boolean mIsolated;
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 371a062..e447072 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -392,8 +392,8 @@
     private static final boolean DISABLE_EPHEMERAL_APPS = false;
     private static final boolean HIDE_EPHEMERAL_APIS = false;
 
-    private static final boolean ENABLE_QUOTA =
-            SystemProperties.getBoolean("persist.fw.quota", false);
+    private static final boolean ENABLE_FREE_CACHE_V2 =
+            SystemProperties.getBoolean("fw.free_cache_v2", false);
 
     private static final int RADIO_UID = Process.PHONE_UID;
     private static final int LOG_UID = Process.LOG_UID;
@@ -3765,25 +3765,19 @@
             final IPackageDataObserver observer) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.CLEAR_APP_CACHE, null);
-        // Queue up an async operation since clearing cache may take a little while.
-        mHandler.post(new Runnable() {
-            public void run() {
-                mHandler.removeCallbacks(this);
-                boolean success = true;
-                synchronized (mInstallLock) {
-                    try {
-                        mInstaller.freeCache(volumeUuid, freeStorageSize, 0);
-                    } catch (InstallerException e) {
-                        Slog.w(TAG, "Couldn't clear application caches: " + e);
-                        success = false;
-                    }
-                }
-                if (observer != null) {
-                    try {
-                        observer.onRemoveCompleted(null, success);
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "RemoveException when invoking call back");
-                    }
+        mHandler.post(() -> {
+            boolean success = false;
+            try {
+                freeStorage(volumeUuid, freeStorageSize, 0);
+                success = true;
+            } catch (IOException e) {
+                Slog.w(TAG, e);
+            }
+            if (observer != null) {
+                try {
+                    observer.onRemoveCompleted(null, success);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, e);
                 }
             }
         });
@@ -3793,43 +3787,77 @@
     public void freeStorage(final String volumeUuid, final long freeStorageSize,
             final IntentSender pi) {
         mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CLEAR_APP_CACHE, null);
-        // Queue up an async operation since clearing cache may take a little while.
-        mHandler.post(new Runnable() {
-            public void run() {
-                mHandler.removeCallbacks(this);
-                boolean success = true;
-                synchronized (mInstallLock) {
-                    try {
-                        mInstaller.freeCache(volumeUuid, freeStorageSize, 0);
-                    } catch (InstallerException e) {
-                        Slog.w(TAG, "Couldn't clear application caches: " + e);
-                        success = false;
-                    }
-                }
-                if(pi != null) {
-                    try {
-                        // Callback via pending intent
-                        int code = success ? 1 : 0;
-                        pi.sendIntent(null, code, null,
-                                null, null);
-                    } catch (SendIntentException e1) {
-                        Slog.i(TAG, "Failed to send pending intent");
-                    }
+                android.Manifest.permission.CLEAR_APP_CACHE, TAG);
+        mHandler.post(() -> {
+            boolean success = false;
+            try {
+                freeStorage(volumeUuid, freeStorageSize, 0);
+                success = true;
+            } catch (IOException e) {
+                Slog.w(TAG, e);
+            }
+            if (pi != null) {
+                try {
+                    pi.sendIntent(null, success ? 1 : 0, null, null, null);
+                } catch (SendIntentException e) {
+                    Slog.w(TAG, e);
                 }
             }
         });
     }
 
-    public void freeStorage(String volumeUuid, long freeStorageSize, int storageFlags)
-            throws IOException {
-        synchronized (mInstallLock) {
-            try {
-                mInstaller.freeCache(volumeUuid, freeStorageSize, 0);
-            } catch (InstallerException e) {
-                throw new IOException("Failed to free enough space", e);
+    /**
+     * Blocking call to clear various types of cached data across the system
+     * until the requested bytes are available.
+     */
+    public void freeStorage(String volumeUuid, long bytes, int storageFlags) throws IOException {
+        final StorageManager storage = mContext.getSystemService(StorageManager.class);
+        final File file = storage.findPathForUuid(volumeUuid);
+
+        if (ENABLE_FREE_CACHE_V2) {
+            final boolean aggressive = (storageFlags
+                    & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0;
+
+            // 1. Pre-flight to determine if we have any chance to succeed
+            // 2. Consider preloaded data (after 1w honeymoon, unless aggressive)
+
+            // 3. Consider parsed APK data (aggressive only)
+            if (aggressive) {
+                FileUtils.deleteContents(mCacheDir);
             }
+            if (file.getUsableSpace() >= bytes) return;
+
+            // 4. Consider cached app data (above quotas)
+            try {
+                mInstaller.freeCache(volumeUuid, bytes, Installer.FLAG_FREE_CACHE_V2);
+            } catch (InstallerException ignored) {
+            }
+            if (file.getUsableSpace() >= bytes) return;
+
+            // 5. Consider shared libraries with refcount=0 and age>2h
+            // 6. Consider dexopt output (aggressive only)
+            // 7. Consider ephemeral apps not used in last week
+
+            // 8. Consider cached app data (below quotas)
+            try {
+                mInstaller.freeCache(volumeUuid, bytes, Installer.FLAG_FREE_CACHE_V2
+                        | Installer.FLAG_FREE_CACHE_V2_DEFY_QUOTA);
+            } catch (InstallerException ignored) {
+            }
+            if (file.getUsableSpace() >= bytes) return;
+
+            // 9. Consider DropBox entries
+            // 10. Consider ephemeral cookies
+
+        } else {
+            try {
+                mInstaller.freeCache(volumeUuid, bytes, 0);
+            } catch (InstallerException ignored) {
+            }
+            if (file.getUsableSpace() >= bytes) return;
         }
+
+        throw new IOException("Failed to free " + bytes + " on storage device at " + file);
     }
 
     /**