Add conditions to check for updated system applications. Restrict them
to internal flash only even before we copy.

Return error codes when install flag options mismatch.
Some conditions for existings apps
 - install flags override existing location
 - explicity manifest option install location overrides previous location
 - if upgraded package's install location is unspecified or auto, fall
   back to recommended install policy which considers user setting as well.

Check for sdcard status before finding available size on sdcard
Add light weight parsing for manifest attributes including package name and
install location only

Change-Id: I5143dda87c88c595f564b317326c926d0ec3ceb8
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index c003355..0964425 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -151,7 +151,7 @@
      */
     public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2;
     /**
-     * The launch mode style requested by the activity.  From the
+     * The install location requested by the activity.  From the
      * {@link android.R.attr#installLocation} attribute, one of
      * {@link #INSTALL_LOCATION_AUTO},
      * {@link #INSTALL_LOCATION_INTERNAL_ONLY},
diff --git a/core/java/android/content/pm/PackageInfoLite.aidl b/core/java/android/content/pm/PackageInfoLite.aidl
new file mode 100755
index 0000000..2c80942
--- /dev/null
+++ b/core/java/android/content/pm/PackageInfoLite.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/WindowManager.aidl
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+package android.content.pm;
+
+parcelable PackageInfoLite;
diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
new file mode 100644
index 0000000..2f38ece
--- /dev/null
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -0,0 +1,63 @@
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Basic information about a package as specified in its manifest.
+ * Utility class used in PackageManager methods
+ * @hide
+ */
+public class PackageInfoLite implements Parcelable {
+    /**
+     * The name of this package.  From the <manifest> tag's "name"
+     * attribute.
+     */
+    public String packageName;
+
+    /**
+     * Specifies the recommended install location. Can be one of
+     * {@link #PackageHelper.RECOMMEND_INSTALL_INTERNAL} to install on internal storage
+     * {@link #PackageHelper.RECOMMEND_INSTALL_EXTERNAL} to install on external media
+     * {@link PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE} for storage errors
+     * {@link PackageHelper.RECOMMEND_FAILED_INVALID_APK} for parse errors.
+     */
+    public int recommendedInstallLocation;
+    public int installLocation;
+
+    public PackageInfoLite() {
+    }
+
+    public String toString() {
+        return "PackageInfoLite{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + " " + packageName + "}";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        dest.writeString(packageName);
+        dest.writeInt(recommendedInstallLocation);
+        dest.writeInt(installLocation);
+    }
+
+    public static final Parcelable.Creator<PackageInfoLite> CREATOR
+            = new Parcelable.Creator<PackageInfoLite>() {
+        public PackageInfoLite createFromParcel(Parcel source) {
+            return new PackageInfoLite(source);
+        }
+
+        public PackageInfoLite[] newArray(int size) {
+            return new PackageInfoLite[size];
+        }
+    };
+
+    private PackageInfoLite(Parcel source) {
+        packageName = source.readString();
+        recommendedInstallLocation = source.readInt();
+        installLocation = source.readInt();
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 7a0337cd..5da7fd1 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -136,7 +136,20 @@
             enabledRes = _enabledRes;
         }
     }
-    
+
+    /* Light weight package info.
+     * @hide
+     */
+    public static class PackageLite {
+        public String packageName;
+        public int installLocation;
+        public String mScanPath;
+        public PackageLite(String packageName, int installLocation) {
+            this.packageName = packageName;
+            this.installLocation = installLocation;
+        }
+    }
+
     private ParsePackageItemArgs mParseInstrumentationArgs;
     private ParseComponentArgs mParseActivityArgs;
     private ParseComponentArgs mParseActivityAliasArgs;
@@ -562,7 +575,14 @@
         return true;
     }
 
-    public static String parsePackageName(String packageFilePath, int flags) {
+    /*
+     * Utility method that retrieves just the package name and install
+     * location from the apk location at the given file path.
+     * @param packageFilePath file location of the apk
+     * @param flags Special parse flags
+     * @return PackageLite object with package information.
+     */
+    public static PackageLite parsePackageLite(String packageFilePath, int flags) {
         XmlResourceParser parser = null;
         AssetManager assmgr = null;
         try {
@@ -577,9 +597,9 @@
         }
         AttributeSet attrs = parser;
         String errors[] = new String[1];
-        String packageName = null;
+        PackageLite packageLite = null;
         try {
-            packageName = parsePackageName(parser, attrs, flags, errors);
+            packageLite = parsePackageLite(parser, attrs, flags, errors);
         } catch (IOException e) {
             Log.w(TAG, packageFilePath, e);
         } catch (XmlPullParserException e) {
@@ -588,11 +608,11 @@
             if (parser != null) parser.close();
             if (assmgr != null) assmgr.close();
         }
-        if (packageName == null) {
-            Log.e(TAG, "parsePackageName error: " + errors[0]);
+        if (packageLite == null) {
+            Log.e(TAG, "parsePackageLite error: " + errors[0]);
             return null;
         }
-        return packageName;
+        return packageLite;
     }
 
     private static String validateName(String name, boolean requiresSeparator) {
@@ -656,6 +676,49 @@
         return pkgName.intern();
     }
 
+    private static PackageLite parsePackageLite(XmlPullParser parser,
+            AttributeSet attrs, int flags, String[] outError)
+            throws IOException, XmlPullParserException {
+
+        int type;
+        while ((type=parser.next()) != parser.START_TAG
+                   && type != parser.END_DOCUMENT) {
+            ;
+        }
+
+        if (type != parser.START_TAG) {
+            outError[0] = "No start tag found";
+            return null;
+        }
+        if ((flags&PARSE_CHATTY) != 0 && Config.LOGV) Log.v(
+            TAG, "Root element name: '" + parser.getName() + "'");
+        if (!parser.getName().equals("manifest")) {
+            outError[0] = "No <manifest> tag";
+            return null;
+        }
+        String pkgName = attrs.getAttributeValue(null, "package");
+        if (pkgName == null || pkgName.length() == 0) {
+            outError[0] = "<manifest> does not specify package";
+            return null;
+        }
+        String nameError = validateName(pkgName, true);
+        if (nameError != null && !"android".equals(pkgName)) {
+            outError[0] = "<manifest> specifies bad package name \""
+                + pkgName + "\": " + nameError;
+            return null;
+        }
+        int installLocation = PackageInfo.INSTALL_LOCATION_AUTO;
+        for (int i = 0; i < attrs.getAttributeCount(); i++) {
+            String attr = attrs.getAttributeName(i);
+            if (attr.equals("installLocation")) {
+                installLocation = attrs.getAttributeIntValue(i,
+                        PackageInfo.INSTALL_LOCATION_AUTO);
+                break;
+            }
+        }
+        return new PackageLite(pkgName.intern(), installLocation);
+    }
+
     /**
      * Temporary.
      */
diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl
index c0e9587..fd1fd58 100755
--- a/core/java/com/android/internal/app/IMediaContainerService.aidl
+++ b/core/java/com/android/internal/app/IMediaContainerService.aidl
@@ -18,6 +18,7 @@
 
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
+import android.content.pm.PackageInfoLite;
 
 interface IMediaContainerService {
     String copyResourceToContainer(in Uri packageURI,
@@ -25,5 +26,5 @@
                 String key, String resFileName);
     boolean copyResource(in Uri packageURI,
                 in ParcelFileDescriptor outStream);
-    int getRecommendedInstallLocation(in Uri fileUri);
+    PackageInfoLite getMinimalPackageInfo(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 04a10b9..de6a175 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -36,6 +36,8 @@
     public static final int RECOMMEND_INSTALL_EXTERNAL = 2;
     public static final int RECOMMEND_FAILED_INSUFFICIENT_STORAGE = -1;
     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;
     private static final boolean localLOGV = true;
     private static final String TAG = "PackageHelper";
 
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index 02e1f07..4635f48 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -5,6 +5,7 @@
 import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.Package;
@@ -28,6 +29,7 @@
 import java.io.InputStream;
 
 import android.os.FileUtils;
+import android.os.storage.IMountService;
 import android.provider.Settings;
 
 /*
@@ -86,46 +88,51 @@
          * specified by file uri location.
          * @param fileUri the uri of resource to be copied. Should be a
          * file uri
-         * @return Returns
-         *  PackageHelper.RECOMMEND_INSTALL_INTERNAL to install on internal storage
-         *  PackageHelper.RECOMMEND_INSTALL_EXTERNAL to install on external media
-         *  PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE for storage errors
-         *  PackageHelper.RECOMMEND_FAILED_INVALID_APK for parse errors.
+         * @return Returns PackageInfoLite object containing
+         * the package info and recommended app location.
          */
-        public int getRecommendedInstallLocation(final Uri fileUri) {
+        public PackageInfoLite getMinimalPackageInfo(final Uri fileUri) {
+            PackageInfoLite ret = new PackageInfoLite();
             if (fileUri == null) {
                 Log.i(TAG, "Invalid package uri " + fileUri);
-                return PackageHelper.RECOMMEND_FAILED_INVALID_APK;
+                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
+                return ret;
             }
             String scheme = fileUri.getScheme();
             if (scheme != null && !scheme.equals("file")) {
                 Log.w(TAG, "Falling back to installing on internal storage only");
-                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                return ret;
             }
             String archiveFilePath = fileUri.getPath();
             PackageParser packageParser = new PackageParser(archiveFilePath);
             File sourceFile = new File(archiveFilePath);
             DisplayMetrics metrics = new DisplayMetrics();
             metrics.setToDefaults();
-            PackageParser.Package pkg = packageParser.parsePackage(sourceFile,
-                    archiveFilePath, metrics, 0);
+            PackageParser.PackageLite pkg = packageParser.parsePackageLite(
+                    archiveFilePath, 0);
+            ret.packageName = pkg.packageName;
+            ret.installLocation = pkg.installLocation;
             // Nuke the parser reference right away and force a gc
             Runtime.getRuntime().gc();
             packageParser = null;
             if (pkg == null) {
                 Log.w(TAG, "Failed to parse package");
-                return PackageHelper.RECOMMEND_FAILED_INVALID_APK;
+                ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK;
+                return ret;
             }
-            int loc = recommendAppInstallLocation(pkg);
+            ret.packageName = pkg.packageName;
+            int loc = recommendAppInstallLocation(pkg.installLocation, archiveFilePath);
             if (loc == PackageManager.INSTALL_EXTERNAL) {
-                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+                ret.recommendedInstallLocation =  PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
             } else if (loc == ERR_LOC) {
                 Log.i(TAG, "Failed to install insufficient storage");
-                return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
+                ret.recommendedInstallLocation =  PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
             } else {
                 // Implies install on internal storage.
-                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                ret.recommendedInstallLocation =  PackageHelper.RECOMMEND_INSTALL_INTERNAL;
             }
+            return ret;
         }
     };
 
@@ -171,62 +178,37 @@
         String codePath = packageURI.getPath();
         File codeFile = new File(codePath);
         String newCachePath = null;
-        final int CREATE_FAILED = 1;
-        final int COPY_FAILED = 2;
-        final int FINALIZE_FAILED = 3;
-        final int PASS = 4;
-        int errCode = CREATE_FAILED;
         // Create new container
         if ((newCachePath = PackageHelper.createSdDir(codeFile,
-                newCid, key, Process.myUid())) != null) {
-            if (localLOGV) Log.i(TAG, "Created container for " + newCid
-                    + " at path : " + newCachePath);
-            File resFile = new File(newCachePath, resFileName);
-            errCode = COPY_FAILED;
-            // Copy file from codePath
-            if (FileUtils.copyFile(new File(codePath), resFile)) {
-                if (localLOGV) Log.i(TAG, "Copied " + codePath + " to " + resFile);
-                errCode = FINALIZE_FAILED;
-                if (PackageHelper.finalizeSdDir(newCid)) {
-                    if (localLOGV) Log.i(TAG, "Finalized container " + newCid);
-                    errCode = PASS;
-                }
-            }
-        }
-        // Print error based on errCode
-        String errMsg = "";
-        switch (errCode) {
-            case CREATE_FAILED:
-                errMsg = "CREATE_FAILED";
-                break;
-            case COPY_FAILED:
-                errMsg = "COPY_FAILED";
-                if (localLOGV) Log.i(TAG, "Destroying " + newCid +
-                        " at path " + newCachePath + " after " + errMsg);
-                PackageHelper.destroySdDir(newCid);
-                break;
-            case FINALIZE_FAILED:
-                errMsg = "FINALIZE_FAILED";
-                if (localLOGV) Log.i(TAG, "Destroying " + newCid +
-                        " at path " + newCachePath + " after " + errMsg);
-                PackageHelper.destroySdDir(newCid);
-                break;
-            default:
-                errMsg = "PASS";
-                if (PackageHelper.isContainerMounted(newCid)) {
-                    if (localLOGV) Log.i(TAG, "Unmounting " + newCid +
-                            " at path " + newCachePath + " after " + errMsg);
-                    // Force a gc to avoid being killed.
-                    Runtime.getRuntime().gc();
-                    PackageHelper.unMountSdDir(newCid);
-                } else {
-                    if (localLOGV) Log.i(TAG, "Container " + newCid + " not mounted");
-                }
-                break;
-        }
-        if (errCode != PASS) {
+                newCid, key, Process.myUid())) == null) {
+            Log.e(TAG, "Failed to create container " + newCid);
             return null;
         }
+        if (localLOGV) Log.i(TAG, "Created container for " + newCid
+                + " at path : " + newCachePath);
+        File resFile = new File(newCachePath, resFileName);
+        if (!FileUtils.copyFile(new File(codePath), resFile)) {
+            Log.e(TAG, "Failed to copy " + codePath + " to " + resFile);
+            // Clean up container
+            PackageHelper.destroySdDir(newCid);
+            return null;
+        }
+        if (localLOGV) Log.i(TAG, "Copied " + codePath + " to " + resFile);
+        if (!PackageHelper.finalizeSdDir(newCid)) {
+            Log.e(TAG, "Failed to finalize " + newCid + " at path " + newCachePath);
+            // Clean up container
+            PackageHelper.destroySdDir(newCid);
+        }
+        if (localLOGV) Log.i(TAG, "Finalized container " + newCid);
+        if (PackageHelper.isContainerMounted(newCid)) {
+            if (localLOGV) Log.i(TAG, "Unmounting " + newCid +
+                    " at path " + newCachePath);
+            // Force a gc to avoid being killed.
+            Runtime.getRuntime().gc();
+            PackageHelper.unMountSdDir(newCid);
+        } else {
+            if (localLOGV) Log.i(TAG, "Container " + newCid + " not mounted");
+        }
         return newCachePath;
     }
 
@@ -307,29 +289,28 @@
     private static final long INSTALL_ON_SD_THRESHOLD = (1024 * 1024);
     private static final int ERR_LOC = -1;
 
-    public int recommendAppInstallLocation(Package pkg) {
+    private int recommendAppInstallLocation(int installLocation,
+            String archiveFilePath) {
         // Initial implementation:
         // Package size = code size + cache size + data size
         // If code size > 1 MB, install on SD card.
         // Else install on internal NAND flash, unless space on NAND is less than 10%
-
-        if (pkg == null) {
-            return ERR_LOC;
+        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();
         }
+        StatFs internalStats = new StatFs(Environment.getDataDirectory().getPath());
+        long totalInternalSize = (long)internalStats.getBlockCount() *
+                (long)internalStats.getBlockSize();
+        long availInternalSize = (long)internalStats.getAvailableBlocks() *
+                (long)internalStats.getBlockSize();
 
-        StatFs internalFlashStats = new StatFs(Environment.getDataDirectory().getPath());
-        StatFs sdcardStats = new StatFs(Environment.getExternalStorageDirectory().getPath());
+        double pctNandFree = (double)availInternalSize / (double)totalInternalSize;
 
-        long totalInternalFlashSize = (long)internalFlashStats.getBlockCount() *
-                (long)internalFlashStats.getBlockSize();
-        long availInternalFlashSize = (long)internalFlashStats.getAvailableBlocks() *
-                (long)internalFlashStats.getBlockSize();
-        long availSDSize = (long)sdcardStats.getAvailableBlocks() *
-                (long)sdcardStats.getBlockSize();
-
-        double pctNandFree = (double)availInternalFlashSize / (double)totalInternalFlashSize;
-
-        final String archiveFilePath = pkg.mScanPath;
         File apkFile = new File(archiveFilePath);
         long pkgLen = apkFile.length();
 
@@ -339,15 +320,15 @@
         // 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) < availInternalFlashSize);
+        boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalSize);
         boolean fitsOnSd = (reqInstallSize < availSDSize) && intThresholdOk &&
-                (reqInternalSize < availInternalFlashSize);
+                (reqInternalSize < availInternalSize);
         boolean fitsOnInt = intThresholdOk && intAvailOk;
 
         // Consider application flags preferences as well...
-        boolean installOnlyOnSd = (pkg.installLocation ==
+        boolean installOnlyOnSd = (installLocation ==
                 PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
-        boolean installOnlyInternal = (pkg.installLocation ==
+        boolean installOnlyInternal = (installLocation ==
                 PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
         if (installOnlyInternal) {
             // If set explicitly in manifest,
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 1ff0244..371fa37 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -51,6 +51,7 @@
 import android.content.pm.IPackageStatsObserver;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageStats;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -78,12 +79,14 @@
 import android.os.FileObserver;
 import android.os.FileUtils;
 import android.os.Handler;
+import android.os.StatFs;
 import android.os.storage.StorageResultCode;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.provider.Settings;
 import android.security.SystemKeyStore;
 import android.util.*;
 import android.view.Display;
@@ -4568,21 +4571,79 @@
             this.installerPackageName = installerPackageName;
         }
 
+        private int installLocationPolicy(PackageInfoLite pkgLite, int flags) {
+            String packageName = pkgLite.packageName;
+            int installLocation = pkgLite.installLocation;
+            boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;
+            synchronized (mPackages) {
+                PackageParser.Package pkg = mPackages.get(packageName);
+                if (pkg != null) {
+                    if ((flags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
+                        // Check for updated system application.
+                        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                            if (onSd) {
+                                Log.w(TAG, "Cannot install update to system app on sdcard");
+                                return PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION;
+                            }
+                            return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                        } else {
+                            // When replacing apps make sure we honour
+                            // the existing app location if not overwritten by other options
+                            boolean prevOnSd = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_ON_SDCARD) != 0;
+                            if (onSd) {
+                                // Install flag overrides everything.
+                                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+                            }
+                            // If current upgrade does not specify install location.
+                            if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+                                // Application explicitly specified internal.
+                                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                            } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
+                                // App explictly prefers external. Let policy decide
+                            } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
+                                // Prefer previous location
+                                return prevOnSd ? PackageHelper.RECOMMEND_INSTALL_EXTERNAL:
+                                    PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                            }
+                        }
+                    } else {
+                        // Invalid install. Return error code
+                        return PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS;
+                    }
+                }
+            }
+            // All the special cases have been taken care of.
+            // Return result based on recommended install location.
+            if (onSd) {
+                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+            }
+            return pkgLite.recommendedInstallLocation;
+        }
+
         public void handleStartCopy() throws RemoteException {
             int ret = PackageManager.INSTALL_SUCCEEDED;
+            boolean fwdLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
+            boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;
             // Dont need to invoke getInstallLocation for forward locked apps.
-            if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) {
-                flags &= ~PackageManager.INSTALL_EXTERNAL;
+            if (fwdLocked && onSd) {
+                Log.w(TAG, "Cannot install fwd locked apps on sdcard");
+                ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
             } else {
                 // Remote call to find out default install location
-                int loc = mContainerService.getRecommendedInstallLocation(packageURI);
+                PackageInfoLite pkgLite = mContainerService.getMinimalPackageInfo(packageURI);
+                int loc = installLocationPolicy(pkgLite, flags);
                 // Use install location to create InstallArgs and temporary
                 // install location
-                if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE){
+                if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION){
+                    ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+                } else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS){
+                    ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
+                } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE){
                     ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
                 } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
                     ret = PackageManager.INSTALL_FAILED_INVALID_APK;
                 } else {
+                    // Override install location with flags
                     if ((flags & PackageManager.INSTALL_EXTERNAL) == 0){
                         if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
                             // Set the flag to install on external media.
diff --git a/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java b/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java
index 50eca02..0b69020 100755
--- a/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java
+++ b/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java
@@ -932,11 +932,86 @@
                 0, true, false, -1, PackageInfo.INSTALL_LOCATION_AUTO);
     }
 
+    public void testManifestInstallLocationFwdLockedFlagSdcard() {
+        installFromRawResource("install.apk", R.raw.install_loc_unspecified,
+                PackageManager.INSTALL_FORWARD_LOCK |
+                PackageManager.INSTALL_EXTERNAL, true, true,
+                PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION,
+                PackageInfo.INSTALL_LOCATION_AUTO);
+    }
+
     public void testManifestInstallLocationFwdLockedSdcard() {
         installFromRawResource("install.apk", R.raw.install_loc_sdcard,
                 PackageManager.INSTALL_FORWARD_LOCK, true, false,
                 -1,
-                PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY);
+                PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+    }
+
+    private void replaceManifestLocation(int iFlags, int rFlags) {
+        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);
+        } catch (Exception e) {
+            failStr("Failed with exception : " + e);
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
+
+    public void testReplaceFlagInternalSdcard() {
+        replaceManifestLocation(0, PackageManager.INSTALL_EXTERNAL);
+    }
+
+    public void testReplaceFlagSdcardInternal() {
+        replaceManifestLocation(PackageManager.INSTALL_EXTERNAL, 0);
+    }
+
+    public void testManifestInstallLocationReplaceInternalSdcard() {
+        int iFlags = 0;
+        int iApk = R.raw.install_loc_unspecified;
+        int rFlags = 0;
+        int rApk = R.raw.install_loc_sdcard;
+        InstallParams ip = installFromRawResource("install.apk", iApk,
+                iFlags, false,
+                false, -1, PackageInfo.INSTALL_LOCATION_AUTO);
+        GenericReceiver receiver = new ReplaceReceiver(ip.pkg.packageName);
+        int replaceFlags = rFlags | PackageManager.INSTALL_REPLACE_EXISTING;
+        try {
+            InstallParams rp = installFromRawResource("install.apk", rApk,
+                    rFlags, false,
+                    false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+            assertInstall(rp.pkg, replaceFlags, rp.pkg.installLocation);
+        } catch (Exception e) {
+            failStr("Failed with exception : " + e);
+        } finally {
+            cleanUpInstall(ip);
+        }
+    }
+
+    public void testManifestInstallLocationReplaceSdcardInternal() {
+        int iFlags = 0;
+        int iApk = R.raw.install_loc_sdcard;
+        int rFlags = 0;
+        int rApk = R.raw.install_loc_unspecified;
+        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,
+                    false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL);
+            assertInstall(rp.pkg, replaceFlags, ip.pkg.installLocation);
+        } catch (Exception e) {
+            failStr("Failed with exception : " + e);
+        } finally {
+            cleanUpInstall(ip);
+        }
     }
 
     public void xxxtestClearAllSecureContainers() {