Package manager changes to store and update user information.

Some API stubs for managing users and storing their details.
List of users is stored in an xml file.
Each user's properties are stored in a separate xml file.

Some unit tests for modifying the XML files.

Change-Id: If2ce2420723111bd426f6762def3c2afc19a0ae5
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b6581e9..d57ffc0 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -16,6 +16,9 @@
 
 package android.app;
 
+import com.android.internal.app.IUsageStats;
+import com.android.internal.os.PkgUsageStats;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -26,17 +29,15 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.os.Debug;
-import android.os.RemoteException;
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.Log;
-import com.android.internal.app.IUsageStats;
-import com.android.internal.os.PkgUsageStats;
 
 import java.util.HashMap;
 import java.util.List;
@@ -614,7 +615,7 @@
     public List<RunningServiceInfo> getRunningServices(int maxNum)
             throws SecurityException {
         try {
-            return (List<RunningServiceInfo>)ActivityManagerNative.getDefault()
+            return ActivityManagerNative.getDefault()
                     .getServices(maxNum, 0);
         } catch (RemoteException e) {
             // System dead, we will be dead too soon!
@@ -1269,4 +1270,17 @@
             return new HashMap<String, Integer>();
         }
     }
+
+    /**
+     * @param userid the user's id. Zero indicates the default user 
+     * @hide
+     */
+    public boolean switchUser(int userid) {
+        try {
+            return ActivityManagerNative.getDefault().switchUser(userid);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
 }
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 6426635..bad757e 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1398,6 +1398,15 @@
             return true;
         }
 
+        case SWITCH_USER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int userid = data.readInt();
+            boolean result = switchUser(userid);
+            reply.writeNoException();
+            reply.writeInt(result ? 1 : 0);
+            return true;
+        }
+
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -3142,5 +3151,18 @@
         return result;
     }
 
+    public boolean switchUser(int userid) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(userid);
+        mRemote.transact(SWITCH_USER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean result = reply.readInt() != 0;
+        reply.recycle();
+        data.recycle();
+        return result;
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 50e56c7..ef8ba8e 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -33,13 +33,13 @@
 import android.content.pm.IPackageStatsObserver;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManager;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
@@ -1106,6 +1106,56 @@
         return PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
     }
 
+    // Multi-user support
+
+    /**
+     * @hide
+     */
+    @Override
+    public UserInfo createUser(String name, int flags) {
+        // TODO
+        return null;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public List<UserInfo> getUsers() {
+        // TODO:
+        // Dummy code, always returns just the primary user
+        ArrayList<UserInfo> users = new ArrayList<UserInfo>();
+        UserInfo primary = new UserInfo(0, "Root!",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
+        users.add(primary);
+        return users;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean removeUser(int id) {
+        // TODO:
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void updateUserName(int id, String name) {
+        // TODO:
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void updateUserFlags(int id, int flags) {
+        // TODO:
+    }
+
     private final ContextImpl mContext;
     private final IPackageManager mPM;
 
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 61e6fc8..f07d9c6 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -342,6 +342,9 @@
     public int startActivitiesInPackage(int uid,
             Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException;
 
+    // Multi-user APIs
+    public boolean switchUser(int userid) throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -557,4 +560,5 @@
     int START_ACTIVITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+120;
     int START_ACTIVITIES_IN_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+121;
     int ACTIVITY_SLEPT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+122;
+    int SWITCH_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+123;
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 80bed0d..99c4c7f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -150,21 +150,21 @@
      * {@link PackageInfo#permissions}.
      */
     public static final int GET_PERMISSIONS               = 0x00001000;
-    
+
     /**
      * Flag parameter to retrieve all applications(even uninstalled ones) with data directories.
-     * This state could have resulted if applications have been deleted with flag 
+     * This state could have resulted if applications have been deleted with flag
      * DONT_DELETE_DATA
      * with a possibility of being replaced or reinstalled in future
      */
     public static final int GET_UNINSTALLED_PACKAGES = 0x00002000;
-    
+
     /**
      * {@link PackageInfo} flag: return information about
      * hardware preferences in
      * {@link PackageInfo#configPreferences PackageInfo.configPreferences} and
      * requested features in {@link PackageInfo#reqFeatures
-     * PackageInfo.reqFeatures}. 
+     * PackageInfo.reqFeatures}.
      */
     public static final int GET_CONFIGURATIONS = 0x00004000;
 
@@ -244,7 +244,7 @@
     public static final int INSTALL_REPLACE_EXISTING = 0x00000002;
 
     /**
-     * Flag parameter for {@link #installPackage} to indicate that you want to 
+     * Flag parameter for {@link #installPackage} to indicate that you want to
      * allow test packages (those that have set android:testOnly in their
      * manifest) to be installed.
      * @hide
@@ -555,7 +555,7 @@
      * Return code for when package deletion succeeds. This is passed to the
      * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
      * succeeded in deleting the package.
-     * 
+     *
      * @hide
      */
     public static final int DELETE_SUCCEEDED = 1;
@@ -564,7 +564,7 @@
      * Deletion failed return code: this is passed to the
      * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
      * failed to delete the package for an unspecified reason.
-     * 
+     *
      * @hide
      */
     public static final int DELETE_FAILED_INTERNAL_ERROR = -1;
@@ -574,7 +574,7 @@
      * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
      * failed to delete the package because it is the active DevicePolicy
      * manager.
-     * 
+     *
      * @hide
      */
     public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2;
@@ -583,7 +583,7 @@
      * Return code that is passed to the {@link IPackageMoveObserver} by
      * {@link #movePackage(android.net.Uri, IPackageMoveObserver)} when the
      * package has been successfully moved by the system.
-     * 
+     *
      * @hide
      */
     public static final int MOVE_SUCCEEDED = 1;
@@ -641,7 +641,7 @@
      * {@link #movePackage(android.net.Uri, IPackageMoveObserver)} if the
      * specified package already has an operation pending in the
      * {@link PackageHandler} queue.
-     * 
+     *
      * @hide
      */
     public static final int MOVE_FAILED_OPERATION_PENDING = -7;
@@ -789,7 +789,7 @@
      */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_SENSOR_PROXIMITY = "android.hardware.sensor.proximity";
-    
+
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device has a telephony radio with data
@@ -797,14 +797,14 @@
      */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
-    
+
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device has a CDMA telephony stack.
      */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
-    
+
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device has a GSM telephony stack.
@@ -847,8 +847,8 @@
      */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
-    
-    
+
+
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device's touch screen supports
@@ -856,7 +856,7 @@
      */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
-    
+
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device's touch screen is capable of
@@ -932,11 +932,11 @@
      * @return Returns a PackageInfo object containing information about the package.
      *         If flag GET_UNINSTALLED_PACKAGES is set and  if the package is not
      *         found in the list of installed applications, the package information is
-     *         retrieved from the list of uninstalled applications(which includes 
+     *         retrieved from the list of uninstalled applications(which includes
      *         installed applications as well as applications
      *         with data directory ie applications which had been
      *         deleted with DONT_DELTE_DATA flag set).
-     *         
+     *
      * @see #GET_ACTIVITIES
      * @see #GET_GIDS
      * @see #GET_CONFIGURATIONS
@@ -947,7 +947,7 @@
      * @see #GET_SERVICES
      * @see #GET_SIGNATURES
      * @see #GET_UNINSTALLED_PACKAGES
-     *                  
+     *
      */
     public abstract PackageInfo getPackageInfo(String packageName, int flags)
             throws NameNotFoundException;
@@ -960,7 +960,7 @@
      * the canonical name for each package.
      */
     public abstract String[] currentToCanonicalPackageNames(String[] names);
-    
+
     /**
      * Map from a packages canonical name to the current name in use on the device.
      * @param names Array of new names to be mapped.
@@ -968,7 +968,7 @@
      * the current name for each package.
      */
     public abstract String[] canonicalToCurrentPackageNames(String[] names);
-    
+
     /**
      * Return a "good" intent to launch a front-door activity in a package,
      * for use for example to implement an "open" button when browsing through
@@ -976,12 +976,12 @@
      * activity in the category {@link Intent#CATEGORY_INFO}, next for a
      * main activity in the category {@link Intent#CATEGORY_LAUNCHER}, or return
      * null if neither are found.
-     * 
+     *
      * <p>Throws {@link NameNotFoundException} if a package with the given
      * name can not be found on the system.
      *
      * @param packageName The name of the package to inspect.
-     * 
+     *
      * @return Returns either a fully-qualified Intent that can be used to
      * launch the main activity in the package, or null if the package does
      * not contain such an activity.
@@ -1077,16 +1077,16 @@
      *
      * @param packageName The full name (i.e. com.google.apps.contacts) of an
      *                    application.
-     * @param flags Additional option flags. Use any combination of 
+     * @param flags Additional option flags. Use any combination of
      * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
      * {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
      *
-     * @return  {@link ApplicationInfo} Returns ApplicationInfo object containing 
+     * @return  {@link ApplicationInfo} Returns ApplicationInfo object containing
      *         information about the package.
      *         If flag GET_UNINSTALLED_PACKAGES is set and  if the package is not
-     *         found in the list of installed applications, 
-     *         the application information is retrieved from the 
-     *         list of uninstalled applications(which includes 
+     *         found in the list of installed applications,
+     *         the application information is retrieved from the
+     *         list of uninstalled applications(which includes
      *         installed applications as well as applications
      *         with data directory ie applications which had been
      *         deleted with DONT_DELTE_DATA flag set).
@@ -1108,7 +1108,7 @@
      * @param component The full component name (i.e.
      * com.google.apps.contacts/com.google.apps.contacts.ContactsList) of an Activity
      * class.
-     * @param flags Additional option flags. Use any combination of 
+     * @param flags Additional option flags. Use any combination of
      * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
      * to modify the data (in ApplicationInfo) returned.
      *
@@ -1131,7 +1131,7 @@
      * @param component The full component name (i.e.
      * com.google.apps.calendar/com.google.apps.calendar.CalendarAlarm) of a Receiver
      * class.
-     * @param flags Additional option flags.  Use any combination of 
+     * @param flags Additional option flags.  Use any combination of
      * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
      * to modify the data returned.
      *
@@ -1154,12 +1154,12 @@
      * @param component The full component name (i.e.
      * com.google.apps.media/com.google.apps.media.BackgroundPlayback) of a Service
      * class.
-     * @param flags Additional option flags.  Use any combination of 
+     * @param flags Additional option flags.  Use any combination of
      * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
      * to modify the data returned.
      *
      * @return ServiceInfo containing information about the service.
-     * 
+     *
      * @see #GET_META_DATA
      * @see #GET_SHARED_LIBRARY_FILES
      */
@@ -1206,7 +1206,7 @@
      *
      * @return A List of PackageInfo objects, one for each package that is
      *         installed on the device.  In the unlikely case of there being no
-     *         installed packages, an empty list is returned. 
+     *         installed packages, an empty list is returned.
      *         If flag GET_UNINSTALLED_PACKAGES is set, a list of all
      *         applications including those deleted with DONT_DELETE_DATA
      *         (partially installed apps with data directory) will be returned.
@@ -1221,7 +1221,7 @@
      * @see #GET_SERVICES
      * @see #GET_SIGNATURES
      * @see #GET_UNINSTALLED_PACKAGES
-     * 
+     *
      */
     public abstract List<PackageInfo> getInstalledPackages(int flags);
 
@@ -1283,7 +1283,7 @@
      * the device is rebooted before it is written.
      */
     public abstract boolean addPermissionAsync(PermissionInfo info);
-    
+
     /**
      * Removes a permission that was previously added with
      * {@link #addPermission(PermissionInfo)}.  The same ownership rules apply
@@ -1370,7 +1370,7 @@
      * user id is not currently assigned.
      */
     public abstract String getNameForUid(int uid);
-    
+
     /**
      * Return the user id associated with a shared user name. Multiple
      * applications can specify a shared user name in their manifest and thus
@@ -1391,38 +1391,38 @@
      * device. If flag GET_UNINSTALLED_PACKAGES has been set, a list of all
      * applications including those deleted with DONT_DELETE_DATA(partially
      * installed apps with data directory) will be returned.
-     * 
-     * @param flags Additional option flags. Use any combination of 
+     *
+     * @param flags Additional option flags. Use any combination of
      * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
      * {link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
      *
      * @return A List of ApplicationInfo objects, one for each application that
      *         is installed on the device.  In the unlikely case of there being
-     *         no installed applications, an empty list is returned. 
+     *         no installed applications, an empty list is returned.
      *         If flag GET_UNINSTALLED_PACKAGES is set, a list of all
      *         applications including those deleted with DONT_DELETE_DATA
      *         (partially installed apps with data directory) will be returned.
-     *         
+     *
      * @see #GET_META_DATA
      * @see #GET_SHARED_LIBRARY_FILES
      * @see #GET_UNINSTALLED_PACKAGES
      */
     public abstract List<ApplicationInfo> getInstalledApplications(int flags);
-    
+
     /**
      * Get a list of shared libraries that are available on the
      * system.
-     * 
+     *
      * @return An array of shared library names that are
      * available on the system, or null if none are installed.
-     * 
+     *
      */
     public abstract String[] getSystemSharedLibraryNames();
 
     /**
      * Get a list of features that are available on the
      * system.
-     * 
+     *
      * @return An array of FeatureInfo classes describing the features
      * that are available on the system, or null if there are none(!!).
      */
@@ -1431,7 +1431,7 @@
     /**
      * Check whether the given feature name is one of the available
      * features as returned by {@link #getSystemAvailableFeatures()}.
-     * 
+     *
      * @return Returns true if the devices supports the feature, else
      * false.
      */
@@ -1448,7 +1448,7 @@
      * that {@link android.content.Context#startActivity(Intent)} and
      * {@link android.content.Intent#resolveActivity(PackageManager)
      * Intent.resolveActivity(PackageManager)} do.</p>
-     * 
+     *
      * @param intent An intent containing all of the desired specification
      *               (action, data, type, category, and/or component).
      * @param flags Additional option flags.  The most important is
@@ -1747,7 +1747,7 @@
      *
      * @return Returns the image of the logo or null if the activity has no
      * logo specified.
-     * 
+     *
      * @throws NameNotFoundException Thrown if the resources for the given
      * activity could not be loaded.
      *
@@ -1768,7 +1768,7 @@
      *
      * @return Returns the image of the logo, or null if the activity has no
      * logo specified.
-     * 
+     *
      * @throws NameNotFoundException Thrown if the resources for application
      * matching the given intent could not be loaded.
      *
@@ -1801,7 +1801,7 @@
      *
      * @return Returns the image of the logo, or null if no application logo
      * has been specified.
-     * 
+     *
      * @throws NameNotFoundException Thrown if the resources for the given
      * application could not be loaded.
      *
@@ -1935,7 +1935,7 @@
      * @see #GET_RECEIVERS
      * @see #GET_SERVICES
      * @see #GET_SIGNATURES
-     * 
+     *
      */
     public PackageInfo getPackageArchiveInfo(String archiveFilePath, int flags) {
         PackageParser packageParser = new PackageParser(archiveFilePath);
@@ -1952,7 +1952,7 @@
 
     /**
      * @hide
-     * 
+     *
      * Install a package. Since this may take a little while, the result will
      * be posted back to the given observer.  An installation will fail if the calling context
      * lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the
@@ -2012,11 +2012,11 @@
     /**
      * Retrieve the package name of the application that installed a package. This identifies
      * which market the package came from.
-     * 
+     *
      * @param packageName The name of the package to query
      */
     public abstract String getInstallerPackageName(String packageName);
-    
+
     /**
      * Attempts to clear the user data directory of an application.
      * Since this may take a little while, the result will
@@ -2071,7 +2071,7 @@
      * of bytes if possible.
      * @param observer call back used to notify when
      * the operation is completed
-     * 
+     *
      * @hide
      */
     public abstract void freeStorageAndNotify(long freeStorageSize, IPackageDataObserver observer);
@@ -2096,7 +2096,7 @@
      * @param pi IntentSender call back used to
      * notify when the operation is completed.May be null
      * to indicate that no call back is desired.
-     * 
+     *
      * @hide
      */
     public abstract void freeStorage(long freeStorageSize, IntentSender pi);
@@ -2172,7 +2172,7 @@
      * @deprecated This is a protected API that should not have been available
      * to third party applications.  It is the platform's responsibility for
      * assigning preferred activities and this can not be directly modified.
-     * 
+     *
      * Add a new preferred activity mapping to the system.  This will be used
      * to automatically select the given activity component when
      * {@link Context#startActivity(Intent) Context.startActivity()} finds
@@ -2195,7 +2195,7 @@
      * @deprecated This is a protected API that should not have been available
      * to third party applications.  It is the platform's responsibility for
      * assigning preferred activities and this can not be directly modified.
-     * 
+     *
      * Replaces an existing preferred activity mapping to the system, and if that were not present
      * adds a new preferred activity.  This will be used
      * to automatically select the given activity component when
@@ -2304,7 +2304,7 @@
      */
     public abstract void setApplicationEnabledSetting(String packageName,
             int newState, int flags);
-    
+
     /**
      * Return the the enabled setting for an application.  This returns
      * the last value set by
@@ -2345,4 +2345,46 @@
      */
     public abstract void movePackage(
             String packageName, IPackageMoveObserver observer, int flags);
+
+    /**
+     * Creates a user with the specified name and options.
+     *
+     * @param name the user's name
+     * @param flags flags that identify the type of user and other properties.
+     * @see UserInfo
+     *
+     * @return the UserInfo object for the created user, or null if the user could not be created.
+     * @hide
+     */
+    public abstract UserInfo createUser(String name, int flags);
+
+    /**
+     * @return the list of users that were created
+     * @hide
+     */
+    public abstract List<UserInfo> getUsers();
+
+    /**
+     * @param id the ID of the user, where 0 is the primary user.
+     * @hide
+     */
+    public abstract boolean removeUser(int id);
+
+    /**
+     * Updates the user's name.
+     *
+     * @param id the user's id
+     * @param name the new name for the user
+     * @hide
+     */
+    public abstract void updateUserName(int id, String name);
+
+    /**
+     * Changes the user's properties specified by the flags.
+     *
+     * @param id the user's id
+     * @param flags the new flags for the user
+     * @hide
+     */
+    public abstract void updateUserFlags(int id, int flags);
 }
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
new file mode 100644
index 0000000..3704d3a
--- /dev/null
+++ b/core/java/android/content/pm/UserInfo.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Per-user information.
+ * @hide
+ */
+public class UserInfo implements Parcelable {
+    /**
+     * Primary user. Only one user can have this flag set. Meaning of this
+     * flag TBD.
+     */
+    public static final int FLAG_PRIMARY = 0x00000001;
+
+    /**
+     * User with administrative privileges. Such a user can create and
+     * delete users.
+     */
+    public static final int FLAG_ADMIN   = 0x00000002;
+
+    /**
+     * Indicates a guest user that may be transient.
+     */
+    public static final int FLAG_GUEST   = 0x00000004;
+
+    public int id;
+    public String name;
+    public int flags;
+
+    public UserInfo(int id, String name, int flags) {
+        this.id = id;
+        this.name = name;
+        this.flags = flags;
+    }
+
+    public boolean isPrimary() {
+        return (flags & FLAG_PRIMARY) == FLAG_PRIMARY;
+    }
+
+    public boolean isAdmin() {
+        return (flags & FLAG_ADMIN) == FLAG_ADMIN;
+    }
+
+    public boolean isGuest() {
+        return (flags & FLAG_GUEST) == FLAG_GUEST;
+    }
+
+    public UserInfo() {
+    }
+
+    public UserInfo(UserInfo orig) {
+        name = orig.name;
+        id = orig.id;
+        flags = orig.flags;
+    }
+
+    @Override
+    public String toString() {
+        return "UserInfo{"
+ + id + ":" + name + ":" + Integer.toHexString(flags) + "}";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int parcelableFlags) {
+        dest.writeInt(id);
+        dest.writeString(name);
+        dest.writeInt(flags);
+    }
+
+    public static final Parcelable.Creator<UserInfo> CREATOR
+            = new Parcelable.Creator<UserInfo>() {
+        public UserInfo createFromParcel(Parcel source) {
+            return new UserInfo(source);
+        }
+        public UserInfo[] newArray(int size) {
+            return new UserInfo[size];
+        }
+    };
+
+    private UserInfo(Parcel source) {
+        id = source.readInt();
+        name = source.readString();
+        flags = source.readInt();
+    }
+}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 5f471fe..4a5bf2a 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -13144,4 +13144,11 @@
             }
         }
     }
+
+    // Multi-user methods
+
+    public boolean switchUser(int userid) {
+        // TODO
+        return true;
+    }
 }
diff --git a/services/java/com/android/server/pm/UserDetails.java b/services/java/com/android/server/pm/UserDetails.java
new file mode 100644
index 0000000..2aeed7c
--- /dev/null
+++ b/services/java/com/android/server/pm/UserDetails.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import android.content.pm.UserInfo;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+public class UserDetails {
+    private static final String TAG_NAME = "name";
+
+    private static final String ATTR_FLAGS = "flags";
+
+    private static final String ATTR_ID = "id";
+
+    private static final String TAG_USERS = "users";
+
+    private static final String TAG_USER = "user";
+
+    private static final String TAG = "UserDetails";
+
+    private static final String USER_INFO_DIR = "system/users";
+    private static final String USER_LIST_FILENAME = "userlist.xml";
+
+    private SparseArray<UserInfo> mUsers;
+
+    private final File mUsersDir;
+    private final File mUserListFile;
+
+    /**
+     * Available for testing purposes.
+     */
+    UserDetails(File dataDir) {
+        mUsersDir = new File(dataDir, USER_INFO_DIR);
+        mUsersDir.mkdirs();
+        FileUtils.setPermissions(mUsersDir.toString(),
+                FileUtils.S_IRWXU|FileUtils.S_IRWXG
+                |FileUtils.S_IROTH|FileUtils.S_IXOTH,
+                -1, -1);
+        mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
+        readUserList();
+    }
+
+    public UserDetails() {
+        this(Environment.getDataDirectory());
+    }
+
+    public List<UserInfo> getUsers() {
+        ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
+        for (int i = 0; i < mUsers.size(); i++) {
+            users.add(mUsers.valueAt(i));
+        }
+        return users;
+    }
+
+    private void readUserList() {
+        mUsers = new SparseArray<UserInfo>();
+        if (!mUserListFile.exists()) {
+            fallbackToSingleUser();
+            return;
+        }
+        FileInputStream fis = null;
+        try {
+            fis = new FileInputStream(mUserListFile);
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(fis, null);
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                ;
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                Slog.e(TAG, "Unable to read user list");
+                fallbackToSingleUser();
+                return;
+            }
+
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
+                    String id = parser.getAttributeValue(null, ATTR_ID);
+                    UserInfo user = readUser(Integer.parseInt(id));
+                    if (user != null) {
+                        mUsers.put(user.id, user);
+                    }
+                }
+            }
+        } catch (IOException ioe) {
+            fallbackToSingleUser();
+        } catch (XmlPullParserException pe) {
+            fallbackToSingleUser();
+        }
+    }
+
+    private void fallbackToSingleUser() {
+        // Create the primary user
+        UserInfo primary = new UserInfo(0, "Primary",
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
+        mUsers.put(0, primary);
+
+        writeUserList();
+        writeUser(primary);
+    }
+
+    /*
+     * Writes the user file in this format:
+     *
+     * <user flags="20039023" id="0">
+     *   <name>Primary</name>
+     * </user>
+     */
+    private void writeUser(UserInfo userInfo) {
+        try {
+            final File mUserFile = new File(mUsersDir, userInfo.id + ".xml");
+            final FileOutputStream fos = new FileOutputStream(mUserFile);
+            final BufferedOutputStream bos = new BufferedOutputStream(fos);
+
+            // XmlSerializer serializer = XmlUtils.serializerInstance();
+            final XmlSerializer serializer = new FastXmlSerializer();
+            serializer.setOutput(bos, "utf-8");
+            serializer.startDocument(null, true);
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+            serializer.startTag(null, TAG_USER);
+            serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id));
+            serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags));
+
+            serializer.startTag(null, TAG_NAME);
+            serializer.text(userInfo.name);
+            serializer.endTag(null, TAG_NAME);
+
+            serializer.endTag(null, TAG_USER);
+
+            serializer.endDocument();
+        } catch (IOException ioe) {
+            Slog.e(TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
+        }
+    }
+
+    /*
+     * Writes the user list file in this format:
+     *
+     * <users>
+     *   <user id="0"></user>
+     *   <user id="2"></user>
+     * </users>
+     */
+    private void writeUserList() {
+        try {
+            final FileOutputStream fos = new FileOutputStream(mUserListFile);
+            final BufferedOutputStream bos = new BufferedOutputStream(fos);
+
+            // XmlSerializer serializer = XmlUtils.serializerInstance();
+            final XmlSerializer serializer = new FastXmlSerializer();
+            serializer.setOutput(bos, "utf-8");
+            serializer.startDocument(null, true);
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+            serializer.startTag(null, TAG_USERS);
+
+            for (int i = 0; i < mUsers.size(); i++) {
+                UserInfo user = mUsers.valueAt(i);
+                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");
+        }
+    }
+
+    private UserInfo readUser(int id) {
+        int flags = 0;
+        String name = null;
+
+        FileInputStream fis = null;
+        try {
+            File userFile = new File(mUsersDir, Integer.toString(id) + ".xml");
+            fis = new FileInputStream(userFile);
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(fis, null);
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                ;
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                Slog.e(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");
+                    return null;
+                }
+                String flagString = parser.getAttributeValue(null, ATTR_FLAGS);
+                flags = Integer.parseInt(flagString);
+
+                while ((type = parser.next()) != XmlPullParser.START_TAG
+                        && type != XmlPullParser.END_DOCUMENT) {
+                }
+                if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) {
+                    type = parser.next();
+                    if (type == XmlPullParser.TEXT) {
+                        name = parser.getText();
+                    }
+                }
+            }
+            fis.close();
+
+            UserInfo userInfo = new UserInfo(id, name, flags);
+            return userInfo;
+
+        } catch (IOException ioe) {
+        } catch (XmlPullParserException pe) {
+        }
+        return null;
+    }
+
+    public UserInfo createUser(String name, int flags) {
+        int id = getNextAvailableId();
+        UserInfo userInfo = new UserInfo(id, name, flags);
+        if (!createPackageFolders(id)) {
+            return null;
+        }
+        mUsers.put(id, userInfo);
+        writeUserList();
+        writeUser(userInfo);
+        return userInfo;
+    }
+
+    public void removeUser(int id) {
+        // Remove from the list
+        UserInfo userInfo = mUsers.get(id);
+        if (userInfo != null) {
+            // Remove this user from the list
+            mUsers.remove(id);
+            // Remove user file
+            File userFile = new File(mUsersDir, id + ".xml");
+            userFile.delete();
+            writeUserList();
+            removePackageFolders(id);
+        }
+    }
+
+    private int getNextAvailableId() {
+        int i = 0;
+        while (i < Integer.MAX_VALUE) {
+            if (mUsers.indexOfKey(i) < 0) {
+                break;
+            }
+            i++;
+        }
+        return i;
+    }
+
+    private boolean createPackageFolders(int id) {
+        // TODO: Create data directories for all the packages for a new user, w/ specified user id.
+        return true;
+    }
+
+    private boolean removePackageFolders(int id) {
+        // TODO: Remove all the data directories for the specified user.
+        return true;
+    }
+}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index f115f42..2fcce78 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -15,7 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.frameworks.servicestests">
+        package="com.android.frameworks.servicestests">
 
     <uses-permission android:name="android.permission.READ_LOGS" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java
new file mode 100644
index 0000000..7b77aac
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import com.android.server.pm.UserDetails;
+
+import android.content.pm.UserInfo;
+import android.os.Debug;
+import android.os.Environment;
+import android.test.AndroidTestCase;
+
+import java.util.List;
+
+/** Test {@link UserDetails} functionality. */
+public class UserDetailsTest extends AndroidTestCase {
+
+    UserDetails mDetails = null;
+
+    @Override
+    public void setUp() throws Exception {
+        mDetails = new UserDetails(Environment.getExternalStorageDirectory());
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        List<UserInfo> users = mDetails.getUsers();
+        // Remove all except the primary user
+        for (UserInfo user : users) {
+            if (!user.isPrimary()) {
+                mDetails.removeUser(user.id);
+            }
+        }
+    }
+
+    public void testHasPrimary() throws Exception {
+        assertTrue(findUser(0));
+    }
+
+    public void testAddUser() throws Exception {
+        final UserDetails details = mDetails;
+
+        UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
+        assertTrue(userInfo != null);
+
+        List<UserInfo> list = details.getUsers();
+        boolean found = false;
+        for (UserInfo user : list) {
+            if (user.id == userInfo.id && user.name.equals("Guest 1")
+                    && user.isGuest()
+                    && !user.isAdmin()
+                    && !user.isPrimary()) {
+                found = true;
+            }
+        }
+        assertTrue(found);
+    }
+
+    public void testAdd2Users() throws Exception {
+        final UserDetails details = mDetails;
+
+        UserInfo user1 = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
+        UserInfo user2 = details.createUser("User 2", UserInfo.FLAG_ADMIN);
+
+        assertTrue(user1 != null);
+        assertTrue(user2 != null);
+
+        assertTrue(findUser(0));
+        assertTrue(findUser(user1.id));
+        assertTrue(findUser(user2.id));
+    }
+
+    public void testRemoveUser() throws Exception {
+        final UserDetails details = mDetails;
+
+        UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
+
+        details.removeUser(userInfo.id);
+
+        assertFalse(findUser(userInfo.id));
+    }
+
+    private boolean findUser(int id) {
+        List<UserInfo> list = mDetails.getUsers();
+
+        for (UserInfo user : list) {
+            if (user.id == id) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 0d20496..d84f1e5 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -23,32 +23,30 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.FeatureInfo;
-import android.content.pm.IPackageDeleteObserver;
 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.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.RemoteException;
 
 import java.util.List;
 
 /**
  * A mock {@link android.content.pm.PackageManager} class.  All methods are non-functional and throw
- * {@link java.lang.UnsupportedOperationException}. Override it to provide the operations that you 
+ * {@link java.lang.UnsupportedOperationException}. Override it to provide the operations that you
  * need.
  */
 public class MockPackageManager extends PackageManager {
@@ -63,12 +61,12 @@
     public String[] currentToCanonicalPackageNames(String[] names) {
         throw new UnsupportedOperationException();
     }
-    
+
     @Override
     public String[] canonicalToCurrentPackageNames(String[] names) {
         throw new UnsupportedOperationException();
     }
-    
+
     @Override
     public Intent getLaunchIntentForPackage(String packageName) {
         throw new UnsupportedOperationException();
@@ -101,7 +99,7 @@
     public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {
         throw new UnsupportedOperationException();
     }
-    
+
     @Override
     public ApplicationInfo getApplicationInfo(String packageName, int flags)
     throws NameNotFoundException {
@@ -176,7 +174,7 @@
     public String getNameForUid(int uid) {
         throw new UnsupportedOperationException();
     }
-    
+
     /**
      * @hide - to match hiding in superclass
      */
@@ -273,7 +271,7 @@
     public Drawable getApplicationIcon(String packageName) throws NameNotFoundException {
         throw new UnsupportedOperationException();
     }
-    
+
     @Override
     public Drawable getActivityLogo(ComponentName activityName) throws NameNotFoundException {
         throw new UnsupportedOperationException();
@@ -354,7 +352,7 @@
     public void movePackage(String packageName, IPackageMoveObserver observer, int flags) {
         throw new UnsupportedOperationException();
     }
-    
+
     @Override
     public String getInstallerPackageName(String packageName) {
         throw new UnsupportedOperationException();
@@ -368,7 +366,7 @@
             String packageName, IPackageDataObserver observer) {
         throw new UnsupportedOperationException();
     }
-    
+
     /**
      * @hide - to match hiding in superclass
      */
@@ -377,7 +375,7 @@
             String packageName, IPackageDataObserver observer) {
         throw new UnsupportedOperationException();
     }
-    
+
     /**
      * @hide - to match hiding in superclass
      */
@@ -435,7 +433,7 @@
     public void setApplicationEnabledSetting(String packageName, int newState, int flags) {
         throw new UnsupportedOperationException();
     }
-    
+
     @Override
     public int getApplicationEnabledSetting(String packageName) {
         throw new UnsupportedOperationException();
@@ -446,7 +444,7 @@
             int match, ComponentName[] set, ComponentName activity) {
         throw new UnsupportedOperationException();
     }
-    
+
     /**
      * @hide - to match hiding in superclass
      */
@@ -475,24 +473,64 @@
             List<ComponentName> outActivities, String packageName) {
         throw new UnsupportedOperationException();
     }
-    
+
     @Override
     public String[] getSystemSharedLibraryNames() {
         throw new UnsupportedOperationException();
     }
-    
+
     @Override
     public FeatureInfo[] getSystemAvailableFeatures() {
         throw new UnsupportedOperationException();
     }
-    
+
     @Override
     public boolean hasSystemFeature(String name) {
         throw new UnsupportedOperationException();
     }
-    
+
     @Override
     public boolean isSafeMode() {
         throw new UnsupportedOperationException();
     }
+
+    /**
+     * @hide
+     */
+    @Override
+    public UserInfo createUser(String name, int flags) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public List<UserInfo> getUsers() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean removeUser(int id) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void updateUserName(int id, String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void updateUserFlags(int id, int flags) {
+        throw new UnsupportedOperationException();
+    }
 }