Plumbing in PackageManager and installd for multi-user support.

- Create /data/user directory and symlink /data/user/0 -> /data/data for
  backward compatibility
- Create data directories for all packages for new user

- Remove data directories when removing a user

- Create data directories for all users when a package is created

- Clear / Remove data for multiple users

- Fixed a bug in verifying the location of a system app

- pm commands for createUser and removeUser (will be disabled later)

- symlink duplicate lib directories to the original lib directory

Change-Id: Id9fdfcf0e62406a8896aa811314dfc08d5f6ed95
diff --git a/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java
index da3ebaf..d10aa97 100644
--- a/services/java/com/android/server/pm/Installer.java
+++ b/services/java/com/android/server/pm/Installer.java
@@ -225,10 +225,12 @@
         return execute(builder.toString());
     }
 
-    public int remove(String name) {
+    public int remove(String name, int userId) {
         StringBuilder builder = new StringBuilder("remove");
         builder.append(' ');
         builder.append(name);
+        builder.append(' ');
+        builder.append(userId);
         return execute(builder.toString());
     }
 
@@ -248,10 +250,30 @@
         return execute(builder.toString());
     }
 
-    public int clearUserData(String name) {
+    public int createUserData(String name, int uid, int userId) {
+        StringBuilder builder = new StringBuilder("mkuserdata");
+        builder.append(' ');
+        builder.append(name);
+        builder.append(' ');
+        builder.append(uid);
+        builder.append(' ');
+        builder.append(userId);
+        return execute(builder.toString());
+    }
+
+    public int removeUserDataDirs(int userId) {
+        StringBuilder builder = new StringBuilder("rmuser");
+        builder.append(' ');
+        builder.append(userId);
+        return execute(builder.toString());
+    }
+
+    public int clearUserData(String name, int userId) {
         StringBuilder builder = new StringBuilder("rmuserdata");
         builder.append(' ');
         builder.append(name);
+        builder.append(' ');
+        builder.append(userId);
         return execute(builder.toString());
     }
 
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index a9d49b4..6e1093f 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -65,6 +65,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.Signature;
+import android.content.pm.UserInfo;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -208,6 +209,9 @@
     // This is where all application persistent data goes.
     final File mAppDataDir;
 
+    // This is where all application persistent data goes for secondary users.
+    final File mUserAppDataDir;
+
     // This is the object monitoring the framework dir.
     final FileObserver mFrameworkInstallObserver;
 
@@ -359,6 +363,8 @@
     // Delay time in millisecs
     static final int BROADCAST_DELAY = 10 * 1000;
 
+    final UserManager mUserManager;
+
     final private DefaultContainerConnection mDefContainerConn =
             new DefaultContainerConnection();
     class DefaultContainerConnection implements ServiceConnection {
@@ -797,8 +803,11 @@
 
             File dataDir = Environment.getDataDirectory();
             mAppDataDir = new File(dataDir, "data");
+            mUserAppDataDir = new File(dataDir, "user");
             mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
 
+            mUserManager = new UserManager(mInstaller, mUserAppDataDir);
+
             if (mInstaller == null) {
                 // Make sure these dirs exist, when we are running in
                 // the simulator.
@@ -806,6 +815,7 @@
                 File miscDir = new File(dataDir, "misc");
                 miscDir.mkdirs();
                 mAppDataDir.mkdirs();
+                mUserAppDataDir.mkdirs();
                 mDrmAppPrivateInstallDir.mkdirs();
             }
 
@@ -974,7 +984,8 @@
                             + " no longer exists; wiping its data";
                     reportSettingsProblem(Log.WARN, msg);
                     if (mInstaller != null) {
-                        mInstaller.remove(ps.name);
+                        mInstaller.remove(ps.name, 0);
+                        mUserManager.removePackageForAllUsers(ps.name);
                     }
                 }
             }
@@ -1059,10 +1070,12 @@
     void cleanupInstallFailedPackage(PackageSetting ps) {
         Slog.i(TAG, "Cleaning up incompletely installed app: " + ps.name);
         if (mInstaller != null) {
-            int retCode = mInstaller.remove(ps.name);
+            int retCode = mInstaller.remove(ps.name, 0);
             if (retCode < 0) {
                 Slog.w(TAG, "Couldn't remove app data directory for package: "
                            + ps.name + ", retcode=" + retCode);
+            } else {
+                mUserManager.removePackageForAllUsers(ps.name);
             }
         } else {
             //for emulator
@@ -1510,7 +1523,8 @@
                 ps.pkg.applicationInfo.flags = ps.pkgFlags;
                 ps.pkg.applicationInfo.publicSourceDir = ps.resourcePathString;
                 ps.pkg.applicationInfo.sourceDir = ps.codePathString;
-                ps.pkg.applicationInfo.dataDir = getDataPathForPackage(ps.pkg).getPath();
+                ps.pkg.applicationInfo.dataDir =
+                        getDataPathForPackage(ps.pkg.packageName, 0).getPath();
                 ps.pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString;
                 ps.pkg.mSetEnabled = ps.enabled;
                 ps.pkg.mSetStopped = ps.stopped;
@@ -2836,11 +2850,15 @@
         return true;
     }
 
-    private File getDataPathForPackage(PackageParser.Package pkg) {
-        final File dataPath = new File(mAppDataDir, pkg.packageName);
-        return dataPath;
+    File getDataPathForUser(int userId) {
+        return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId);
     }
-    
+
+    private File getDataPathForPackage(String packageName, int userId) {
+        return new File(mUserAppDataDir.getAbsolutePath() + File.separator
+                    + userId + File.separator + packageName);
+    }
+
     private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
             int parseFlags, int scanMode, long currentTime) {
         File scanFile = new File(pkg.mScanPath);
@@ -3162,7 +3180,7 @@
             pkg.applicationInfo.dataDir = dataPath.getPath();
         } else {
             // This is a normal package, need to make its data directory.
-            dataPath = getDataPathForPackage(pkg);
+            dataPath = getDataPathForPackage(pkg.packageName, 0);
             
             boolean uidError = false;
             
@@ -3178,8 +3196,11 @@
                         // If this is a system app, we can at least delete its
                         // current data so the application will still work.
                         if (mInstaller != null) {
-                            int ret = mInstaller.remove(pkgName);
+                            int ret = mInstaller.remove(pkgName, 0);
                             if (ret >= 0) {
+                                // TODO: Kill the processes first
+                                // Remove the data directories for all users
+                                mUserManager.removePackageForAllUsers(pkgName);
                                 // Old data gone!
                                 String msg = "System package " + pkg.packageName
                                         + " has changed from uid: "
@@ -3199,6 +3220,9 @@
                                     mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
                                     return null;
                                 }
+                                // Create data directories for all users
+                                mUserManager.installPackageForAllUsers(pkgName,
+                                        pkg.applicationInfo.uid);
                             }
                         }
                         if (!recovered) {
@@ -3235,11 +3259,13 @@
                 if (mInstaller != null) {
                     int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid,
                             pkg.applicationInfo.uid);
-                    if(ret < 0) {
+                    if (ret < 0) {
                         // Error from installer
                         mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
                         return null;
                     }
+                    // Create data directories for all users
+                    mUserManager.installPackageForAllUsers(pkgName, pkg.applicationInfo.uid);
                 } else {
                     dataPath.mkdirs();
                     if (dataPath.exists()) {
@@ -5703,7 +5729,7 @@
         // Remember this for later, in case we need to rollback this install
         String pkgName = pkg.packageName;
 
-        boolean dataDirExists = getDataPathForPackage(pkg).exists();
+        boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists();
         res.name = pkgName;
         synchronized(mPackages) {
             if (mSettings.mRenamedPackages.containsKey(pkgName)) {
@@ -6390,11 +6416,14 @@
         }
         if ((flags&PackageManager.DONT_DELETE_DATA) == 0) {
             if (mInstaller != null) {
-                int retCode = mInstaller.remove(packageName);
+                int retCode = mInstaller.remove(packageName, 0);
                 if (retCode < 0) {
                     Slog.w(TAG, "Couldn't remove app data or cache directory for package: "
                                + packageName + ", retcode=" + retCode);
                     // we don't consider this to be a failure of the core package deletion
+                } else {
+                    // TODO: Kill the processes first
+                    mUserManager.removePackageForAllUsers(packageName);
                 }
             } else {
                 // for simulator
@@ -6654,7 +6683,7 @@
             }
         }
         if (mInstaller != null) {
-            int retCode = mInstaller.clearUserData(packageName);
+            int retCode = mInstaller.clearUserData(packageName, 0); // TODO - correct userId
             if (retCode < 0) {
                 Slog.w(TAG, "Couldn't remove cache files for package: "
                         + packageName);
@@ -8015,4 +8044,17 @@
                 android.provider.Settings.Secure.DEFAULT_INSTALL_LOCATION,
                 PackageHelper.APP_INSTALL_AUTO);
     }
+
+    public UserInfo createUser(String name, int flags) {
+        UserInfo userInfo = mUserManager.createUser(name, flags, getInstalledApplications(0));
+        return userInfo;
+    }
+
+    public boolean removeUser(int userId) {
+        if (userId == 0) {
+            return false;
+        }
+        mUserManager.removeUser(userId);
+        return true;
+    }
 }
diff --git a/services/java/com/android/server/pm/UserDetails.java b/services/java/com/android/server/pm/UserManager.java
similarity index 65%
rename from services/java/com/android/server/pm/UserDetails.java
rename to services/java/com/android/server/pm/UserManager.java
index 2aeed7c..76fa5ab 100644
--- a/services/java/com/android/server/pm/UserDetails.java
+++ b/services/java/com/android/server/pm/UserManager.java
@@ -18,9 +18,13 @@
 
 import com.android.internal.util.FastXmlSerializer;
 
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.os.Environment;
 import android.os.FileUtils;
+import android.os.SystemClock;
+import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
@@ -37,7 +41,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
-public class UserDetails {
+public class UserManager {
     private static final String TAG_NAME = "name";
 
     private static final String ATTR_FLAGS = "flags";
@@ -48,22 +52,27 @@
 
     private static final String TAG_USER = "user";
 
-    private static final String TAG = "UserDetails";
+    private static final String LOG_TAG = "UserManager";
 
-    private static final String USER_INFO_DIR = "system/users";
+    private static final String USER_INFO_DIR = "system" + File.separator + "users";
     private static final String USER_LIST_FILENAME = "userlist.xml";
 
     private SparseArray<UserInfo> mUsers;
 
     private final File mUsersDir;
     private final File mUserListFile;
+    private int[] mUserIds;
+
+    private Installer mInstaller;
+    private File mBaseUserPath;
 
     /**
      * Available for testing purposes.
      */
-    UserDetails(File dataDir) {
+    UserManager(File dataDir, File baseUserPath) {
         mUsersDir = new File(dataDir, USER_INFO_DIR);
         mUsersDir.mkdirs();
+        mBaseUserPath = baseUserPath;
         FileUtils.setPermissions(mUsersDir.toString(),
                 FileUtils.S_IRWXU|FileUtils.S_IRWXG
                 |FileUtils.S_IROTH|FileUtils.S_IXOTH,
@@ -72,8 +81,9 @@
         readUserList();
     }
 
-    public UserDetails() {
-        this(Environment.getDataDirectory());
+    public UserManager(Installer installer, File baseUserPath) {
+        this(Environment.getDataDirectory(), baseUserPath);
+        mInstaller = installer;
     }
 
     public List<UserInfo> getUsers() {
@@ -84,6 +94,15 @@
         return users;
     }
 
+    /**
+     * Returns an array of user ids. This array is cached here for quick access, so do not modify or
+     * cache it elsewhere.
+     * @return the array of user ids.
+     */
+    int[] getUserIds() {
+        return mUserIds;
+    }
+
     private void readUserList() {
         mUsers = new SparseArray<UserInfo>();
         if (!mUserListFile.exists()) {
@@ -102,7 +121,7 @@
             }
 
             if (type != XmlPullParser.START_TAG) {
-                Slog.e(TAG, "Unable to read user list");
+                Slog.e(LOG_TAG, "Unable to read user list");
                 fallbackToSingleUser();
                 return;
             }
@@ -116,6 +135,7 @@
                     }
                 }
             }
+            updateUserIds();
         } catch (IOException ioe) {
             fallbackToSingleUser();
         } catch (XmlPullParserException pe) {
@@ -128,6 +148,7 @@
         UserInfo primary = new UserInfo(0, "Primary",
                 UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
         mUsers.put(0, primary);
+        updateUserIds();
 
         writeUserList();
         writeUser(primary);
@@ -164,7 +185,7 @@
 
             serializer.endDocument();
         } catch (IOException ioe) {
-            Slog.e(TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
+            Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
         }
     }
 
@@ -194,14 +215,13 @@
                 serializer.startTag(null, TAG_USER);
                 serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
                 serializer.endTag(null, TAG_USER);
-                Slog.e(TAG, "Wrote user " + user.id + " to userlist.xml");
             }
 
             serializer.endTag(null, TAG_USERS);
 
             serializer.endDocument();
         } catch (IOException ioe) {
-            Slog.e(TAG, "Error writing user list");
+            Slog.e(LOG_TAG, "Error writing user list");
         }
     }
 
@@ -222,14 +242,14 @@
             }
 
             if (type != XmlPullParser.START_TAG) {
-                Slog.e(TAG, "Unable to read user " + id);
+                Slog.e(LOG_TAG, "Unable to read user " + id);
                 return null;
             }
 
             if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
                 String storedId = parser.getAttributeValue(null, ATTR_ID);
                 if (Integer.parseInt(storedId) != id) {
-                    Slog.e(TAG, "User id does not match the file name");
+                    Slog.e(LOG_TAG, "User id does not match the file name");
                     return null;
                 }
                 String flagString = parser.getAttributeValue(null, ATTR_FLAGS);
@@ -256,18 +276,25 @@
         return null;
     }
 
-    public UserInfo createUser(String name, int flags) {
-        int id = getNextAvailableId();
-        UserInfo userInfo = new UserInfo(id, name, flags);
-        if (!createPackageFolders(id)) {
+    public UserInfo createUser(String name, int flags, List<ApplicationInfo> apps) {
+        int userId = getNextAvailableId();
+        UserInfo userInfo = new UserInfo(userId, name, flags);
+        File userPath = new File(mBaseUserPath, Integer.toString(userId));
+        if (!createPackageFolders(userId, userPath, apps)) {
             return null;
         }
-        mUsers.put(id, userInfo);
+        mUsers.put(userId, userInfo);
         writeUserList();
         writeUser(userInfo);
+        updateUserIds();
         return userInfo;
     }
 
+    /**
+     * Removes a user and all data directories created for that user. This method should be called
+     * after the user's processes have been terminated.
+     * @param id the user's id
+     */
     public void removeUser(int id) {
         // Remove from the list
         UserInfo userInfo = mUsers.get(id);
@@ -277,11 +304,58 @@
             // Remove user file
             File userFile = new File(mUsersDir, id + ".xml");
             userFile.delete();
+            // Update the user list
             writeUserList();
+            // Remove the data directories for all packages for this user
             removePackageFolders(id);
+            updateUserIds();
         }
     }
 
+    public void installPackageForAllUsers(String packageName, int uid) {
+        for (int userId : mUserIds) {
+            // Don't do it for the primary user, it will become recursive.
+            if (userId == 0)
+                continue;
+            mInstaller.createUserData(packageName, PackageManager.getUid(userId, uid),
+                    userId);
+        }
+    }
+
+    public void clearUserDataForAllUsers(String packageName) {
+        for (int userId : mUserIds) {
+            // Don't do it for the primary user, it will become recursive.
+            if (userId == 0)
+                continue;
+            mInstaller.clearUserData(packageName, userId);
+        }
+    }
+
+    public void removePackageForAllUsers(String packageName) {
+        for (int userId : mUserIds) {
+            // Don't do it for the primary user, it will become recursive.
+            if (userId == 0)
+                continue;
+            mInstaller.remove(packageName, userId);
+        }
+    }
+
+    /**
+     * Caches the list of user ids in an array, adjusting the array size when necessary.
+     */
+    private void updateUserIds() {
+        if (mUserIds == null || mUserIds.length != mUsers.size()) {
+            mUserIds = new int[mUsers.size()];
+        }
+        for (int i = 0; i < mUsers.size(); i++) {
+            mUserIds[i] = mUsers.keyAt(i);
+        }
+    }
+
+    /**
+     * Returns the next available user id, filling in any holes in the ids.
+     * @return
+     */
     private int getNextAvailableId() {
         int i = 0;
         while (i < Integer.MAX_VALUE) {
@@ -293,13 +367,35 @@
         return i;
     }
 
-    private boolean createPackageFolders(int id) {
-        // TODO: Create data directories for all the packages for a new user, w/ specified user id.
+    private boolean createPackageFolders(int id, File userPath, final List<ApplicationInfo> apps) {
+        // mInstaller may not be available for unit-tests.
+        if (mInstaller == null || apps == null) return true;
+
+        final long startTime = SystemClock.elapsedRealtime();
+        // Create the user path
+        userPath.mkdir();
+        FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
+                | FileUtils.S_IXOTH, -1, -1);
+
+        // Create the individual data directories
+        for (ApplicationInfo app : apps) {
+            if (app.uid > android.os.Process.FIRST_APPLICATION_UID
+                    && app.uid < PackageManager.PER_USER_RANGE) {
+                mInstaller.createUserData(app.packageName,
+                        PackageManager.getUid(id, app.uid), id);
+            }
+        }
+        final long stopTime = SystemClock.elapsedRealtime();
+        Log.i(LOG_TAG,
+                "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms");
         return true;
     }
 
     private boolean removePackageFolders(int id) {
-        // TODO: Remove all the data directories for the specified user.
+        // mInstaller may not be available for unit-tests.
+        if (mInstaller == null) return true;
+
+        mInstaller.removeUserDataDirs(id);
         return true;
     }
 }