Merge "Package and storage movement callbacks."
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index d5cc8cc..b84b1e2 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -51,9 +51,11 @@
 import android.os.IUserManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.text.TextUtils;
+import android.text.format.DateUtils;
 import android.util.Log;
 
 import libcore.io.IoUtils;
@@ -1283,20 +1285,6 @@
         }
     }
 
-    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();
@@ -1304,24 +1292,21 @@
             volumeUuid = null;
         }
 
-        final LocalPackageMoveObserver obs = new LocalPackageMoveObserver();
         try {
-            mPm.movePackageAndData(packageName, volumeUuid, obs);
+            final int moveId = mPm.movePackage(packageName, volumeUuid);
 
-            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;
-                }
+            int status = mPm.getMoveStatus(moveId);
+            while (!PackageManager.isMoveStatusFinished(status)) {
+                SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
+                status = mPm.getMoveStatus(moveId);
+            }
+
+            if (status == PackageManager.MOVE_SUCCEEDED) {
+                System.out.println("Success");
+                return 0;
+            } else {
+                System.err.println("Failure [" + status + "]");
+                return 1;
             }
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index dfe7e18..10f5960 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -62,6 +62,9 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -81,7 +84,9 @@
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 
 /*package*/
 final class ApplicationPackageManager extends PackageManager {
@@ -98,6 +103,9 @@
     @GuardedBy("mLock")
     private PackageInstaller mInstaller;
 
+    @GuardedBy("mDelegates")
+    private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>();
+
     UserManager getUserManager() {
         synchronized (mLock) {
             if (mUserManager == null) {
@@ -1410,57 +1418,100 @@
     }
 
     @Override
-    public void movePackage(String packageName, IPackageMoveObserver observer, int flags) {
+    public String getInstallerPackageName(String packageName) {
         try {
-            mPM.movePackage(packageName, observer, flags);
+            return mPM.getInstallerPackageName(packageName);
+        } catch (RemoteException e) {
+            // Should never happen!
+        }
+        return null;
+    }
+
+    @Override
+    public int getMoveStatus(int moveId) {
+        try {
+            return mPM.getMoveStatus(moveId);
         } catch (RemoteException e) {
             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();
+    public void registerMoveCallback(MoveCallback callback, Handler handler) {
+        synchronized (mDelegates) {
+            final MoveCallbackDelegate delegate = new MoveCallbackDelegate(callback,
+                    handler.getLooper());
+            try {
+                mPM.registerMoveCallback(delegate);
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+            mDelegates.add(delegate);
         }
     }
 
     @Override
-    public @NonNull VolumeInfo getApplicationCurrentVolume(ApplicationInfo app) {
-        final StorageManager storage = mContext.getSystemService(StorageManager.class);
-        if (app.isInternal()) {
-            return Preconditions.checkNotNull(
-                    storage.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL));
-        } else if (app.isExternalAsec()) {
-            final List<VolumeInfo> vols = storage.getVolumes();
-            for (VolumeInfo vol : vols) {
-                if ((vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.isPrimary()) {
-                    return vol;
+    public void unregisterMoveCallback(MoveCallback callback) {
+        synchronized (mDelegates) {
+            for (Iterator<MoveCallbackDelegate> i = mDelegates.iterator(); i.hasNext();) {
+                final MoveCallbackDelegate delegate = i.next();
+                if (delegate.mCallback == callback) {
+                    try {
+                        mPM.unregisterMoveCallback(delegate);
+                    } catch (RemoteException e) {
+                        throw e.rethrowAsRuntimeException();
+                    }
+                    i.remove();
                 }
             }
-            throw new IllegalStateException("Failed to find primary public volume");
-        } else {
-            return Preconditions.checkNotNull(storage.findVolumeByUuid(app.volumeUuid));
         }
     }
 
     @Override
-    public @NonNull List<VolumeInfo> getApplicationCandidateVolumes(ApplicationInfo app) {
+    public int movePackage(String packageName, VolumeInfo vol) {
+        try {
+            final String volumeUuid;
+            if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) {
+                volumeUuid = StorageManager.UUID_PRIVATE_INTERNAL;
+            } else if (vol.isPrimaryPhysical()) {
+                volumeUuid = StorageManager.UUID_PRIMARY_PHYSICAL;
+            } else {
+                volumeUuid = Preconditions.checkNotNull(vol.fsUuid);
+            }
+
+            return mPM.movePackage(packageName, volumeUuid);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app) {
         final StorageManager storage = mContext.getSystemService(StorageManager.class);
+        if (app.isInternal()) {
+            return storage.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL);
+        } else if (app.isExternalAsec()) {
+            return storage.getPrimaryPhysicalVolume();
+        } else {
+            return storage.findVolumeByUuid(app.volumeUuid);
+        }
+    }
+
+    @Override
+    public @NonNull List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app) {
+        final StorageManager storage = mContext.getSystemService(StorageManager.class);
+        final VolumeInfo currentVol = getPackageCurrentVolume(app);
         final List<VolumeInfo> vols = storage.getVolumes();
         final List<VolumeInfo> candidates = new ArrayList<>();
         for (VolumeInfo vol : vols) {
-            if (isCandidateVolume(app, vol)) {
+            if (Objects.equals(vol, currentVol) || isPackageCandidateVolume(app, vol)) {
                 candidates.add(vol);
             }
         }
         return candidates;
     }
 
-    private static boolean isCandidateVolume(ApplicationInfo app, VolumeInfo vol) {
+    private static boolean isPackageCandidateVolume(ApplicationInfo app, VolumeInfo vol) {
         // Private internal is always an option
         if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) {
             return true;
@@ -1473,10 +1524,14 @@
             return false;
         }
 
-        // Moving into an ASEC on public primary is only an option when app is
-        // internal, or already in ASEC
-        if ((vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.isPrimary()) {
-            return app.isInternal() || app.isExternalAsec();
+        // Gotta be able to write there
+        if (!vol.isMountedWritable()) {
+            return false;
+        }
+
+        // Moving into an ASEC on public primary is only option internal
+        if (vol.isPrimaryPhysical()) {
+            return app.isInternal();
         }
 
         // Otherwise we can move to any private volume
@@ -1484,13 +1539,66 @@
     }
 
     @Override
-    public String getInstallerPackageName(String packageName) {
+    public int movePrimaryStorage(VolumeInfo vol) {
         try {
-            return mPM.getInstallerPackageName(packageName);
+            final String volumeUuid;
+            if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) {
+                volumeUuid = StorageManager.UUID_PRIVATE_INTERNAL;
+            } else if (vol.isPrimaryPhysical()) {
+                volumeUuid = StorageManager.UUID_PRIMARY_PHYSICAL;
+            } else {
+                volumeUuid = Preconditions.checkNotNull(vol.fsUuid);
+            }
+
+            return mPM.movePrimaryStorage(volumeUuid);
         } catch (RemoteException e) {
-            // Should never happen!
+            throw e.rethrowAsRuntimeException();
         }
-        return null;
+    }
+
+    public @Nullable VolumeInfo getPrimaryStorageCurrentVolume() {
+        final StorageManager storage = mContext.getSystemService(StorageManager.class);
+        final String volumeUuid = storage.getPrimaryStorageUuid();
+        if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) {
+            return storage.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL);
+        } else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) {
+            return storage.getPrimaryPhysicalVolume();
+        } else {
+            return storage.findVolumeByUuid(volumeUuid);
+        }
+    }
+
+    public @NonNull List<VolumeInfo> getPrimaryStorageCandidateVolumes() {
+        final StorageManager storage = mContext.getSystemService(StorageManager.class);
+        final VolumeInfo currentVol = getPrimaryStorageCurrentVolume();
+        final List<VolumeInfo> vols = storage.getVolumes();
+        final List<VolumeInfo> candidates = new ArrayList<>();
+        for (VolumeInfo vol : vols) {
+            if (Objects.equals(vol, currentVol) || isPrimaryStorageCandidateVolume(vol)) {
+                candidates.add(vol);
+            }
+        }
+        return candidates;
+    }
+
+    private static boolean isPrimaryStorageCandidateVolume(VolumeInfo vol) {
+        // Private internal is always an option
+        if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) {
+            return true;
+        }
+
+        // Gotta be able to write there
+        if (!vol.isMountedWritable()) {
+            return false;
+        }
+
+        // We can move to public volumes on legacy devices
+        if ((vol.getType() == VolumeInfo.TYPE_PUBLIC) && vol.getDisk().isDefaultPrimary()) {
+            return true;
+        }
+
+        // Otherwise we can move to any private volume
+        return (vol.getType() == VolumeInfo.TYPE_PRIVATE);
     }
 
     @Override
@@ -1941,6 +2049,45 @@
         return null;
     }
 
+    /** {@hide} */
+    private static class MoveCallbackDelegate extends IPackageMoveObserver.Stub implements
+            Handler.Callback {
+        private static final int MSG_STARTED = 1;
+        private static final int MSG_STATUS_CHANGED = 2;
+
+        final MoveCallback mCallback;
+        final Handler mHandler;
+
+        public MoveCallbackDelegate(MoveCallback callback, Looper looper) {
+            mCallback = callback;
+            mHandler = new Handler(looper, this);
+        }
+
+        @Override
+        public boolean handleMessage(Message msg) {
+            final int moveId = msg.arg1;
+            switch (msg.what) {
+                case MSG_STARTED:
+                    mCallback.onStarted(moveId, (String) msg.obj);
+                    return true;
+                case MSG_STATUS_CHANGED:
+                    mCallback.onStatusChanged(moveId, msg.arg2, (long) msg.obj);
+                    return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void onStarted(int moveId, String title) {
+            mHandler.obtainMessage(MSG_STARTED, moveId, 0, title).sendToTarget();
+        }
+
+        @Override
+        public void onStatusChanged(int moveId, int status, long estMillis) {
+            mHandler.obtainMessage(MSG_STATUS_CHANGED, moveId, status, estMillis).sendToTarget();
+        }
+    }
+
     private final ContextImpl mContext;
     private final IPackageManager mPM;
 
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 447c668..ae59bfc 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -50,7 +50,6 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.content.IntentSender;
-import com.android.internal.os.IResultReceiver;
 
 /**
  *  See {@link PackageManager} for documentation on most of the APIs
@@ -431,8 +430,13 @@
 
     PackageCleanItem nextPackageToClean(in PackageCleanItem lastPackage);
 
-    void movePackage(String packageName, IPackageMoveObserver observer, int flags);
-    void movePackageAndData(String packageName, String volumeUuid, IPackageMoveObserver observer);
+    int getMoveStatus(int moveId);
+
+    void registerMoveCallback(in IPackageMoveObserver callback);
+    void unregisterMoveCallback(in IPackageMoveObserver callback);
+
+    int movePackage(in String packageName, in String volumeUuid);
+    int movePrimaryStorage(in String volumeUuid);
 
     boolean addPermissionAsync(in PermissionInfo info);
 
diff --git a/core/java/android/content/pm/IPackageMoveObserver.aidl b/core/java/android/content/pm/IPackageMoveObserver.aidl
index baa1595..50ab3b5 100644
--- a/core/java/android/content/pm/IPackageMoveObserver.aidl
+++ b/core/java/android/content/pm/IPackageMoveObserver.aidl
@@ -22,6 +22,6 @@
  * @hide
  */
 oneway interface IPackageMoveObserver {
-    void packageMoved(in String packageName, int returnCode);
+    void onStarted(int moveId, String title);
+    void onStatusChanged(int moveId, int status, long estMillis);
 }
-
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index e4108b1..e1c271d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -42,6 +42,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Environment;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.storage.VolumeInfo;
@@ -875,7 +876,8 @@
      *
      * @hide
      */
-    public static final int MOVE_SUCCEEDED = 1;
+    public static final int MOVE_SUCCEEDED = -100;
+
     /**
      * Error code that is passed to the {@link IPackageMoveObserver} by
      * {@link #movePackage(android.net.Uri, IPackageMoveObserver)}
@@ -941,6 +943,7 @@
      * been installed on external media.
      * @hide
      */
+    @Deprecated
     public static final int MOVE_INTERNAL = 0x00000001;
 
     /**
@@ -948,8 +951,12 @@
      * the package should be moved to external media.
      * @hide
      */
+    @Deprecated
     public static final int MOVE_EXTERNAL_MEDIA = 0x00000002;
 
+    /** {@hide} */
+    public static final String EXTRA_MOVE_ID = "android.content.pm.extra.MOVE_ID";
+
     /**
      * Usable by the required verifier as the {@code verificationCode} argument
      * for {@link PackageManager#verifyPendingInstall} to indicate that it will
@@ -4183,17 +4190,42 @@
      * @hide
      */
     @Deprecated
-    public abstract void movePackage(String packageName, IPackageMoveObserver observer, int flags);
+    public void movePackage(String packageName, IPackageMoveObserver observer, int flags) {
+        throw new UnsupportedOperationException();
+    }
 
     /** {@hide} */
-    public abstract void movePackageAndData(String packageName, String volumeUuid,
-            IPackageMoveObserver observer);
+    public static boolean isMoveStatusFinished(int status) {
+        return (status < 0 || status > 100);
+    }
 
     /** {@hide} */
-    public abstract @Nullable VolumeInfo getApplicationCurrentVolume(ApplicationInfo app);
+    public static abstract class MoveCallback {
+        public abstract void onStarted(int moveId, String title);
+        public abstract void onStatusChanged(int moveId, int status, long estMillis);
+    }
 
     /** {@hide} */
-    public abstract @NonNull List<VolumeInfo> getApplicationCandidateVolumes(ApplicationInfo app);
+    public abstract int getMoveStatus(int moveId);
+
+    /** {@hide} */
+    public abstract void registerMoveCallback(MoveCallback callback, Handler handler);
+    /** {@hide} */
+    public abstract void unregisterMoveCallback(MoveCallback callback);
+
+    /** {@hide} */
+    public abstract int movePackage(String packageName, VolumeInfo vol);
+    /** {@hide} */
+    public abstract @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app);
+    /** {@hide} */
+    public abstract @NonNull List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app);
+
+    /** {@hide} */
+    public abstract int movePrimaryStorage(VolumeInfo vol);
+    /** {@hide} */
+    public abstract @Nullable VolumeInfo getPrimaryStorageCurrentVolume();
+    /** {@hide} */
+    public abstract @NonNull List<VolumeInfo> getPrimaryStorageCandidateVolumes();
 
     /**
      * Returns the device identity that verifiers can use to associate their scheme to a particular
diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java
index 64f2a05..9623695 100644
--- a/core/java/android/os/storage/DiskInfo.java
+++ b/core/java/android/os/storage/DiskInfo.java
@@ -36,7 +36,10 @@
  * @hide
  */
 public class DiskInfo implements Parcelable {
-    public static final String EXTRA_DISK_ID = "android.os.storage.extra.DISK_ID";
+    public static final String ACTION_DISK_SCANNED =
+            "android.os.storage.action.DISK_SCANNED";
+    public static final String EXTRA_DISK_ID =
+            "android.os.storage.extra.DISK_ID";
 
     public static final int FLAG_ADOPTABLE = 1 << 0;
     public static final int FLAG_DEFAULT_PRIMARY = 1 << 1;
@@ -96,6 +99,14 @@
         }
     }
 
+    public boolean isAdoptable() {
+        return (flags & FLAG_ADOPTABLE) != 0;
+    }
+
+    public boolean isDefaultPrimary() {
+        return (flags & FLAG_DEFAULT_PRIMARY) != 0;
+    }
+
     public boolean isSd() {
         return (flags & FLAG_SD) != 0;
     }
@@ -104,10 +115,6 @@
         return (flags & FLAG_USB) != 0;
     }
 
-    public boolean isAdoptable() {
-        return (flags & FLAG_ADOPTABLE) != 0;
-    }
-
     @Override
     public String toString() {
         final CharArrayWriter writer = new CharArrayWriter();
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 0a8187e..0b1031c 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -1063,6 +1063,38 @@
                     _data.recycle();
                 }
             }
+
+            @Override
+            public String getPrimaryStorageUuid() throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                String _result;
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    mRemote.transact(Stub.TRANSACTION_getPrimaryStorageUuid, _data, _reply, 0);
+                    _reply.readException();
+                    _result = _reply.readString();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+                return _result;
+            }
+
+            @Override
+            public void setPrimaryStorageUuid(String volumeUuid) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeString(volumeUuid);
+                    mRemote.transact(Stub.TRANSACTION_setPrimaryStorageUuid, _data, _reply, 0);
+                    _reply.readException();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+            }
         }
 
         private static final String DESCRIPTOR = "IMountService";
@@ -1169,6 +1201,9 @@
         static final int TRANSACTION_setVolumeNickname = IBinder.FIRST_CALL_TRANSACTION + 52;
         static final int TRANSACTION_setVolumeUserFlags = IBinder.FIRST_CALL_TRANSACTION + 53;
 
+        static final int TRANSACTION_getPrimaryStorageUuid = IBinder.FIRST_CALL_TRANSACTION + 54;
+        static final int TRANSACTION_setPrimaryStorageUuid = IBinder.FIRST_CALL_TRANSACTION + 55;
+
         /**
          * Cast an IBinder object into an IMountService interface, generating a
          * proxy if needed.
@@ -1669,6 +1704,20 @@
                     reply.writeNoException();
                     return true;
                 }
+                case TRANSACTION_getPrimaryStorageUuid: {
+                    data.enforceInterface(DESCRIPTOR);
+                    String volumeUuid = getPrimaryStorageUuid();
+                    reply.writeNoException();
+                    reply.writeString(volumeUuid);
+                    return true;
+                }
+                case TRANSACTION_setPrimaryStorageUuid: {
+                    data.enforceInterface(DESCRIPTOR);
+                    String volumeUuid = data.readString();
+                    setPrimaryStorageUuid(volumeUuid);
+                    reply.writeNoException();
+                    return true;
+                }
             }
             return super.onTransact(code, data, reply, flags);
         }
@@ -1969,4 +2018,7 @@
 
     public void setVolumeNickname(String volId, String nickname) throws RemoteException;
     public void setVolumeUserFlags(String volId, int flags, int mask) throws RemoteException;
+
+    public String getPrimaryStorageUuid() throws RemoteException;
+    public void setPrimaryStorageUuid(String volumeUuid) throws RemoteException;
 }
diff --git a/core/java/android/os/storage/IMountServiceListener.java b/core/java/android/os/storage/IMountServiceListener.java
index 8e878a4..fcb4779 100644
--- a/core/java/android/os/storage/IMountServiceListener.java
+++ b/core/java/android/os/storage/IMountServiceListener.java
@@ -98,10 +98,11 @@
                     reply.writeNoException();
                     return true;
                 }
-                case TRANSACTION_onDiskUnsupported: {
+                case TRANSACTION_onDiskScanned: {
                     data.enforceInterface(DESCRIPTOR);
                     final DiskInfo disk = (DiskInfo) data.readParcelable(null);
-                    onDiskUnsupported(disk);
+                    final int volumeCount = data.readInt();
+                    onDiskScanned(disk, volumeCount);
                     reply.writeNoException();
                     return true;
                 }
@@ -207,13 +208,14 @@
             }
 
             @Override
-            public void onDiskUnsupported(DiskInfo disk) throws RemoteException {
+            public void onDiskScanned(DiskInfo disk, int volumeCount) throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
                 try {
                     _data.writeInterfaceToken(DESCRIPTOR);
                     _data.writeParcelable(disk, 0);
-                    mRemote.transact(Stub.TRANSACTION_onDiskUnsupported, _data, _reply,
+                    _data.writeInt(volumeCount);
+                    mRemote.transact(Stub.TRANSACTION_onDiskScanned, _data, _reply,
                             android.os.IBinder.FLAG_ONEWAY);
                     _reply.readException();
                 } finally {
@@ -224,12 +226,10 @@
         }
 
         static final int TRANSACTION_onUsbMassStorageConnectionChanged = (IBinder.FIRST_CALL_TRANSACTION + 0);
-
         static final int TRANSACTION_onStorageStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 1);
-
         static final int TRANSACTION_onVolumeStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 2);
         static final int TRANSACTION_onVolumeMetadataChanged = (IBinder.FIRST_CALL_TRANSACTION + 3);
-        static final int TRANSACTION_onDiskUnsupported = (IBinder.FIRST_CALL_TRANSACTION + 4);
+        static final int TRANSACTION_onDiskScanned = (IBinder.FIRST_CALL_TRANSACTION + 4);
     }
 
     /**
@@ -255,5 +255,5 @@
 
     public void onVolumeMetadataChanged(VolumeInfo vol) throws RemoteException;
 
-    public void onDiskUnsupported(DiskInfo disk) throws RemoteException;
+    public void onDiskScanned(DiskInfo disk, int volumeCount) throws RemoteException;
 }
diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java
index ad2fae0..6a0140e 100644
--- a/core/java/android/os/storage/StorageEventListener.java
+++ b/core/java/android/os/storage/StorageEventListener.java
@@ -44,6 +44,6 @@
     public void onVolumeMetadataChanged(VolumeInfo vol) {
     }
 
-    public void onDiskUnsupported(DiskInfo disk) {
+    public void onDiskScanned(DiskInfo disk, int volumeCount) {
     }
 }
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index f101352..747fb40 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -73,6 +73,11 @@
     public static final String PROP_FORCE_ADOPTABLE = "persist.fw.force_adoptable";
 
     /** {@hide} */
+    public static final String UUID_PRIVATE_INTERNAL = null;
+    /** {@hide} */
+    public static final String UUID_PRIMARY_PHYSICAL = "primary_physical";
+
+    /** {@hide} */
     public static final int FLAG_ALL_METADATA = 1 << 0;
 
     private final Context mContext;
@@ -89,7 +94,7 @@
         private static final int MSG_STORAGE_STATE_CHANGED = 1;
         private static final int MSG_VOLUME_STATE_CHANGED = 2;
         private static final int MSG_VOLUME_METADATA_CHANGED = 3;
-        private static final int MSG_DISK_UNSUPPORTED = 4;
+        private static final int MSG_DISK_SCANNED = 4;
 
         final StorageEventListener mCallback;
         final Handler mHandler;
@@ -116,8 +121,8 @@
                     mCallback.onVolumeMetadataChanged((VolumeInfo) args.arg1);
                     args.recycle();
                     return true;
-                case MSG_DISK_UNSUPPORTED:
-                    mCallback.onDiskUnsupported((DiskInfo) args.arg1);
+                case MSG_DISK_SCANNED:
+                    mCallback.onDiskScanned((DiskInfo) args.arg1, args.argi2);
                     args.recycle();
                     return true;
             }
@@ -156,10 +161,11 @@
         }
 
         @Override
-        public void onDiskUnsupported(DiskInfo disk) {
+        public void onDiskScanned(DiskInfo disk, int volumeCount) {
             final SomeArgs args = SomeArgs.obtain();
             args.arg1 = disk;
-            mHandler.obtainMessage(MSG_DISK_UNSUPPORTED, args).sendToTarget();
+            args.argi2 = volumeCount;
+            mHandler.obtainMessage(MSG_DISK_SCANNED, args).sendToTarget();
         }
     }
 
@@ -534,17 +540,26 @@
     /** {@hide} */
     public @Nullable String getBestVolumeDescription(VolumeInfo vol) {
         String descrip = vol.getDescription();
-
         if (vol.disk != null) {
             if (TextUtils.isEmpty(descrip)) {
                 descrip = vol.disk.getDescription();
             }
         }
-
         return descrip;
     }
 
     /** {@hide} */
+    public @Nullable VolumeInfo getPrimaryPhysicalVolume() {
+        final List<VolumeInfo> vols = getVolumes();
+        for (VolumeInfo vol : vols) {
+            if (vol.isPrimaryPhysical()) {
+                return vol;
+            }
+        }
+        return null;
+    }
+
+    /** {@hide} */
     public void mount(String volId) {
         try {
             mMountService.mount(volId);
@@ -628,6 +643,24 @@
     }
 
     /** {@hide} */
+    public String getPrimaryStorageUuid() {
+        try {
+            return mMountService.getPrimaryStorageUuid();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
+    public void setPrimaryStorageUuid(String volumeUuid) {
+        try {
+            mMountService.setPrimaryStorageUuid(volumeUuid);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
     public @Nullable StorageVolume getStorageVolume(File file) {
         return getStorageVolume(getVolumeList(), file);
     }
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index f3498d5..4e9cfc7 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -242,6 +242,10 @@
         return (mountFlags & MOUNT_FLAG_PRIMARY) != 0;
     }
 
+    public boolean isPrimaryPhysical() {
+        return isPrimary() && (getType() == TYPE_PUBLIC);
+    }
+
     public boolean isVisible() {
         return (mountFlags & MOUNT_FLAG_VISIBLE) != 0;
     }
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 414b7bc..b692a18 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -24,6 +24,12 @@
  */
 public class Preconditions {
 
+    public static void checkArgument(boolean expression) {
+        if (!expression) {
+            throw new IllegalArgumentException();
+        }
+    }
+
     /**
      * Ensures that an object reference passed as a parameter to the calling
      * method is not null.
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 279bfbf..baa772e 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -1587,91 +1587,13 @@
         }
     }
 
-    private class PackageMoveObserver extends IPackageMoveObserver.Stub {
-        public int returnCode;
-
-        private boolean doneFlag = false;
-
-        public String packageName;
-
-        public PackageMoveObserver(String pkgName) {
-            packageName = pkgName;
-        }
-
-        public void packageMoved(String packageName, int returnCode) {
-            Log.i("DEBUG_MOVE::", "pkg = " + packageName + ", " + "ret = " + returnCode);
-            if (!packageName.equals(this.packageName)) {
-                return;
-            }
-            synchronized (this) {
-                this.returnCode = returnCode;
-                doneFlag = true;
-                notifyAll();
-            }
-        }
-
-        public boolean isDone() {
-            return doneFlag;
-        }
-    }
-
     public boolean invokeMovePackage(String pkgName, int flags, GenericReceiver receiver)
             throws Exception {
-        PackageMoveObserver observer = new PackageMoveObserver(pkgName);
-        final boolean received = false;
-        mContext.registerReceiver(receiver, receiver.filter);
-        try {
-            // Wait on observer
-            synchronized (observer) {
-                synchronized (receiver) {
-                    getPm().movePackage(pkgName, observer, flags);
-                    long waitTime = 0;
-                    while ((!observer.isDone()) && (waitTime < MAX_WAIT_TIME)) {
-                        observer.wait(WAIT_TIME_INCR);
-                        waitTime += WAIT_TIME_INCR;
-                    }
-                    if (!observer.isDone()) {
-                        throw new Exception("Timed out waiting for pkgmove callback");
-                    }
-                    if (observer.returnCode != PackageManager.MOVE_SUCCEEDED) {
-                        return false;
-                    }
-                    // Verify we received the broadcast
-                    waitTime = 0;
-                    while ((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME)) {
-                        receiver.wait(WAIT_TIME_INCR);
-                        waitTime += WAIT_TIME_INCR;
-                    }
-                    if (!receiver.isDone()) {
-                        throw new Exception("Timed out waiting for MOVE notifications");
-                    }
-                    return receiver.received;
-                }
-            }
-        } finally {
-            mContext.unregisterReceiver(receiver);
-        }
+        throw new UnsupportedOperationException();
     }
 
     private boolean invokeMovePackageFail(String pkgName, int flags, int errCode) throws Exception {
-        PackageMoveObserver observer = new PackageMoveObserver(pkgName);
-        try {
-            // Wait on observer
-            synchronized (observer) {
-                getPm().movePackage(pkgName, observer, flags);
-                long waitTime = 0;
-                while ((!observer.isDone()) && (waitTime < MAX_WAIT_TIME)) {
-                    observer.wait(WAIT_TIME_INCR);
-                    waitTime += WAIT_TIME_INCR;
-                }
-                if (!observer.isDone()) {
-                    throw new Exception("Timed out waiting for pkgmove callback");
-                }
-                assertEquals(errCode, observer.returnCode);
-            }
-        } finally {
-        }
-        return true;
+        throw new UnsupportedOperationException();
     }
 
     private int getDefaultInstallLoc() {
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index f88802a..89a7173 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -232,7 +232,12 @@
         public static final int FstrimCompleted                = 700;
     }
 
+    private static final int VERSION_INIT = 1;
+    private static final int VERSION_ADD_PRIMARY = 2;
+
     private static final String TAG_VOLUMES = "volumes";
+    private static final String ATTR_VERSION = "version";
+    private static final String ATTR_PRIMARY_STORAGE_UUID = "primaryStorageUuid";
     private static final String TAG_VOLUME = "volume";
     private static final String ATTR_TYPE = "type";
     private static final String ATTR_FS_UUID = "fsUuid";
@@ -302,6 +307,8 @@
     /** Map from UUID to metadata */
     @GuardedBy("mLock")
     private ArrayMap<String, VolumeMetadata> mMetadata = new ArrayMap<>();
+    @GuardedBy("mLock")
+    private String mPrimaryStorageUuid;
 
     /** Map from disk ID to latches */
     @GuardedBy("mLock")
@@ -943,22 +950,25 @@
     }
 
     private void onDiskScannedLocked(DiskInfo disk) {
+        final Intent intent = new Intent(DiskInfo.ACTION_DISK_SCANNED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                android.Manifest.permission.WRITE_MEDIA_STORAGE);
+
         final CountDownLatch latch = mDiskScanLatches.remove(disk.id);
         if (latch != null) {
             latch.countDown();
         }
 
-        boolean empty = true;
+        int volumeCount = 0;
         for (int i = 0; i < mVolumes.size(); i++) {
             final VolumeInfo vol = mVolumes.valueAt(i);
             if (Objects.equals(disk.id, vol.getDiskId())) {
-                empty = false;
+                volumeCount++;
             }
         }
 
-        if (empty) {
-            mCallbacks.notifyDiskUnsupported(disk);
-        }
+        mCallbacks.notifyDiskScanned(disk, volumeCount);
     }
 
     private void onVolumeCreatedLocked(VolumeInfo vol) {
@@ -1022,8 +1032,8 @@
         if (isBroadcastWorthy(vol)) {
             final Intent intent = new Intent(VolumeInfo.ACTION_VOLUME_STATE_CHANGED);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-            // TODO: require receiver to hold permission
-            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                    android.Manifest.permission.WRITE_MEDIA_STORAGE);
         }
 
         final String oldStateEnv = VolumeInfo.getEnvironmentForState(oldState);
@@ -1166,7 +1176,21 @@
             while ((type = in.next()) != END_DOCUMENT) {
                 if (type == START_TAG) {
                     final String tag = in.getName();
-                    if (TAG_VOLUME.equals(tag)) {
+                    if (TAG_VOLUMES.equals(tag)) {
+                        final int version = readIntAttribute(in, ATTR_VERSION, VERSION_INIT);
+                        if (version >= VERSION_ADD_PRIMARY) {
+                            mPrimaryStorageUuid = readStringAttribute(in,
+                                    ATTR_PRIMARY_STORAGE_UUID);
+                        } else {
+                            if (SystemProperties.getBoolean(StorageManager.PROP_PRIMARY_PHYSICAL,
+                                    false)) {
+                                mPrimaryStorageUuid = StorageManager.UUID_PRIMARY_PHYSICAL;
+                            } else {
+                                mPrimaryStorageUuid = StorageManager.UUID_PRIVATE_INTERNAL;
+                            }
+                        }
+
+                    } else if (TAG_VOLUME.equals(tag)) {
                         final VolumeMetadata meta = VolumeMetadata.read(in);
                         mMetadata.put(meta.fsUuid, meta);
                     }
@@ -1192,6 +1216,8 @@
             out.setOutput(fos, "utf-8");
             out.startDocument(null, true);
             out.startTag(null, TAG_VOLUMES);
+            writeIntAttribute(out, ATTR_VERSION, VERSION_ADD_PRIMARY);
+            writeStringAttribute(out, ATTR_PRIMARY_STORAGE_UUID, mPrimaryStorageUuid);
             final int size = mMetadata.size();
             for (int i = 0; i < size; i++) {
                 final VolumeMetadata meta = mMetadata.valueAt(i);
@@ -1398,6 +1424,24 @@
     }
 
     @Override
+    public String getPrimaryStorageUuid() throws RemoteException {
+        synchronized (mLock) {
+            return mPrimaryStorageUuid;
+        }
+    }
+
+    @Override
+    public void setPrimaryStorageUuid(String volumeUuid) throws RemoteException {
+        synchronized (mLock) {
+            Slog.d(TAG, "Changing primary storage UUID to " + volumeUuid);
+            mPrimaryStorageUuid = volumeUuid;
+            writeMetadataLocked();
+
+            // TODO: reevaluate all volumes we know about!
+        }
+    }
+
+    @Override
     public int[] getStorageUsers(String path) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
         waitForReady();
@@ -2700,7 +2744,7 @@
         private static final int MSG_STORAGE_STATE_CHANGED = 1;
         private static final int MSG_VOLUME_STATE_CHANGED = 2;
         private static final int MSG_VOLUME_METADATA_CHANGED = 3;
-        private static final int MSG_DISK_UNSUPPORTED = 4;
+        private static final int MSG_DISK_SCANNED = 4;
 
         private final RemoteCallbackList<IMountServiceListener>
                 mCallbacks = new RemoteCallbackList<>();
@@ -2748,8 +2792,8 @@
                     callback.onVolumeMetadataChanged((VolumeInfo) args.arg1);
                     break;
                 }
-                case MSG_DISK_UNSUPPORTED: {
-                    callback.onDiskUnsupported((DiskInfo) args.arg1);
+                case MSG_DISK_SCANNED: {
+                    callback.onDiskScanned((DiskInfo) args.arg1, args.argi2);
                     break;
                 }
             }
@@ -2777,10 +2821,11 @@
             obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget();
         }
 
-        private void notifyDiskUnsupported(DiskInfo disk) {
+        private void notifyDiskScanned(DiskInfo disk, int volumeCount) {
             final SomeArgs args = SomeArgs.obtain();
             args.arg1 = disk;
-            obtainMessage(MSG_DISK_UNSUPPORTED, args).sendToTarget();
+            args.argi2 = volumeCount;
+            obtainMessage(MSG_DISK_SCANNED, args).sendToTarget();
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 89fa320..a406175 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -717,7 +717,7 @@
         } else {
             final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid);
             if (vol != null && vol.type == VolumeInfo.TYPE_PRIVATE
-                    && vol.state == VolumeInfo.STATE_MOUNTED) {
+                    && vol.isMountedWritable()) {
                 return new File(vol.path, "app");
             } else {
                 throw new FileNotFoundException("Failed to find volume for UUID " + volumeUuid);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1c339f5..bd22524 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -49,12 +49,10 @@
 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.MOVE_EXTERNAL_MEDIA;
 import static android.content.pm.PackageManager.MOVE_FAILED_DOESNT_EXIST;
 import static android.content.pm.PackageManager.MOVE_FAILED_INTERNAL_ERROR;
 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;
@@ -144,6 +142,7 @@
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SELinux;
 import android.os.ServiceManager;
@@ -174,6 +173,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 import android.util.Xml;
 import android.view.Display;
 
@@ -189,11 +189,14 @@
 import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.content.PackageHelper;
 import com.android.internal.os.IParcelFileDescriptorFactory;
+import com.android.internal.os.SomeArgs;
 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.internal.util.Preconditions;
 import com.android.server.EventLogTags;
+import com.android.server.FgThread;
 import com.android.server.IntentResolver;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
@@ -237,6 +240,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
 /**
@@ -504,6 +508,10 @@
     final PackageInstallerService mInstallerService;
 
     private final PackageDexOptimizer mPackageDexOptimizer;
+
+    private AtomicInteger mNextMoveId = new AtomicInteger();
+    private final MoveCallbacks mMoveCallbacks;
+
     // Cache of users who need badging.
     SparseBooleanArray mUserNeedsBadging = new SparseBooleanArray();
 
@@ -1698,6 +1706,7 @@
 
         mInstaller = installer;
         mPackageDexOptimizer = new PackageDexOptimizer(this);
+        mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
 
         getDefaultDisplayMetrics(context, mMetrics);
 
@@ -14137,49 +14146,25 @@
     }
 
     @Override
-    public void movePackage(final String packageName, final IPackageMoveObserver observer,
-            final int flags) {
+    public int movePackage(final String packageName, final String volumeUuid) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null);
 
-        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);
-        }
-
+        final int moveId = mNextMoveId.getAndIncrement();
         try {
-            movePackageInternal(packageName, null, installFlags, false, observer);
+            movePackageInternal(packageName, volumeUuid, moveId);
         } catch (PackageManagerException e) {
             Slog.d(TAG, "Failed to move " + packageName, e);
-            try {
-                observer.packageMoved(packageName, e.error);
-            } catch (RemoteException ignored) {
-            }
+            mMoveCallbacks.notifyStatusChanged(moveId, PackageManager.MOVE_FAILED_INTERNAL_ERROR);
         }
+        return moveId;
     }
 
-    @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 {
+    private void movePackageInternal(final String packageName, final String volumeUuid,
+            final int moveId) throws PackageManagerException {
         final UserHandle user = new UserHandle(UserHandle.getCallingUserId());
+        final PackageManager pm = mContext.getPackageManager();
 
+        final boolean currentAsec;
         final String currentVolumeUuid;
         final File codeFile;
         final String installerPackageName;
@@ -14205,8 +14190,13 @@
 
             // TODO: yell if already in desired location
 
+            mMoveCallbacks.notifyStarted(moveId,
+                    String.valueOf(pm.getApplicationLabel(pkg.applicationInfo)));
+
             pkg.mOperationPending = true;
 
+            currentAsec = pkg.applicationInfo.isForwardLocked()
+                    || pkg.applicationInfo.isExternalAsec();
             currentVolumeUuid = ps.volumeUuid;
             codeFile = new File(pkg.codePath);
             installerPackageName = ps.installerPackageName;
@@ -14215,10 +14205,36 @@
             seinfo = pkg.applicationInfo.seinfo;
         }
 
-        if (andData) {
-            Slog.d(TAG, "Moving " + packageName + " private data from " + currentVolumeUuid + " to "
-                    + volumeUuid);
+        int installFlags;
+        final boolean moveData;
+
+        if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid)) {
+            installFlags = INSTALL_INTERNAL;
+            moveData = !currentAsec;
+        } else if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) {
+            installFlags = INSTALL_EXTERNAL;
+            moveData = false;
+        } else {
+            final StorageManager storage = mContext.getSystemService(StorageManager.class);
+            final VolumeInfo volume = storage.findVolumeByUuid(volumeUuid);
+            if (volume == null || volume.getType() != VolumeInfo.TYPE_PRIVATE
+                    || !volume.isMountedWritable()) {
+                throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR,
+                        "Move location not mounted private volume");
+            }
+
+            Preconditions.checkState(!currentAsec);
+
+            installFlags = INSTALL_INTERNAL;
+            moveData = true;
+        }
+
+        Slog.d(TAG, "Moving " + packageName + " from " + currentVolumeUuid + " to " + volumeUuid);
+        mMoveCallbacks.notifyStatusChanged(moveId, 10, -1);
+
+        if (moveData) {
             synchronized (mInstallLock) {
+                // TODO: split this into separate copy and delete operations
                 if (mInstaller.moveUserDataDirs(currentVolumeUuid, volumeUuid, packageName, appId,
                         seinfo) != 0) {
                     synchronized (mPackages) {
@@ -14234,6 +14250,8 @@
             }
         }
 
+        mMoveCallbacks.notifyStatusChanged(moveId, 50);
+
         final IPackageInstallObserver2 installObserver = new IPackageInstallObserver2.Stub() {
             @Override
             public void onUserActionRequired(Intent intent) throws RemoteException {
@@ -14259,13 +14277,16 @@
                 final int status = PackageManager.installStatusToPublicStatus(returnCode);
                 switch (status) {
                     case PackageInstaller.STATUS_SUCCESS:
-                        observer.packageMoved(packageName, PackageManager.MOVE_SUCCEEDED);
+                        mMoveCallbacks.notifyStatusChanged(moveId,
+                                PackageManager.MOVE_SUCCEEDED);
                         break;
                     case PackageInstaller.STATUS_FAILURE_STORAGE:
-                        observer.packageMoved(packageName, PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE);
+                        mMoveCallbacks.notifyStatusChanged(moveId,
+                                PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE);
                         break;
                     default:
-                        observer.packageMoved(packageName, PackageManager.MOVE_FAILED_INTERNAL_ERROR);
+                        mMoveCallbacks.notifyStatusChanged(moveId,
+                                PackageManager.MOVE_FAILED_INTERNAL_ERROR);
                         break;
                 }
             }
@@ -14283,6 +14304,39 @@
     }
 
     @Override
+    public int movePrimaryStorage(String volumeUuid) throws RemoteException {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null);
+
+        final int moveId = mNextMoveId.getAndIncrement();
+
+        // TODO: ask mountservice to take down both, connect over to DCS to
+        // migrate, and then bring up new storage
+
+        return moveId;
+    }
+
+    @Override
+    public int getMoveStatus(int moveId) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);
+        return mMoveCallbacks.mLastStatus.get(moveId);
+    }
+
+    @Override
+    public void registerMoveCallback(IPackageMoveObserver callback) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);
+        mMoveCallbacks.register(callback);
+    }
+
+    @Override
+    public void unregisterMoveCallback(IPackageMoveObserver callback) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);
+        mMoveCallbacks.unregister(callback);
+    }
+
+    @Override
     public boolean setInstallLocation(int loc) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS,
                 null);
@@ -14605,4 +14659,82 @@
             }
         }
     }
+
+    private static class MoveCallbacks extends Handler {
+        private static final int MSG_STARTED = 1;
+        private static final int MSG_STATUS_CHANGED = 2;
+
+        private final RemoteCallbackList<IPackageMoveObserver>
+                mCallbacks = new RemoteCallbackList<>();
+
+        private final SparseIntArray mLastStatus = new SparseIntArray();
+
+        public MoveCallbacks(Looper looper) {
+            super(looper);
+        }
+
+        public void register(IPackageMoveObserver callback) {
+            mCallbacks.register(callback);
+        }
+
+        public void unregister(IPackageMoveObserver callback) {
+            mCallbacks.unregister(callback);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final SomeArgs args = (SomeArgs) msg.obj;
+            final int n = mCallbacks.beginBroadcast();
+            for (int i = 0; i < n; i++) {
+                final IPackageMoveObserver callback = mCallbacks.getBroadcastItem(i);
+                try {
+                    invokeCallback(callback, msg.what, args);
+                } catch (RemoteException ignored) {
+                }
+            }
+            mCallbacks.finishBroadcast();
+            args.recycle();
+        }
+
+        private void invokeCallback(IPackageMoveObserver callback, int what, SomeArgs args)
+                throws RemoteException {
+            switch (what) {
+                case MSG_STARTED: {
+                    callback.onStarted(args.argi1, (String) args.arg2);
+                    break;
+                }
+                case MSG_STATUS_CHANGED: {
+                    callback.onStatusChanged(args.argi1, args.argi2, (long) args.arg3);
+                    break;
+                }
+            }
+        }
+
+        private void notifyStarted(int moveId, String title) {
+            Slog.v(TAG, "Move " + moveId + " started with title " + title);
+
+            final SomeArgs args = SomeArgs.obtain();
+            args.argi1 = moveId;
+            args.arg2 = title;
+            obtainMessage(MSG_STARTED, args).sendToTarget();
+        }
+
+        private void notifyStatusChanged(int moveId, int status) {
+            notifyStatusChanged(moveId, status, -1);
+        }
+
+        private void notifyStatusChanged(int moveId, int status, long estMillis) {
+            Slog.v(TAG, "Move " + moveId + " status " + status);
+
+            final SomeArgs args = SomeArgs.obtain();
+            args.argi1 = moveId;
+            args.argi2 = status;
+            args.arg3 = estMillis;
+            obtainMessage(MSG_STATUS_CHANGED, args).sendToTarget();
+
+            synchronized (mLastStatus) {
+                mLastStatus.put(moveId, status);
+            }
+        }
+    }
 }
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 276b713..9efea0d 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -16,7 +16,6 @@
 
 package android.test.mock;
 
-import android.annotation.NonNull;
 import android.app.PackageInstallObserver;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -29,7 +28,6 @@
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.IPackageInstallObserver;
-import android.content.pm.IPackageMoveObserver;
 import android.content.pm.IPackageStatsObserver;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.IntentFilterVerificationInfo;
@@ -46,11 +44,14 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.VerificationParams;
 import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.PackageManager.MoveCallback;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.Handler;
+import android.os.ResultReceiver;
 import android.os.UserHandle;
 import android.os.storage.VolumeInfo;
 
@@ -487,36 +488,65 @@
         throw new UnsupportedOperationException();
     }
 
-    /** {@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();
-    }
-
-    /** {@hide} */
-    @Override
-    public @NonNull VolumeInfo getApplicationCurrentVolume(ApplicationInfo app) {
-        throw new UnsupportedOperationException();
-    }
-
-    /** {@hide} */
-    @Override
-    public @NonNull List<VolumeInfo> getApplicationCandidateVolumes(ApplicationInfo app) {
-        throw new UnsupportedOperationException();
-    }
-
     @Override
     public String getInstallerPackageName(String packageName) {
         throw new UnsupportedOperationException();
     }
 
+    /** {@hide} */
+    @Override
+    public int getMoveStatus(int moveId) {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@hide} */
+    @Override
+    public void registerMoveCallback(MoveCallback callback, Handler handler) {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@hide} */
+    @Override
+    public void unregisterMoveCallback(MoveCallback callback) {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@hide} */
+    @Override
+    public int movePackage(String packageName, VolumeInfo vol) {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@hide} */
+    @Override
+    public VolumeInfo getPackageCurrentVolume(ApplicationInfo app) {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@hide} */
+    @Override
+    public List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app) {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@hide} */
+    @Override
+    public int movePrimaryStorage(VolumeInfo vol) {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@hide} */
+    @Override
+    public VolumeInfo getPrimaryStorageCurrentVolume() {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@hide} */
+    @Override
+    public List<VolumeInfo> getPrimaryStorageCandidateVolumes() {
+        throw new UnsupportedOperationException();
+    }
+
     /**
      * @hide - to match hiding in superclass
      */