Query users excluding any being removed
Keep track of user creation and last logged-in time.
adb shell dumpsys users
User switcher shouldn't show users about to be removed.
No need to check for singleton for activities.
Bug: 7194894
Change-Id: Ic9a59ea5bd544920479e191d1a1e8a77f8b6ddcf
diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java
index 2dc9a6a..2edc700 100644
--- a/services/java/com/android/server/pm/UserManagerService.java
+++ b/services/java/com/android/server/pm/UserManagerService.java
@@ -43,15 +43,19 @@
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.TimeUtils;
import android.util.Xml;
import java.io.BufferedOutputStream;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import org.xmlpull.v1.XmlPullParser;
@@ -66,6 +70,8 @@
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_CREATION_TIME = "created";
+ private static final String ATTR_LAST_LOGGED_IN_TIME = "lastLoggedIn";
private static final String ATTR_SERIAL_NO = "serialNumber";
private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber";
private static final String TAG_USERS = "users";
@@ -75,6 +81,8 @@
private static final String USER_LIST_FILENAME = "userlist.xml";
private static final String USER_PHOTO_FILENAME = "photo.png";
+ private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
+
private final Context mContext;
private final PackageManagerService mPm;
private final Object mInstallLock;
@@ -85,6 +93,7 @@
private final File mBaseUserPath;
private SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
+ private HashSet<Integer> mRemovingUserIds = new HashSet<Integer>();
private int[] mUserIds;
private boolean mGuestEnabled;
@@ -145,12 +154,14 @@
}
@Override
- public List<UserInfo> getUsers() {
+ public List<UserInfo> getUsers(boolean excludeDying) {
checkManageUsersPermission("query users");
synchronized (mPackagesLock) {
ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
for (int i = 0; i < mUsers.size(); i++) {
- users.add(mUsers.valueAt(i));
+ if (!excludeDying || !mRemovingUserIds.contains(mUsers.keyAt(i))) {
+ users.add(mUsers.valueAt(i));
+ }
}
return users;
}
@@ -436,6 +447,9 @@
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));
+ serializer.attribute(null, ATTR_CREATION_TIME, Long.toString(userInfo.creationTime));
+ serializer.attribute(null, ATTR_LAST_LOGGED_IN_TIME,
+ Long.toString(userInfo.lastLoggedInTime));
if (userInfo.iconPath != null) {
serializer.attribute(null, ATTR_ICON_PATH, userInfo.iconPath);
}
@@ -500,6 +514,8 @@
int serialNumber = id;
String name = null;
String iconPath = null;
+ long creationTime = 0L;
+ long lastLoggedInTime = 0L;
FileInputStream fis = null;
try {
@@ -520,18 +536,16 @@
}
if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
- String storedId = parser.getAttributeValue(null, ATTR_ID);
- if (Integer.parseInt(storedId) != id) {
+ int storedId = readIntAttribute(parser, ATTR_ID, -1);
+ if (storedId != id) {
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);
+ serialNumber = readIntAttribute(parser, ATTR_SERIAL_NO, id);
+ flags = readIntAttribute(parser, ATTR_FLAGS, 0);
iconPath = parser.getAttributeValue(null, ATTR_ICON_PATH);
+ creationTime = readLongAttribute(parser, ATTR_CREATION_TIME, 0);
+ lastLoggedInTime = readLongAttribute(parser, ATTR_LAST_LOGGED_IN_TIME, 0);
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
@@ -546,6 +560,8 @@
UserInfo userInfo = new UserInfo(id, name, iconPath, flags);
userInfo.serialNumber = serialNumber;
+ userInfo.creationTime = creationTime;
+ userInfo.lastLoggedInTime = lastLoggedInTime;
return userInfo;
} catch (IOException ioe) {
@@ -561,6 +577,26 @@
return null;
}
+ private int readIntAttribute(XmlPullParser parser, String attr, int defaultValue) {
+ String valueString = parser.getAttributeValue(null, attr);
+ if (valueString == null) return defaultValue;
+ try {
+ return Integer.parseInt(valueString);
+ } catch (NumberFormatException nfe) {
+ return defaultValue;
+ }
+ }
+
+ private long readLongAttribute(XmlPullParser parser, String attr, long defaultValue) {
+ String valueString = parser.getAttributeValue(null, attr);
+ if (valueString == null) return defaultValue;
+ try {
+ return Long.parseLong(valueString);
+ } catch (NumberFormatException nfe) {
+ return defaultValue;
+ }
+ }
+
@Override
public UserInfo createUser(String name, int flags) {
checkManageUsersPermission("Only the system can create users");
@@ -575,6 +611,8 @@
userInfo = new UserInfo(userId, name, null, flags);
File userPath = new File(mBaseUserPath, Integer.toString(userId));
userInfo.serialNumber = mNextSerialNumber++;
+ long now = System.currentTimeMillis();
+ userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
mUsers.put(userId, userInfo);
writeUserListLocked();
writeUserLocked(userInfo);
@@ -607,6 +645,7 @@
if (userHandle == 0 || user == null) {
return false;
}
+ mRemovingUserIds.add(userHandle);
}
int res;
@@ -636,6 +675,7 @@
// Remove this user from the list
mUsers.remove(userHandle);
+ mRemovingUserIds.remove(userHandle);
// Remove user file
AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + ".xml"));
userFile.delete();
@@ -700,6 +740,21 @@
}
/**
+ * Make a note of the last started time of a user.
+ * @param userId the user that was just foregrounded
+ */
+ public void userForeground(int userId) {
+ synchronized (mPackagesLock) {
+ UserInfo user = mUsers.get(userId);
+ long now = System.currentTimeMillis();
+ if (user != null && now > EPOCH_PLUS_30_YEARS) {
+ user.lastLoggedInTime = now;
+ writeUserLocked(user);
+ }
+ }
+ }
+
+ /**
* Returns the next available user id, filling in any holes in the ids.
* TODO: May not be a good idea to recycle ids, in case it results in confusion
* for data and battery stats collection, or unexpected cross-talk.
@@ -709,7 +764,7 @@
synchronized (mPackagesLock) {
int i = 10;
while (i < Integer.MAX_VALUE) {
- if (mUsers.indexOfKey(i) < 0) {
+ if (mUsers.indexOfKey(i) < 0 && !mRemovingUserIds.contains(i)) {
break;
}
i++;
@@ -717,4 +772,47 @@
return i;
}
}
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump UserManager from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " without permission "
+ + android.Manifest.permission.DUMP);
+ return;
+ }
+
+ long now = System.currentTimeMillis();
+ StringBuilder sb = new StringBuilder();
+ synchronized (mPackagesLock) {
+ pw.println("Users:");
+ for (int i = 0; i < mUsers.size(); i++) {
+ UserInfo user = mUsers.valueAt(i);
+ if (user == null) continue;
+ pw.print(" "); pw.print(user);
+ pw.println(mRemovingUserIds.contains(mUsers.keyAt(i)) ? " <removing> " : "");
+ pw.print(" Created: ");
+ if (user.creationTime == 0) {
+ pw.println("<unknown>");
+ } else {
+ sb.setLength(0);
+ TimeUtils.formatDuration(now - user.creationTime, sb);
+ sb.append(" ago");
+ pw.println(sb);
+ }
+ pw.print(" Last logged in: ");
+ if (user.lastLoggedInTime == 0) {
+ pw.println("<unknown>");
+ } else {
+ sb.setLength(0);
+ TimeUtils.formatDuration(now - user.lastLoggedInTime, sb);
+ sb.append(" ago");
+ pw.println(sb);
+ }
+ }
+ }
+ }
}