UserHandle to UserSerialNo mapping

Use AtomicFile for usermanager files.

Added a MANAGE_USERS permission that apps (signature permission) can use
to create/query/modify/remove users.

Change-Id: I5cf232232d0539e7508df8ec9b216e29c2351cd9
diff --git a/api/current.txt b/api/current.txt
index e60a7b0..84132c2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -68,6 +68,7 @@
     field public static final java.lang.String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES";
     field public static final java.lang.String MANAGE_ACCOUNTS = "android.permission.MANAGE_ACCOUNTS";
     field public static final java.lang.String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS";
+    field public static final java.lang.String MANAGE_USERS = "android.permission.MANAGE_USERS";
     field public static final java.lang.String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
     field public static final java.lang.String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS";
     field public static final java.lang.String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index a4d28b0..d61d9da 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -369,7 +369,7 @@
     }
 
     private void onUserRemoved(Intent intent) {
-        int userId = intent.getIntExtra(Intent.EXTRA_USERID, -1);
+        int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
         if (userId < 1) return;
 
         UserAccounts accounts;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index d325186..0190555 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2279,24 +2279,24 @@
             "android.intent.action.PRE_BOOT_COMPLETED";
 
     /**
-     * Broadcast sent to the system when a user is added. Carries an extra EXTRA_USERID that has the
-     * userid of the new user.
+     * Broadcast sent to the system when a user is added. Carries an extra EXTRA_USER_HANDLE that has the
+     * userHandle of the new user.
      * @hide
      */
     public static final String ACTION_USER_ADDED =
             "android.intent.action.USER_ADDED";
 
     /**
-     * Broadcast sent to the system when a user is removed. Carries an extra EXTRA_USERID that has
-     * the userid of the user.
+     * Broadcast sent to the system when a user is removed. Carries an extra EXTRA_USER_HANDLE that has
+     * the userHandle of the user.
      * @hide
      */
     public static final String ACTION_USER_REMOVED =
             "android.intent.action.USER_REMOVED";
 
     /**
-     * Broadcast sent to the system when the user switches. Carries an extra EXTRA_USERID that has
-     * the userid of the user to become the current one.
+     * Broadcast sent to the system when the user switches. Carries an extra EXTRA_USER_HANDLE that has
+     * the userHandle of the user to become the current one.
      * @hide
      */
     public static final String ACTION_USER_SWITCHED =
@@ -2852,12 +2852,12 @@
         "android.intent.extra.LOCAL_ONLY";
 
     /**
-     * The userid carried with broadcast intents related to addition, removal and switching of users
+     * The userHandle carried with broadcast intents related to addition, removal and switching of users
      * - {@link #ACTION_USER_ADDED}, {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}.
      * @hide
      */
-    public static final String EXTRA_USERID =
-            "android.intent.extra.user_id";
+    public static final String EXTRA_USER_HANDLE =
+            "android.intent.extra.user_handle";
 
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 3d3ff51..ee075b4 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -895,7 +895,7 @@
     }
 
     private void onUserRemoved(Intent intent) {
-        int userId = intent.getIntExtra(Intent.EXTRA_USERID, -1);
+        int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
         if (userId == -1) return;
 
         // Clean up the storage engine database
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 638e273..060a235 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -53,6 +53,7 @@
     public static final int FLAG_RESTRICTED = 0x00000008;
 
     public int id;
+    public int serialNumber;
     public String name;
     public String iconPath;
     public int flags;
@@ -88,6 +89,7 @@
         iconPath = orig.iconPath;
         id = orig.id;
         flags = orig.flags;
+        serialNumber = orig.serialNumber;
     }
 
     @Override
@@ -104,6 +106,7 @@
         dest.writeString(name);
         dest.writeString(iconPath);
         dest.writeInt(flags);
+        dest.writeInt(serialNumber);
     }
 
     public static final Parcelable.Creator<UserInfo> CREATOR
@@ -121,5 +124,6 @@
         name = source.readString();
         iconPath = source.readString();
         flags = source.readInt();
+        serialNumber = source.readInt();
     }
 }
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index cb1b962..c7a8493 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -33,4 +33,6 @@
     void setGuestEnabled(boolean enable);
     boolean isGuestEnabled();
     void wipeUser(int userHandle);
+    int getUserSerialNumber(int userHandle);
+    int getUserHandle(int userSerialNumber);
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 9c73392..93da44a 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -209,4 +209,40 @@
     public int getMaxSupportedUsers() {
         return mContext.getResources().getInteger(R.integer.config_multiuserMaximumUsers);
     }
+
+    /**
+     * Returns a serial number on this device for a given userHandle. User handles can be recycled
+     * when deleting and creating users, but serial numbers are not reused until the device is wiped.
+     * @param userHandle
+     * @return a serial number associated with that user, or -1 if the userHandle is not valid.
+     * @hide
+     */
+    public int getUserSerialNumber(int userHandle) {
+        try {
+            return mService.getUserSerialNumber(userHandle);
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not get serial number for user " + userHandle);
+        }
+        return -1;
+    }
+
+    /**
+     * Returns a userHandle on this device for a given user serial number. User handles can be
+     * recycled when deleting and creating users, but serial numbers are not reused until the device
+     * is wiped.
+     * @param userSerialNumber
+     * @return the userHandle associated with that user serial number, or -1 if the serial number
+     * is not valid.
+     * @hide
+     */
+    public int getUserHandle(int userSerialNumber) {
+        try {
+            return mService.getUserHandle(userSerialNumber);
+        } catch (RemoteException re) {
+            Log.w(TAG, "Could not get userHandle for user " + userSerialNumber);
+        }
+        return -1;
+    }
+
+
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f0fe4a7..89be5cb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -766,7 +766,6 @@
         android:protectionLevel="dangerous"
         android:label="@string/permlab_getTasks"
         android:description="@string/permdesc_getTasks" />
-
     <!-- Allows an application to call APIs that allow it to do interactions
          across the users on the device, using singleton services and
          user-targeted broadcasts.  This permission is not available to
@@ -786,6 +785,15 @@
         android:label="@string/permlab_interactAcrossUsersFull"
         android:description="@string/permdesc_interactAcrossUsersFull" />
 
+    <!-- Allows an application to call APIs that allow it to query and manage
+         users on the device. This permission is not available to
+         third party applications. -->
+    <permission android:name="android.permission.MANAGE_USERS"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="signature"
+        android:label="@string/permlab_manageUsers"
+        android:description="@string/permdesc_manageUsers" />
+    
     <!-- Allows an application to get full detailed information about
          recently running tasks, with full fidelity to the real state.
          @hide -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 352c409..620a002 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -567,6 +567,11 @@
     <string name="permdesc_interactAcrossUsersFull">Allows all possible interactions across
         users.</string>
 
+    <!--  Title of an application permission, listed so the user can choose whether they want to allow the application to create/remove/query users. [CHAR LIMIT=none] -->
+    <string name="permlab_manageUsers">manage users</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to create/remove/query users. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_manageUsers">Allows apps to manage users on the device, including query, creation and deletion.</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=50] -->
     <string name="permlab_getDetailedTasks">retrieve details of running apps</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 676bfd0..e13378f 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -15,7 +15,7 @@
     <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
     <uses-permission android:name="android.permission.REMOTE_AUDIO_PLAYBACK" />
 
-    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
 
     <!-- Networking and telephony -->
     <uses-permission android:name="android.permission.BLUETOOTH" />
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 7598537..646f98a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -274,7 +274,7 @@
             public void onReceive(Context context, Intent intent) {
                 String action = intent.getAction();
                 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                    mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USERID, -1);
+                    mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                     if (true) Slog.v(TAG, "userId " + mCurrentUserId + " is in the house");
                     userSwitched(mCurrentUserId);
                 }
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java b/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
index a939222..279314d 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardUpdateMonitor.java
@@ -178,10 +178,10 @@
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_DPM_STATE_CHANGED));
             } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHED,
-                        intent.getIntExtra(Intent.EXTRA_USERID, 0), 0));
+                        intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0));
             } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_REMOVED,
-                        intent.getIntExtra(Intent.EXTRA_USERID, 0), 0));
+                        intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0));
             }
         }
     };
diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java
index 38f4554..8e341df 100644
--- a/services/java/com/android/server/AppWidgetService.java
+++ b/services/java/com/android/server/AppWidgetService.java
@@ -177,7 +177,7 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                onUserRemoved(intent.getIntExtra(Intent.EXTRA_USERID, -1));
+                onUserRemoved(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1));
             }
         }, userFilter);
     }
diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java
index baf33a9..0bf7aad 100644
--- a/services/java/com/android/server/ClipboardService.java
+++ b/services/java/com/android/server/ClipboardService.java
@@ -96,7 +96,7 @@
             public void onReceive(Context context, Intent intent) {
                 String action = intent.getAction();
                 if (Intent.ACTION_USER_REMOVED.equals(action)) {
-                    removeClipboard(intent.getIntExtra(Intent.EXTRA_USERID, 0));
+                    removeClipboard(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
                 }
             }
         }, userFilter);
diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java
index c6e43e6..1d248b2 100644
--- a/services/java/com/android/server/WallpaperManagerService.java
+++ b/services/java/com/android/server/WallpaperManagerService.java
@@ -423,9 +423,9 @@
             public void onReceive(Context context, Intent intent) {
                 String action = intent.getAction();
                 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                    switchUser(intent.getIntExtra(Intent.EXTRA_USERID, 0));
+                    switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
                 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
-                    removeUser(intent.getIntExtra(Intent.EXTRA_USERID, 0));
+                    removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
                 }
             }
         }, userFilter);
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 2de6931..d36114c 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -13492,7 +13492,7 @@
 
         // Inform of user switch
         Intent addedIntent = new Intent(Intent.ACTION_USER_SWITCHED);
-        addedIntent.putExtra(Intent.EXTRA_USERID, userId);
+        addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
         mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_ACCOUNTS);
 
         return true;
@@ -13509,7 +13509,7 @@
     }
 
     private void onUserRemoved(Intent intent) {
-        int extraUserId = intent.getIntExtra(Intent.EXTRA_USERID, -1);
+        int extraUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
         if (extraUserId < 1) return;
 
         // Kill all the processes for the user
diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java
index 725d67d3..a828864 100644
--- a/services/java/com/android/server/pm/UserManagerService.java
+++ b/services/java/com/android/server/pm/UserManagerService.java
@@ -22,6 +22,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastXmlSerializer;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -35,6 +36,7 @@
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.util.AtomicFile;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -55,22 +57,17 @@
 
 public class UserManagerService extends IUserManager.Stub {
 
-    private static final String TAG = "UserManagerService";
+    private static final String LOG_TAG = "UserManagerService";
 
     private static final String TAG_NAME = "name";
-
     private static final String ATTR_FLAGS = "flags";
-
     private static final String ATTR_ICON_PATH = "icon";
-
     private static final String ATTR_ID = "id";
-
+    private static final String ATTR_SERIAL_NO = "serialNumber";
+    private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber";
     private static final String TAG_USERS = "users";
-
     private static final String TAG_USER = "user";
 
-    private static final String LOG_TAG = "UserManager";
-
     private static final String USER_INFO_DIR = "system" + File.separator + "users";
     private static final String USER_LIST_FILENAME = "userlist.xml";
     private static final String USER_PHOTO_FILENAME = "photo.png";
@@ -81,6 +78,7 @@
     private final File mUserListFile;
     private int[] mUserIds;
     private boolean mGuestEnabled;
+    private int mNextSerialNumber;
 
     private Installer mInstaller;
     private File mBaseUserPath;
@@ -125,7 +123,7 @@
 
     @Override
     public List<UserInfo> getUsers() {
-        enforceSystemOrRoot("Only the system can query users");
+        checkManageUsersPermission("query users");
         synchronized (mUsers) {
             ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
             for (int i = 0; i < mUsers.size(); i++) {
@@ -137,7 +135,7 @@
 
     @Override
     public UserInfo getUserInfo(int userId) {
-        enforceSystemOrRoot("Only the system can query user");
+        checkManageUsersPermission("query user");
         synchronized (mUsers) {
             UserInfo info = mUsers.get(userId);
             return info;
@@ -152,7 +150,7 @@
 
     @Override
     public void setUserName(int userId, String name) {
-        enforceSystemOrRoot("Only the system can rename users");
+        checkManageUsersPermission("rename users");
         synchronized (mUsers) {
             UserInfo info = mUsers.get(userId);
             if (name != null && !name.equals(info.name)) {
@@ -164,7 +162,7 @@
 
     @Override
     public ParcelFileDescriptor setUserIcon(int userId) {
-        enforceSystemOrRoot("Only the system can update users");
+        checkManageUsersPermission("update users");
         synchronized (mUsers) {
             UserInfo info = mUsers.get(userId);
             if (info == null) return null;
@@ -178,7 +176,7 @@
 
     @Override
     public void setGuestEnabled(boolean enable) {
-        enforceSystemOrRoot("Only the system can enable guest users");
+        checkManageUsersPermission("enable guest users");
         synchronized (mUsers) {
             if (mGuestEnabled != enable) {
                 mGuestEnabled = enable;
@@ -209,7 +207,7 @@
 
     @Override
     public void wipeUser(int userHandle) {
-        enforceSystemOrRoot("Only the system can wipe users");
+        checkManageUsersPermission("wipe user");
         // TODO:
     }
 
@@ -220,10 +218,13 @@
      * @param message used as message if SecurityException is thrown
      * @throws SecurityException if the caller is not system or root
      */
-    private static final void enforceSystemOrRoot(String message) {
+    private static final void checkManageUsersPermission(String message) {
         final int uid = Binder.getCallingUid();
-        if (uid != Process.SYSTEM_UID && uid != 0) {
-            throw new SecurityException(message);
+        if (uid != Process.SYSTEM_UID && uid != 0
+                && ActivityManager.checkComponentPermission(
+                        android.Manifest.permission.MANAGE_USERS,
+                        uid, -1, true) != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("You need MANAGE_USERS permission to: " + message);
         }
     }
 
@@ -243,7 +244,7 @@
             info.iconPath = file.getAbsolutePath();
             return fd;
         } catch (FileNotFoundException e) {
-            Slog.w(TAG, "Error setting photo for user ", e);
+            Slog.w(LOG_TAG, "Error setting photo for user ", e);
         }
         return null;
     }
@@ -270,8 +271,9 @@
             return;
         }
         FileInputStream fis = null;
+        AtomicFile userListFile = new AtomicFile(mUserListFile);
         try {
-            fis = new FileInputStream(mUserListFile);
+            fis = userListFile.openRead();
             XmlPullParser parser = Xml.newPullParser();
             parser.setInput(fis, null);
             int type;
@@ -286,15 +288,26 @@
                 return;
             }
 
+            mNextSerialNumber = -1;
+            if (parser.getName().equals(TAG_USERS)) {
+                String lastSerialNumber = parser.getAttributeValue(null, ATTR_NEXT_SERIAL_NO);
+                if (lastSerialNumber != null) {
+                    mNextSerialNumber = Integer.parseInt(lastSerialNumber);
+                }
+            }
+
             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);
-                    }
-                    if (user.isGuest()) {
-                        mGuestEnabled = true;
+                        if (user.isGuest()) {
+                            mGuestEnabled = true;
+                        }
+                        if (mNextSerialNumber < 0 || mNextSerialNumber <= user.id) {
+                            mNextSerialNumber = user.id + 1;
+                        }
                     }
                 }
             }
@@ -333,9 +346,9 @@
      */
     private void writeUserLocked(UserInfo userInfo) {
         FileOutputStream fos = null;
+        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userInfo.id + ".xml"));
         try {
-            final File mUserFile = new File(mUsersDir, userInfo.id + ".xml");
-            fos = new FileOutputStream(mUserFile);
+            fos = userFile.startWrite();
             final BufferedOutputStream bos = new BufferedOutputStream(fos);
 
             // XmlSerializer serializer = XmlUtils.serializerInstance();
@@ -346,6 +359,7 @@
 
             serializer.startTag(null, TAG_USER);
             serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id));
+            serializer.attribute(null, ATTR_SERIAL_NO, Integer.toString(userInfo.serialNumber));
             serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags));
             if (userInfo.iconPath != null) {
                 serializer.attribute(null,  ATTR_ICON_PATH, userInfo.iconPath);
@@ -358,30 +372,26 @@
             serializer.endTag(null, TAG_USER);
 
             serializer.endDocument();
-        } catch (IOException ioe) {
+            userFile.finishWrite(fos);
+        } catch (Exception ioe) {
             Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
-        } finally {
-            if (fos != null) {
-                try {
-                    fos.close();
-                } catch (IOException ioe) {
-                }
-            }
+            userFile.failWrite(fos);
         }
     }
 
     /*
      * Writes the user list file in this format:
      *
-     * <users>
+     * <users nextSerialNumber="3">
      *   <user id="0"></user>
      *   <user id="2"></user>
      * </users>
      */
     private void writeUserListLocked() {
         FileOutputStream fos = null;
+        AtomicFile userListFile = new AtomicFile(mUserListFile);
         try {
-            fos = new FileOutputStream(mUserListFile);
+            fos = userListFile.startWrite();
             final BufferedOutputStream bos = new BufferedOutputStream(fos);
 
             // XmlSerializer serializer = XmlUtils.serializerInstance();
@@ -391,6 +401,7 @@
             serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
 
             serializer.startTag(null, TAG_USERS);
+            serializer.attribute(null, ATTR_NEXT_SERIAL_NO, Integer.toString(mNextSerialNumber));
 
             for (int i = 0; i < mUsers.size(); i++) {
                 UserInfo user = mUsers.valueAt(i);
@@ -402,27 +413,24 @@
             serializer.endTag(null, TAG_USERS);
 
             serializer.endDocument();
-        } catch (IOException ioe) {
+            userListFile.finishWrite(fos);
+        } catch (Exception e) {
+            userListFile.failWrite(fos);
             Slog.e(LOG_TAG, "Error writing user list");
-        } finally {
-            if (fos != null) {
-                try {
-                    fos.close();
-                } catch (IOException ioe) {
-                }
-            }
         }
     }
 
     private UserInfo readUser(int id) {
         int flags = 0;
+        int serialNumber = id;
         String name = null;
         String iconPath = null;
 
         FileInputStream fis = null;
         try {
-            File userFile = new File(mUsersDir, Integer.toString(id) + ".xml");
-            fis = new FileInputStream(userFile);
+            AtomicFile userFile =
+                    new AtomicFile(new File(mUsersDir, Integer.toString(id) + ".xml"));
+            fis = userFile.openRead();
             XmlPullParser parser = Xml.newPullParser();
             parser.setInput(fis, null);
             int type;
@@ -442,6 +450,10 @@
                     Slog.e(LOG_TAG, "User id does not match the file name");
                     return null;
                 }
+                String serialNumberValue = parser.getAttributeValue(null, ATTR_SERIAL_NO);
+                if (serialNumberValue != null) {
+                    serialNumber = Integer.parseInt(serialNumberValue);
+                }
                 String flagString = parser.getAttributeValue(null, ATTR_FLAGS);
                 flags = Integer.parseInt(flagString);
                 iconPath = parser.getAttributeValue(null, ATTR_ICON_PATH);
@@ -458,6 +470,7 @@
             }
 
             UserInfo userInfo = new UserInfo(id, name, iconPath, flags);
+            userInfo.serialNumber = serialNumber;
             return userInfo;
 
         } catch (IOException ioe) {
@@ -475,7 +488,7 @@
 
     @Override
     public UserInfo createUser(String name, int flags) {
-        enforceSystemOrRoot("Only the system can create users");
+        checkManageUsersPermission("Only the system can create users");
         int userId = getNextAvailableId();
         UserInfo userInfo = new UserInfo(userId, name, null, flags);
         File userPath = new File(mBaseUserPath, Integer.toString(userId));
@@ -483,6 +496,7 @@
             return null;
         }
         synchronized (mUsers) {
+            userInfo.serialNumber = mNextSerialNumber++;
             mUsers.put(userId, userInfo);
             writeUserListLocked();
             writeUserLocked(userInfo);
@@ -490,8 +504,8 @@
         }
         if (userInfo != null) {
             Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
-            addedIntent.putExtra(Intent.EXTRA_USERID, userInfo.id);
-            mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_ACCOUNTS);
+            addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
+            mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_USERS);
         }
         return userInfo;
     }
@@ -502,9 +516,34 @@
      * @param id the user's id
      */
     public boolean removeUser(int userHandle) {
-        enforceSystemOrRoot("Only the system can remove users");
+        checkManageUsersPermission("Only the system can remove users");
+        boolean result;
         synchronized (mUsers) {
-            return removeUserLocked(userHandle);
+            result = removeUserLocked(userHandle);
+        }
+        // Let other services shutdown any activity
+        Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
+        addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle);
+        mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_USERS);
+        return result;
+    }
+
+    @Override
+    public int getUserSerialNumber(int userHandle) {
+        synchronized (mUsers) {
+            if (!exists(userHandle)) return -1;
+            return getUserInfo(userHandle).serialNumber;
+        }
+    }
+
+    @Override
+    public int getUserHandle(int userSerialNumber) {
+        synchronized (mUsers) {
+            for (int userId : mUserIds) {
+                if (getUserInfo(userId).serialNumber == userSerialNumber) return userId;
+            }
+            // Not found
+            return -1;
         }
     }
 
@@ -519,17 +558,12 @@
         // Remove this user from the list
         mUsers.remove(userHandle);
         // Remove user file
-        File userFile = new File(mUsersDir, userHandle + ".xml");
+        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + ".xml"));
         userFile.delete();
         // Update the user list
         writeUserListLocked();
         updateUserIdsLocked();
 
-        // Let other services shutdown any activity
-        Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
-        addedIntent.putExtra(Intent.EXTRA_USERID, userHandle);
-        mContext.sendBroadcast(addedIntent, android.Manifest.permission.MANAGE_ACCOUNTS);
-
         removePackageFolders(userHandle);
         return true;
     }
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 3f66de6..89b2474 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -33,6 +33,7 @@
     <uses-permission android:name="android.permission.MODIFY_NETWORK_ACCOUNTING" />
     <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index bc3649c..59a86c2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -16,35 +16,23 @@
 
 package com.android.server.pm;
 
-import com.android.server.pm.UserManagerService;
-
+import android.content.Context;
 import android.content.pm.UserInfo;
 import android.os.Debug;
 import android.os.Environment;
+import android.os.UserManager;
 import android.test.AndroidTestCase;
 
 import java.util.List;
 
-/** Test {@link UserManagerService} functionality. */
+/** Test {@link UserManager} functionality. */
 public class UserManagerTest extends AndroidTestCase {
 
-    UserManagerService mUserManager = null;
+    UserManager mUserManager = null;
 
     @Override
     public void setUp() throws Exception {
-        mUserManager = new UserManagerService(Environment.getExternalStorageDirectory(),
-                Environment.getExternalStorageDirectory());
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        List<UserInfo> users = mUserManager.getUsers();
-        // Remove all except the primary user
-        for (UserInfo user : users) {
-            if (!user.isPrimary()) {
-                mUserManager.removeUser(user.id);
-            }
-        }
+        mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
     }
 
     public void testHasPrimary() throws Exception {
@@ -52,12 +40,10 @@
     }
 
     public void testAddUser() throws Exception {
-        final UserManagerService details = mUserManager;
-
-        UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
+        UserInfo userInfo = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST);
         assertTrue(userInfo != null);
 
-        List<UserInfo> list = details.getUsers();
+        List<UserInfo> list = mUserManager.getUsers();
         boolean found = false;
         for (UserInfo user : list) {
             if (user.id == userInfo.id && user.name.equals("Guest 1")
@@ -68,13 +54,12 @@
             }
         }
         assertTrue(found);
+        mUserManager.removeUser(userInfo.id);
     }
 
     public void testAdd2Users() throws Exception {
-        final UserManagerService details = mUserManager;
-
-        UserInfo user1 = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
-        UserInfo user2 = details.createUser("User 2", UserInfo.FLAG_ADMIN);
+        UserInfo user1 = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST);
+        UserInfo user2 = mUserManager.createUser("User 2", UserInfo.FLAG_ADMIN);
 
         assertTrue(user1 != null);
         assertTrue(user2 != null);
@@ -82,14 +67,14 @@
         assertTrue(findUser(0));
         assertTrue(findUser(user1.id));
         assertTrue(findUser(user2.id));
+        mUserManager.removeUser(user1.id);
+        mUserManager.removeUser(user2.id);
     }
 
     public void testRemoveUser() throws Exception {
-        final UserManagerService details = mUserManager;
+        UserInfo userInfo = mUserManager.createUser("Guest 1", UserInfo.FLAG_GUEST);
 
-        UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
-
-        details.removeUser(userInfo.id);
+        mUserManager.removeUser(userInfo.id);
 
         assertFalse(findUser(userInfo.id));
     }
@@ -104,4 +89,18 @@
         }
         return false;
     }
+
+    public void testSerialNumber() {
+        UserInfo user1 = mUserManager.createUser("User 1", UserInfo.FLAG_RESTRICTED);
+        int serialNumber1 = user1.serialNumber;
+        assertEquals(serialNumber1, mUserManager.getUserSerialNumber(user1.id));
+        assertEquals(user1.id, mUserManager.getUserHandle(serialNumber1));
+        mUserManager.removeUser(user1.id);
+        UserInfo user2 = mUserManager.createUser("User 2", UserInfo.FLAG_RESTRICTED);
+        int serialNumber2 = user2.serialNumber;
+        assertFalse(serialNumber1 == serialNumber2);
+        assertEquals(serialNumber2, mUserManager.getUserSerialNumber(user2.id));
+        assertEquals(user2.id, mUserManager.getUserHandle(serialNumber2));
+        mUserManager.removeUser(user2.id);
+    }
 }