Do storage checks before initiating a move.
Add new remote method to check for insufficient error conditions.
Some fixes in MountService when updating media status on PackageManagerService
Fix size calculation condition in installd.

Add new error code if media is unavailable.
New tests for testing error codes.
Some additional debugging statements in MountService.

Change-Id: Ibfe90d5ed6c71d57f9c1c67806f38b5ae9ecdfbf
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index 654ee68..b8ba3f6 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -389,9 +389,10 @@
     int cachesize = 0;
 
         /* count the source apk as code -- but only if it's not
-         * on the /system partition
+         * on the /system partition and its not on the sdcard.
          */
-    if (strncmp(apkpath, "/system", 7) != 0) {
+    if (strncmp(apkpath, "/system", 7) != 0 &&
+            strncmp(apkpath, SDCARD_DIR_PREFIX, 7) != 0) {
         if (stat(apkpath, &s) == 0) {
             codesize += stat_size(&s);
         }
@@ -403,17 +404,6 @@
             codesize += stat_size(&s);
         }
     }
-
-        /* count the source apk as code -- but only if it's not
-         * installed on the sdcard
-         */
-    if (strncmp(apkpath, SDCARD_DIR_PREFIX, 7) != 0) {
-        if (stat(apkpath, &s) == 0) {
-            codesize += stat_size(&s);
-        }
-    }
-
-
         /* count the cached dexfile as code */
     if (!create_cache_path(path, apkpath)) {
         if (stat(path, &s) == 0) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 271f477..e1fbe48 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -439,6 +439,15 @@
     public static final int INSTALL_FAILED_INVALID_INSTALL_LOCATION = -19;
 
     /**
+     * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+     * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+     * the new package couldn't be installed in the specified install
+     * location because the media is not available.
+     * @hide
+     */
+    public static final int INSTALL_FAILED_MEDIA_UNAVAILABLE = -20;
+
+    /**
      * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
      * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
      * if the parser was given a path that is not a file, or does not end with the expected
@@ -586,6 +595,14 @@
     public static final int MOVE_FAILED_INVALID_LOCATION = -5;
 
     /**
+     * Error code that is passed to the {@link IPackageMoveObserver} by
+     * {@link #movePackage(android.net.Uri, IPackageMoveObserver)}
+     * if the specified package cannot be moved to the specified location.
+     * @hide
+     */
+    public static final int MOVE_FAILED_INTERNAL_ERROR = -6;
+
+    /**
      * Flag parameter for {@link #movePackage} to indicate that
      * the package should be moved to internal storage if its
      * been installed on external media.
diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl
index fd1fd58..badabb0 100755
--- a/core/java/com/android/internal/app/IMediaContainerService.aidl
+++ b/core/java/com/android/internal/app/IMediaContainerService.aidl
@@ -27,4 +27,5 @@
     boolean copyResource(in Uri packageURI,
                 in ParcelFileDescriptor outStream);
     PackageInfoLite getMinimalPackageInfo(in Uri fileUri);
+    boolean checkFreeStorage(boolean external, in Uri fileUri);
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index 80efca4..4d0a9e0 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -38,6 +38,7 @@
     public static final int RECOMMEND_FAILED_INVALID_APK = -2;
     public static final int RECOMMEND_FAILED_INVALID_LOCATION = -3;
     public static final int RECOMMEND_FAILED_ALREADY_EXISTS = -4;
+    public static final int RECOMMEND_MEDIA_UNAVAILABLE = -5;
     private static final boolean localLOGV = true;
     private static final String TAG = "PackageHelper";
     // App installation location settings values
@@ -70,7 +71,7 @@
 
         try {
             int rc = mountService.createSecureContainer(
-                    cid, mbLen, "vfat", sdEncKey, uid);
+                    cid, mbLen, "fat", sdEncKey, uid);
             if (rc != StorageResultCode.OperationSucceeded) {
                 Log.e(TAG, "Failed to create secure container " + cid);
                 return null;
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index c6b617d..77d11cc 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -24,7 +24,6 @@
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.Package;
 import android.net.Uri;
 import android.os.Environment;
 import android.os.IBinder;
@@ -45,7 +44,6 @@
 import java.io.InputStream;
 
 import android.os.FileUtils;
-import android.os.storage.IMountService;
 import android.provider.Settings;
 
 /*
@@ -130,26 +128,21 @@
             ret.packageName = pkg.packageName;
             ret.installLocation = pkg.installLocation;
             // Nuke the parser reference right away and force a gc
-            Runtime.getRuntime().gc();
             packageParser = null;
+            Runtime.getRuntime().gc();
             if (pkg == null) {
                 Log.w(TAG, "Failed to parse package");
                 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
                 return ret;
             }
             ret.packageName = pkg.packageName;
-            int loc = recommendAppInstallLocation(pkg.installLocation, archiveFilePath);
-            if (loc == PackageManager.INSTALL_EXTERNAL) {
-                ret.recommendedInstallLocation =  PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
-            } else if (loc == ERR_LOC) {
-                Log.i(TAG, "Failed to install insufficient storage");
-                ret.recommendedInstallLocation =  PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
-            } else {
-                // Implies install on internal storage.
-                ret.recommendedInstallLocation =  PackageHelper.RECOMMEND_INSTALL_INTERNAL;
-            }
+            ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, archiveFilePath);
             return ret;
         }
+
+        public boolean checkFreeStorage(boolean external, Uri fileUri) {
+            return checkFreeStorageInner(external, fileUri);
+        }
     };
 
     public DefaultContainerService() {
@@ -190,6 +183,12 @@
     }
 
     private String copyResourceInner(Uri packageURI, String newCid, String key, String resFileName) {
+        // Make sure the sdcard is mounted.
+        String status = Environment.getExternalStorageState();
+        if (!status.equals(Environment.MEDIA_MOUNTED)) {
+            Log.w(TAG, "Make sure sdcard is mounted.");
+            return null;
+        }
         // Create new container at newCachePath
         String codePath = packageURI.getPath();
         File codeFile = new File(codePath);
@@ -313,11 +312,13 @@
         // Else install on internal NAND flash, unless space on NAND is less than 10%
         String status = Environment.getExternalStorageState();
         long availSDSize = -1;
+        boolean mediaAvailable = false;
         if (status.equals(Environment.MEDIA_MOUNTED)) {
             StatFs sdStats = new StatFs(
                     Environment.getExternalStorageDirectory().getPath());
             availSDSize = (long)sdStats.getAvailableBlocks() *
                     (long)sdStats.getBlockSize();
+            mediaAvailable = true;
         }
         StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());
         long totalInternalSize = (long)internalStats.getBlockCount() *
@@ -337,7 +338,8 @@
         long reqInternalSize = 0;
         boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD);
         boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize);
-        boolean fitsOnSd = (reqInstallSize < availSDSize) && intThresholdOk &&
+        boolean fitsOnSd = mediaAvailable && (reqInstallSize < availSDSize)
+                 && intThresholdOk &&
                 (reqInternalSize < availInternalSize);
         boolean fitsOnInt = intThresholdOk && intAvailOk;
 
@@ -377,21 +379,58 @@
         }
         if (!auto) {
             if (installOnlyOnSd) {
-                return fitsOnSd ? PackageManager.INSTALL_EXTERNAL : ERR_LOC;
+                if (fitsOnSd) {
+                    return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+                }
+                if (!mediaAvailable) {
+                    return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;
+                }
+                return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
             } else if (installOnlyInternal){
                 // Check on internal flash
-                return fitsOnInt ? 0 : ERR_LOC;
+                return fitsOnInt ?  PackageHelper.RECOMMEND_INSTALL_INTERNAL :
+                    PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
             }
         }
         // Try to install internally
         if (fitsOnInt) {
-            return 0;
+            return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
         }
         // Try the sdcard now.
         if (fitsOnSd) {
-            return PackageManager.INSTALL_EXTERNAL;
+            return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
         }
         // Return error code
-        return ERR_LOC;
+        return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
+    }
+
+    private boolean checkFreeStorageInner(boolean external, Uri packageURI) {
+        File apkFile = new File(packageURI.getPath());
+        long size = apkFile.length();
+        if (external) {
+            String status = Environment.getExternalStorageState();
+            long availSDSize = -1;
+            if (status.equals(Environment.MEDIA_MOUNTED)) {
+                StatFs sdStats = new StatFs(
+                        Environment.getExternalStorageDirectory().getPath());
+                availSDSize = (long)sdStats.getAvailableBlocks() *
+                (long)sdStats.getBlockSize();
+            }
+            return availSDSize > size;
+        }
+        StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());
+        long totalInternalSize = (long)internalStats.getBlockCount() *
+        (long)internalStats.getBlockSize();
+        long availInternalSize = (long)internalStats.getAvailableBlocks() *
+        (long)internalStats.getBlockSize();
+
+        double pctNandFree = (double)availInternalSize / (double)totalInternalSize;
+        // To make final copy
+        long reqInstallSize = size;
+        // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now.
+        long reqInternalSize = 0;
+        boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD);
+        boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize);
+        return intThresholdOk && intAvailOk;
     }
 }
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index b8b94a1..d50f5910 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -51,6 +51,8 @@
 class MountService extends IMountService.Stub
         implements INativeDaemonConnectorCallbacks {
     private static final boolean LOCAL_LOGD = false;
+    private static final boolean DEBUG_UNMOUNT = false;
+    private static final boolean DEBUG_EVENTS = false;
     
     private static final String TAG = "MountService";
 
@@ -156,6 +158,7 @@
         }
 
         void handleFinished() {
+            if (DEBUG_UNMOUNT) Log.i(TAG, "Unmounting " + path);
             doUnmountVolume(path, true);
         }
     }
@@ -205,22 +208,27 @@
 
         void registerReceiver() {
             mRegistered = true;
+            if (DEBUG_UNMOUNT) Log.i(TAG, "Registering receiver");
             mContext.registerReceiver(mPmReceiver, mPmFilter);
         }
 
         void unregisterReceiver() {
             mRegistered = false;
+            if (DEBUG_UNMOUNT) Log.i(TAG, "Unregistering receiver");
             mContext.unregisterReceiver(mPmReceiver);
         }
 
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case H_UNMOUNT_PM_UPDATE: {
+                    if (DEBUG_UNMOUNT) Log.i(TAG, "H_UNMOUNT_PM_UPDATE");
                     UnmountCallBack ucb = (UnmountCallBack) msg.obj;
                     mForceUnmounts.add(ucb);
+                    if (DEBUG_UNMOUNT) Log.i(TAG, " registered = " + mRegistered);
                     // Register only if needed.
                     if (!mRegistered) {
                         registerReceiver();
+                        if (DEBUG_UNMOUNT) Log.i(TAG, "Updating external media status");
                         boolean hasExtPkgs = mPms.updateExternalMediaStatus(false);
                         if (!hasExtPkgs) {
                             // Unregister right away
@@ -230,6 +238,7 @@
                     break;
                 }
                 case H_UNMOUNT_PM_DONE: {
+                    if (DEBUG_UNMOUNT) Log.i(TAG, "H_UNMOUNT_PM_DONE");
                     // Unregister now.
                     if (mRegistered) {
                         unregisterReceiver();
@@ -286,6 +295,7 @@
                     break;
                 }
                 case H_UNMOUNT_MS : {
+                    if (DEBUG_UNMOUNT) Log.i(TAG, "H_UNMOUNT_MS");
                     UnmountCallBack ucb = (UnmountCallBack) msg.obj;
                     ucb.handleFinished();
                     break;
@@ -393,7 +403,12 @@
             Log.w(TAG, String.format("Duplicate state transition (%s -> %s)", mLegacyState, state));
             return;
         }
-
+        // Update state on PackageManager
+        if (Environment.MEDIA_UNMOUNTED.equals(state)) {
+            mPms.updateExternalMediaStatus(false);
+        } else if (Environment.MEDIA_MOUNTED.equals(state)) {
+            mPms.updateExternalMediaStatus(true);
+        }
         String oldState = mLegacyState;
         mLegacyState = state;
 
@@ -456,6 +471,7 @@
                         }
                     }
                     if (state != null) {
+                        if (DEBUG_EVENTS) Log.i(TAG, "Updating valid state " + state);
                         updatePublicVolumeState(path, state);
                     }
                 } catch (Exception e) {
@@ -484,6 +500,18 @@
     public boolean onEvent(int code, String raw, String[] cooked) {
         Intent in = null;
 
+        if (DEBUG_EVENTS) {
+            StringBuilder builder = new StringBuilder();
+            builder.append("onEvent::");
+            builder.append(" raw= " + raw);
+            if (cooked != null) {
+                builder.append(" cooked = " );
+                for (String str : cooked) {
+                    builder.append(" " + str);
+                }
+            }
+            Log.i(TAG, builder.toString());
+        }
         if (code == VoldResponseCode.VolumeStateChange) {
             /*
              * One of the volumes we're managing has changed state.
@@ -541,18 +569,22 @@
                     return true;
                 }
                 /* Send the media unmounted event first */
+                if (DEBUG_EVENTS) Log.i(TAG, "Sending unmounted event first");
                 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
                 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
                 mContext.sendBroadcast(in);
 
+                if (DEBUG_EVENTS) Log.i(TAG, "Sending media removed");
                 updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
                 in = new Intent(Intent.ACTION_MEDIA_REMOVED, Uri.parse("file://" + path));
             } else if (code == VoldResponseCode.VolumeBadRemoval) {
+                if (DEBUG_EVENTS) Log.i(TAG, "Sending unmounted event first");
                 /* Send the media unmounted event first */
                 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
                 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
                 mContext.sendBroadcast(in);
 
+                if (DEBUG_EVENTS) Log.i(TAG, "Sending media bad removal");
                 updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
                 in = new Intent(Intent.ACTION_MEDIA_BAD_REMOVAL, Uri.parse("file://" + path));
             } else {
@@ -570,6 +602,7 @@
 
     private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
         String vs = getVolumeState(path);
+        if (DEBUG_EVENTS) Log.i(TAG, "notifyVolumeStateChanged::" + vs);
 
         Intent in = null;
 
@@ -591,29 +624,31 @@
                     Environment.MEDIA_BAD_REMOVAL) && !vs.equals(
                             Environment.MEDIA_NOFS) && !vs.equals(
                                     Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) {
+                if (DEBUG_EVENTS) Log.i(TAG, "updating volume state for media bad removal nofs and unmountable");
                 updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
                 in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
             }
         } else if (newState == VolumeState.Pending) {
         } else if (newState == VolumeState.Checking) {
+            if (DEBUG_EVENTS) Log.i(TAG, "updating volume state checking");
             updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
             in = new Intent(Intent.ACTION_MEDIA_CHECKING, Uri.parse("file://" + path));
         } else if (newState == VolumeState.Mounted) {
+            if (DEBUG_EVENTS) Log.i(TAG, "updating volume state mounted");
             updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
-            // Update media status on PackageManagerService to mount packages on sdcard
-            mPms.updateExternalMediaStatus(true);
             in = new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + path));
             in.putExtra("read-only", false);
         } else if (newState == VolumeState.Unmounting) {
-            mPms.updateExternalMediaStatus(false);
             in = new Intent(Intent.ACTION_MEDIA_EJECT, Uri.parse("file://" + path));
         } else if (newState == VolumeState.Formatting) {
         } else if (newState == VolumeState.Shared) {
+            if (DEBUG_EVENTS) Log.i(TAG, "Updating volume state media mounted");
             /* Send the media unmounted event first */
             updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
             in = new Intent(Intent.ACTION_MEDIA_UNMOUNTED, Uri.parse("file://" + path));
             mContext.sendBroadcast(in);
 
+            if (DEBUG_EVENTS) Log.i(TAG, "Updating media shared");
             updatePublicVolumeState(path, Environment.MEDIA_SHARED);
             in = new Intent(Intent.ACTION_MEDIA_SHARED, Uri.parse("file://" + path));
             if (LOCAL_LOGD) Log.d(TAG, "Sending ACTION_MEDIA_SHARED intent");
@@ -657,6 +692,7 @@
     private int doMountVolume(String path) {
         int rc = StorageResultCode.OperationSucceeded;
 
+        if (DEBUG_EVENTS) Log.i(TAG, "doMountVolume: Mouting " + path);
         try {
             mConnector.doCommand(String.format("volume mount %s", path));
         } catch (NativeDaemonConnectorException e) {
@@ -671,6 +707,7 @@
                  */
                 rc = StorageResultCode.OperationFailedNoMedia;
             } else if (code == VoldResponseCode.OpFailedMediaBlank) {
+                if (DEBUG_EVENTS) Log.i(TAG, " updating volume state :: media nofs");
                 /*
                  * Media is blank or does not contain a supported filesystem
                  */
@@ -678,6 +715,7 @@
                 in = new Intent(Intent.ACTION_MEDIA_NOFS, Uri.parse("file://" + path));
                 rc = StorageResultCode.OperationFailedMediaBlank;
             } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
+                if (DEBUG_EVENTS) Log.i(TAG, "updating volume state media corrupt");
                 /*
                  * Volume consistency check failed
                  */
@@ -1040,6 +1078,16 @@
         validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
         waitForReady();
 
+        String volState = getVolumeState(path);
+        if (DEBUG_UNMOUNT) Log.i(TAG, "Unmounting " + path + " force = " + force);
+        if (Environment.MEDIA_UNMOUNTED.equals(volState) ||
+                Environment.MEDIA_REMOVED.equals(volState) ||
+                Environment.MEDIA_SHARED.equals(volState) ||
+                Environment.MEDIA_UNMOUNTABLE.equals(volState)) {
+            // Media already unmounted or cannot be unmounted.
+            // TODO return valid return code when adding observer call back.
+            return;
+        }
         UnmountCallBack ucb = new UnmountCallBack(path, force);
         mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
     }
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 139c05f..818e99e 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -4613,6 +4613,12 @@
             return pkgLite.recommendedInstallLocation;
         }
 
+        /*
+         * Invoke remote method to get package information and install
+         * location values. Override install location based on default
+         * policy if needed and then create install arguments based
+         * on the install location.
+         */
         public void handleStartCopy() throws RemoteException {
             int ret = PackageManager.INSTALL_SUCCEEDED;
             boolean fwdLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
@@ -4624,9 +4630,7 @@
             } else {
                 // Remote call to find out default install location
                 PackageInfoLite pkgLite = mContainerService.getMinimalPackageInfo(packageURI);
-                int loc = installLocationPolicy(pkgLite, flags);
-                // Use install location to create InstallArgs and temporary
-                // install location
+                int loc = pkgLite.recommendedInstallLocation;
                 if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION){
                     ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
                 } else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS){
@@ -4635,7 +4639,11 @@
                     ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
                 } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
                     ret = PackageManager.INSTALL_FAILED_INVALID_APK;
+                } else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
+                  ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
                 } else {
+                    // Override with defaults if needed.
+                    loc = installLocationPolicy(pkgLite, flags);
                     // Override install location with flags
                     if ((flags & PackageManager.INSTALL_EXTERNAL) == 0){
                         if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
@@ -4701,6 +4709,12 @@
         }
 
         public void handleStartCopy() throws RemoteException {
+            mRet = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+            // Check for storage space on target medium
+            if (!targetArgs.checkFreeStorage(mContainerService)) {
+                Log.w(TAG, "Insufficient storage to install");
+                return;
+            }
             // Create the file args now.
             mRet = targetArgs.copyApk(mContainerService, false);
             targetArgs.doPreInstall(mRet);
@@ -4714,15 +4728,20 @@
                     builder.append(" target : ");
                     builder.append(targetArgs.getCodePath());
                 }
-                Log.i(TAG, "Posting move MCS_UNBIND for " + builder.toString());
+                Log.i(TAG, builder.toString());
             }
         }
 
         @Override
         void handleReturnCode() {
             targetArgs.doPostInstall(mRet);
-            // TODO invoke pending move
-            processPendingMove(this, mRet);
+            int currentStatus = PackageManager.MOVE_FAILED_INTERNAL_ERROR;
+            if (mRet == PackageManager.INSTALL_SUCCEEDED) {
+                currentStatus = PackageManager.MOVE_SUCCEEDED;
+            } else if (mRet == PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE){
+                currentStatus = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE;
+            }
+            processPendingMove(this, currentStatus);
         }
 
         @Override
@@ -4782,6 +4801,7 @@
         // Need installer lock especially for dex file removal.
         abstract void cleanUpResourcesLI();
         abstract boolean doPostDeleteLI(boolean delete);
+        abstract boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException;
     }
 
     class FileInstallArgs extends InstallArgs {
@@ -4812,6 +4832,10 @@
             resourceFileName = getResourcePathFromCodePath();
         }
 
+        boolean  checkFreeStorage(IMediaContainerService imcs) throws RemoteException {
+            return imcs.checkFreeStorage(false, packageURI);
+        }
+
         String getCodePath() {
             return codeFileName;
         }
@@ -5013,6 +5037,10 @@
             cid = getTempContainerId();
         }
 
+        boolean  checkFreeStorage(IMediaContainerService imcs) throws RemoteException {
+            return imcs.checkFreeStorage(true, packageURI);
+        }
+
         int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
             if (temp) {
                 createCopyFile();
@@ -9109,6 +9137,9 @@
    public boolean updateExternalMediaStatus(final boolean mediaStatus) {
        final boolean ret;
        synchronized (mPackages) {
+           Log.i(TAG, "Updating external media status from " +
+                   (mMediaMounted ? "mounted" : "unmounted") + " to " +
+                   (mediaStatus ? "mounted" : "unmounted"));
            if (DEBUG_SD_INSTALL) Log.i(TAG, "updateExternalMediaStatus:: mediaStatus=" +
                    mediaStatus+", mMediaMounted=" + mMediaMounted);
            if (mediaStatus == mMediaMounted) {
@@ -9117,6 +9148,14 @@
            mMediaMounted = mediaStatus;
            Set<String> appList = mSettings.findPackagesWithFlag(ApplicationInfo.FLAG_EXTERNAL_STORAGE);
            ret = appList != null && appList.size() > 0;
+           if (DEBUG_SD_INSTALL) {
+               if (appList != null) {
+                   for (String app : appList) {
+                       Log.i(TAG, "Should enable " + app + " on sdcard");
+                   }
+               }
+           }
+           if (DEBUG_SD_INSTALL)  Log.i(TAG, "updateExternalMediaStatus returning " + ret);
        }
        // Queue up an async operation since the package installation may take a little while.
        mHandler.post(new Runnable() {
@@ -9134,6 +9173,7 @@
        // enabled or disabled.
        final String list[] = PackageHelper.getSecureContainerList();
        if (list == null || list.length == 0) {
+           Log.i(TAG, "No secure containers on sdcard");
            return;
        }
 
@@ -9141,16 +9181,6 @@
        int num = 0;
        HashSet<String> removeCids = new HashSet<String>();
        HashMap<SdInstallArgs, String> processCids = new HashMap<SdInstallArgs, String>();
-       /*HashMap<String, String> cidPathMap = new HashMap<String, String>();
-       // Don't hold any locks when getting cache paths
-       for (String cid : list) {
-           String cpath = PackageHelper.getSdDir(cid);
-           if (cpath == null) {
-               removeCids.add(cid);
-           } else {
-               cidPathMap.put(cid, cpath);
-           }
-       }*/
        synchronized (mPackages) {
            for (String cid : list) {
                SdInstallArgs args = new SdInstallArgs(cid);
diff --git a/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java b/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java
index 8c8b00c..a06a13b 100755
--- a/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java
@@ -72,8 +72,8 @@
 public class PackageManagerTests extends AndroidTestCase {
     private static final boolean localLOGV = true;
     public static final String TAG="PackageManagerTests";
-    public final long MAX_WAIT_TIME=120*1000;
-    public final long WAIT_TIME_INCR=20*1000;
+    public final long MAX_WAIT_TIME = 25*1000;
+    public final long WAIT_TIME_INCR = 5*1000;
     private static final String SECURE_CONTAINERS_PREFIX = "/mnt/asec";
     private static final int APP_INSTALL_AUTO = PackageHelper.APP_INSTALL_AUTO;
     private static final int APP_INSTALL_DEVICE = PackageHelper.APP_INSTALL_INTERNAL;
@@ -378,39 +378,50 @@
     private InstallParams installFromRawResource(String outFileName,
             int rawResId, int flags, boolean cleanUp, boolean fail, int result,
             int expInstallLocation) {
+        PackageManager pm = mContext.getPackageManager();
         File filesDir = mContext.getFilesDir();
         File outFile = new File(filesDir, outFileName);
         Uri packageURI = getInstallablePackage(rawResId, outFile);
         PackageParser.Package pkg = parsePackage(packageURI);
         assertNotNull(pkg);
-        InstallParams ip = null;
-        // Make sure the package doesn't exist
-        getPm().deletePackage(pkg.packageName, null, 0);
-        // Clean up the containers as well
-        clearSecureContainersForPkg(pkg.packageName);
-        try {
+        if ((flags & PackageManager.INSTALL_REPLACE_EXISTING) == 0) {
+            // Make sure the package doesn't exist
             try {
-                if (fail) {
-                    assertTrue(invokeInstallPackageFail(packageURI, flags,
-                            pkg.packageName, result));
-                    assertNotInstalled(pkg.packageName);
-                } else {
-                    InstallReceiver receiver = new InstallReceiver(pkg.packageName);
-                    assertTrue(invokeInstallPackage(packageURI, flags,
-                            pkg.packageName, receiver));
-                    // Verify installed information
-                    assertInstall(pkg, flags, expInstallLocation);
-                    ip = new InstallParams(pkg, outFileName, packageURI);
-                }
+                ApplicationInfo appInfo = pm.getApplicationInfo(pkg.packageName,
+                        PackageManager.GET_UNINSTALLED_PACKAGES);
+                GenericReceiver receiver = new DeleteReceiver(pkg.packageName);
+                invokeDeletePackage(packageURI, 0,
+                        pkg.packageName, receiver);
+            } catch (NameNotFoundException e1) {
             } catch (Exception e) {
-                failStr("Failed with exception : " + e);
+                failStr(e);
+            }
+            // Clean up the containers as well
+            clearSecureContainersForPkg(pkg.packageName);
+        }
+        InstallParams ip = null;
+        try {
+            if (fail) {
+                assertTrue(invokeInstallPackageFail(packageURI, flags,
+                        pkg.packageName, result));
+                assertNotInstalled(pkg.packageName);
+            } else {
+                InstallReceiver receiver = new InstallReceiver(pkg.packageName);
+                assertTrue(invokeInstallPackage(packageURI, flags,
+                        pkg.packageName, receiver));
+                // Verify installed information
+                assertInstall(pkg, flags, expInstallLocation);
+                ip = new InstallParams(pkg, outFileName, packageURI);
             }
             return ip;
+        } catch (Exception e) {
+            failStr("Failed with exception : " + e);
         } finally {
             if (cleanUp) {
                 cleanUpInstall(ip);
             }
         }
+        return ip;
     }
 
     @MediumTest
@@ -820,7 +831,7 @@
         try {
             // Wait on observer
             synchronized(observer) {
-                getMs().unmountVolume(path, false);
+                getMs().unmountVolume(path, true);
                 long waitTime = 0;
                 while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
                     observer.wait(WAIT_TIME_INCR);
@@ -949,14 +960,21 @@
                 PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
     }
 
-    private void replaceManifestLocation(int iFlags, int rFlags) {
+    /*
+     * Install a package on internal flash via PackageManager install flag. Replace
+     * the package via flag to install on sdcard. Make sure the new flag overrides
+     * the old install location.
+     */
+    public void testReplaceFlagInternalSdcard() {
+        int iFlags = 0;
+        int rFlags = PackageManager.INSTALL_EXTERNAL;
         InstallParams ip = sampleInstallFromRawResource(iFlags, false);
         GenericReceiver receiver = new ReplaceReceiver(ip.pkg.packageName);
         int replaceFlags = rFlags | PackageManager.INSTALL_REPLACE_EXISTING;
         try {
             assertEquals(invokeInstallPackage(ip.packageURI, replaceFlags,
                     ip.pkg.packageName, receiver), true);
-            assertInstall(ip.pkg, replaceFlags, ip.pkg.installLocation);
+            assertInstall(ip.pkg, rFlags, ip.pkg.installLocation);
         } catch (Exception e) {
             failStr("Failed with exception : " + e);
         } finally {
@@ -964,12 +982,26 @@
         }
     }
 
-    public void testReplaceFlagInternalSdcard() {
-        replaceManifestLocation(0, PackageManager.INSTALL_EXTERNAL);
-    }
-
+    /*
+     * Install a package on sdcard via PackageManager install flag. Replace
+     * the package with no flags or manifest option and make sure the old
+     * install location is retained.
+     */
     public void testReplaceFlagSdcardInternal() {
-        replaceManifestLocation(PackageManager.INSTALL_EXTERNAL, 0);
+        int iFlags = PackageManager.INSTALL_EXTERNAL;
+        int rFlags = 0;
+        InstallParams ip = sampleInstallFromRawResource(iFlags, false);
+        GenericReceiver receiver = new ReplaceReceiver(ip.pkg.packageName);
+        int replaceFlags = rFlags | PackageManager.INSTALL_REPLACE_EXISTING;
+        try {
+            assertEquals(invokeInstallPackage(ip.packageURI, replaceFlags,
+                    ip.pkg.packageName, receiver), true);
+            assertInstall(ip.pkg, iFlags, ip.pkg.installLocation);
+        } catch (Exception e) {
+            failStr("Failed with exception : " + e);
+        } finally {
+            cleanUpInstall(ip);
+        }
     }
 
     public void testManifestInstallLocationReplaceInternalSdcard() {
@@ -984,7 +1016,7 @@
         int replaceFlags = rFlags | PackageManager.INSTALL_REPLACE_EXISTING;
         try {
             InstallParams rp = installFromRawResource("install.apk", rApk,
-                    rFlags, false,
+                    replaceFlags, false,
                     false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
             assertInstall(rp.pkg, replaceFlags, rp.pkg.installLocation);
         } catch (Exception e) {
@@ -1002,11 +1034,10 @@
         InstallParams ip = installFromRawResource("install.apk", iApk,
                 iFlags, false,
                 false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
-        GenericReceiver receiver = new ReplaceReceiver(ip.pkg.packageName);
         int replaceFlags = rFlags | PackageManager.INSTALL_REPLACE_EXISTING;
         try {
             InstallParams rp = installFromRawResource("install.apk", rApk,
-                    rFlags, false,
+                    replaceFlags, false,
                     false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
             assertInstall(rp.pkg, replaceFlags, ip.pkg.installLocation);
         } catch (Exception e) {
@@ -1211,6 +1242,56 @@
         moveFromRawResource(PackageManager.INSTALL_EXTERNAL, PackageManager.MOVE_INTERNAL,
                 PackageManager.MOVE_SUCCEEDED);
     }
+
+    /*
+     * Test that an install error code is returned when media is unmounted
+     * and package installed on sdcard via package manager flag.
+     */
+    public void testInstallSdcardUnmount() {
+        boolean origState = getMediaState();
+        try {
+            // Unmount sdcard
+            assertTrue(unmountMedia());
+            // Try to install and make sure an error code is returned.
+            assertNull(installFromRawResource("install.apk", R.raw.install,
+                    PackageManager.INSTALL_EXTERNAL, false,
+                    true, PackageManager.INSTALL_FAILED_CONTAINER_ERROR,
+                    PackageInfo.INSTALL_LOCATION_AUTO));
+        } finally {
+            // Restore original media state
+            if (origState) {
+                mountMedia();
+            } else {
+                unmountMedia();
+            }
+        }
+    }
+
+    /*
+    * Unmount sdcard. Try installing an app with manifest option to install
+    * on sdcard. Make sure it gets installed on internal flash.
+    */
+   public void testInstallManifestSdcardUnmount() {
+       boolean origState = getMediaState();
+       try {
+           // Unmount sdcard
+           assertTrue(unmountMedia());
+           // Try to install and make sure an error code is returned.
+           assertNotNull(installFromRawResource("install.apk", R.raw.install_loc_sdcard,
+                   0, false,
+                   false, -1,
+                   PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY));
+       } finally {
+           // Restore original media state
+           if (origState) {
+               mountMedia();
+           } else {
+               unmountMedia();
+           }
+       }
+   }
+
+   /*---------- Recommended install location tests ----*/
     /*
      * TODO's
      * check version numbers for upgrades