Adopted Storage: Prepare user storage before move

Moving "primary storage" between internal and adoptable storage involves
accessing the CE and DE storage of all users.  Similarly, moving an app
involves accessing the CE and DE storage of all users who have the app
installed.  CE and DE storage must be unlocked and prepared on both
volumes for all these users.  Currently this as handled as follows:

- Before starting either operation, Settings requires LSKF verification
  for each user whose CE storage is currently locked.

- For "move app", PMS prepares CE and DE storage on the target volume
  for users who don't have a CE directory on the target volume.

This isn't enough, however.  First, because unlocking of adoptable
storage is done by SMS.prepareUserStorage and not by SMS.unlockUserKey,
LSKF verification doesn't do it unless the user is already running.
Therefore, CE adoptable storage isn't unlocked for stopped users.
Second, since a stopped user might never have had adoptable storage
prepared at all, and "move primary storage" skips the prepare, the
target directory /mnt/expand/${volume_uuid}/media/${user_id} can end up
with the wrong SELinux label, encryption policy, and other metadata.

Fix both issues by making PMS and SMS call prepareUserStorage on the
source and target volumes for all relevant users for both types of move.

Test: manual
Test A: Check move storage works as expected:
    1. Create multiple users via Settings
    2. Switch to one user and set a lockscreen credentail
    3. Switch back to system user
    4. Execute adb shell am stop-user <user_id>
    5. Format SD card as adopted storage.
    6. Opt in for Move storage
    7. Check that the storage is prepared before moving the storage
       by checking the logs:
       adb logcat | grep fscrypt_prepare_user_storage
    8. Also check move storage is successful

Test B: Check move app works as expected:
    1. Create multiple users apart from system user
    2. Make ExternalLocTestApp and install for all the
       users.
    3. Go to Settings > All Apps >  ExternalLocTestApp > Storage & Cache
    4. Tap on Change under Storage User and Select Adopted storage.
    5. Check that the process is not failing

Change-Id: I8aef053968af957d52261bfe86d8fea64dcae6e7
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index 059bd84..22e8251 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.pm.UserInfo;
 import android.os.IVold;
 
 import java.util.List;
@@ -169,4 +170,19 @@
      */
     public abstract void registerCloudProviderChangeListener(
             @NonNull CloudProviderChangeListener listener);
+
+    /**
+     * Prepares user data directories before moving storage or apps. This is required as adoptable
+     * storage unlock is tied to the prepare user data and storage needs to be unlocked before
+     * performing any operations on it. This will also create user data directories before
+     * initiating the move operations, which essential for ensuring the directories to have correct
+     * SELinux labels and permissions.
+     *
+     * @param fromVolumeUuid the source volume UUID from which content needs to be transferred
+     * @param toVolumeUuid the destination volume UUID to which contents are to be transferred
+     * @param users a list of users for whom to prepare storage
+     */
+    public abstract void prepareUserStorageForMove(String fromVolumeUuid, String toVolumeUuid,
+            List<UserInfo> users);
+
 }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 08d8be8..08c71c3 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1377,6 +1377,16 @@
         return mVold.supportsBlockCheckpoint();
     }
 
+    private void prepareUserStorageForMoveInternal(String fromVolumeUuid, String toVolumeUuid,
+            List<UserInfo> users) throws Exception {
+
+        final int flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
+        for (UserInfo user : users) {
+            prepareUserStorageInternal(fromVolumeUuid, user.id, user.serialNumber, flags);
+            prepareUserStorageInternal(toVolumeUuid, user.id, user.serialNumber, flags);
+        }
+    }
+
     @Override
     public void onAwakeStateChanged(boolean isAwake) {
         // Ignored
@@ -2986,6 +2996,7 @@
 
         final VolumeInfo from;
         final VolumeInfo to;
+        final List<UserInfo> users;
 
         synchronized (mLock) {
             if (Objects.equals(mPrimaryStorageUuid, volumeUuid)) {
@@ -2999,7 +3010,7 @@
             mMoveTargetUuid = volumeUuid;
 
             // We need all the users unlocked to move their primary storage
-            final List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
+            users = mContext.getSystemService(UserManager.class).getUsers();
             for (UserInfo user : users) {
                 if (StorageManager.isFileEncrypted() && !isUserKeyUnlocked(user.id)) {
                     Slog.w(TAG, "Failing move due to locked user " + user.id);
@@ -3035,6 +3046,19 @@
             }
         }
 
+        // Prepare the storage before move, this is required to unlock adoptable storage (as the
+        // keys are tied to prepare user data step) & also is required for the destination files to
+        // end up with the correct SELinux labels and encryption policies for directories
+        try {
+            prepareUserStorageForMoveInternal(mPrimaryStorageUuid, volumeUuid, users);
+        } catch (Exception e) {
+            Slog.w(TAG, "Failing move due to failure on prepare user data", e);
+            synchronized (mLock) {
+                onMoveStatusLocked(PackageManager.MOVE_FAILED_INTERNAL_ERROR);
+            }
+            return;
+        }
+
         try {
             mVold.moveStorage(from.id, to.id, new IVoldTaskListener.Stub() {
                 @Override
@@ -5024,5 +5048,16 @@
             mCloudProviderChangeListeners.add(listener);
             mHandler.obtainMessage(H_CLOUD_MEDIA_PROVIDER_CHANGED, listener);
         }
+
+        @Override
+        public void prepareUserStorageForMove(String fromVolumeUuid, String toVolumeUuid,
+                List<UserInfo> users) {
+            try {
+                prepareUserStorageForMoveInternal(fromVolumeUuid, toVolumeUuid, users);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
     }
 }
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index 1f96205..139d9cf 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -48,8 +48,8 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
+import android.os.storage.StorageManagerInternal;
 import android.os.storage.VolumeInfo;
-import android.text.TextUtils;
 import android.util.MathUtils;
 import android.util.Slog;
 import android.util.SparseIntArray;
@@ -63,6 +63,7 @@
 import com.android.server.pm.pkg.PackageStateUtils;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -223,9 +224,7 @@
         }
 
         try {
-            for (int index = 0; index < installedUserIds.length; index++) {
-                prepareUserDataForVolumeIfRequired(volumeUuid, installedUserIds[index], storage);
-            }
+            prepareUserStorageForMove(currentVolumeUuid, volumeUuid, installedUserIds);
         } catch (RuntimeException e) {
             freezer.close();
             throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR,
@@ -380,27 +379,20 @@
         return true;
     }
 
-    private void prepareUserDataForVolumeIfRequired(String volumeUuid, int userId,
-            StorageManager storageManager) {
-        if (TextUtils.isEmpty(volumeUuid)
-                || doesDataDirectoryExistForUser(volumeUuid, userId)) {
-            return;
-        }
+    private void prepareUserStorageForMove(String fromVolumeUuid, String toVolumeUuid,
+            int[] userIds) {
         if (DEBUG_INSTALL) {
-            Slog.d(TAG, "Preparing user directories for user u" + userId + " for UUID "
-                    + volumeUuid);
+            Slog.d(TAG, "Preparing user directories before moving app, from UUID " + fromVolumeUuid
+                    + " to UUID " + toVolumeUuid);
         }
-        final UserInfo user = mPm.mUserManager.getUserInfo(userId);
-        if (user == null) return;
-        // This call is same as StorageEventHelper#loadPrivatePackagesInner which prepares
-        // the storage before reconciling apps
-        storageManager.prepareUserStorage(volumeUuid, user.id, user.serialNumber,
-                StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
-    }
-
-    private boolean doesDataDirectoryExistForUser(String uuid, int userId) {
-        final File userDirectoryFile = Environment.getDataUserCeDirectory(uuid, userId);
-        return userDirectoryFile != null && userDirectoryFile.exists();
+        final StorageManagerInternal smInternal =
+                mPm.mInjector.getLocalService(StorageManagerInternal.class);
+        final ArrayList<UserInfo> users = new ArrayList<>();
+        for (int userId : userIds) {
+            final UserInfo user = mPm.mUserManager.getUserInfo(userId);
+            users.add(user);
+        }
+        smInternal.prepareUserStorageForMove(fromVolumeUuid, toVolumeUuid, users);
     }
 
     public static class MoveCallbacks extends Handler {