Allow overriding max profile in debugable builds.
Support a system property on debugable builds to
override the max number of managed profiles to
allow easier dogfooding of multiple profiles.
Support 3 different badge colours for managed profiles.
Bug: 30473760
Test: runtest -c com.android.server.pm.UserManagerServiceCreateProfileTest frameworks-services
Test: runtest -c com.android.server.pm.UserManagerServiceUserInfoTest frameworks-services
Test: manual - attempting to create 2 profiles with adb fails, passes once I set the property.
Change-Id: Ie7fb19048a04a01572666f229283152254d0ffc3
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5b47b6f..20afed7 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -63,6 +63,7 @@
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
@@ -110,6 +111,8 @@
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
@@ -149,6 +152,7 @@
private static final String ATTR_GUEST_TO_REMOVE = "guestToRemove";
private static final String ATTR_USER_VERSION = "version";
private static final String ATTR_PROFILE_GROUP_ID = "profileGroupId";
+ private static final String ATTR_PROFILE_BADGE = "profileBadge";
private static final String ATTR_RESTRICTED_PROFILE_PARENT_ID = "restrictedProfileParentId";
private static final String ATTR_SEED_ACCOUNT_NAME = "seedAccountName";
private static final String ATTR_SEED_ACCOUNT_TYPE = "seedAccountType";
@@ -203,7 +207,8 @@
// Maximum number of managed profiles permitted per user is 1. This cannot be increased
// without first making sure that the rest of the framework is prepared for it.
- private static final int MAX_MANAGED_PROFILES = 1;
+ @VisibleForTesting
+ static final int MAX_MANAGED_PROFILES = 1;
static final int WRITE_USER_MSG = 1;
static final int WRITE_USER_DELAY = 2*1000; // 2 seconds
@@ -232,7 +237,8 @@
* User-related information that is used for persisting to flash. Only UserInfo is
* directly exposed to other system apps.
*/
- private static class UserData {
+ @VisibleForTesting
+ static class UserData {
// Basic user information and properties
UserInfo info;
// Account name used when there is a strong association between a user and an account
@@ -874,6 +880,22 @@
}
@Override
+ public int getManagedProfileBadge(@UserIdInt int userId) {
+ int callingUserId = UserHandle.getCallingUserId();
+ if (callingUserId != userId && !hasManageUsersPermission()) {
+ if (!isSameProfileGroupNoChecks(callingUserId, userId)) {
+ throw new SecurityException(
+ "You need MANAGE_USERS permission to: check if specified user a " +
+ "managed profile outside your profile group");
+ }
+ }
+ synchronized (mUsersLock) {
+ UserInfo userInfo = getUserInfoLU(userId);
+ return userInfo != null ? userInfo.profileBadge : 0;
+ }
+ }
+
+ @Override
public boolean isManagedProfile(int userId) {
int callingUserId = UserHandle.getCallingUserId();
if (callingUserId != userId && !hasManageUsersPermission()) {
@@ -1464,7 +1486,7 @@
// Limit number of managed profiles that can be created
final int managedProfilesCount = getProfiles(userId, true).size() - 1;
final int profilesRemovedCount = managedProfilesCount > 0 && allowedToRemoveOne ? 1 : 0;
- if (managedProfilesCount - profilesRemovedCount >= MAX_MANAGED_PROFILES) {
+ if (managedProfilesCount - profilesRemovedCount >= getMaxManagedProfiles()) {
return false;
}
synchronized(mUsersLock) {
@@ -1859,13 +1881,6 @@
}
}
- /*
- * Writes the user file in this format:
- *
- * <user flags="20039023" id="0">
- * <name>Primary</name>
- * </user>
- */
private void writeUserLP(UserData userData) {
if (DBG) {
debug("writeUserLP " + userData);
@@ -1875,78 +1890,7 @@
try {
fos = userFile.startWrite();
final BufferedOutputStream bos = new BufferedOutputStream(fos);
-
- // XmlSerializer serializer = XmlUtils.serializerInstance();
- final XmlSerializer serializer = new FastXmlSerializer();
- serializer.setOutput(bos, StandardCharsets.UTF_8.name());
- serializer.startDocument(null, true);
- serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
-
- final UserInfo userInfo = userData.info;
- 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));
- serializer.attribute(null, ATTR_CREATION_TIME, Long.toString(userInfo.creationTime));
- serializer.attribute(null, ATTR_LAST_LOGGED_IN_TIME,
- Long.toString(userInfo.lastLoggedInTime));
- if (userInfo.lastLoggedInFingerprint != null) {
- serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT,
- userInfo.lastLoggedInFingerprint);
- }
- if (userInfo.iconPath != null) {
- serializer.attribute(null, ATTR_ICON_PATH, userInfo.iconPath);
- }
- if (userInfo.partial) {
- serializer.attribute(null, ATTR_PARTIAL, "true");
- }
- if (userInfo.guestToRemove) {
- serializer.attribute(null, ATTR_GUEST_TO_REMOVE, "true");
- }
- if (userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
- serializer.attribute(null, ATTR_PROFILE_GROUP_ID,
- Integer.toString(userInfo.profileGroupId));
- }
- if (userInfo.restrictedProfileParentId != UserInfo.NO_PROFILE_GROUP_ID) {
- serializer.attribute(null, ATTR_RESTRICTED_PROFILE_PARENT_ID,
- Integer.toString(userInfo.restrictedProfileParentId));
- }
- // Write seed data
- if (userData.persistSeedData) {
- if (userData.seedAccountName != null) {
- serializer.attribute(null, ATTR_SEED_ACCOUNT_NAME, userData.seedAccountName);
- }
- if (userData.seedAccountType != null) {
- serializer.attribute(null, ATTR_SEED_ACCOUNT_TYPE, userData.seedAccountType);
- }
- }
- if (userInfo.name != null) {
- serializer.startTag(null, TAG_NAME);
- serializer.text(userInfo.name);
- serializer.endTag(null, TAG_NAME);
- }
- synchronized (mRestrictionsLock) {
- UserRestrictionsUtils.writeRestrictions(serializer,
- mBaseUserRestrictions.get(userInfo.id), TAG_RESTRICTIONS);
- UserRestrictionsUtils.writeRestrictions(serializer,
- mDevicePolicyLocalUserRestrictions.get(userInfo.id),
- TAG_DEVICE_POLICY_RESTRICTIONS);
- }
-
- if (userData.account != null) {
- serializer.startTag(null, TAG_ACCOUNT);
- serializer.text(userData.account);
- serializer.endTag(null, TAG_ACCOUNT);
- }
-
- if (userData.persistSeedData && userData.seedAccountOptions != null) {
- serializer.startTag(null, TAG_SEED_ACCOUNT_OPTIONS);
- userData.seedAccountOptions.saveToXml(serializer);
- serializer.endTag(null, TAG_SEED_ACCOUNT_OPTIONS);
- }
- serializer.endTag(null, TAG_USER);
-
- serializer.endDocument();
+ writeUserLP(userData, bos);
userFile.finishWrite(fos);
} catch (Exception ioe) {
Slog.e(LOG_TAG, "Error writing user info " + userData.info.id, ioe);
@@ -1955,6 +1899,92 @@
}
/*
+ * Writes the user file in this format:
+ *
+ * <user flags="20039023" id="0">
+ * <name>Primary</name>
+ * </user>
+ */
+ @VisibleForTesting
+ void writeUserLP(UserData userData, OutputStream os)
+ throws IOException, XmlPullParserException {
+ // XmlSerializer serializer = XmlUtils.serializerInstance();
+ final XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(os, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+ final UserInfo userInfo = userData.info;
+ 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));
+ serializer.attribute(null, ATTR_CREATION_TIME, Long.toString(userInfo.creationTime));
+ serializer.attribute(null, ATTR_LAST_LOGGED_IN_TIME,
+ Long.toString(userInfo.lastLoggedInTime));
+ if (userInfo.lastLoggedInFingerprint != null) {
+ serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT,
+ userInfo.lastLoggedInFingerprint);
+ }
+ if (userInfo.iconPath != null) {
+ serializer.attribute(null, ATTR_ICON_PATH, userInfo.iconPath);
+ }
+ if (userInfo.partial) {
+ serializer.attribute(null, ATTR_PARTIAL, "true");
+ }
+ if (userInfo.guestToRemove) {
+ serializer.attribute(null, ATTR_GUEST_TO_REMOVE, "true");
+ }
+ if (userInfo.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
+ serializer.attribute(null, ATTR_PROFILE_GROUP_ID,
+ Integer.toString(userInfo.profileGroupId));
+ }
+ serializer.attribute(null, ATTR_PROFILE_BADGE,
+ Integer.toString(userInfo.profileBadge));
+ if (userInfo.restrictedProfileParentId != UserInfo.NO_PROFILE_GROUP_ID) {
+ serializer.attribute(null, ATTR_RESTRICTED_PROFILE_PARENT_ID,
+ Integer.toString(userInfo.restrictedProfileParentId));
+ }
+ // Write seed data
+ if (userData.persistSeedData) {
+ if (userData.seedAccountName != null) {
+ serializer.attribute(null, ATTR_SEED_ACCOUNT_NAME, userData.seedAccountName);
+ }
+ if (userData.seedAccountType != null) {
+ serializer.attribute(null, ATTR_SEED_ACCOUNT_TYPE, userData.seedAccountType);
+ }
+ }
+ if (userInfo.name != null) {
+ serializer.startTag(null, TAG_NAME);
+ serializer.text(userInfo.name);
+ serializer.endTag(null, TAG_NAME);
+ }
+ synchronized (mRestrictionsLock) {
+ UserRestrictionsUtils.writeRestrictions(serializer,
+ mBaseUserRestrictions.get(userInfo.id), TAG_RESTRICTIONS);
+ UserRestrictionsUtils.writeRestrictions(serializer,
+ mDevicePolicyLocalUserRestrictions.get(userInfo.id),
+ TAG_DEVICE_POLICY_RESTRICTIONS);
+ }
+
+ if (userData.account != null) {
+ serializer.startTag(null, TAG_ACCOUNT);
+ serializer.text(userData.account);
+ serializer.endTag(null, TAG_ACCOUNT);
+ }
+
+ if (userData.persistSeedData && userData.seedAccountOptions != null) {
+ serializer.startTag(null, TAG_SEED_ACCOUNT_OPTIONS);
+ userData.seedAccountOptions.saveToXml(serializer);
+ serializer.endTag(null, TAG_SEED_ACCOUNT_OPTIONS);
+ }
+
+ serializer.endTag(null, TAG_USER);
+
+ serializer.endDocument();
+ }
+
+ /*
* Writes the user list file in this format:
*
* <users nextSerialNumber="3">
@@ -2020,6 +2050,25 @@
}
private UserData readUserLP(int id) {
+ FileInputStream fis = null;
+ try {
+ AtomicFile userFile =
+ new AtomicFile(new File(mUsersDir, Integer.toString(id) + XML_SUFFIX));
+ fis = userFile.openRead();
+ return readUserLP(id, fis);
+ } catch (IOException ioe) {
+ Slog.e(LOG_TAG, "Error reading user list");
+ } catch (XmlPullParserException pe) {
+ Slog.e(LOG_TAG, "Error reading user list");
+ } finally {
+ IoUtils.closeQuietly(fis);
+ }
+ return null;
+ }
+
+ @VisibleForTesting
+ UserData readUserLP(int id, InputStream is) throws IOException,
+ XmlPullParserException {
int flags = 0;
int serialNumber = id;
String name = null;
@@ -2029,6 +2078,7 @@
long lastLoggedInTime = 0L;
String lastLoggedInFingerprint = null;
int profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
+ int profileBadge = 0;
int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
boolean partial = false;
boolean guestToRemove = false;
@@ -2039,120 +2089,106 @@
Bundle baseRestrictions = new Bundle();
Bundle localRestrictions = new Bundle();
- FileInputStream fis = null;
- try {
- AtomicFile userFile =
- new AtomicFile(new File(mUsersDir, Integer.toString(id) + XML_SUFFIX));
- fis = userFile.openRead();
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(fis, StandardCharsets.UTF_8.name());
- int type;
- while ((type = parser.next()) != XmlPullParser.START_TAG
- && type != XmlPullParser.END_DOCUMENT) {
- // Skip
- }
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(is, StandardCharsets.UTF_8.name());
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Skip
+ }
- if (type != XmlPullParser.START_TAG) {
- Slog.e(LOG_TAG, "Unable to read user " + id);
+ if (type != XmlPullParser.START_TAG) {
+ Slog.e(LOG_TAG, "Unable to read user " + id);
+ return null;
+ }
+
+ if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
+ int storedId = readIntAttribute(parser, ATTR_ID, -1);
+ if (storedId != id) {
+ Slog.e(LOG_TAG, "User id does not match the file name");
return null;
}
+ 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);
+ lastLoggedInFingerprint = parser.getAttributeValue(null,
+ ATTR_LAST_LOGGED_IN_FINGERPRINT);
+ profileGroupId = readIntAttribute(parser, ATTR_PROFILE_GROUP_ID,
+ UserInfo.NO_PROFILE_GROUP_ID);
+ profileBadge = readIntAttribute(parser, ATTR_PROFILE_BADGE, 0);
+ restrictedProfileParentId = readIntAttribute(parser,
+ ATTR_RESTRICTED_PROFILE_PARENT_ID, UserInfo.NO_PROFILE_GROUP_ID);
+ String valueString = parser.getAttributeValue(null, ATTR_PARTIAL);
+ if ("true".equals(valueString)) {
+ partial = true;
+ }
+ valueString = parser.getAttributeValue(null, ATTR_GUEST_TO_REMOVE);
+ if ("true".equals(valueString)) {
+ guestToRemove = true;
+ }
- if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
- int storedId = readIntAttribute(parser, ATTR_ID, -1);
- if (storedId != id) {
- Slog.e(LOG_TAG, "User id does not match the file name");
- return null;
- }
- 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);
- lastLoggedInFingerprint = parser.getAttributeValue(null,
- ATTR_LAST_LOGGED_IN_FINGERPRINT);
- profileGroupId = readIntAttribute(parser, ATTR_PROFILE_GROUP_ID,
- UserInfo.NO_PROFILE_GROUP_ID);
- restrictedProfileParentId = readIntAttribute(parser,
- ATTR_RESTRICTED_PROFILE_PARENT_ID, UserInfo.NO_PROFILE_GROUP_ID);
- String valueString = parser.getAttributeValue(null, ATTR_PARTIAL);
- if ("true".equals(valueString)) {
- partial = true;
- }
- valueString = parser.getAttributeValue(null, ATTR_GUEST_TO_REMOVE);
- if ("true".equals(valueString)) {
- guestToRemove = true;
- }
+ seedAccountName = parser.getAttributeValue(null, ATTR_SEED_ACCOUNT_NAME);
+ seedAccountType = parser.getAttributeValue(null, ATTR_SEED_ACCOUNT_TYPE);
+ if (seedAccountName != null || seedAccountType != null) {
+ persistSeedData = true;
+ }
- seedAccountName = parser.getAttributeValue(null, ATTR_SEED_ACCOUNT_NAME);
- seedAccountType = parser.getAttributeValue(null, ATTR_SEED_ACCOUNT_TYPE);
- if (seedAccountName != null || seedAccountType != null) {
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tag = parser.getName();
+ if (TAG_NAME.equals(tag)) {
+ type = parser.next();
+ if (type == XmlPullParser.TEXT) {
+ name = parser.getText();
+ }
+ } else if (TAG_RESTRICTIONS.equals(tag)) {
+ UserRestrictionsUtils.readRestrictions(parser, baseRestrictions);
+ } else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) {
+ UserRestrictionsUtils.readRestrictions(parser, localRestrictions);
+ } else if (TAG_ACCOUNT.equals(tag)) {
+ type = parser.next();
+ if (type == XmlPullParser.TEXT) {
+ account = parser.getText();
+ }
+ } else if (TAG_SEED_ACCOUNT_OPTIONS.equals(tag)) {
+ seedAccountOptions = PersistableBundle.restoreFromXml(parser);
persistSeedData = true;
}
-
- int outerDepth = parser.getDepth();
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- String tag = parser.getName();
- if (TAG_NAME.equals(tag)) {
- type = parser.next();
- if (type == XmlPullParser.TEXT) {
- name = parser.getText();
- }
- } else if (TAG_RESTRICTIONS.equals(tag)) {
- UserRestrictionsUtils.readRestrictions(parser, baseRestrictions);
- } else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) {
- UserRestrictionsUtils.readRestrictions(parser, localRestrictions);
- } else if (TAG_ACCOUNT.equals(tag)) {
- type = parser.next();
- if (type == XmlPullParser.TEXT) {
- account = parser.getText();
- }
- } else if (TAG_SEED_ACCOUNT_OPTIONS.equals(tag)) {
- seedAccountOptions = PersistableBundle.restoreFromXml(parser);
- persistSeedData = true;
- }
- }
- }
-
- // Create the UserInfo object that gets passed around
- UserInfo userInfo = new UserInfo(id, name, iconPath, flags);
- userInfo.serialNumber = serialNumber;
- userInfo.creationTime = creationTime;
- userInfo.lastLoggedInTime = lastLoggedInTime;
- userInfo.lastLoggedInFingerprint = lastLoggedInFingerprint;
- userInfo.partial = partial;
- userInfo.guestToRemove = guestToRemove;
- userInfo.profileGroupId = profileGroupId;
- userInfo.restrictedProfileParentId = restrictedProfileParentId;
-
- // Create the UserData object that's internal to this class
- UserData userData = new UserData();
- userData.info = userInfo;
- userData.account = account;
- userData.seedAccountName = seedAccountName;
- userData.seedAccountType = seedAccountType;
- userData.persistSeedData = persistSeedData;
- userData.seedAccountOptions = seedAccountOptions;
-
- synchronized (mRestrictionsLock) {
- mBaseUserRestrictions.put(id, baseRestrictions);
- mDevicePolicyLocalUserRestrictions.put(id, localRestrictions);
- }
- return userData;
- } catch (IOException ioe) {
- } catch (XmlPullParserException pe) {
- } finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- }
}
}
- return null;
+
+ // Create the UserInfo object that gets passed around
+ UserInfo userInfo = new UserInfo(id, name, iconPath, flags);
+ userInfo.serialNumber = serialNumber;
+ userInfo.creationTime = creationTime;
+ userInfo.lastLoggedInTime = lastLoggedInTime;
+ userInfo.lastLoggedInFingerprint = lastLoggedInFingerprint;
+ userInfo.partial = partial;
+ userInfo.guestToRemove = guestToRemove;
+ userInfo.profileGroupId = profileGroupId;
+ userInfo.profileBadge = profileBadge;
+ userInfo.restrictedProfileParentId = restrictedProfileParentId;
+
+ // Create the UserData object that's internal to this class
+ UserData userData = new UserData();
+ userData.info = userInfo;
+ userData.account = account;
+ userData.seedAccountName = seedAccountName;
+ userData.seedAccountType = seedAccountType;
+ userData.persistSeedData = persistSeedData;
+ userData.seedAccountOptions = seedAccountOptions;
+
+ synchronized (mRestrictionsLock) {
+ mBaseUserRestrictions.put(id, baseRestrictions);
+ mDevicePolicyLocalUserRestrictions.put(id, localRestrictions);
+ }
+ return userData;
}
private int readIntAttribute(XmlPullParser parser, String attr, int defaultValue) {
@@ -2316,6 +2352,9 @@
userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
userInfo.partial = true;
userInfo.lastLoggedInFingerprint = Build.FINGERPRINT;
+ if (isManagedProfile && parentId != UserHandle.USER_NULL) {
+ userInfo.profileBadge = getFreeProfileBadgeLU(parentId);
+ }
userData = new UserData();
userData.info = userInfo;
mUsers.put(userId, userData);
@@ -3643,4 +3682,39 @@
Log.d(LOG_TAG, message +
(DBG_WITH_STACKTRACE ? " called at\n" + Debug.getCallers(10, " ") : ""));
}
+
+ @VisibleForTesting
+ static int getMaxManagedProfiles() {
+ // Allow overriding max managed profiles on debuggable builds for testing
+ // of multiple profiles.
+ if (!Build.IS_DEBUGGABLE) {
+ return MAX_MANAGED_PROFILES;
+ } else {
+ return SystemProperties.getInt("persist.sys.max_profiles",
+ MAX_MANAGED_PROFILES);
+ }
+ }
+
+ @VisibleForTesting
+ int getFreeProfileBadgeLU(int parentUserId) {
+ int maxManagedProfiles = getMaxManagedProfiles();
+ boolean[] usedBadges = new boolean[maxManagedProfiles];
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ UserInfo ui = mUsers.valueAt(i).info;
+ // Check which badge indexes are already used by this profile group.
+ if (ui.isManagedProfile()
+ && ui.profileGroupId == parentUserId
+ && !mRemovingUserIds.get(ui.id)
+ && ui.profileBadge < maxManagedProfiles) {
+ usedBadges[ui.profileBadge] = true;
+ }
+ }
+ for (int i = 0; i < maxManagedProfiles; i++) {
+ if (!usedBadges[i]) {
+ return i;
+ }
+ }
+ return 0;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
new file mode 100644
index 0000000..ad514cf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2016 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 android.app.ApplicationPackageManager;
+import android.content.pm.UserInfo;
+import android.os.Looper;
+import android.os.UserManagerInternal;
+import android.os.UserHandle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.MediumTest;
+
+import com.android.server.LocalServices;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * <p>Run with:<pre>
+ * runtest -c com.android.server.pm.UserManagerServiceCreateProfileTest frameworks-services
+ * </pre>
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class UserManagerServiceCreateProfileTest {
+ private UserManagerService mUserManagerService;
+
+ @Before
+ public void setup() {
+ // Currently UserManagerService cannot be instantiated twice inside a VM without a cleanup
+ // TODO: Remove once UMS supports proper dependency injection
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext());
+
+ // The tests assume that the device has one user and its the system user.
+ List<UserInfo> users = mUserManagerService.getUsers(/* excludeDying */ false);
+ assertEquals("Multiple users so this test can't run.", 1, users.size());
+ assertEquals("Only user present isn't the system user.",
+ UserHandle.USER_SYSTEM, users.get(0).id);
+ }
+
+ @Test
+ public void testGetProfiles() {
+ try {
+ // Pretend we have a secondary user with a profile.
+ UserInfo secondaryUser = addUser();
+ UserInfo profile = addProfile(secondaryUser);
+
+ // System user should still have no profile so getProfiles should just return 1 user.
+ List<UserInfo> users =
+ mUserManagerService.getProfiles(UserHandle.USER_SYSTEM, /*excludeDying*/ false);
+ assertEquals("Profiles returned where none should exist", 1, users.size());
+ assertEquals("Missing system user from profile list of system user",
+ UserHandle.USER_SYSTEM, users.get(0).id);
+
+ // Secondary user should have 1 profile, so return that and itself.
+ users = mUserManagerService.getProfiles(secondaryUser.id, /*excludeDying*/ false);
+ assertEquals("Profiles returned where none should exist", 2, users.size());
+ assertTrue("Missing secondary user id", users.get(0).id == secondaryUser.id
+ || users.get(1).id == secondaryUser.id);
+ assertTrue("Missing profile user id", users.get(0).id == profile.id
+ || users.get(1).id == profile.id);
+ } finally {
+ removeUsers();
+ }
+ }
+
+ @Test
+ public void testProfileBadge() {
+ try {
+ // First profile for system user should get badge 0
+ assertEquals("First profile isn't given badge index 0", 0,
+ mUserManagerService.getFreeProfileBadgeLU(UserHandle.USER_SYSTEM));
+
+ // Pretend we have a secondary user.
+ UserInfo secondaryUser = addUser();
+
+ // Check first profile badge for secondary user is also 0.
+ assertEquals("First profile for secondary user isn't given badge index 0", 0,
+ mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id));
+
+ // Shouldn't impact the badge for profile in system user
+ assertEquals("First profile isn't given badge index 0 with secondary user", 0,
+ mUserManagerService.getFreeProfileBadgeLU(UserHandle.USER_SYSTEM));
+
+ // Pretend a secondary user has a profile.
+ addProfile(secondaryUser);
+
+ // Shouldn't have impacted the badge for the system user
+ assertEquals("First profile isn't given badge index 0 in secondary user", 0,
+ mUserManagerService.getFreeProfileBadgeLU(UserHandle.USER_SYSTEM));
+ } finally {
+ removeUsers();
+ }
+ }
+
+ @Test
+ public void testProfileBadgeUnique() {
+ try {
+ List<UserInfo> users = mUserManagerService.getUsers(/* excludeDying */ false);
+ UserInfo system = users.get(0);
+ // Badges should get allocated 0 -> max
+ for (int i = 0; i < UserManagerService.getMaxManagedProfiles(); ++i) {
+ int nextBadge = mUserManagerService.getFreeProfileBadgeLU(UserHandle.USER_SYSTEM);
+ assertEquals("Wrong badge allocated", i, nextBadge);
+ UserInfo profile = addProfile(system);
+ profile.profileBadge = nextBadge;
+ }
+ } finally {
+ removeUsers();
+ }
+ }
+
+ @Test
+ public void testProfileBadgeReuse() {
+ try {
+ // Pretend we have a secondary user with a profile.
+ UserInfo secondaryUser = addUser();
+ UserInfo profile = addProfile(secondaryUser);
+ // Add the profile it to the users being removed.
+ mUserManagerService.addRemovingUserIdLocked(profile.id);
+ // We should reuse the badge from the profile being removed.
+ assertEquals("Badge index not reused while removing a user", 0,
+ mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id));
+
+ // Edge case of reuse that only applies if we ever support 3 managed profiles
+ // We should prioritise using lower badge indexes
+ if (UserManagerService.getMaxManagedProfiles() > 2) {
+ UserInfo profileBadgeOne = addProfile(secondaryUser);
+ profileBadgeOne.profileBadge = 1;
+ // 0 and 2 are free, we should reuse 0 rather than 2.
+ assertEquals("Lower index not used", 0,
+ mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id));
+ }
+ } finally {
+ removeUsers();
+ }
+ }
+
+ @Test
+ public void testNumberOfBadges() {
+ assertTrue("Max profiles greater than number of badges",
+ UserManagerService.MAX_MANAGED_PROFILES
+ <= ApplicationPackageManager.CORP_BADGE_COLORS.length);
+ assertEquals("Num colors doesn't match number of badge labels",
+ ApplicationPackageManager.CORP_BADGE_COLORS.length,
+ ApplicationPackageManager.CORP_BADGE_LABEL_RES_ID.length);
+ }
+
+ private void removeUsers() {
+ List<UserInfo> users = mUserManagerService.getUsers(/* excludeDying */ false);
+ for (UserInfo user: users) {
+ if (user.id != UserHandle.USER_SYSTEM) {
+ mUserManagerService.removeUserInfo(user.id);
+ }
+ }
+ }
+
+ private UserInfo addProfile(UserInfo user) {
+ user.profileGroupId = user.id;
+ UserInfo profile = new UserInfo(
+ mUserManagerService.getNextAvailableId(), "profile",
+ UserInfo.FLAG_MANAGED_PROFILE);
+ profile.profileGroupId = user.id;
+ mUserManagerService.putUserInfo(profile);
+ return profile;
+ }
+
+ private UserInfo addUser() {
+ UserInfo secondaryUser = new UserInfo(
+ mUserManagerService.getNextAvailableId(), "secondary", /* flags */ 0);
+ mUserManagerService.putUserInfo(secondaryUser);
+ return secondaryUser;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
new file mode 100644
index 0000000..575d7a3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2016 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 android.content.pm.UserInfo;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.UserManagerInternal;
+import android.os.UserHandle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.filters.MediumTest;
+
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerService.UserData;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * <p>Run with:<pre>
+ * runtest -c com.android.server.pm.UserManagerServiceUserInfoTest frameworks-services
+ * </pre>
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class UserManagerServiceUserInfoTest {
+ private UserManagerService mUserManagerService;
+
+ @Before
+ public void setup() {
+ // Currently UserManagerService cannot be instantiated twice inside a VM without a cleanup
+ // TODO: Remove once UMS supports proper dependency injection
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ LocalServices.removeServiceForTest(UserManagerInternal.class);
+ mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext());
+
+ // The tests assume that the device has one user and its the system user.
+ List<UserInfo> users = mUserManagerService.getUsers(/* excludeDying */ false);
+ assertEquals("Multiple users so this test can't run.", 1, users.size());
+ assertEquals("Only user present isn't the system user.",
+ UserHandle.USER_SYSTEM, users.get(0).id);
+ }
+
+ @Test
+ public void testWriteReadUserInfo() throws Exception {
+ UserData data = new UserData();
+ data.info = createUser();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(baos);
+ mUserManagerService.writeUserLP(data, out);
+ byte[] bytes = baos.toByteArray();
+
+ UserData read = mUserManagerService.readUserLP(
+ data.info.id, new ByteArrayInputStream(bytes));
+
+ assertUserInfoEquals(data.info, read.info);
+ }
+
+ @Test
+ public void testParcelUnparcelUserInfo() throws Exception {
+ UserInfo info = createUser();
+
+ Parcel out = Parcel.obtain();
+ info.writeToParcel(out, 0);
+ byte[] data = out.marshall();
+ out.recycle();
+
+ Parcel in = Parcel.obtain();
+ in.unmarshall(data, 0, data.length);
+ in.setDataPosition(0);
+ UserInfo read = UserInfo.CREATOR.createFromParcel(in);
+ in.recycle();
+
+ assertUserInfoEquals(info, read);
+ }
+
+ private UserInfo createUser() {
+ UserInfo user = new UserInfo(/*id*/ 21, "A Name", "A path", /*flags*/ 0x0ff0ff);
+ user.serialNumber = 5;
+ user.creationTime = 4L << 32;
+ user.lastLoggedInTime = 5L << 32;
+ user.lastLoggedInFingerprint = "afingerprint";
+ user.profileGroupId = 45;
+ user.restrictedProfileParentId = 4;
+ user.profileBadge = 2;
+ user.partial = true;
+ user.guestToRemove = true;
+ return user;
+ }
+
+ private void assertUserInfoEquals(UserInfo one, UserInfo two) {
+ assertEquals("Id not preserved", one.id, two.id);
+ assertEquals("Name not preserved", one.name, two.name);
+ assertEquals("Icon path not preserved", one.iconPath, two.iconPath);
+ assertEquals("Flags not preserved", one.flags, two.flags);
+ assertEquals("profile group not preserved", one.profileGroupId,
+ two.profileGroupId);
+ assertEquals("restricted profile parent not preseved", one.restrictedProfileParentId,
+ two.restrictedProfileParentId);
+ assertEquals("profile badge not preseved", one.profileBadge, two.profileBadge);
+ assertEquals("partial not preseved", one.partial, two.partial);
+ assertEquals("guestToRemove not preseved", one.guestToRemove, two.guestToRemove);
+ }
+}