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/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;
}