Merge "Installing packages to expanded storage."
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 51152af..23a8629 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -30,6 +30,7 @@
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageInstaller;
 import android.content.pm.IPackageManager;
+import android.content.pm.IPackageMoveObserver;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
@@ -55,12 +56,12 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import libcore.io.IoUtils;
+
 import com.android.internal.content.PackageHelper;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.SizedInputStream;
 
-import libcore.io.IoUtils;
-
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
@@ -234,6 +235,10 @@
             return runForceDexOpt();
         }
 
+        if ("move".equals(op)) {
+            return runMove();
+        }
+
         try {
             if (args.length == 1) {
                 if (args[0].equalsIgnoreCase("-l")) {
@@ -1278,6 +1283,51 @@
         }
     }
 
+    class LocalPackageMoveObserver extends IPackageMoveObserver.Stub {
+        boolean finished;
+        int returnCode;
+
+        @Override
+        public void packageMoved(String packageName, int returnCode) throws RemoteException {
+            synchronized (this) {
+                this.finished = true;
+                this.returnCode = returnCode;
+                notifyAll();
+            }
+        }
+    }
+
+    public int runMove() {
+        final String packageName = nextArg();
+        String volumeUuid = nextArg();
+        if ("internal".equals(volumeUuid)) {
+            volumeUuid = null;
+        }
+
+        final LocalPackageMoveObserver obs = new LocalPackageMoveObserver();
+        try {
+            mPm.movePackageAndData(packageName, volumeUuid, obs);
+
+            synchronized (obs) {
+                while (!obs.finished) {
+                    try {
+                        obs.wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+                if (obs.returnCode == PackageManager.MOVE_SUCCEEDED) {
+                    System.out.println("Success");
+                    return 0;
+                } else {
+                    System.err.println("Failure [" + obs.returnCode + "]");
+                    return 1;
+                }
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
     private int runUninstall() throws RemoteException {
         int flags = 0;
         int userId = UserHandle.USER_ALL;
@@ -1820,6 +1870,7 @@
         System.err.println("       pm install-abandon SESSION_ID");
         System.err.println("       pm uninstall [-k] [--user USER_ID] PACKAGE");
         System.err.println("       pm set-installer PACKAGE INSTALLER");
+        System.err.println("       pm move PACKAGE [internal|UUID]");
         System.err.println("       pm clear [--user USER_ID] PACKAGE");
         System.err.println("       pm enable [--user USER_ID] PACKAGE_OR_COMPONENT");
         System.err.println("       pm disable [--user USER_ID] PACKAGE_OR_COMPONENT");
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 6ec5457..10dcd85 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -62,19 +62,19 @@
 import android.net.Uri;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.Display;
-import android.os.SystemProperties;
+
+import dalvik.system.VMRuntime;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.UserIcons;
 
-import dalvik.system.VMRuntime;
-
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
@@ -1363,7 +1363,17 @@
         try {
             mPM.movePackage(packageName, observer, flags);
         } catch (RemoteException e) {
-            // Should never happen!
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public void movePackageAndData(String packageName, String volumeUuid,
+            IPackageMoveObserver observer) {
+        try {
+            mPM.movePackageAndData(packageName, volumeUuid, observer);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 8567c01..5bdb7bb 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -459,6 +459,8 @@
     public int largestWidthLimitDp = 0;
 
     /** {@hide} */
+    public String volumeUuid;
+    /** {@hide} */
     public String scanSourceDir;
     /** {@hide} */
     public String scanPublicSourceDir;
@@ -726,6 +728,7 @@
         requiresSmallestWidthDp = orig.requiresSmallestWidthDp;
         compatibleWidthLimitDp = orig.compatibleWidthLimitDp;
         largestWidthLimitDp = orig.largestWidthLimitDp;
+        volumeUuid = orig.volumeUuid;
         scanSourceDir = orig.scanSourceDir;
         scanPublicSourceDir = orig.scanPublicSourceDir;
         sourceDir = orig.sourceDir;
@@ -778,6 +781,7 @@
         dest.writeInt(requiresSmallestWidthDp);
         dest.writeInt(compatibleWidthLimitDp);
         dest.writeInt(largestWidthLimitDp);
+        dest.writeString(volumeUuid);
         dest.writeString(scanSourceDir);
         dest.writeString(scanPublicSourceDir);
         dest.writeString(sourceDir);
@@ -829,6 +833,7 @@
         requiresSmallestWidthDp = source.readInt();
         compatibleWidthLimitDp = source.readInt();
         largestWidthLimitDp = source.readInt();
+        volumeUuid = source.readString();
         scanSourceDir = source.readString();
         scanPublicSourceDir = source.readString();
         sourceDir = source.readString();
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 649bb47..eed0df5 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -432,7 +432,8 @@
     PackageCleanItem nextPackageToClean(in PackageCleanItem lastPackage);
 
     void movePackage(String packageName, IPackageMoveObserver observer, int flags);
-    
+    void movePackageAndData(String packageName, String volumeUuid, IPackageMoveObserver observer);
+
     boolean addPermissionAsync(in PermissionInfo info);
 
     boolean setInstallLocation(int loc);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 80efd0b..15a7bf9 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -887,6 +887,8 @@
         public Uri referrerUri;
         /** {@hide} */
         public String abiOverride;
+        /** {@hide} */
+        public String volumeUuid;
 
         /**
          * Construct parameters for a new package install session.
@@ -911,6 +913,7 @@
             originatingUri = source.readParcelable(null);
             referrerUri = source.readParcelable(null);
             abiOverride = source.readString();
+            volumeUuid = source.readString();
         }
 
         /**
@@ -1008,6 +1011,7 @@
             pw.printPair("originatingUri", originatingUri);
             pw.printPair("referrerUri", referrerUri);
             pw.printPair("abiOverride", abiOverride);
+            pw.printPair("volumeUuid", volumeUuid);
             pw.println();
         }
 
@@ -1028,6 +1032,7 @@
             dest.writeParcelable(originatingUri, flags);
             dest.writeParcelable(referrerUri, flags);
             dest.writeString(abiOverride);
+            dest.writeString(volumeUuid);
         }
 
         public static final Parcelable.Creator<SessionParams>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 46d6ffb3..4c99d09 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -43,7 +43,9 @@
 import android.os.Environment;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.storage.VolumeInfo;
 import android.util.AndroidException;
+
 import com.android.internal.util.ArrayUtils;
 
 import java.io.File;
@@ -339,15 +341,17 @@
     public static final int INSTALL_ALLOW_TEST = 0x00000004;
 
     /**
-     * Flag parameter for {@link #installPackage} to indicate that this
-     * package has to be installed on the sdcard.
+     * Flag parameter for {@link #installPackage} to indicate that this package
+     * must be installed to an ASEC on a {@link VolumeInfo#TYPE_PUBLIC}.
+     *
      * @hide
      */
     public static final int INSTALL_EXTERNAL = 0x00000008;
 
     /**
      * Flag parameter for {@link #installPackage} to indicate that this package
-     * has to be installed on the sdcard.
+     * must be installed to internal storage.
+     *
      * @hide
      */
     public static final int INSTALL_INTERNAL = 0x00000010;
@@ -4099,8 +4103,12 @@
      *
      * @hide
      */
-    public abstract void movePackage(
-            String packageName, IPackageMoveObserver observer, int flags);
+    @Deprecated
+    public abstract void movePackage(String packageName, IPackageMoveObserver observer, int flags);
+
+    /** {@hide} */
+    public abstract void movePackageAndData(String packageName, String volumeUuid,
+            IPackageMoveObserver observer);
 
     /**
      * Returns the device identity that verifiers can use to associate their scheme to a particular
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index f4ad434..c1e6a4d 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -604,7 +604,7 @@
     public final static int PARSE_MUST_BE_APK = 1<<2;
     public final static int PARSE_IGNORE_PROCESSES = 1<<3;
     public final static int PARSE_FORWARD_LOCK = 1<<4;
-    public final static int PARSE_ON_SDCARD = 1<<5;
+    public final static int PARSE_EXTERNAL_STORAGE = 1<<5;
     public final static int PARSE_IS_SYSTEM_DIR = 1<<6;
     public final static int PARSE_IS_PRIVILEGED = 1<<7;
     public final static int PARSE_COLLECT_CERTIFICATES = 1<<8;
@@ -1408,7 +1408,7 @@
         }
 
         /* Set the global "on SD card" flag */
-        if ((flags & PARSE_ON_SDCARD) != 0) {
+        if ((flags & PARSE_EXTERNAL_STORAGE) != 0) {
             pkg.applicationInfo.flags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE;
         }
 
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 486f9b4..5a5d8b7 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -43,6 +43,7 @@
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -231,8 +232,7 @@
         mLooper = looper;
         mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
         if (mMountService == null) {
-            Log.e(TAG, "Unable to connect to mount service! - is it running yet?");
-            return;
+            throw new IllegalStateException("Failed to find running mount service");
         }
     }
 
@@ -442,6 +442,42 @@
     }
 
     /** {@hide} */
+    public @Nullable DiskInfo findDiskById(String id) {
+        Preconditions.checkNotNull(id);
+        // TODO; go directly to service to make this faster
+        for (DiskInfo disk : getDisks()) {
+            if (Objects.equals(disk.id, id)) {
+                return disk;
+            }
+        }
+        return null;
+    }
+
+    /** {@hide} */
+    public @Nullable VolumeInfo findVolumeById(String id) {
+        Preconditions.checkNotNull(id);
+        // TODO; go directly to service to make this faster
+        for (VolumeInfo vol : getVolumes()) {
+            if (Objects.equals(vol.id, id)) {
+                return vol;
+            }
+        }
+        return null;
+    }
+
+    /** {@hide} */
+    public @Nullable VolumeInfo findVolumeByUuid(String fsUuid) {
+        Preconditions.checkNotNull(fsUuid);
+        // TODO; go directly to service to make this faster
+        for (VolumeInfo vol : getVolumes()) {
+            if (Objects.equals(vol.fsUuid, fsUuid)) {
+                return vol;
+            }
+        }
+        return null;
+    }
+
+    /** {@hide} */
     public @NonNull List<VolumeInfo> getVolumes() {
         try {
             return Arrays.asList(mMountService.getVolumes());
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index c8c2f65..2dc0361 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -44,6 +44,7 @@
  * @hide
  */
 public class VolumeInfo implements Parcelable {
+    /** Real volume representing internal emulated storage */
     public static final String ID_EMULATED_INTERNAL = "emulated";
 
     public static final int TYPE_PUBLIC = 0;
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index 7bdb4be..255f1fd 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -25,15 +25,16 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageParser.PackageLite;
 import android.os.Environment;
-import android.os.Environment.UserEnvironment;
 import android.os.FileUtils;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.UserHandle;
 import android.os.storage.IMountService;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageResultCode;
+import android.os.storage.StorageVolume;
+import android.os.storage.VolumeInfo;
+import android.util.ArraySet;
 import android.util.Log;
 
 import libcore.io.IoUtils;
@@ -335,6 +336,94 @@
 
     /**
      * Given a requested {@link PackageInfo#installLocation} and calculated
+     * install size, pick the actual volume to install the app. Only considers
+     * internal and private volumes, and prefers to keep an existing package on
+     * its current volume.
+     *
+     * @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 {
+        // TODO: handle existing apps installed in ASEC; currently assumes
+        // they'll end up back on internal storage
+        ApplicationInfo existingInfo = null;
+        try {
+            existingInfo = context.getPackageManager().getApplicationInfo(packageName,
+                    PackageManager.GET_UNINSTALLED_PACKAGES);
+        } catch (NameNotFoundException ignored) {
+        }
+
+        final StorageManager storageManager = context.getSystemService(StorageManager.class);
+        final boolean fitsOnInternal = fitsOnInternal(context, sizeBytes);
+
+        final ArraySet<String> allCandidates = new ArraySet<>();
+        VolumeInfo bestCandidate = null;
+        long bestCandidateAvailBytes = Long.MIN_VALUE;
+        for (VolumeInfo vol : storageManager.getVolumes()) {
+            if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.state == VolumeInfo.STATE_MOUNTED) {
+                final long availBytes = storageManager.getStorageBytesUntilLow(new File(vol.path));
+                if (availBytes >= sizeBytes) {
+                    allCandidates.add(vol.fsUuid);
+                }
+                if (availBytes >= bestCandidateAvailBytes) {
+                    bestCandidate = vol;
+                    bestCandidateAvailBytes = availBytes;
+                }
+            }
+        }
+
+        // System apps always forced to internal storage
+        if (existingInfo != null && existingInfo.isSystemApp()) {
+            installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+        }
+
+        // If app expresses strong desire for internal space, honor it
+        if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+            if (fitsOnInternal) {
+                return null;
+            } else {
+                throw new IOException("Requested internal only, but not enough space");
+            }
+        }
+
+        // If app already exists somewhere, prefer to stay on that volume
+        if (existingInfo != null) {
+            if (existingInfo.volumeUuid == null && fitsOnInternal) {
+                return null;
+            }
+            if (allCandidates.contains(existingInfo.volumeUuid)) {
+                return existingInfo.volumeUuid;
+            }
+        }
+
+        // We're left with either preferring external or auto, so just pick
+        // volume with most space
+        if (bestCandidate != null) {
+            return bestCandidate.fsUuid;
+        } else if (fitsOnInternal) {
+            return null;
+        } else {
+            throw new IOException("No special requests, but no room anywhere");
+        }
+    }
+
+    public static boolean fitsOnInternal(Context context, long sizeBytes) {
+        final StorageManager storage = context.getSystemService(StorageManager.class);
+        final File target = Environment.getDataDirectory();
+        return (sizeBytes <= storage.getStorageBytesUntilLow(target));
+    }
+
+    public static boolean fitsOnExternal(Context context, long sizeBytes) {
+        final StorageManager storage = context.getSystemService(StorageManager.class);
+        final StorageVolume primary = storage.getPrimaryVolume();
+        return (sizeBytes > 0) && !primary.isEmulated()
+                && Environment.MEDIA_MOUNTED.equals(primary.getState())
+                && sizeBytes <= storage.getStorageBytesUntilLow(primary.getPathFile());
+    }
+
+    /**
+     * 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,
@@ -363,6 +452,7 @@
         } else if (installLocation == PackageInfo.INSTALL_LOCATION_AUTO) {
             // When app is already installed, prefer same medium
             if (existingInfo != null) {
+                // TODO: distinguish if this is external ASEC
                 if ((existingInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
                     prefer = RECOMMEND_INSTALL_EXTERNAL;
                 } else {
@@ -377,30 +467,21 @@
             checkBoth = false;
         }
 
-        final boolean emulated = Environment.isExternalStorageEmulated();
-        final StorageManager storage = StorageManager.from(context);
-
         boolean fitsOnInternal = false;
         if (checkBoth || prefer == RECOMMEND_INSTALL_INTERNAL) {
-            final File target = Environment.getDataDirectory();
-            fitsOnInternal = (sizeBytes <= storage.getStorageBytesUntilLow(target));
+            fitsOnInternal = fitsOnInternal(context, sizeBytes);
         }
 
         boolean fitsOnExternal = false;
-        if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL)) {
-            final File target = new UserEnvironment(UserHandle.USER_OWNER)
-                    .getExternalStorageDirectory();
-            // External is only an option when size is known
-            if (sizeBytes > 0) {
-                fitsOnExternal = (sizeBytes <= storage.getStorageBytesUntilLow(target));
-            }
+        if (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL) {
+            fitsOnExternal = fitsOnExternal(context, sizeBytes);
         }
 
         if (prefer == RECOMMEND_INSTALL_INTERNAL) {
             if (fitsOnInternal) {
                 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
             }
-        } else if (!emulated && prefer == RECOMMEND_INSTALL_EXTERNAL) {
+        } else if (prefer == RECOMMEND_INSTALL_EXTERNAL) {
             if (fitsOnExternal) {
                 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
             }
@@ -409,22 +490,12 @@
         if (checkBoth) {
             if (fitsOnInternal) {
                 return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
-            } else if (!emulated && fitsOnExternal) {
+            } else if (fitsOnExternal) {
                 return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
             }
         }
 
-        /*
-         * If they requested to be on the external media by default, return that
-         * the media was unavailable. Otherwise, indicate there was insufficient
-         * storage space available.
-         */
-        if (!emulated && (checkBoth || prefer == RECOMMEND_INSTALL_EXTERNAL)
-                && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
-            return PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE;
-        } else {
-            return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
-        }
+        return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
     }
 
     public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 456f1fb..a341c95 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -833,6 +833,9 @@
             vol.userId = UserHandle.USER_OWNER;
             mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
 
+        } else if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+            mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
+
         } else {
             Slog.d(TAG, "Skipping automatic mounting of " + vol);
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 95d7a52..aafb7a9 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -63,6 +63,8 @@
 import android.os.SELinux;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.text.TextUtils;
@@ -75,6 +77,8 @@
 import android.util.SparseBooleanArray;
 import android.util.Xml;
 
+import libcore.io.IoUtils;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.PackageHelper;
 import com.android.internal.util.FastXmlSerializer;
@@ -82,8 +86,6 @@
 import com.android.server.IoThread;
 import com.google.android.collect.Sets;
 
-import libcore.io.IoUtils;
-
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -131,6 +133,7 @@
     private static final String ATTR_ORIGINATING_URI = "originatingUri";
     private static final String ATTR_REFERRER_URI = "referrerUri";
     private static final String ATTR_ABI_OVERRIDE = "abiOverride";
+    private static final String ATTR_VOLUME_UUID = "volumeUuid";
 
     /** Automatically destroy sessions older than this */
     private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS;
@@ -141,9 +144,10 @@
 
     private final Context mContext;
     private final PackageManagerService mPm;
-    private final AppOpsManager mAppOps;
 
-    private final File mStagingDir;
+    private AppOpsManager mAppOps;
+    private StorageManager mStorage;
+
     private final HandlerThread mInstallThread;
     private final Handler mInstallHandler;
 
@@ -186,12 +190,9 @@
         }
     };
 
-    public PackageInstallerService(Context context, PackageManagerService pm, File stagingDir) {
+    public PackageInstallerService(Context context, PackageManagerService pm) {
         mContext = context;
         mPm = pm;
-        mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-
-        mStagingDir = stagingDir;
 
         mInstallThread = new HandlerThread(TAG);
         mInstallThread.start();
@@ -208,8 +209,9 @@
         synchronized (mSessions) {
             readSessionsLocked();
 
+            final File internalStagingDir = buildInternalStagingDir();
             final ArraySet<File> unclaimedStages = Sets.newArraySet(
-                    mStagingDir.listFiles(sStageFilter));
+                    internalStagingDir.listFiles(sStageFilter));
             final ArraySet<File> unclaimedIcons = Sets.newArraySet(
                     mSessionsDir.listFiles());
 
@@ -238,6 +240,11 @@
         }
     }
 
+    public void systemReady() {
+        mAppOps = mContext.getSystemService(AppOpsManager.class);
+        mStorage = mContext.getSystemService(StorageManager.class);
+    }
+
     public void onSecureContainersAvailable() {
         synchronized (mSessions) {
             final ArraySet<String> unclaimed = new ArraySet<>();
@@ -275,13 +282,13 @@
     }
 
     @Deprecated
-    public File allocateInternalStageDirLegacy() throws IOException {
+    public File allocateStageDirLegacy(String volumeUuid) throws IOException {
         synchronized (mSessions) {
             try {
                 final int sessionId = allocateSessionIdLocked();
                 mLegacySessions.put(sessionId, true);
-                final File stageDir = buildInternalStageDir(sessionId);
-                prepareInternalStageDir(stageDir);
+                final File stageDir = buildStageDir(volumeUuid, sessionId);
+                prepareStageDir(stageDir);
                 return stageDir;
             } catch (IllegalStateException e) {
                 throw new IOException(e);
@@ -322,11 +329,6 @@
                             Slog.w(TAG, "Abandoning old session first created at "
                                     + session.createdMillis);
                             valid = false;
-                        } else if (session.stageDir != null
-                                && !session.stageDir.exists()) {
-                            Slog.w(TAG, "Abandoning internal session with missing stage "
-                                    + session.stageDir);
-                            valid = false;
                         } else {
                             valid = true;
                         }
@@ -378,6 +380,7 @@
         params.originatingUri = readUriAttribute(in, ATTR_ORIGINATING_URI);
         params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI);
         params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE);
+        params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
 
         final File appIconFile = buildAppIconFile(sessionId);
         if (appIconFile.exists()) {
@@ -448,6 +451,7 @@
         writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri);
         writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri);
         writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
+        writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
 
         // Persist app icon if changed since last written
         final File appIconFile = buildAppIconFile(session.sessionId);
@@ -528,28 +532,40 @@
             }
         }
 
-        if (params.mode == SessionParams.MODE_FULL_INSTALL
-                || params.mode == SessionParams.MODE_INHERIT_EXISTING) {
+        switch (params.mode) {
+            case SessionParams.MODE_FULL_INSTALL:
+            case SessionParams.MODE_INHERIT_EXISTING:
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid install mode: " + params.mode);
+        }
+
+        // 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)) {
+                throw new IOException("No suitable internal storage available");
+            }
+
+        } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
+            if (!PackageHelper.fitsOnExternal(mContext, params.sizeBytes)) {
+                throw new IOException("No suitable external storage available");
+            }
+
+        } else {
+            // For now, installs to adopted media are treated as internal from
+            // an install flag point-of-view.
+            params.setInstallFlagsInternal();
+
             // Resolve best location for install, based on combination of
             // requested install flags, delta size, and manifest settings.
             final long ident = Binder.clearCallingIdentity();
             try {
-                final int resolved = PackageHelper.resolveInstallLocation(mContext,
-                        params.appPackageName, params.installLocation, params.sizeBytes,
-                        params.installFlags);
-
-                if (resolved == PackageHelper.RECOMMEND_INSTALL_INTERNAL) {
-                    params.setInstallFlagsInternal();
-                } else if (resolved == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
-                    params.setInstallFlagsExternal();
-                } else {
-                    throw new IOException("No storage with enough free space; res=" + resolved);
-                }
+                params.volumeUuid = PackageHelper.resolveInstallVolume(mContext,
+                        params.appPackageName, params.installLocation, params.sizeBytes);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
-        } else {
-            throw new IllegalArgumentException("Invalid install mode: " + params.mode);
         }
 
         final int sessionId;
@@ -574,7 +590,7 @@
             File stageDir = null;
             String stageCid = null;
             if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
-                stageDir = buildInternalStageDir(sessionId);
+                stageDir = buildStageDir(params.volumeUuid, sessionId);
             } else {
                 stageCid = buildExternalStageCid(sessionId);
             }
@@ -673,11 +689,30 @@
         throw new IllegalStateException("Failed to allocate session ID");
     }
 
-    private File buildInternalStageDir(int sessionId) {
-        return new File(mStagingDir, "vmdl" + sessionId + ".tmp");
+    private File buildInternalStagingDir() {
+        return new File(Environment.getDataDirectory(), "app");
     }
 
-    static void prepareInternalStageDir(File stageDir) throws IOException {
+    private File buildStagingDir(String volumeUuid) throws FileNotFoundException {
+        if (volumeUuid == null) {
+            return buildInternalStagingDir();
+        } else {
+            final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid);
+            if (vol != null && vol.type == VolumeInfo.TYPE_PRIVATE
+                    && vol.state == VolumeInfo.STATE_MOUNTED) {
+                return new File(vol.path, "app");
+            } else {
+                throw new FileNotFoundException("Failed to find volume for UUID " + volumeUuid);
+            }
+        }
+    }
+
+    private File buildStageDir(String volumeUuid, int sessionId) throws FileNotFoundException {
+        final File stagingDir = buildStagingDir(volumeUuid);
+        return new File(stagingDir, "vmdl" + sessionId + ".tmp");
+    }
+
+    static void prepareStageDir(File stageDir) throws IOException {
         if (stageDir.exists()) {
             throw new IOException("Session dir already exists: " + stageDir);
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index cc1b3ad..09e990c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -25,7 +25,7 @@
 import static android.system.OsConstants.O_RDONLY;
 import static android.system.OsConstants.O_WRONLY;
 import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid;
-import static com.android.server.pm.PackageInstallerService.prepareInternalStageDir;
+import static com.android.server.pm.PackageInstallerService.prepareStageDir;
 
 import android.content.Context;
 import android.content.Intent;
@@ -61,6 +61,9 @@
 import android.util.MathUtils;
 import android.util.Slog;
 
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.content.PackageHelper;
@@ -69,9 +72,6 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapter;
 
-import libcore.io.IoUtils;
-import libcore.io.Libcore;
-
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -892,7 +892,7 @@
         synchronized (mLock) {
             if (!mPrepared) {
                 if (stageDir != null) {
-                    prepareInternalStageDir(stageDir);
+                    prepareStageDir(stageDir);
                 } else if (stageCid != null) {
                     prepareExternalStageCid(stageCid, params.sizeBytes);
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2ff1718..84dc748 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -43,11 +43,17 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_USER_RESTRICTED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
 import static android.content.pm.PackageManager.INSTALL_FORWARD_LOCK;
+import static android.content.pm.PackageManager.INSTALL_INTERNAL;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
 import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
 import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+import static android.content.pm.PackageManager.MOVE_EXTERNAL_MEDIA;
+import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST;
+import static android.content.pm.PackageManager.MOVE_FAILED_OPERATION_PENDING;
+import static android.content.pm.PackageManager.MOVE_FAILED_SYSTEM_PACKAGE;
+import static android.content.pm.PackageManager.MOVE_INTERNAL;
 import static android.content.pm.PackageParser.isApkFile;
 import static android.os.Process.PACKAGE_INFO_GID;
 import static android.os.Process.SYSTEM_UID;
@@ -65,31 +71,7 @@
 import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
 
 import android.Manifest;
-import android.content.pm.IntentFilterVerificationInfo;
-import android.util.ArrayMap;
-
-import com.android.internal.R;
-import com.android.internal.app.IMediaContainerService;
-import com.android.internal.app.ResolverActivity;
-import com.android.internal.content.NativeLibraryHelper;
-import com.android.internal.content.PackageHelper;
-import com.android.internal.os.IParcelFileDescriptorFactory;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.EventLogTags;
-import com.android.server.IntentResolver;
-import com.android.server.LocalServices;
-import com.android.server.ServiceThread;
-import com.android.server.SystemConfig;
-import com.android.server.Watchdog;
-import com.android.server.pm.Settings.DatabaseVersion;
-import com.android.server.storage.DeviceStorageMonitorInternal;
-
 import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlSerializer;
-
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.AppGlobals;
@@ -119,6 +101,7 @@
 import android.content.pm.IPackageMoveObserver;
 import android.content.pm.IPackageStatsObserver;
 import android.content.pm.InstrumentationInfo;
+import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.KeySet;
 import android.content.pm.ManifestDigest;
 import android.content.pm.PackageCleanItem;
@@ -127,10 +110,10 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
+import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.ActivityIntentInfo;
 import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.PackageParser;
 import android.content.pm.PackageStats;
 import android.content.pm.PackageUserState;
 import android.content.pm.ParceledListSlice;
@@ -150,13 +133,9 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Debug;
 import android.os.Environment;
 import android.os.Environment.UserEnvironment;
-import android.os.storage.IMountService;
-import android.os.storage.StorageEventListener;
-import android.os.storage.StorageManager;
-import android.os.storage.VolumeInfo;
-import android.os.Debug;
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.IBinder;
@@ -172,6 +151,10 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.storage.IMountService;
+import android.os.storage.StorageEventListener;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
 import android.security.KeyStore;
 import android.security.SystemKeyStore;
 import android.system.ErrnoException;
@@ -179,6 +162,7 @@
 import android.system.StructStat;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.DisplayMetrics;
@@ -193,6 +177,33 @@
 import android.util.Xml;
 import android.view.Display;
 
+import dalvik.system.DexFile;
+import dalvik.system.VMRuntime;
+
+import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
+
+import com.android.internal.R;
+import com.android.internal.app.IMediaContainerService;
+import com.android.internal.app.ResolverActivity;
+import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.content.PackageHelper;
+import com.android.internal.os.IParcelFileDescriptorFactory;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.EventLogTags;
+import com.android.server.IntentResolver;
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
+import com.android.server.SystemConfig;
+import com.android.server.Watchdog;
+import com.android.server.pm.Settings.DatabaseVersion;
+import com.android.server.storage.DeviceStorageMonitorInternal;
+
+import org.xmlpull.v1.XmlSerializer;
+
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.BufferedReader;
@@ -227,12 +238,6 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 
-import dalvik.system.DexFile;
-import dalvik.system.VMRuntime;
-
-import libcore.io.IoUtils;
-import libcore.util.EmptyArray;
-
 /**
  * Keep track of all those .apks everywhere.
  * 
@@ -1539,9 +1544,14 @@
     private StorageEventListener mStorageListener = new StorageEventListener() {
         @Override
         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
-            Slog.v(TAG, vol.toString());
+            if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+                if (vol.state == VolumeInfo.STATE_MOUNTED) {
+                    loadPrivatePackages(vol);
+                } else if (vol.state == VolumeInfo.STATE_UNMOUNTING) {
+                    unloadPrivatePackages(vol);
+                }
+            }
 
-            // TODO: when private volume shows up, look for packages there too
             if (vol.isPrimary() && vol.type == VolumeInfo.TYPE_PUBLIC) {
                 if (vol.state == VolumeInfo.STATE_MOUNTED) {
                     updateExternalMediaStatus(true, false);
@@ -2160,7 +2170,7 @@
 
             mRequiredVerifierPackage = getRequiredVerifierLPr();
 
-            mInstallerService = new PackageInstallerService(context, this, mAppInstallDir);
+            mInstallerService = new PackageInstallerService(context, this);
 
             mIntentFilterVerifierComponent = getIntentFilterVerifierComponentNameLPr();
             mIntentFilterVerifier = new IntentVerifierProxy(mContext,
@@ -8476,8 +8486,8 @@
     public void installPackage(String originPath, IPackageInstallObserver2 observer,
             int installFlags, String installerPackageName, VerificationParams verificationParams,
             String packageAbiOverride) {
-        installPackageAsUser(originPath, observer, installFlags, installerPackageName, verificationParams,
-                packageAbiOverride, UserHandle.getCallingUserId());
+        installPackageAsUser(originPath, observer, installFlags, installerPackageName,
+                verificationParams, packageAbiOverride, UserHandle.getCallingUserId());
     }
 
     @Override
@@ -8524,7 +8534,7 @@
 
         final Message msg = mHandler.obtainMessage(INIT_COPY);
         msg.obj = new InstallParams(origin, observer, installFlags,
-                installerPackageName, verificationParams, user, packageAbiOverride);
+                installerPackageName, null, verificationParams, user, packageAbiOverride);
         mHandler.sendMessage(msg);
     }
 
@@ -8543,7 +8553,7 @@
 
         final Message msg = mHandler.obtainMessage(INIT_COPY);
         msg.obj = new InstallParams(origin, observer, params.installFlags,
-                installerPackageName, verifParams, user, params.abiOverride);
+                installerPackageName, params.volumeUuid, verifParams, user, params.abiOverride);
         mHandler.sendMessage(msg);
     }
 
@@ -9345,19 +9355,21 @@
         final IPackageInstallObserver2 observer;
         int installFlags;
         final String installerPackageName;
+        final String volumeUuid;
         final VerificationParams verificationParams;
         private InstallArgs mArgs;
         private int mRet;
         final String packageAbiOverride;
 
         InstallParams(OriginInfo origin, IPackageInstallObserver2 observer, int installFlags,
-                String installerPackageName, VerificationParams verificationParams, UserHandle user,
-                String packageAbiOverride) {
+                String installerPackageName, String volumeUuid,
+                VerificationParams verificationParams, UserHandle user, String packageAbiOverride) {
             super(user);
             this.origin = origin;
             this.observer = observer;
             this.installFlags = installFlags;
             this.installerPackageName = installerPackageName;
+            this.volumeUuid = volumeUuid;
             this.verificationParams = verificationParams;
             this.packageAbiOverride = packageAbiOverride;
         }
@@ -9711,7 +9723,7 @@
      * @param installFlags package installation flags
      * @return true if should be installed on external storage
      */
-    private static boolean installOnSd(int installFlags) {
+    private static boolean installOnExternalAsec(int installFlags) {
         if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
             return false;
         }
@@ -9732,7 +9744,7 @@
     }
 
     private InstallArgs createInstallArgs(InstallParams params) {
-        if (installOnSd(params.installFlags) || params.isForwardLocked()) {
+        if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) {
             return new AsecInstallArgs(params);
         } else {
             return new FileInstallArgs(params);
@@ -9746,7 +9758,7 @@
     private InstallArgs createInstallArgsForExisting(int installFlags, String codePath,
             String resourcePath, String nativeLibraryRoot, String[] instructionSets) {
         final boolean isInAsec;
-        if (installOnSd(installFlags)) {
+        if (installOnExternalAsec(installFlags)) {
             /* Apps on SD card are always in ASEC containers. */
             isInAsec = true;
         } else if (installForwardLocked(installFlags)
@@ -9762,7 +9774,7 @@
 
         if (isInAsec) {
             return new AsecInstallArgs(codePath, instructionSets,
-                    installOnSd(installFlags), installForwardLocked(installFlags));
+                    installOnExternalAsec(installFlags), installForwardLocked(installFlags));
         } else {
             return new FileInstallArgs(codePath, resourcePath, nativeLibraryRoot,
                     instructionSets);
@@ -9777,6 +9789,7 @@
         // Always refers to PackageManager flags only
         final int installFlags;
         final String installerPackageName;
+        final String volumeUuid;
         final ManifestDigest manifestDigest;
         final UserHandle user;
         final String abiOverride;
@@ -9787,12 +9800,13 @@
         /* nullable */ String[] instructionSets;
 
         InstallArgs(OriginInfo origin, IPackageInstallObserver2 observer, int installFlags,
-                String installerPackageName, ManifestDigest manifestDigest, UserHandle user,
-                String[] instructionSets, String abiOverride) {
+                String installerPackageName, String volumeUuid, ManifestDigest manifestDigest,
+                UserHandle user, String[] instructionSets, String abiOverride) {
             this.origin = origin;
             this.installFlags = installFlags;
             this.observer = observer;
             this.installerPackageName = installerPackageName;
+            this.volumeUuid = volumeUuid;
             this.manifestDigest = manifestDigest;
             this.user = user;
             this.instructionSets = instructionSets;
@@ -9844,7 +9858,7 @@
             return (installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
         }
 
-        protected boolean isExternal() {
+        protected boolean isExternalAsec() {
             return (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
         }
 
@@ -9891,8 +9905,8 @@
         /** New install */
         FileInstallArgs(InstallParams params) {
             super(params.origin, params.observer, params.installFlags,
-                    params.installerPackageName, params.getManifestDigest(), params.getUser(),
-                    null /* instruction sets */, params.packageAbiOverride);
+                    params.installerPackageName, params.volumeUuid, params.getManifestDigest(),
+                    params.getUser(), null /* instruction sets */, params.packageAbiOverride);
             if (isFwdLocked()) {
                 throw new IllegalArgumentException("Forward locking only supported in ASEC");
             }
@@ -9901,7 +9915,7 @@
         /** Existing install */
         FileInstallArgs(String codePath, String resourcePath, String legacyNativeLibraryPath,
                 String[] instructionSets) {
-            super(OriginInfo.fromNothing(), null, 0, null, null, null, instructionSets, null);
+            super(OriginInfo.fromNothing(), null, 0, null, null, null, null, instructionSets, null);
             this.codeFile = (codePath != null) ? new File(codePath) : null;
             this.resourceFile = (resourcePath != null) ? new File(resourcePath) : null;
             this.legacyNativeLibraryPath = (legacyNativeLibraryPath != null) ?
@@ -9925,7 +9939,7 @@
             }
 
             try {
-                final File tempDir = mInstallerService.allocateInternalStageDirLegacy();
+                final File tempDir = mInstallerService.allocateStageDirLegacy(volumeUuid);
                 codeFile = tempDir;
                 resourceFile = tempDir;
             } catch (IOException e) {
@@ -9986,8 +10000,9 @@
                 cleanUp();
                 return false;
             } else {
+                final File targetDir = codeFile.getParentFile();
                 final File beforeCodeFile = codeFile;
-                final File afterCodeFile = getNextCodePath(pkg.packageName);
+                final File afterCodeFile = getNextCodePath(targetDir, pkg.packageName);
 
                 Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
                 try {
@@ -10137,16 +10152,15 @@
         /** New install */
         AsecInstallArgs(InstallParams params) {
             super(params.origin, params.observer, params.installFlags,
-                    params.installerPackageName, params.getManifestDigest(),
-                    params.getUser(), null /* instruction sets */,
-                    params.packageAbiOverride);
+                    params.installerPackageName, params.volumeUuid, params.getManifestDigest(),
+                    params.getUser(), null /* instruction sets */, params.packageAbiOverride);
         }
 
         /** Existing install */
         AsecInstallArgs(String fullCodePath, String[] instructionSets,
                         boolean isExternal, boolean isForwardLocked) {
             super(OriginInfo.fromNothing(), null, (isExternal ? INSTALL_EXTERNAL : 0)
-                    | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null,
+                    | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, null,
                     instructionSets, null);
             // Hackily pretend we're still looking at a full code path
             if (!fullCodePath.endsWith(RES_FILE_NAME)) {
@@ -10163,7 +10177,7 @@
 
         AsecInstallArgs(String cid, String[] instructionSets, boolean isForwardLocked) {
             super(OriginInfo.fromNothing(), null, (isAsecExternal(cid) ? INSTALL_EXTERNAL : 0)
-                    | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null,
+                    | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null, null,
                     instructionSets, null);
             this.cid = cid;
             setMountPath(PackageHelper.getSdDir(cid));
@@ -10178,7 +10192,7 @@
                     abiOverride);
 
             final File target;
-            if (isExternal()) {
+            if (isExternalAsec()) {
                 target = new UserEnvironment(UserHandle.USER_OWNER).getExternalStorageDirectory();
             } else {
                 target = Environment.getDataDirectory();
@@ -10207,7 +10221,7 @@
             }
 
             final String newMountPath = imcs.copyPackageToContainer(
-                    origin.file.getAbsolutePath(), cid, getEncryptKey(), isExternal(),
+                    origin.file.getAbsolutePath(), cid, getEncryptKey(), isExternalAsec(),
                     isFwdLocked(), deriveAbiOverride(abiOverride, null /* settings */));
 
             if (newMountPath != null) {
@@ -10488,11 +10502,11 @@
         return prefix + idxStr;
     }
 
-    private File getNextCodePath(String packageName) {
+    private File getNextCodePath(File targetDir, String packageName) {
         int suffix = 1;
         File result;
         do {
-            result = new File(mAppInstallDir, packageName + "-" + suffix);
+            result = new File(targetDir, packageName + "-" + suffix);
             suffix++;
         } while (result.exists());
         return result;
@@ -10556,9 +10570,9 @@
     /*
      * Install a non-existing package.
      */
-    private void installNewPackageLI(PackageParser.Package pkg,
-            int parseFlags, int scanFlags, UserHandle user,
-            String installerPackageName, PackageInstalledInfo res) {
+    private void installNewPackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags,
+            UserHandle user, String installerPackageName, String volumeUuid,
+            PackageInstalledInfo res) {
         // Remember this for later, in case we need to rollback this install
         String pkgName = pkg.packageName;
 
@@ -10587,7 +10601,7 @@
             PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanFlags,
                     System.currentTimeMillis(), user);
 
-            updateSettingsLI(newPackage, installerPackageName, null, null, res, user);
+            updateSettingsLI(newPackage, installerPackageName, volumeUuid, null, null, res, user);
             // delete the partially installed application. the data directory will have to be
             // restored if it was already existing
             if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
@@ -10619,9 +10633,9 @@
         return false;
     }
 
-    private void replacePackageLI(PackageParser.Package pkg,
-            int parseFlags, int scanFlags, UserHandle user,
-            String installerPackageName, PackageInstalledInfo res) {
+    private void replacePackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags,
+            UserHandle user, String installerPackageName, String volumeUuid,
+            PackageInstalledInfo res) {
         PackageParser.Package oldPackage;
         String pkgName = pkg.packageName;
         int[] allUsers;
@@ -10660,17 +10674,17 @@
         boolean sysPkg = (isSystemApp(oldPackage));
         if (sysPkg) {
             replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanFlags,
-                    user, allUsers, perUserInstalled, installerPackageName, res);
+                    user, allUsers, perUserInstalled, installerPackageName, volumeUuid, res);
         } else {
             replaceNonSystemPackageLI(oldPackage, pkg, parseFlags, scanFlags,
-                    user, allUsers, perUserInstalled, installerPackageName, res);
+                    user, allUsers, perUserInstalled, installerPackageName, volumeUuid, res);
         }
     }
 
     private void replaceNonSystemPackageLI(PackageParser.Package deletedPackage,
             PackageParser.Package pkg, int parseFlags, int scanFlags, UserHandle user,
-            int[] allUsers, boolean[] perUserInstalled,
-            String installerPackageName, PackageInstalledInfo res) {
+            int[] allUsers, boolean[] perUserInstalled, String installerPackageName,
+            String volumeUuid, PackageInstalledInfo res) {
         String pkgName = deletedPackage.packageName;
         boolean deletedPkg = true;
         boolean updatedSettings = false;
@@ -10709,8 +10723,8 @@
             try {
                 final PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags,
                         scanFlags | SCAN_UPDATE_TIME, System.currentTimeMillis(), user);
-                updateSettingsLI(newPackage, installerPackageName, allUsers, perUserInstalled, res,
-                        user);
+                updateSettingsLI(newPackage, installerPackageName, volumeUuid, allUsers,
+                        perUserInstalled, res, user);
                 updatedSettings = true;
             } catch (PackageManagerException e) {
                 res.setError("Package couldn't be installed in " + pkg.codePath, e);
@@ -10735,10 +10749,10 @@
                 if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, reinstalling: " + deletedPackage);
                 File restoreFile = new File(deletedPackage.codePath);
                 // Parse old package
-                boolean oldOnSd = isExternal(deletedPackage);
+                boolean oldExternal = isExternal(deletedPackage);
                 int oldParseFlags  = mDefParseFlags | PackageParser.PARSE_CHATTY |
                         (deletedPackage.isForwardLocked() ? PackageParser.PARSE_FORWARD_LOCK : 0) |
-                        (oldOnSd ? PackageParser.PARSE_ON_SDCARD : 0);
+                        (oldExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0);
                 int oldScanFlags = SCAN_UPDATE_SIGNATURE | SCAN_UPDATE_TIME;
                 try {
                     scanPackageLI(restoreFile, oldParseFlags, oldScanFlags, origUpdateTime, null);
@@ -10762,8 +10776,8 @@
 
     private void replaceSystemPackageLI(PackageParser.Package deletedPackage,
             PackageParser.Package pkg, int parseFlags, int scanFlags, UserHandle user,
-            int[] allUsers, boolean[] perUserInstalled,
-            String installerPackageName, PackageInstalledInfo res) {
+            int[] allUsers, boolean[] perUserInstalled, String installerPackageName,
+            String volumeUuid, PackageInstalledInfo res) {
         if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg
                 + ", old=" + deletedPackage);
         boolean disabledSystem = false;
@@ -10840,8 +10854,8 @@
             }
 
             if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
-                updateSettingsLI(newPackage, installerPackageName, allUsers, perUserInstalled, res,
-                        user);
+                updateSettingsLI(newPackage, installerPackageName, volumeUuid, allUsers,
+                        perUserInstalled, res, user);
                 updatedSettings = true;
             }
 
@@ -10876,8 +10890,8 @@
     }
 
     private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
-            int[] allUsers, boolean[] perUserInstalled,
-            PackageInstalledInfo res, UserHandle user) {
+            String volumeUuid, int[] allUsers, boolean[] perUserInstalled, PackageInstalledInfo res,
+            UserHandle user) {
         String pkgName = newPackage.packageName;
         synchronized (mPackages) {
             //write settings. the installStatus will be incomplete at this stage.
@@ -10935,6 +10949,7 @@
             res.pkg = newPackage;
             mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_COMPLETE);
             mSettings.setInstallerPackageName(pkgName, installerPackageName);
+            mSettings.setVolumeUuid(pkgName, volumeUuid);
             res.returnCode = PackageManager.INSTALL_SUCCEEDED;
             //to update install status
             mSettings.writeLPr();
@@ -10943,10 +10958,12 @@
 
     private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
         final int installFlags = args.installFlags;
-        String installerPackageName = args.installerPackageName;
-        File tmpPackageFile = new File(args.getCodePath());
-        boolean forwardLocked = ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);
-        boolean onSd = ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0);
+        final String installerPackageName = args.installerPackageName;
+        final String volumeUuid = args.volumeUuid;
+        final File tmpPackageFile = new File(args.getCodePath());
+        final boolean forwardLocked = ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);
+        final boolean onExternal = (((installFlags & PackageManager.INSTALL_EXTERNAL) != 0)
+                || (args.volumeUuid != null));
         boolean replace = false;
         final int scanFlags = SCAN_NEW_INSTALL | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE;
         // Result object to be returned
@@ -10956,7 +10973,7 @@
         // Retrieve PackageSettings and parse package
         final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
                 | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
-                | (onSd ? PackageParser.PARSE_ON_SDCARD : 0);
+                | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0);
         PackageParser pp = new PackageParser();
         pp.setSeparateProcesses(mSeparateProcesses);
         pp.setDisplayMetrics(mMetrics);
@@ -11108,7 +11125,7 @@
 
         }
 
-        if (systemApp && onSd) {
+        if (systemApp && onExternal) {
             // Disable updates to system apps on sdcard
             res.setError(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
                     "Cannot install updates to system apps on sdcard");
@@ -11134,10 +11151,10 @@
         if (replace) {
             // Call replacePackageLI with SCAN_NO_DEX, since we already made dexopt
             replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING | SCAN_NO_DEX, args.user,
-                    installerPackageName, res);
+                    installerPackageName, volumeUuid, res);
         } else {
             installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
-                    args.user, installerPackageName, res);
+                    args.user, installerPackageName, volumeUuid, res);
         }
         synchronized (mPackages) {
             final PackageSetting ps = mSettings.mPackages.get(pkgName);
@@ -11265,7 +11282,9 @@
 
     private int packageFlagsToInstallFlags(PackageSetting ps) {
         int installFlags = 0;
-        if (isExternal(ps)) {
+        if (isExternal(ps) && TextUtils.isEmpty(ps.volumeUuid)) {
+            // This existing package was an external ASEC install when we have
+            // the external flag without a UUID
             installFlags |= PackageManager.INSTALL_EXTERNAL;
         }
         if (ps.isForwardLocked()) {
@@ -12975,6 +12994,8 @@
         // Watch for external volumes that come and go over time
         final StorageManager storage = mContext.getSystemService(StorageManager.class);
         storage.registerListener(mStorageListener);
+
+        mInstallerService.systemReady();
     }
 
     @Override
@@ -13713,13 +13734,32 @@
     }
 
     private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing,
+            ArrayList<ApplicationInfo> infos, IIntentReceiver finishedReceiver) {
+        final int size = infos.size();
+        final String[] packageNames = new String[size];
+        final int[] packageUids = new int[size];
+        for (int i = 0; i < size; i++) {
+            final ApplicationInfo info = infos.get(i);
+            packageNames[i] = info.packageName;
+            packageUids[i] = info.uid;
+        }
+        sendResourcesChangedBroadcast(mediaStatus, replacing, packageNames, packageUids,
+                finishedReceiver);
+    }
+
+    private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing,
             ArrayList<String> pkgList, int uidArr[], IIntentReceiver finishedReceiver) {
-        int size = pkgList.size();
+        sendResourcesChangedBroadcast(mediaStatus, replacing,
+                pkgList.toArray(new String[pkgList.size()]), uidArr, finishedReceiver);
+    }
+
+    private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing,
+            String[] pkgList, int uidArr[], IIntentReceiver finishedReceiver) {
+        int size = pkgList.length;
         if (size > 0) {
             // Send broadcasts here
             Bundle extras = new Bundle();
-            extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList
-                    .toArray(new String[size]));
+            extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
             if (uidArr != null) {
                 extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidArr);
             }
@@ -13762,8 +13802,8 @@
                 }
                 // Parse package
                 int parseFlags = mDefParseFlags;
-                if (args.isExternal()) {
-                    parseFlags |= PackageParser.PARSE_ON_SDCARD;
+                if (args.isExternalAsec()) {
+                    parseFlags |= PackageParser.PARSE_EXTERNAL_STORAGE;
                 }
                 if (args.isFwdLocked()) {
                     parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
@@ -13910,73 +13950,130 @@
         }
     }
 
-    /** Binder call */
+    private void loadPrivatePackages(VolumeInfo vol) {
+        final ArrayList<ApplicationInfo> loaded = new ArrayList<>();
+        final int parseFlags = mDefParseFlags | PackageParser.PARSE_EXTERNAL_STORAGE;
+        synchronized (mPackages) {
+            final List<PackageSetting> packages = mSettings.getVolumePackagesLPr(vol.fsUuid);
+            for (PackageSetting ps : packages) {
+                synchronized (mInstallLock) {
+                    final PackageParser.Package pkg;
+                    try {
+                        pkg = scanPackageLI(ps.codePath, parseFlags, 0, 0, null);
+                        loaded.add(pkg.applicationInfo);
+                    } catch (PackageManagerException e) {
+                        Slog.w(TAG, "Failed to scan " + ps.codePath + ": " + e.getMessage());
+                    }
+                }
+            }
+
+            // TODO: regrant any permissions that changed based since original install
+
+            mSettings.writeLPr();
+        }
+
+        Slog.d(TAG, "Loaded packages " + loaded);
+        sendResourcesChangedBroadcast(true, false, loaded, null);
+    }
+
+    private void unloadPrivatePackages(VolumeInfo vol) {
+        final ArrayList<ApplicationInfo> unloaded = new ArrayList<>();
+        synchronized (mPackages) {
+            final List<PackageSetting> packages = mSettings.getVolumePackagesLPr(vol.fsUuid);
+            for (PackageSetting ps : packages) {
+                if (ps.pkg == null) continue;
+                synchronized (mInstallLock) {
+                    final ApplicationInfo info = ps.pkg.applicationInfo;
+                    final PackageRemovedInfo outInfo = new PackageRemovedInfo();
+                    if (deletePackageLI(ps.name, null, false, null, null,
+                            PackageManager.DELETE_KEEP_DATA, outInfo, false)) {
+                        unloaded.add(info);
+                    } else {
+                        Slog.w(TAG, "Failed to unload " + ps.codePath);
+                    }
+                }
+            }
+
+            mSettings.writeLPr();
+        }
+
+        Slog.d(TAG, "Unloaded packages " + unloaded);
+        sendResourcesChangedBroadcast(false, false, unloaded, null);
+    }
+
     @Override
     public void movePackage(final String packageName, final IPackageMoveObserver observer,
             final int flags) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null);
-        UserHandle user = new UserHandle(UserHandle.getCallingUserId());
-        int returnCode = PackageManager.MOVE_SUCCEEDED;
-        int currInstallFlags = 0;
-        int newInstallFlags = 0;
+
+        final int installFlags;
+        if ((flags & MOVE_INTERNAL) != 0) {
+            installFlags = INSTALL_INTERNAL;
+        } else if ((flags & MOVE_EXTERNAL_MEDIA) != 0) {
+            installFlags = INSTALL_EXTERNAL;
+        } else {
+            throw new IllegalArgumentException("Unsupported move flags " + flags);
+        }
+
+        try {
+            movePackageInternal(packageName, null, installFlags, false, observer);
+        } catch (PackageManagerException e) {
+            Slog.d(TAG, "Failed to move " + packageName, e);
+            try {
+                observer.packageMoved(packageName, e.error);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    @Override
+    public void movePackageAndData(final String packageName, final String volumeUuid,
+            final IPackageMoveObserver observer) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null);
+        try {
+            movePackageInternal(packageName, volumeUuid, INSTALL_INTERNAL, true, observer);
+        } catch (PackageManagerException e) {
+            Slog.d(TAG, "Failed to move " + packageName, e);
+            try {
+                observer.packageMoved(packageName, e.error);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    private void movePackageInternal(final String packageName, String volumeUuid, int installFlags,
+            boolean andData, final IPackageMoveObserver observer) throws PackageManagerException {
+        final UserHandle user = new UserHandle(UserHandle.getCallingUserId());
 
         File codeFile = null;
         String installerPackageName = null;
         String packageAbiOverride = null;
 
+        // TOOD: move app private data before installing
+
         // reader
         synchronized (mPackages) {
             final PackageParser.Package pkg = mPackages.get(packageName);
             final PackageSetting ps = mSettings.mPackages.get(packageName);
             if (pkg == null || ps == null) {
-                returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST;
-            } else {
-                // Disable moving fwd locked apps and system packages
-                if (pkg.applicationInfo != null && isSystemApp(pkg)) {
-                    Slog.w(TAG, "Cannot move system application");
-                    returnCode = PackageManager.MOVE_FAILED_SYSTEM_PACKAGE;
-                } else if (pkg.mOperationPending) {
-                    Slog.w(TAG, "Attempt to move package which has pending operations");
-                    returnCode = PackageManager.MOVE_FAILED_OPERATION_PENDING;
-                } else {
-                    // Find install location first
-                    if ((flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0
-                            && (flags & PackageManager.MOVE_INTERNAL) != 0) {
-                        Slog.w(TAG, "Ambigous flags specified for move location.");
-                        returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION;
-                    } else {
-                        newInstallFlags = (flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0
-                                ? PackageManager.INSTALL_EXTERNAL : PackageManager.INSTALL_INTERNAL;
-                        currInstallFlags = isExternal(pkg)
-                                ? PackageManager.INSTALL_EXTERNAL : PackageManager.INSTALL_INTERNAL;
-
-                        if (newInstallFlags == currInstallFlags) {
-                            Slog.w(TAG, "No move required. Trying to move to same location");
-                            returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION;
-                        } else {
-                            if (pkg.isForwardLocked()) {
-                                currInstallFlags |= PackageManager.INSTALL_FORWARD_LOCK;
-                                newInstallFlags |= PackageManager.INSTALL_FORWARD_LOCK;
-                            }
-                        }
-                    }
-                    if (returnCode == PackageManager.MOVE_SUCCEEDED) {
-                        pkg.mOperationPending = true;
-                    }
-                }
-
-                codeFile = new File(pkg.codePath);
-                installerPackageName = ps.installerPackageName;
-                packageAbiOverride = ps.cpuAbiOverrideString;
+                throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package");
             }
-        }
 
-        if (returnCode != PackageManager.MOVE_SUCCEEDED) {
-            try {
-                observer.packageMoved(packageName, returnCode);
-            } catch (RemoteException ignored) {
+            if (pkg.applicationInfo.isSystemApp()) {
+                throw new PackageManagerException(MOVE_FAILED_SYSTEM_PACKAGE,
+                        "Cannot move system application");
+            } else if (pkg.mOperationPending) {
+                throw new PackageManagerException(MOVE_FAILED_OPERATION_PENDING,
+                        "Attempt to move package which has pending operations");
             }
-            return;
+
+            // TODO: yell if already in desired location
+
+            pkg.mOperationPending = true;
+
+            codeFile = new File(pkg.codePath);
+            installerPackageName = ps.installerPackageName;
+            packageAbiOverride = ps.cpuAbiOverrideString;
         }
 
         final IPackageInstallObserver2 installObserver = new IPackageInstallObserver2.Stub() {
@@ -14018,12 +14115,12 @@
 
         // Treat a move like reinstalling an existing app, which ensures that we
         // process everythign uniformly, like unpacking native libraries.
-        newInstallFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+        installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
 
         final Message msg = mHandler.obtainMessage(INIT_COPY);
         final OriginInfo origin = OriginInfo.fromExistingFile(codeFile);
-        msg.obj = new InstallParams(origin, installObserver, newInstallFlags,
-                installerPackageName, null, user, packageAbiOverride);
+        msg.obj = new InstallParams(origin, installObserver, installFlags,
+                installerPackageName, volumeUuid, null, user, packageAbiOverride);
         mHandler.sendMessage(msg);
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index daa6d64..f294b32 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -23,6 +23,7 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageUserState;
+import android.os.storage.VolumeInfo;
 import android.util.ArraySet;
 import android.util.SparseArray;
 
@@ -108,8 +109,10 @@
 
     PackageSettingBase origPackage;
 
-    /* package name of the app that installed this package */
+    /** Package name of the app that installed this package */
     String installerPackageName;
+    /** UUID of {@link VolumeInfo} hosting this app */
+    String volumeUuid;
 
     IntentFilterVerificationInfo verificationInfo;
 
@@ -161,6 +164,7 @@
         origPackage = base.origPackage;
 
         installerPackageName = base.installerPackageName;
+        volumeUuid = base.volumeUuid;
 
         keySetData = new PackageKeySetData(base.keySetData);
     }
@@ -183,10 +187,18 @@
         installerPackageName = packageName;
     }
 
-    String getInstallerPackageName() {
+    public String getInstallerPackageName() {
         return installerPackageName;
     }
 
+    public void setVolumeUuid(String volumeUuid) {
+        this.volumeUuid = volumeUuid;
+    }
+
+    public String getVolumeUuid() {
+        return volumeUuid;
+    }
+
     public void setInstallStatus(int newStatus) {
         installStatus = newStatus;
     }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 6930965..6b7c35c 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -42,6 +42,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.storage.VolumeInfo;
 import android.util.AtomicFile;
 import android.text.TextUtils;
 import android.util.LogPrinter;
@@ -53,6 +54,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.JournaledFile;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.server.backup.PreferredActivityBackupHelper;
 import com.android.server.pm.PackageManagerService.DumpState;
@@ -333,14 +335,20 @@
         }
     }
 
-    void setInstallerPackageName(String pkgName,
-            String installerPkgName) {
+    void setInstallerPackageName(String pkgName, String installerPkgName) {
         PackageSetting p = mPackages.get(pkgName);
-        if(p != null) {
+        if (p != null) {
             p.setInstallerPackageName(installerPkgName);
         }
     }
 
+    void setVolumeUuid(String pkgName, String volumeUuid) {
+        PackageSetting p = mPackages.get(pkgName);
+        if (p != null) {
+            p.setVolumeUuid(volumeUuid);
+        }
+    }
+
     SharedUserSetting getSharedUserLPw(String name,
             int pkgFlags, int pkgPrivateFlags, boolean create) {
         SharedUserSetting s = mSharedUsers.get(name);
@@ -2066,6 +2074,9 @@
         if (pkg.installerPackageName != null) {
             serializer.attribute(null, "installer", pkg.installerPackageName);
         }
+        if (pkg.volumeUuid != null) {
+            serializer.attribute(null, "volumeUuid", pkg.volumeUuid);
+        }
         pkg.signatures.writeXml(serializer, "sigs", mPastSignatures);
         if ((pkg.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
             writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissions());
@@ -2908,6 +2919,7 @@
         String cpuAbiOverrideString = null;
         String systemStr = null;
         String installerPackageName = null;
+        String volumeUuid = null;
         String uidError = null;
         int pkgFlags = 0;
         int pkgPrivateFlags = 0;
@@ -2945,6 +2957,7 @@
                 }
             }
             installerPackageName = parser.getAttributeValue(null, "installer");
+            volumeUuid = parser.getAttributeValue(null, "volumeUuid");
 
             systemStr = parser.getAttributeValue(null, "publicFlags");
             if (systemStr != null) {
@@ -3093,6 +3106,7 @@
         if (packageSetting != null) {
             packageSetting.uidError = "true".equals(uidError);
             packageSetting.installerPackageName = installerPackageName;
+            packageSetting.volumeUuid = volumeUuid;
             packageSetting.legacyNativeLibraryPathString = legacyNativeLibraryPathStr;
             packageSetting.primaryCpuAbiString = primaryCpuAbiString;
             packageSetting.secondaryCpuAbiString = secondaryCpuAbiString;
@@ -3549,6 +3563,22 @@
         return null;
     }
 
+    /**
+     * Return all {@link PackageSetting} that are actively installed on the
+     * given {@link VolumeInfo#fsUuid}.
+     */
+    List<PackageSetting> getVolumePackagesLPr(String volumeUuid) {
+        Preconditions.checkNotNull(volumeUuid);
+        ArrayList<PackageSetting> res = new ArrayList<>();
+        for (int i = 0; i < mPackages.size(); i++) {
+            final PackageSetting setting = mPackages.valueAt(i);
+            if (Objects.equals(volumeUuid, setting.volumeUuid)) {
+                res.add(setting);
+            }
+        }
+        return res;
+    }
+
     static void printFlags(PrintWriter pw, int val, Object[] spec) {
         pw.print("[ ");
         for (int i=0; i<spec.length; i+=2) {
@@ -3755,6 +3785,10 @@
             pw.print(prefix); pw.print("  installerPackageName=");
                     pw.println(ps.installerPackageName);
         }
+        if (ps.volumeUuid != null) {
+            pw.print(prefix); pw.print("  volumeUuid=");
+                    pw.println(ps.volumeUuid);
+        }
         pw.print(prefix); pw.print("  signatures="); pw.println(ps.signatures);
         pw.print(prefix); pw.print("  installPermissionsFixed=");
                 pw.print(ps.installPermissionsFixed);
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index a204376..c8b6846 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -485,14 +485,19 @@
         throw new UnsupportedOperationException();
     }
 
-    /**
-     * @hide - to match hiding in superclass
-     */
+    /** {@hide} */
     @Override
     public void movePackage(String packageName, IPackageMoveObserver observer, int flags) {
         throw new UnsupportedOperationException();
     }
 
+    /** {@hide} */
+    @Override
+    public void movePackageAndData(String packageName, String volumeUuid,
+            IPackageMoveObserver observer) {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public String getInstallerPackageName(String packageName) {
         throw new UnsupportedOperationException();