Support moving apps to expanded storage.

Start deriving the data path for apps based on the volume UUID where
the app lives.  This path is used for all higher-level APIs, giving
us a clean place to switch app storage.

When parsing a package, keep track of the volume UUID where it lives
and update PackageSetting once installed.  For now continue treating
moves as installs, but we'll eventually clean this up to avoid the
additional dexopt pass.  Wire up move to use the new installd command
to move private data between devices.

Cache LoadedApk only for the current user, since otherwise the data
dir points at the wrong path.

Bug: 19993667
Change-Id: I53336e3b147d5fd3130e6800869af172b628da37
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ed05321..ed7d11b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -220,6 +220,7 @@
     // which means this lock gets held while the activity and window managers
     // holds their own lock.  Thus you MUST NEVER call back into the activity manager
     // or window manager or anything that depends on them while holding this lock.
+    // These LoadedApk are only valid for the userId that we're running as.
     final ArrayMap<String, WeakReference<LoadedApk>> mPackages
             = new ArrayMap<String, WeakReference<LoadedApk>>();
     final ArrayMap<String, WeakReference<LoadedApk>> mResourcePackages
@@ -1705,13 +1706,18 @@
 
     public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
             int flags, int userId) {
+        final boolean differentUser = (UserHandle.myUserId() != userId);
         synchronized (mResourcesManager) {
             WeakReference<LoadedApk> ref;
-            if ((flags&Context.CONTEXT_INCLUDE_CODE) != 0) {
+            if (differentUser) {
+                // Caching not supported across users
+                ref = null;
+            } else if ((flags & Context.CONTEXT_INCLUDE_CODE) != 0) {
                 ref = mPackages.get(packageName);
             } else {
                 ref = mResourcePackages.get(packageName);
             }
+
             LoadedApk packageInfo = ref != null ? ref.get() : null;
             //Slog.i(TAG, "getPackageInfo " + packageName + ": " + packageInfo);
             //if (packageInfo != null) Slog.i(TAG, "isUptoDate " + packageInfo.mResDir
@@ -1791,13 +1797,18 @@
     private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
             ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
             boolean registerPackage) {
+        final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
         synchronized (mResourcesManager) {
             WeakReference<LoadedApk> ref;
-            if (includeCode) {
+            if (differentUser) {
+                // Caching not supported across users
+                ref = null;
+            } else if (includeCode) {
                 ref = mPackages.get(aInfo.packageName);
             } else {
                 ref = mResourcePackages.get(aInfo.packageName);
             }
+
             LoadedApk packageInfo = ref != null ? ref.get() : null;
             if (packageInfo == null || (packageInfo.mResources != null
                     && !packageInfo.mResources.getAssets().isUpToDate())) {
@@ -1816,7 +1827,9 @@
                             getSystemContext().mPackageInfo.getClassLoader());
                 }
 
-                if (includeCode) {
+                if (differentUser) {
+                    // Caching not supported across users
+                } else if (includeCode) {
                     mPackages.put(aInfo.packageName,
                             new WeakReference<LoadedApk>(packageInfo));
                 } else {
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 83c6c2b..9604789 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -18,7 +18,6 @@
 
 import android.text.TextUtils;
 import android.util.ArrayMap;
-
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -46,6 +45,7 @@
 import android.view.DisplayAdjustments;
 import android.view.Display;
 import android.os.SystemProperties;
+
 import dalvik.system.VMRuntime;
 
 import java.io.File;
@@ -136,10 +136,6 @@
         mSplitAppDirs = aInfo.splitSourceDirs;
         mSplitResDirs = aInfo.uid == myUid ? aInfo.splitSourceDirs : aInfo.splitPublicSourceDirs;
         mOverlayDirs = aInfo.resourceDirs;
-        if (!UserHandle.isSameUser(aInfo.uid, myUid) && !Process.isIsolated()) {
-            aInfo.dataDir = PackageManager.getDataDirForUser(UserHandle.getUserId(myUid),
-                    mPackageName);
-        }
         mSharedLibraries = aInfo.sharedLibraryFiles;
         mDataDir = aInfo.dataDir;
         mDataDirFile = mDataDir != null ? new File(mDataDir) : null;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 303b709..491fc94 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -44,6 +44,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.storage.VolumeInfo;
+import android.text.TextUtils;
 import android.util.AndroidException;
 
 import com.android.internal.util.ArrayUtils;
@@ -4149,16 +4150,19 @@
     public abstract @NonNull PackageInstaller getPackageInstaller();
 
     /**
-     * Returns the data directory for a particular user and package, given the uid of the package.
-     * @param uid uid of the package, including the userId and appId
-     * @param packageName name of the package
-     * @return the user-specific data directory for the package
+     * Returns the data directory for a particular package and user.
+     *
      * @hide
      */
-    public static String getDataDirForUser(int userId, String packageName) {
+    public static File getDataDirForUser(String volumeUuid, String packageName, int userId) {
         // TODO: This should be shared with Installer's knowledge of user directory
-        return Environment.getDataDirectory().toString() + "/user/" + userId
-                + "/" + packageName;
+        final File base;
+        if (TextUtils.isEmpty(volumeUuid)) {
+            base = Environment.getDataDirectory();
+        } else {
+            base = new File("/mnt/expand/" + volumeUuid);
+        }
+        return new File(base, "user/" + userId + "/" + packageName);
     }
 
     /**
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 7523675..763a017 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -111,6 +111,9 @@
     /** File name in an APK for the Android manifest. */
     private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
 
+    /** Path prefix for apps on expanded storage */
+    private static final String MNT_EXPAND = "/mnt/expand/";
+
     /** @hide */
     public static class NewPermissionInfo {
         public final String name;
@@ -860,6 +863,12 @@
             throws PackageParserException {
         final String apkPath = apkFile.getAbsolutePath();
 
+        String volumeUuid = null;
+        if (apkPath.startsWith(MNT_EXPAND)) {
+            final int end = apkPath.indexOf('/', MNT_EXPAND.length());
+            volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
+        }
+
         mParseError = PackageManager.INSTALL_SUCCEEDED;
         mArchiveSourcePath = apkFile.getAbsolutePath();
 
@@ -882,6 +891,7 @@
                         apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
             }
 
+            pkg.volumeUuid = volumeUuid;
             pkg.baseCodePath = apkPath;
             pkg.mSignatures = null;
 
@@ -4206,6 +4216,8 @@
 
         // TODO: work towards making these paths invariant
 
+        public String volumeUuid;
+
         /**
          * Path where this package was found on disk. For monolithic packages
          * this is path to single base APK file; for cluster packages this is
@@ -4727,7 +4739,8 @@
         ApplicationInfo ai = new ApplicationInfo(p.applicationInfo);
         if (userId != 0) {
             ai.uid = UserHandle.getUid(userId, ai.uid);
-            ai.dataDir = PackageManager.getDataDirForUser(userId, ai.packageName);
+            ai.dataDir = PackageManager.getDataDirForUser(ai.volumeUuid, ai.packageName, userId)
+                    .getAbsolutePath();
         }
         if ((flags & PackageManager.GET_META_DATA) != 0) {
             ai.metaData = p.mAppMetaData;
@@ -4755,7 +4768,8 @@
         ai = new ApplicationInfo(ai);
         if (userId != 0) {
             ai.uid = UserHandle.getUid(userId, ai.uid);
-            ai.dataDir = PackageManager.getDataDirForUser(userId, ai.packageName);
+            ai.dataDir = PackageManager.getDataDirForUser(ai.volumeUuid, ai.packageName, userId)
+                    .getAbsolutePath();
         }
         if (state.stopped) {
             ai.flags |= ApplicationInfo.FLAG_STOPPED;
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index a59581b..279bfbf 100644
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -576,14 +576,6 @@
             fail(pkgName + " shouldnt be installed");
         } catch (NameNotFoundException e) {
         }
-
-        UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        List<UserInfo> users = um.getUsers();
-        for (UserInfo user : users) {
-            String dataDir = PackageManager.getDataDirForUser(user.id, pkgName);
-            assertFalse("Application data directory should not exist: " + dataDir,
-                    new File(dataDir).exists());
-        }
     }
 
     class InstallParams {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7193384..ebf1930 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19608,8 +19608,8 @@
         if (info == null) return null;
         ApplicationInfo newInfo = new ApplicationInfo(info);
         newInfo.uid = applyUserId(info.uid, userId);
-        newInfo.dataDir = USER_DATA_DIR + userId + "/"
-                + info.packageName;
+        newInfo.dataDir = PackageManager.getDataDirForUser(info.volumeUuid, info.packageName,
+                userId).getAbsolutePath();
         return newInfo;
     }
 
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index ce31f98..a32363d 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -261,6 +261,22 @@
         return mInstaller.execute(builder.toString());
     }
 
+    public int moveUserDataDirs(String fromUuid, String toUuid, String packageName, int appId,
+            String seinfo) {
+        StringBuilder builder = new StringBuilder("mvuserdata");
+        builder.append(' ');
+        builder.append(escapeNull(fromUuid));
+        builder.append(' ');
+        builder.append(escapeNull(toUuid));
+        builder.append(' ');
+        builder.append(packageName);
+        builder.append(' ');
+        builder.append(appId);
+        builder.append(' ');
+        builder.append(seinfo);
+        return mInstaller.execute(builder.toString());
+    }
+
     @Deprecated
     public int clearUserData(String name, int userId) {
         return clearUserData(null, name, userId);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f5042ed..9465682 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -51,6 +51,7 @@
 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;
@@ -71,7 +72,6 @@
 import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
 
 import android.Manifest;
-import org.xmlpull.v1.XmlPullParser;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
 import android.app.AppGlobals;
@@ -202,6 +202,7 @@
 import com.android.server.pm.Settings.DatabaseVersion;
 import com.android.server.storage.DeviceStorageMonitorInternal;
 
+import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.BufferedInputStream;
@@ -2597,8 +2598,8 @@
                 pkg.applicationInfo.packageName = packageName;
                 pkg.applicationInfo.flags = ps.pkgFlags | ApplicationInfo.FLAG_IS_DATA_ONLY;
                 pkg.applicationInfo.privateFlags = ps.pkgPrivateFlags;
-                pkg.applicationInfo.dataDir =
-                        getDataPathForPackage(packageName, 0).getPath();
+                pkg.applicationInfo.dataDir = PackageManager.getDataDirForUser(ps.volumeUuid,
+                        packageName, userId).getAbsolutePath();
                 pkg.applicationInfo.primaryCpuAbi = ps.primaryCpuAbiString;
                 pkg.applicationInfo.secondaryCpuAbi = ps.secondaryCpuAbiString;
             }
@@ -5103,6 +5104,7 @@
         }
 
         // Set application objects path explicitly.
+        pkg.applicationInfo.volumeUuid = pkg.volumeUuid;
         pkg.applicationInfo.setCodePath(pkg.codePath);
         pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath);
         pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths);
@@ -5510,20 +5512,6 @@
         return true;
     }
 
-    private File getDataPathForPackage(String packageName, int userId) {
-        /*
-         * Until we fully support multiple users, return the directory we
-         * previously would have. The PackageManagerTests will need to be
-         * revised when this is changed back..
-         */
-        if (userId == 0) {
-            return new File(mAppDataDir, packageName);
-        } else {
-            return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId
-                + File.separator + packageName);
-        }
-    }
-
     private int createDataDirsLI(String packageName, int uid, String seinfo) {
         int[] users = sUserManager.getUserIds();
         int res = mInstaller.install(packageName, uid, uid, seinfo);
@@ -6043,7 +6031,8 @@
 
         } else {
             // This is a normal package, need to make its data directory.
-            dataPath = getDataPathForPackage(pkg.packageName, 0);
+            dataPath = PackageManager.getDataDirForUser(pkg.volumeUuid, pkg.packageName,
+                    UserHandle.USER_OWNER);
 
             boolean uidError = false;
             if (dataPath.exists()) {
@@ -10136,6 +10125,7 @@
                         pkg.splitCodePaths);
 
                 // Reflect the rename in app info
+                pkg.applicationInfo.volumeUuid = pkg.volumeUuid;
                 pkg.applicationInfo.setCodePath(pkg.codePath);
                 pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath);
                 pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths);
@@ -10426,6 +10416,7 @@
                     pkg.splitCodePaths);
 
             // Reflect the rename in app info
+            pkg.applicationInfo.volumeUuid = pkg.volumeUuid;
             pkg.applicationInfo.setCodePath(pkg.codePath);
             pkg.applicationInfo.setBaseCodePath(pkg.baseCodePath);
             pkg.applicationInfo.setSplitCodePaths(pkg.splitCodePaths);
@@ -10684,7 +10675,8 @@
         String pkgName = pkg.packageName;
 
         if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg);
-        boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists();
+        final boolean dataDirExists = PackageManager.getDataDirForUser(volumeUuid, pkgName,
+                UserHandle.USER_OWNER).exists();
         synchronized(mPackages) {
             if (mSettings.mRenamedPackages.containsKey(pkgName)) {
                 // A package with the same name is already installed, though
@@ -11056,7 +11048,6 @@
             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();
@@ -14175,11 +14166,12 @@
             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
+        final String currentVolumeUuid;
+        final File codeFile;
+        final String installerPackageName;
+        final String packageAbiOverride;
+        final int appId;
+        final String seinfo;
 
         // reader
         synchronized (mPackages) {
@@ -14201,9 +14193,31 @@
 
             pkg.mOperationPending = true;
 
+            currentVolumeUuid = ps.volumeUuid;
             codeFile = new File(pkg.codePath);
             installerPackageName = ps.installerPackageName;
             packageAbiOverride = ps.cpuAbiOverrideString;
+            appId = UserHandle.getAppId(pkg.applicationInfo.uid);
+            seinfo = pkg.applicationInfo.seinfo;
+        }
+
+        if (andData) {
+            Slog.d(TAG, "Moving " + packageName + " private data from " + currentVolumeUuid + " to "
+                    + volumeUuid);
+            synchronized (mInstallLock) {
+                if (mInstaller.moveUserDataDirs(currentVolumeUuid, volumeUuid, packageName, appId,
+                        seinfo) != 0) {
+                    synchronized (mPackages) {
+                        final PackageParser.Package pkg = mPackages.get(packageName);
+                        if (pkg != null) {
+                            pkg.mOperationPending = false;
+                        }
+                    }
+
+                    throw new PackageManagerException(MOVE_FAILED_INTERNAL_ERROR,
+                            "Failed to move private data");
+                }
+            }
         }
 
         final IPackageInstallObserver2 installObserver = new IPackageInstallObserver2.Stub() {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index bfcc3db..c068934 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -344,13 +344,6 @@
         }
     }
 
-    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);
@@ -693,19 +686,26 @@
         p.pkg = pkg;
         // pkg.mSetEnabled = p.getEnabled(userId);
         // pkg.mSetStopped = p.getStopped(userId);
+        final String volumeUuid = pkg.applicationInfo.volumeUuid;
         final String codePath = pkg.applicationInfo.getCodePath();
         final String resourcePath = pkg.applicationInfo.getResourcePath();
         final String legacyNativeLibraryPath = pkg.applicationInfo.nativeLibraryRootDir;
+        // Update volume if needed
+        if (!Objects.equals(volumeUuid, p.volumeUuid)) {
+            Slog.w(PackageManagerService.TAG, "Volume for " + p.pkg.packageName +
+                    " changing from " + p.volumeUuid + " to " + volumeUuid);
+            p.volumeUuid = volumeUuid;
+        }
         // Update code path if needed
         if (!Objects.equals(codePath, p.codePathString)) {
-            Slog.w(PackageManagerService.TAG, "Code path for pkg : " + p.pkg.packageName +
+            Slog.w(PackageManagerService.TAG, "Code path for " + p.pkg.packageName +
                     " changing from " + p.codePathString + " to " + codePath);
             p.codePath = new File(codePath);
             p.codePathString = codePath;
         }
         //Update resource path if needed
         if (!Objects.equals(resourcePath, p.resourcePathString)) {
-            Slog.w(PackageManagerService.TAG, "Resource path for pkg : " + p.pkg.packageName +
+            Slog.w(PackageManagerService.TAG, "Resource path for " + p.pkg.packageName +
                     " changing from " + p.resourcePathString + " to " + resourcePath);
             p.resourcePath = new File(resourcePath);
             p.resourcePathString = resourcePath;