Merge "Revert "Revert "[pm] remove old stage dirs on low storage""" into sc-dev
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index d7b2449..7fdeb27 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -135,6 +135,8 @@
     private static final long MAX_ACTIVE_SESSIONS_NO_PERMISSION = 50;
     /** Upper bound on number of historical sessions for a UID */
     private static final long MAX_HISTORICAL_SESSIONS = 1048576;
+    /** Destroy sessions older than this on storage free request */
+    private static final long MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS = 8 * DateUtils.HOUR_IN_MILLIS;
 
     /**
      * Allow verification-skipping if it's a development app installed through ADB with
@@ -334,22 +336,28 @@
 
     @GuardedBy("mSessions")
     private void reconcileStagesLocked(String volumeUuid) {
-        final File stagingDir = getTmpSessionDir(volumeUuid);
-        final ArraySet<File> unclaimedStages = newArraySet(
-                stagingDir.listFiles(sStageFilter));
-
-        // We also need to clean up orphaned staging directory for staged sessions
-        final File stagedSessionStagingDir = Environment.getDataStagingDirectory(volumeUuid);
-        unclaimedStages.addAll(newArraySet(stagedSessionStagingDir.listFiles()));
-
+        final ArraySet<File> unclaimedStages = getStagingDirsOnVolume(volumeUuid);
         // Ignore stages claimed by active sessions
         for (int i = 0; i < mSessions.size(); i++) {
             final PackageInstallerSession session = mSessions.valueAt(i);
             unclaimedStages.remove(session.stageDir);
         }
+        removeStagingDirs(unclaimedStages);
+    }
 
+    private ArraySet<File> getStagingDirsOnVolume(String volumeUuid) {
+        final File stagingDir = getTmpSessionDir(volumeUuid);
+        final ArraySet<File> stagingDirs = newArraySet(stagingDir.listFiles(sStageFilter));
+
+        // We also need to clean up orphaned staging directory for staged sessions
+        final File stagedSessionStagingDir = Environment.getDataStagingDirectory(volumeUuid);
+        stagingDirs.addAll(newArraySet(stagedSessionStagingDir.listFiles()));
+        return stagingDirs;
+    }
+
+    private void removeStagingDirs(ArraySet<File> stagingDirsToRemove) {
         // Clean up orphaned staging directories
-        for (File stage : unclaimedStages) {
+        for (File stage : stagingDirsToRemove) {
             Slog.w(TAG, "Deleting orphan stage " + stage);
             synchronized (mPm.mInstallLock) {
                 mPm.removeCodePathLI(stage);
@@ -363,6 +371,33 @@
         }
     }
 
+    /**
+     * Called to free up some storage space from obsolete installation files
+     */
+    public void freeStageDirs(String volumeUuid) {
+        final ArraySet<File> unclaimedStagingDirsOnVolume = getStagingDirsOnVolume(volumeUuid);
+        final long currentTimeMillis = System.currentTimeMillis();
+        synchronized (mSessions) {
+            for (int i = 0; i < mSessions.size(); i++) {
+                final PackageInstallerSession session = mSessions.valueAt(i);
+                if (!unclaimedStagingDirsOnVolume.contains(session.stageDir)) {
+                    // Only handles sessions stored on the target volume
+                    continue;
+                }
+                final long age = currentTimeMillis - session.createdMillis;
+                if (age >= MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS) {
+                    // Aggressively close old sessions because we are running low on storage
+                    // Their staging dirs will be removed too
+                    session.abandon();
+                } else {
+                    // Session is new enough, so it deserves to be kept even on low storage
+                    unclaimedStagingDirsOnVolume.remove(session.stageDir);
+                }
+            }
+        }
+        removeStagingDirs(unclaimedStagingDirsOnVolume);
+    }
+
     public static boolean isStageName(String name) {
         final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp");
         final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp");
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5429484..d0e4457 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -673,7 +673,7 @@
             final Runnable r;
             synchronized (mLock) {
                 assertNotChildLocked("StagedSession#abandon");
-                assertCallerIsOwnerOrRoot();
+                assertCallerIsOwnerOrRootOrSystem();
                 if (isInTerminalState()) {
                     // We keep the session in the database if it's in a finalized state. It will be
                     // removed by PackageInstallerService when the last update time is old enough.
@@ -3704,7 +3704,7 @@
     private void abandonNonStaged() {
         synchronized (mLock) {
             assertNotChildLocked("abandonNonStaged");
-            assertCallerIsOwnerOrRoot();
+            assertCallerIsOwnerOrRootOrSystem();
             if (mRelinquished) {
                 if (LOGD) Slog.d(TAG, "Ignoring abandon after commit relinquished control");
                 return;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c477546..7968f8f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -8962,6 +8962,10 @@
             if (freeBytesRequired > 0) {
                 smInternal.freeCache(volumeUuid, freeBytesRequired);
             }
+
+            // 12. Clear temp install session files
+            mInstallerService.freeStageDirs(volumeUuid);
+
             if (file.getUsableSpace() >= bytes) return;
         } else {
             try {