Use allocatable space when measuring for install.

The system is often willing to clear cached data to make room for
incoming installs, so use StorageManager.getAllocatableBytes() when
making "does it fit?" style decisions.

Add new INSTALL_ALLOCATE_AGGRESSIVE flag, which will flow through
to use StorageManager.FLAG_ALLOCATE_AGGRESSIVE when making allocation
related requests.  (This can be used by installers to indicate
packages that are critical to system health or security.

Test: runtest -x frameworks/base/core/tests/coretests/src/android/content/pm/PackageHelperTests.java
Bug: 36131437
Change-Id: If8118762fd1ca1f497d2cdd1787bdb3c9759dcc0
diff --git a/api/system-current.txt b/api/system-current.txt
index 0f42a54..5e6717c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -11086,6 +11086,7 @@
   public static class PackageInstaller.SessionParams implements android.os.Parcelable {
     ctor public PackageInstaller.SessionParams(int);
     method public int describeContents();
+    method public void setAllocateAggressive(boolean);
     method public void setAllowDowngrade(boolean);
     method public void setAppIcon(android.graphics.Bitmap);
     method public void setAppLabel(java.lang.CharSequence);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 320c733..9e4a86d 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1153,6 +1153,16 @@
         }
 
         /** {@hide} */
+        @SystemApi
+        public void setAllocateAggressive(boolean allocateAggressive) {
+            if (allocateAggressive) {
+                installFlags |= PackageManager.INSTALL_ALLOCATE_AGGRESSIVE;
+            } else {
+                installFlags &= ~PackageManager.INSTALL_ALLOCATE_AGGRESSIVE;
+            }
+        }
+
+        /** {@hide} */
         public void dump(IndentingPrintWriter pw) {
             pw.printPair("mode", mode);
             pw.printHexPair("installFlags", installFlags);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 10ffab2..6dd1833 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -51,6 +51,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
 import android.util.AndroidException;
 import android.util.Log;
@@ -610,6 +611,9 @@
             INSTALL_FORCE_PERMISSION_PROMPT,
             INSTALL_INSTANT_APP,
             INSTALL_DONT_KILL_APP,
+            INSTALL_FORCE_SDK,
+            INSTALL_FULL_APP,
+            INSTALL_ALLOCATE_AGGRESSIVE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface InstallFlags {}
@@ -717,15 +721,6 @@
     public static final int INSTALL_INSTANT_APP = 0x00000800;
 
     /**
-     * Flag parameter for {@link #installPackage} to indicate that this package is
-     * to be installed as a heavy weight app. This is fundamentally the opposite of
-     * {@link #INSTALL_INSTANT_APP}.
-     *
-     * @hide
-     */
-    public static final int INSTALL_FULL_APP = 0x00004000;
-
-    /**
      * Flag parameter for {@link #installPackage} to indicate that this package contains
      * a feature split to an existing application and the existing application should not
      * be killed during the installation process.
@@ -743,6 +738,24 @@
     public static final int INSTALL_FORCE_SDK = 0x00002000;
 
     /**
+     * Flag parameter for {@link #installPackage} to indicate that this package is
+     * to be installed as a heavy weight app. This is fundamentally the opposite of
+     * {@link #INSTALL_INSTANT_APP}.
+     *
+     * @hide
+     */
+    public static final int INSTALL_FULL_APP = 0x00004000;
+
+    /**
+     * Flag parameter for {@link #installPackage} to indicate that this package
+     * is critical to system health or security, meaning the system should use
+     * {@link StorageManager#FLAG_ALLOCATE_AGGRESSIVE} internally.
+     *
+     * @hide
+     */
+    public static final int INSTALL_ALLOCATE_AGGRESSIVE = 0x00008000;
+
+    /**
      * Flag parameter for
      * {@link #setComponentEnabledSetting(android.content.ComponentName, int, int)} to indicate
      * that you don't want to kill the app containing the component.  Be careful when you set this
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index e088717..9c361b3 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -20,6 +20,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageParser.PackageLite;
 import android.os.Environment;
@@ -354,10 +355,12 @@
         abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName);
         abstract public File getDataDirectory();
 
-        public boolean fitsOnInternalStorage(Context context, long sizeBytes) {
+        public boolean fitsOnInternalStorage(Context context, SessionParams params)
+                throws IOException {
             StorageManager storage = getStorageManager(context);
             File target = getDataDirectory();
-            return (sizeBytes <= storage.getStorageBytesUntilLow(target));
+            return (params.sizeBytes <= storage.getAllocatableBytes(target,
+                    translateAllocateFlags(params.installFlags)));
         }
     }
 
@@ -401,6 +404,18 @@
         return sDefaultTestableInterface;
     }
 
+    @VisibleForTesting
+    @Deprecated
+    public static String resolveInstallVolume(Context context, String packageName,
+            int installLocation, long sizeBytes, TestableInterface testInterface)
+            throws IOException {
+        final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
+        params.appPackageName = packageName;
+        params.installLocation = installLocation;
+        params.sizeBytes = sizeBytes;
+        return resolveInstallVolume(context, params, testInterface);
+    }
+
     /**
      * Given a requested {@link PackageInfo#installLocation} and calculated
      * install size, pick the actual volume to install the app. Only considers
@@ -410,25 +425,25 @@
      * @return the {@link VolumeInfo#fsUuid} to install onto, or {@code null}
      *         for internal storage.
      */
-    public static String resolveInstallVolume(Context context, String packageName,
-            int installLocation, long sizeBytes) throws IOException {
+    public static String resolveInstallVolume(Context context, SessionParams params)
+            throws IOException {
         TestableInterface testableInterface = getDefaultTestableInterface();
-        return resolveInstallVolume(context, packageName,
-                installLocation, sizeBytes, testableInterface);
+        return resolveInstallVolume(context, params.appPackageName, params.installLocation,
+                params.sizeBytes, testableInterface);
     }
 
     @VisibleForTesting
-    public static String resolveInstallVolume(Context context, String packageName,
-            int installLocation, long sizeBytes, TestableInterface testInterface)
-            throws IOException {
+    public static String resolveInstallVolume(Context context, SessionParams params,
+            TestableInterface testInterface) throws IOException {
         final boolean forceAllowOnExternal = testInterface.getForceAllowOnExternalSetting(context);
         final boolean allow3rdPartyOnInternal =
                 testInterface.getAllow3rdPartyOnInternalConfig(context);
         // TODO: handle existing apps installed in ASEC; currently assumes
         // they'll end up back on internal storage
-        ApplicationInfo existingInfo = testInterface.getExistingAppInfo(context, packageName);
+        ApplicationInfo existingInfo = testInterface.getExistingAppInfo(context,
+                params.appPackageName);
 
-        final boolean fitsOnInternal = testInterface.fitsOnInternalStorage(context, sizeBytes);
+        final boolean fitsOnInternal = testInterface.fitsOnInternalStorage(context, params);
         final StorageManager storageManager =
                 testInterface.getStorageManager(context);
 
@@ -438,7 +453,8 @@
                 return StorageManager.UUID_PRIVATE_INTERNAL;
             } else {
                 throw new IOException("Not enough space on existing volume "
-                        + existingInfo.volumeUuid + " for system app " + packageName + " upgrade");
+                        + existingInfo.volumeUuid + " for system app " + params.appPackageName
+                        + " upgrade");
             }
         }
 
@@ -450,8 +466,9 @@
             boolean isInternalStorage = ID_PRIVATE_INTERNAL.equals(vol.id);
             if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()
                     && (!isInternalStorage || allow3rdPartyOnInternal)) {
-                final long availBytes = storageManager.getStorageBytesUntilLow(new File(vol.path));
-                if (availBytes >= sizeBytes) {
+                final long availBytes = storageManager.getAllocatableBytes(new File(vol.path),
+                        translateAllocateFlags(params.installFlags));
+                if (availBytes >= params.sizeBytes) {
                     allCandidates.add(vol.fsUuid);
                 }
                 if (availBytes >= bestCandidateAvailBytes) {
@@ -463,11 +480,11 @@
 
         // If app expresses strong desire for internal storage, honor it
         if (!forceAllowOnExternal
-                && installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+                && params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
             if (existingInfo != null && !Objects.equals(existingInfo.volumeUuid,
                     StorageManager.UUID_PRIVATE_INTERNAL)) {
-                throw new IOException("Cannot automatically move " + packageName + " from "
-                        + existingInfo.volumeUuid + " to internal storage");
+                throw new IOException("Cannot automatically move " + params.appPackageName
+                        + " from " + existingInfo.volumeUuid + " to internal storage");
             }
 
             if (!allow3rdPartyOnInternal) {
@@ -490,7 +507,7 @@
                 return existingInfo.volumeUuid;
             } else {
                 throw new IOException("Not enough space on existing volume "
-                        + existingInfo.volumeUuid + " for " + packageName + " upgrade");
+                        + existingInfo.volumeUuid + " for " + params.appPackageName + " upgrade");
             }
         }
 
@@ -504,29 +521,45 @@
         }
     }
 
-    public static boolean fitsOnInternal(Context context, long sizeBytes) {
+    public static boolean fitsOnInternal(Context context, SessionParams params) throws IOException {
         final StorageManager storage = context.getSystemService(StorageManager.class);
         final File target = Environment.getDataDirectory();
-        return (sizeBytes <= storage.getStorageBytesUntilLow(target));
+        return (params.sizeBytes <= storage.getAllocatableBytes(target,
+                translateAllocateFlags(params.installFlags)));
     }
 
-    public static boolean fitsOnExternal(Context context, long sizeBytes) {
+    public static boolean fitsOnExternal(Context context, SessionParams params) {
         final StorageManager storage = context.getSystemService(StorageManager.class);
         final StorageVolume primary = storage.getPrimaryVolume();
-        return (sizeBytes > 0) && !primary.isEmulated()
+        return (params.sizeBytes > 0) && !primary.isEmulated()
                 && Environment.MEDIA_MOUNTED.equals(primary.getState())
-                && sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile());
+                && params.sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile());
+    }
+
+    @Deprecated
+    public static int resolveInstallLocation(Context context, String packageName,
+            int installLocation, long sizeBytes, int installFlags) {
+        final SessionParams params = new SessionParams(SessionParams.MODE_INVALID);
+        params.appPackageName = packageName;
+        params.installLocation = installLocation;
+        params.sizeBytes = sizeBytes;
+        params.installFlags = installFlags;
+        try {
+            return resolveInstallLocation(context, params);
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
     }
 
     /**
      * Given a requested {@link PackageInfo#installLocation} and calculated
      * install size, pick the actual location to install the app.
      */
-    public static int resolveInstallLocation(Context context, String packageName,
-            int installLocation, long sizeBytes, int installFlags) {
+    public static int resolveInstallLocation(Context context, SessionParams params)
+            throws IOException {
         ApplicationInfo existingInfo = null;
         try {
-            existingInfo = context.getPackageManager().getApplicationInfo(packageName,
+            existingInfo = context.getPackageManager().getApplicationInfo(params.appPackageName,
                     PackageManager.MATCH_ANY_USER);
         } catch (NameNotFoundException ignored) {
         }
@@ -534,23 +567,23 @@
         final int prefer;
         final boolean checkBoth;
         boolean ephemeral = false;
-        if ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
+        if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) {
             prefer = RECOMMEND_INSTALL_INTERNAL;
             ephemeral = true;
             checkBoth = false;
-        } else if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
+        } else if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
             prefer = RECOMMEND_INSTALL_INTERNAL;
             checkBoth = false;
-        } else if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
+        } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
             prefer = RECOMMEND_INSTALL_EXTERNAL;
             checkBoth = false;
-        } else if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+        } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
             prefer = RECOMMEND_INSTALL_INTERNAL;
             checkBoth = false;
-        } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
+        } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
             prefer = RECOMMEND_INSTALL_EXTERNAL;
             checkBoth = true;
-        } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
+        } else if (params.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
             // When app is already installed, prefer same medium
             if (existingInfo != null) {
                 // TODO: distinguish if this is external ASEC
@@ -570,12 +603,12 @@
 
         boolean fitsOnInternal = false;
         if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
-            fitsOnInternal = fitsOnInternal(context, sizeBytes);
+            fitsOnInternal = fitsOnInternal(context, params);
         }
 
         boolean fitsOnExternal = false;
         if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {
-            fitsOnExternal = fitsOnExternal(context, sizeBytes);
+            fitsOnExternal = fitsOnExternal(context, params);
         }
 
         if (prefer == RECOMMEND_INSTALL_INTERNAL) {
@@ -641,4 +674,12 @@
         }
         return str.substring(0, str.length() - before.length()) + after;
     }
+
+    public static int translateAllocateFlags(int installFlags) {
+        if ((installFlags & PackageManager.INSTALL_ALLOCATE_AGGRESSIVE) != 0) {
+            return StorageManager.FLAG_ALLOCATE_AGGRESSIVE;
+        } else {
+            return 0;
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java b/core/tests/coretests/src/android/content/pm/PackageHelperTests.java
index c4d00c6..5c497b4 100644
--- a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageHelperTests.java
@@ -58,9 +58,9 @@
     private static final long sAdoptedSize = 10000;
     private static final long sPublicSize = 1000000;
 
-    private static final StorageManager sStorageManager = createStorageManagerMock();
+    private static StorageManager sStorageManager;
 
-    private static StorageManager createStorageManagerMock() {
+    private static StorageManager createStorageManagerMock() throws Exception {
         VolumeInfo internalVol = new VolumeInfo("private",
                 VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/);
         internalVol.path = sInternalVolPath;
@@ -93,6 +93,12 @@
         Mockito.when(storageManager.getStorageBytesUntilLow(internalFile)).thenReturn(sInternalSize);
         Mockito.when(storageManager.getStorageBytesUntilLow(adoptedFile)).thenReturn(sAdoptedSize);
         Mockito.when(storageManager.getStorageBytesUntilLow(publicFile)).thenReturn(sPublicSize);
+        Mockito.when(storageManager.getAllocatableBytes(Mockito.eq(internalFile), Mockito.anyInt()))
+                .thenReturn(sInternalSize);
+        Mockito.when(storageManager.getAllocatableBytes(Mockito.eq(adoptedFile), Mockito.anyInt()))
+                .thenReturn(sAdoptedSize);
+        Mockito.when(storageManager.getAllocatableBytes(Mockito.eq(publicFile), Mockito.anyInt()))
+                .thenReturn(sPublicSize);
         return storageManager;
     }
 
@@ -156,18 +162,10 @@
         }
     }
 
-    void failStr(String errMsg) {
-        Log.w(TAG, "errMsg=" + errMsg);
-        fail(errMsg);
-    }
-
-    void failStr(Exception e) {
-        failStr(e.getMessage());
-    }
-
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        sStorageManager = createStorageManagerMock();
         if (localLOGV) Log.i(TAG, "Cleaning out old test containers");
         cleanupContainers();
     }
@@ -175,28 +173,25 @@
     @Override
     protected void tearDown() throws Exception {
         super.tearDown();
+        sStorageManager = null;
         if (localLOGV) Log.i(TAG, "Cleaning out old test containers");
         cleanupContainers();
     }
 
-    public void testMountAndPullSdCard() {
-        try {
-            fullId = PREFIX;
-            fullId2 = PackageHelper.createSdDir(1024 * MB_IN_BYTES, fullId, "none",
-                    android.os.Process.myUid(), true);
+    public void testMountAndPullSdCard() throws Exception {
+        fullId = PREFIX;
+        fullId2 = PackageHelper.createSdDir(1024 * MB_IN_BYTES, fullId, "none",
+                android.os.Process.myUid(), true);
 
-            Log.d(TAG,PackageHelper.getSdDir(fullId));
-            PackageHelper.unMountSdDir(fullId);
+        Log.d(TAG, "getSdDir=" + PackageHelper.getSdDir(fullId));
+        PackageHelper.unMountSdDir(fullId);
 
-            Runnable r1 = getMountRunnable();
-            Runnable r2 = getDestroyRunnable();
-            Thread thread = new Thread(r1);
-            Thread thread2 = new Thread(r2);
-            thread2.start();
-            thread.start();
-        } catch (Exception e) {
-            failStr(e);
-        }
+        Runnable r1 = getMountRunnable();
+        Runnable r2 = getDestroyRunnable();
+        Thread thread = new Thread(r1);
+        Thread thread2 = new Thread(r2);
+        thread2.start();
+        thread.start();
     }
 
     public Runnable getMountRunnable() {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 37f78b4..a317ca5 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -637,12 +637,12 @@
         // If caller requested explicit location, sanity check it, otherwise
         // resolve the best internal or adopted location.
         if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
-            if (!PackageHelper.fitsOnInternal(mContext, params.sizeBytes)) {
+            if (!PackageHelper.fitsOnInternal(mContext, params)) {
                 throw new IOException("No suitable internal storage available");
             }
 
         } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
-            if (!PackageHelper.fitsOnExternal(mContext, params.sizeBytes)) {
+            if (!PackageHelper.fitsOnExternal(mContext, params)) {
                 throw new IOException("No suitable external storage available");
             }
 
@@ -660,8 +660,7 @@
             // requested install flags, delta size, and manifest settings.
             final long ident = Binder.clearCallingIdentity();
             try {
-                params.volumeUuid = PackageHelper.resolveInstallVolume(mContext,
-                        params.appPackageName, params.installLocation, params.sizeBytes);
+                params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 2e4a3a3..1129076 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -472,8 +472,8 @@
             // If caller specified a total length, allocate it for them. Free up
             // cache space to grow, if needed.
             if (stageDir != null && lengthBytes > 0) {
-                mContext.getSystemService(StorageManager.class).allocateBytes(targetFd,
-                        lengthBytes, 0);
+                mContext.getSystemService(StorageManager.class).allocateBytes(targetFd, lengthBytes,
+                        PackageHelper.translateAllocateFlags(params.installFlags));
             }
 
             if (offsetBytes > 0) {