Non-system users can now have restricted profiles
In the non split system user, only USER_OWNER is allowed to have restricted
profiles. This is now changed in split user mode, where multiple secondary
users can have restricted profiles.
Added UserInfo.restrictedProfileGroupId field, which defines parent/child
relationship between secondary users and linked restricted profiles. Adjusted
shared accounts handling logic to not assume that USER_OWNER is the only owner.
Bug: 23191995
Change-Id: I5f3fc2aa4f229103d6e75ec2c3dfce866b8007de
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 2aae1de..7392563 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -77,7 +77,7 @@
public static final int FLAG_DISABLED = 0x00000040;
- public static final int NO_PROFILE_GROUP_ID = -1;
+ public static final int NO_PROFILE_GROUP_ID = UserHandle.USER_NULL;
public int id;
public int serialNumber;
@@ -87,6 +87,7 @@
public long creationTime;
public long lastLoggedInTime;
public int profileGroupId;
+ public int restrictedProfileParentId;
/** User is only partially created. */
public boolean partial;
@@ -102,6 +103,7 @@
this.flags = flags;
this.iconPath = iconPath;
this.profileGroupId = NO_PROFILE_GROUP_ID;
+ this.restrictedProfileParentId = NO_PROFILE_GROUP_ID;
}
public boolean isPrimary() {
@@ -206,6 +208,7 @@
dest.writeInt(partial ? 1 : 0);
dest.writeInt(profileGroupId);
dest.writeInt(guestToRemove ? 1 : 0);
+ dest.writeInt(restrictedProfileParentId);
}
public static final Parcelable.Creator<UserInfo> CREATOR
@@ -229,5 +232,6 @@
partial = source.readInt() != 0;
profileGroupId = source.readInt();
guestToRemove = source.readInt() != 0;
+ restrictedProfileParentId = source.readInt();
}
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 849f5de..64e2505 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -927,6 +927,28 @@
}
/**
+ * Creates a restricted profile with the specified name.
+ *
+ * @param name profile's name
+ * @return UserInfo object for the created user, or null if the user could not be created.
+ * @hide
+ */
+ public UserInfo createRestrictedProfile(String name) {
+ try {
+ if (isSplitSystemUser()) {
+ return mService.createProfileForUser(name, UserInfo.FLAG_RESTRICTED,
+ UserHandle.getCallingUserId());
+ } else {
+ return mService.createProfileForUser(name, UserInfo.FLAG_RESTRICTED,
+ UserHandle.USER_SYSTEM);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Could not create a restricted profile", e);
+ }
+ return null;
+ }
+
+ /**
* @hide
* Marks the guest user for deletion to allow a new guest to be created before deleting
* the current user who is a guest.
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 8b0e6f2..7aef38d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -511,10 +511,17 @@
Account[] sharedAccounts = getSharedAccountsAsUser(userId);
if (sharedAccounts == null || sharedAccounts.length == 0) return;
Account[] accounts = getAccountsAsUser(null, userId);
+ int parentUserId = UserManager.isSplitSystemUser()
+ ? mUserManager.getUserInfo(userId).restrictedProfileParentId
+ : UserHandle.USER_SYSTEM;
+ if (parentUserId < 0) {
+ Log.w(TAG, "User " + userId + " has shared accounts, but no parent user");
+ return;
+ }
for (Account sa : sharedAccounts) {
if (ArrayUtils.contains(accounts, sa)) continue;
// Account doesn't exist. Copy it now.
- copyAccountToUser(null /*no response*/, sa, UserHandle.USER_OWNER, userId);
+ copyAccountToUser(null /*no response*/, sa, parentUserId, userId);
}
}
@@ -740,7 +747,7 @@
@Override
public void copyAccountToUser(final IAccountManagerResponse response, final Account account,
- int userFrom, int userTo) {
+ final int userFrom, int userTo) {
int callingUid = Binder.getCallingUid();
if (isCrossUser(callingUid, UserHandle.USER_ALL)) {
throw new SecurityException("Calling copyAccountToUser requires "
@@ -784,7 +791,7 @@
if (result != null
&& result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
// Create a Session for the target user and pass in the bundle
- completeCloningAccount(response, result, account, toAccounts);
+ completeCloningAccount(response, result, account, toAccounts, userFrom);
} else {
super.onResult(result);
}
@@ -851,7 +858,8 @@
}
private void completeCloningAccount(IAccountManagerResponse response,
- final Bundle accountCredentials, final Account account, final UserAccounts targetUser) {
+ final Bundle accountCredentials, final Account account, final UserAccounts targetUser,
+ final int parentUserId){
long id = clearCallingIdentity();
try {
new Session(targetUser, response, account.type, false,
@@ -866,9 +874,9 @@
@Override
public void run() throws RemoteException {
// Confirm that the owner's account still exists before this step.
- UserAccounts owner = getUserAccounts(UserHandle.USER_OWNER);
+ UserAccounts owner = getUserAccounts(parentUserId);
synchronized (owner.cacheLock) {
- for (Account acc : getAccounts(UserHandle.USER_OWNER)) {
+ for (Account acc : getAccounts(parentUserId)) {
if (acc.equals(account)) {
mAuthenticator.addAccountFromCredentials(
this, account, accountCredentials);
@@ -949,27 +957,27 @@
}
sendAccountsChangedBroadcast(accounts.userId);
}
- if (accounts.userId == UserHandle.USER_OWNER) {
- addAccountToLimitedUsers(account);
+ if (getUserManager().getUserInfo(accounts.userId).canHaveProfile()) {
+ addAccountToLinkedRestrictedUsers(account, accounts.userId);
}
return true;
}
/**
- * Adds the account to all limited users as shared accounts. If the user is currently
+ * Adds the account to all linked restricted users as shared accounts. If the user is currently
* running, then clone the account too.
* @param account the account to share with limited users
+ *
*/
- private void addAccountToLimitedUsers(Account account) {
+ private void addAccountToLinkedRestrictedUsers(Account account, int parentUserId) {
List<UserInfo> users = getUserManager().getUsers();
for (UserInfo user : users) {
- if (user.isRestricted()) {
+ if (user.isRestricted() && (parentUserId == user.restrictedProfileParentId)) {
addSharedAccountAsUser(account, user.id);
try {
if (ActivityManagerNative.getDefault().isUserRunning(user.id, false)) {
mMessageHandler.sendMessage(mMessageHandler.obtainMessage(
- MESSAGE_COPY_SHARED_ACCOUNT, UserHandle.USER_OWNER, user.id,
- account));
+ MESSAGE_COPY_SHARED_ACCOUNT, parentUserId, user.id, account));
}
} catch (RemoteException re) {
// Shouldn't happen
@@ -1172,14 +1180,16 @@
new AtomicReference<String>(accountToRename.name));
resultAccount = renamedAccount;
- if (accounts.userId == UserHandle.USER_OWNER) {
+ int parentUserId = accounts.userId;
+ if (canHaveProfile(parentUserId)) {
/*
- * Owner's account was renamed, rename the account for
+ * Owner or system user account was renamed, rename the account for
* those users with which the account was shared.
*/
List<UserInfo> users = mUserManager.getUsers(true);
for (UserInfo user : users) {
- if (!user.isPrimary() && user.isRestricted()) {
+ if (user.isRestricted()
+ && (user.restrictedProfileParentId == parentUserId)) {
renameSharedAccountAsUser(accountToRename, newName, user.id);
}
}
@@ -1191,6 +1201,11 @@
return resultAccount;
}
+ private boolean canHaveProfile(final int parentUserId) {
+ final UserInfo userInfo = mUserManager.getUserInfo(parentUserId);
+ return userInfo != null && userInfo.canHaveProfile();
+ }
+
@Override
public void removeAccount(IAccountManagerResponse response, Account account,
boolean expectActivityLaunch) {
@@ -1304,7 +1319,7 @@
logRecord(accounts, DebugDbHelper.ACTION_CALLED_ACCOUNT_REMOVE, TABLE_ACCOUNTS);
long identityToken = clearCallingIdentity();
try {
- return removeAccountInternal(accounts, account);
+ return removeAccountInternal(accounts, account, callingUid);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -1337,7 +1352,7 @@
&& !result.containsKey(AccountManager.KEY_INTENT)) {
final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
if (removalAllowed) {
- removeAccountInternal(mAccounts, mAccount);
+ removeAccountInternal(mAccounts, mAccount, getCallingUid());
}
IAccountManagerResponse response = getResponseAndClose();
if (response != null) {
@@ -1360,10 +1375,10 @@
/* For testing */
protected void removeAccountInternal(Account account) {
- removeAccountInternal(getUserAccountsForCaller(), account);
+ removeAccountInternal(getUserAccountsForCaller(), account, getCallingUid());
}
- private boolean removeAccountInternal(UserAccounts accounts, Account account) {
+ private boolean removeAccountInternal(UserAccounts accounts, Account account, int callingUid) {
int deleted;
synchronized (accounts.cacheLock) {
final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
@@ -1376,21 +1391,20 @@
logRecord(db, DebugDbHelper.ACTION_ACCOUNT_REMOVE, TABLE_ACCOUNTS, accountId, accounts);
}
- if (accounts.userId == UserHandle.USER_OWNER) {
- // Owner's account was removed, remove from any users that are sharing
- // this account.
- int callingUid = getCallingUid();
- long id = Binder.clearCallingIdentity();
- try {
+ long id = Binder.clearCallingIdentity();
+ try {
+ int parentUserId = accounts.userId;
+ if (canHaveProfile(parentUserId)) {
+ // Remove from any restricted profiles that are sharing this account.
List<UserInfo> users = mUserManager.getUsers(true);
for (UserInfo user : users) {
- if (!user.isPrimary() && user.isRestricted()) {
+ if (user.isRestricted() && parentUserId == (user.restrictedProfileParentId)) {
removeSharedAccountAsUser(account, user.id, callingUid);
}
}
- } finally {
- Binder.restoreCallingIdentity(id);
}
+ } finally {
+ Binder.restoreCallingIdentity(id);
}
return (deleted > 0);
}
@@ -2707,7 +2721,7 @@
if (r > 0) {
logRecord(db, DebugDbHelper.ACTION_ACCOUNT_REMOVE, TABLE_SHARED_ACCOUNTS,
sharedTableAccountId, accounts, callingUid);
- removeAccountInternal(accounts, account);
+ removeAccountInternal(accounts, account, callingUid);
}
return r > 0;
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4bfcb90..1924bab 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -61,7 +61,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsService;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
@@ -102,6 +101,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_RESTRICTED_PROFILE_PARENT_ID = "restrictedProfileParentId";
private static final String TAG_GUEST_RESTRICTIONS = "guestRestrictions";
private static final String TAG_USERS = "users";
private static final String TAG_USER = "user";
@@ -927,7 +927,10 @@
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));
+ }
serializer.startTag(null, TAG_NAME);
serializer.text(userInfo.name);
serializer.endTag(null, TAG_NAME);
@@ -1037,6 +1040,7 @@
long creationTime = 0L;
long lastLoggedInTime = 0L;
int profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
+ int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
boolean partial = false;
boolean guestToRemove = false;
Bundle restrictions = new Bundle();
@@ -1072,6 +1076,8 @@
lastLoggedInTime = readLongAttribute(parser, ATTR_LAST_LOGGED_IN_TIME, 0);
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;
@@ -1106,6 +1112,7 @@
userInfo.partial = partial;
userInfo.guestToRemove = guestToRemove;
userInfo.profileGroupId = profileGroupId;
+ userInfo.restrictedProfileParentId = restrictedProfileParentId;
mUserRestrictions.append(id, restrictions);
return userInfo;
@@ -1262,6 +1269,7 @@
}
final boolean isGuest = (flags & UserInfo.FLAG_GUEST) != 0;
final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
+ final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
final long ident = Binder.clearCallingIdentity();
UserInfo userInfo = null;
final int userId;
@@ -1286,6 +1294,24 @@
if (isGuest && findCurrentGuestUserLocked() != null) {
return null;
}
+ // In legacy mode, restricted profile's parent can only be the owner user
+ if (isRestricted && !UserManager.isSplitSystemUser()
+ && (parentId != UserHandle.USER_SYSTEM)) {
+ Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
+ return null;
+ }
+ if (isRestricted && UserManager.isSplitSystemUser()) {
+ if (parent == null) {
+ Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be "
+ + "specified");
+ return null;
+ }
+ if (!parent.canHaveProfile()) {
+ Log.w(LOG_TAG, "Cannot add restricted profile - profiles cannot be "
+ + "created for the specified parent user id " + parentId);
+ return null;
+ }
+ }
// In split system user mode, we assign the first human user the primary flag.
// And if there is no device owner, we also assign the admin flag to primary
// user.
@@ -1309,11 +1335,22 @@
mUsers.put(userId, userInfo);
writeUserListLocked();
if (parent != null) {
- if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
- parent.profileGroupId = parent.id;
- scheduleWriteUserLocked(parent);
+ if (isManagedProfile) {
+ if (parent.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) {
+ parent.profileGroupId = parent.id;
+ scheduleWriteUserLocked(parent);
+ }
+ userInfo.profileGroupId = parent.profileGroupId;
+ } else if (isRestricted) {
+ if (!parent.canHaveProfile()) {
+ Log.w(LOG_TAG, "Cannot add restricted profile - parent user must be owner");
+ }
+ if (parent.restrictedProfileParentId == UserInfo.NO_PROFILE_GROUP_ID) {
+ parent.restrictedProfileParentId = parent.id;
+ scheduleWriteUserLocked(parent);
+ }
+ userInfo.restrictedProfileParentId = parent.restrictedProfileParentId;
}
- userInfo.profileGroupId = parent.profileGroupId;
}
final StorageManager storage = mContext.getSystemService(StorageManager.class);
for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
@@ -1348,24 +1385,9 @@
return userInfo;
}
- private int numberOfUsersOfTypeLocked(int flags, boolean excludeDying) {
- int count = 0;
- for (int i = mUsers.size() - 1; i >= 0; i--) {
- UserInfo user = mUsers.valueAt(i);
- if (!excludeDying || !mRemovingUserIds.get(user.id)) {
- if ((user.flags & flags) != 0) {
- count++;
- }
- }
- }
- return count;
- }
-
/**
* Find the current guest user. If the Guest user is partial,
* then do not include it in the results as it is about to die.
- * This is different than {@link #numberOfUsersOfTypeLocked(int, boolean)} due to
- * the special handling of Guests being removed.
*/
private UserInfo findCurrentGuestUserLocked() {
final int size = mUsers.size();