Merge "Use allocatable space when measuring for install."
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) {