Change the data structure of restrictions in UM

Background
* In an org-owned managed profile device, two admins
  can set user restrictions on a user. The current
  data structure of local user restrictions uses a
  SparseArray<Bundle> where the key is the userId
  and the value is the restriction bundle.
* The existing data structure cannot store both the
  target userId and the originating userId.

Changes
* Introduces RestrictionsSet data structure, which is now
  used as a replacement of SparseArray<Bundle>.
* Updates the data structure of local restrictions to
  SparseArray<RestrictionsSet>. The first key is the
  target userId and the second key is the originating userId.
* targetUserId -> originatingUserId -> restrictionBundle
* Numerous methods in UserManagerService and
  UserRestrictionsUtils had to be changed to support this.

There will be follow up changes to move the logic of
sorting the restrictions into local and global restrictions
to DPMS.

Bug: 149743941
Test: atest com.android.server.pm.UserManagerTest
      atest com.android.server.pm.RestrictionsSetTest
      atest com.android.server.pm.UserRestrictionsUtilsTest
      atest com.android.cts.devicepolicy.UserRestrictionsTest

Change-Id: I08c9d60aa7a5e7cc3c661aaa4ba6dcd4f218323b
This commit is contained in:
Alex Johnston
2020-02-19 16:29:38 +00:00
parent 777531511f
commit d4c416fd7a
6 changed files with 674 additions and 157 deletions

View File

@@ -0,0 +1,256 @@
/*
* Copyright (C) 2020 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.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.os.Bundle;
import android.os.UserManager;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
* Data structure that contains the mapping of users to user restrictions (either the user
* restrictions that apply to them, or the user restrictions that they set, depending on the
* circumstances).
*
* @hide
*/
public class RestrictionsSet {
private static final String USER_ID = "user_id";
private static final String TAG_RESTRICTIONS = "restrictions";
private static final String TAG_RESTRICTIONS_USER = "restrictions_user";
/**
* Mapping of user restrictions.
* Only non-empty restriction bundles are stored.
* The key is the user id of the user.
* userId -> restrictionBundle
*/
private final SparseArray<Bundle> mUserRestrictions = new SparseArray<>(0);
public RestrictionsSet() {
}
public RestrictionsSet(@UserIdInt int userId, @NonNull Bundle restrictions) {
if (restrictions.isEmpty()) {
throw new IllegalArgumentException("empty restriction bundle cannot be added.");
}
mUserRestrictions.put(userId, restrictions);
}
/**
* Updates restriction bundle for a given user.
* If new bundle is empty, record is removed from the array.
*
* @return whether restrictions bundle is different from the old one.
*/
public boolean updateRestrictions(@UserIdInt int userId, @Nullable Bundle restrictions) {
final boolean changed =
!UserRestrictionsUtils.areEqual(mUserRestrictions.get(userId), restrictions);
if (!changed) {
return false;
}
if (!UserRestrictionsUtils.isEmpty(restrictions)) {
mUserRestrictions.put(userId, restrictions);
} else {
mUserRestrictions.delete(userId);
}
return true;
}
/**
* Moves a particular restriction from one restriction set to another, e.g. for all users.
*/
public void moveRestriction(@NonNull RestrictionsSet destRestrictions, String restriction) {
for (int i = 0; i < mUserRestrictions.size(); i++) {
final int userId = mUserRestrictions.keyAt(i);
final Bundle from = mUserRestrictions.valueAt(i);
if (UserRestrictionsUtils.contains(from, restriction)) {
from.remove(restriction);
Bundle to = destRestrictions.getRestrictions(userId);
if (to == null) {
to = new Bundle();
to.putBoolean(restriction, true);
destRestrictions.updateRestrictions(userId, to);
} else {
to.putBoolean(restriction, true);
}
// Don't keep empty bundles.
if (from.isEmpty()) {
mUserRestrictions.removeAt(i);
i--;
}
}
}
}
/**
* @return whether restrictions set has no restrictions.
*/
public boolean isEmpty() {
return mUserRestrictions.size() == 0;
}
/**
* Merge all restrictions in restrictions set into one bundle. The original user restrictions
* set does not get modified, instead a new bundle is returned.
*
* @return restrictions bundle containing all user restrictions.
*/
public @NonNull Bundle mergeAll() {
final Bundle result = new Bundle();
for (int i = 0; i < mUserRestrictions.size(); i++) {
UserRestrictionsUtils.merge(result, mUserRestrictions.valueAt(i));
}
return result;
}
/**
* @return list of enforcing users that enforce a particular restriction.
*/
public @NonNull List<UserManager.EnforcingUser> getEnforcingUsers(String restriction,
@UserIdInt int deviceOwnerUserId) {
final List<UserManager.EnforcingUser> result = new ArrayList<>();
for (int i = 0; i < mUserRestrictions.size(); i++) {
if (UserRestrictionsUtils.contains(mUserRestrictions.valueAt(i), restriction)) {
result.add(getEnforcingUser(mUserRestrictions.keyAt(i), deviceOwnerUserId));
}
}
return result;
}
private UserManager.EnforcingUser getEnforcingUser(@UserIdInt int userId,
@UserIdInt int deviceOwnerUserId) {
int source = deviceOwnerUserId == userId
? UserManager.RESTRICTION_SOURCE_DEVICE_OWNER
: UserManager.RESTRICTION_SOURCE_PROFILE_OWNER;
return new UserManager.EnforcingUser(userId, source);
}
/**
* @return list of user restrictions for a given user. Null is returned if the user does not
* have any restrictions.
*/
public @Nullable Bundle getRestrictions(@UserIdInt int userId) {
return mUserRestrictions.get(userId);
}
/**
* Removes a given user from the restrictions set, returning true if the user has non-empty
* restrictions before removal.
*/
public boolean remove(@UserIdInt int userId) {
boolean hasUserRestriction = mUserRestrictions.contains(userId);
mUserRestrictions.remove(userId);
return hasUserRestriction;
}
/**
* Remove list of users and user restrictions.
*/
public void removeAllRestrictions() {
mUserRestrictions.clear();
}
/**
* Serialize a given {@link RestrictionsSet} to XML.
*/
public void writeRestrictions(@NonNull XmlSerializer serializer, @NonNull String outerTag)
throws IOException {
serializer.startTag(null, outerTag);
for (int i = 0; i < mUserRestrictions.size(); i++) {
serializer.startTag(null, TAG_RESTRICTIONS_USER);
serializer.attribute(null, USER_ID, String.valueOf(mUserRestrictions.keyAt(i)));
UserRestrictionsUtils.writeRestrictions(serializer, mUserRestrictions.valueAt(i),
TAG_RESTRICTIONS);
serializer.endTag(null, TAG_RESTRICTIONS_USER);
}
serializer.endTag(null, outerTag);
}
/**
* Read restrictions from XML.
*/
public static RestrictionsSet readRestrictions(@NonNull XmlPullParser parser,
@NonNull String outerTag) throws IOException, XmlPullParserException {
RestrictionsSet restrictionsSet = new RestrictionsSet();
int userId = 0;
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
String tag = parser.getName();
if (type == XmlPullParser.END_TAG && outerTag.equals(tag)) {
return restrictionsSet;
} else if (type == XmlPullParser.START_TAG && TAG_RESTRICTIONS_USER.equals(tag)) {
userId = Integer.parseInt(parser.getAttributeValue(null, USER_ID));
} else if (type == XmlPullParser.START_TAG && TAG_RESTRICTIONS.equals(tag)) {
Bundle restrictions = UserRestrictionsUtils.readRestrictions(parser);
restrictionsSet.updateRestrictions(userId, restrictions);
}
}
throw new XmlPullParserException("restrictions cannot be read as xml is malformed.");
}
/**
* Dumps {@link RestrictionsSet}.
*/
public void dumpRestrictions(PrintWriter pw, String prefix) {
boolean noneSet = true;
for (int i = 0; i < mUserRestrictions.size(); i++) {
pw.println(prefix + "User Id: " + mUserRestrictions.keyAt(i));
UserRestrictionsUtils.dumpRestrictions(pw, prefix + " ", mUserRestrictions.valueAt(i));
noneSet = false;
}
if (noneSet) {
pw.println(prefix + "none");
}
}
public boolean containsKey(@UserIdInt int userId) {
return mUserRestrictions.contains(userId);
}
@VisibleForTesting
public int size() {
return mUserRestrictions.size();
}
@VisibleForTesting
public int keyAt(int index) {
return mUserRestrictions.keyAt(index);
}
@VisibleForTesting
public Bundle valueAt(int index) {
return mUserRestrictions.valueAt(index);
}
}

View File

@@ -182,6 +182,8 @@ public class UserManagerService extends IUserManager.Stub {
private static final String TAG_USER = "user";
private static final String TAG_RESTRICTIONS = "restrictions";
private static final String TAG_DEVICE_POLICY_RESTRICTIONS = "device_policy_restrictions";
private static final String TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS =
"device_policy_local_restrictions";
private static final String TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS =
"device_policy_global_restrictions";
/** Legacy name for device owner id tag. */
@@ -329,7 +331,7 @@ public class UserManagerService extends IUserManager.Stub {
* {@link #updateUserRestrictionsInternalLR}.
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mBaseUserRestrictions = new SparseArray<>();
private final RestrictionsSet mBaseUserRestrictions = new RestrictionsSet();
/**
* Cached user restrictions that are in effect -- i.e. {@link #mBaseUserRestrictions} combined
@@ -344,7 +346,7 @@ public class UserManagerService extends IUserManager.Stub {
* {@link #updateUserRestrictionsInternalLR}.
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mCachedEffectiveUserRestrictions = new SparseArray<>();
private final RestrictionsSet mCachedEffectiveUserRestrictions = new RestrictionsSet();
/**
* User restrictions that have already been applied in
@@ -353,7 +355,7 @@ public class UserManagerService extends IUserManager.Stub {
* {@link #updateUserRestrictionsInternalLR(Bundle, int)} call.
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mAppliedUserRestrictions = new SparseArray<>();
private final RestrictionsSet mAppliedUserRestrictions = new RestrictionsSet();
/**
* User restrictions set by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
@@ -362,7 +364,7 @@ public class UserManagerService extends IUserManager.Stub {
* The key is the user id of the user whom the restriction originated from.
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mDevicePolicyGlobalUserRestrictions = new SparseArray<>();
private final RestrictionsSet mDevicePolicyGlobalUserRestrictions = new RestrictionsSet();
/**
* Id of the user that set global restrictions.
@@ -372,11 +374,15 @@ public class UserManagerService extends IUserManager.Stub {
/**
* User restrictions set by {@link com.android.server.devicepolicy.DevicePolicyManagerService}
* for each user. Only non-empty restriction bundles are stored.
* The key is the user id of the user whom the restriction originated from.
* for each user.
* The key is the user id of the user whom the restrictions are targeting.
* The key inside the restrictionsSet is the user id of the user whom the restriction
* originated from.
* targetUserId -> originatingUserId -> restrictionBundle
*/
@GuardedBy("mRestrictionsLock")
private final SparseArray<Bundle> mDevicePolicyLocalUserRestrictions = new SparseArray<>();
private final SparseArray<RestrictionsSet> mDevicePolicyLocalUserRestrictions =
new SparseArray<>();
@GuardedBy("mGuestRestrictions")
private final Bundle mGuestRestrictions = new Bundle();
@@ -1709,9 +1715,6 @@ public class UserManagerService extends IUserManager.Stub {
}
}
/**
* See {@link UserManagerInternal#setDevicePolicyUserRestrictions}
*/
private void setDevicePolicyUserRestrictionsInner(@UserIdInt int originatingUserId,
@Nullable Bundle restrictions,
@UserManagerInternal.OwnerType int restrictionOwnerType) {
@@ -1721,16 +1724,36 @@ public class UserManagerService extends IUserManager.Stub {
// Sort restrictions into local and global ensuring they don't overlap.
UserRestrictionsUtils.sortToGlobalAndLocal(restrictions, restrictionOwnerType, global,
local);
boolean isDeviceOwner = restrictionOwnerType == UserManagerInternal.OWNER_TYPE_DEVICE_OWNER;
RestrictionsSet localRestrictionsSet;
if (UserRestrictionsUtils.isEmpty(local)) {
localRestrictionsSet = new RestrictionsSet();
} else {
localRestrictionsSet = new RestrictionsSet(originatingUserId, local);
}
setDevicePolicyUserRestrictionsInner(originatingUserId, global, localRestrictionsSet,
isDeviceOwner);
}
/**
* See {@link UserManagerInternal#setDevicePolicyUserRestrictions}
*/
private void setDevicePolicyUserRestrictionsInner(@UserIdInt int originatingUserId,
@NonNull Bundle global, @NonNull RestrictionsSet local,
boolean isDeviceOwner) {
boolean globalChanged, localChanged;
List<Integer> updatedLocalTargetUserIds;
synchronized (mRestrictionsLock) {
// Update global and local restrictions if they were changed.
globalChanged = updateRestrictionsIfNeededLR(
originatingUserId, global, mDevicePolicyGlobalUserRestrictions);
localChanged = updateRestrictionsIfNeededLR(
originatingUserId, local, mDevicePolicyLocalUserRestrictions);
globalChanged = mDevicePolicyGlobalUserRestrictions
.updateRestrictions(originatingUserId, global);
updatedLocalTargetUserIds = getUpdatedTargetUserIdsFromLocalRestrictions(
originatingUserId, local);
localChanged = updateLocalRestrictionsForTargetUsersLR(originatingUserId, local,
updatedLocalTargetUserIds);
if (restrictionOwnerType == UserManagerInternal.OWNER_TYPE_DEVICE_OWNER) {
if (isDeviceOwner) {
// Remember the global restriction owner userId to be able to make a distinction
// in getUserRestrictionSource on who set local policies.
mDeviceOwnerUserId = originatingUserId;
@@ -1753,8 +1776,20 @@ public class UserManagerService extends IUserManager.Stub {
}
// Don't call them within the mRestrictionsLock.
synchronized (mPackagesLock) {
if (localChanged || globalChanged) {
writeUserLP(getUserDataNoChecks(originatingUserId));
if (globalChanged || localChanged) {
if (updatedLocalTargetUserIds.size() == 1
&& updatedLocalTargetUserIds.contains(originatingUserId)) {
writeUserLP(getUserDataNoChecks(originatingUserId));
} else {
if (globalChanged) {
writeUserLP(getUserDataNoChecks(originatingUserId));
}
if (localChanged) {
for (int targetUserId : updatedLocalTargetUserIds) {
writeAllTargetUsersLP(targetUserId);
}
}
}
}
}
@@ -1762,44 +1797,88 @@ public class UserManagerService extends IUserManager.Stub {
if (globalChanged) {
applyUserRestrictionsForAllUsersLR();
} else if (localChanged) {
applyUserRestrictionsLR(originatingUserId);
for (int targetUserId : updatedLocalTargetUserIds) {
applyUserRestrictionsLR(targetUserId);
}
}
}
}
/**
* Updates restriction bundle for a given user in a given restriction array. If new bundle is
* empty, record is removed from the array.
* @return whether restrictions bundle is different from the old one.
* @return the list of updated target user ids in device policy local restrictions for a
* given originating user id.
*/
private boolean updateRestrictionsIfNeededLR(@UserIdInt int userId,
@Nullable Bundle restrictions, SparseArray<Bundle> restrictionsArray) {
final boolean changed =
!UserRestrictionsUtils.areEqual(restrictionsArray.get(userId), restrictions);
if (changed) {
if (!UserRestrictionsUtils.isEmpty(restrictions)) {
restrictionsArray.put(userId, restrictions);
} else {
restrictionsArray.delete(userId);
private List<Integer> getUpdatedTargetUserIdsFromLocalRestrictions(int originatingUserId,
@NonNull RestrictionsSet local) {
List<Integer> targetUserIds = new ArrayList<>();
// Update all the target user ids from the local restrictions set
for (int i = 0; i < local.size(); i++) {
targetUserIds.add(local.keyAt(i));
}
// Update the target user id from device policy local restrictions if the local
// restrictions set does not contain the target user id.
for (int i = 0; i < mDevicePolicyLocalUserRestrictions.size(); i++) {
int targetUserId = mDevicePolicyLocalUserRestrictions.keyAt(i);
RestrictionsSet restrictionsSet = mDevicePolicyLocalUserRestrictions.valueAt(i);
if (!local.containsKey(targetUserId)
&& restrictionsSet.containsKey(originatingUserId)) {
targetUserIds.add(targetUserId);
}
}
return targetUserIds;
}
/**
* Update restrictions for all target users in the restriction set. If a target user does not
* exist in device policy local restrictions, remove the restrictions bundle for that target
* user originating from the specified originating user.
*/
private boolean updateLocalRestrictionsForTargetUsersLR(int originatingUserId,
RestrictionsSet local, List<Integer> updatedTargetUserIds) {
boolean changed = false;
for (int targetUserId : updatedTargetUserIds) {
Bundle restrictions = local.getRestrictions(targetUserId);
if (restrictions == null) {
restrictions = new Bundle();
}
if (getDevicePolicyLocalRestrictionsForTargetUserLR(targetUserId)
.updateRestrictions(originatingUserId, restrictions)) {
changed = true;
}
}
return changed;
}
/**
* A new restriction set is created if a restriction set does not already exist for a given
* target user.
*
* @return restrictions set for a given target user.
*/
private @NonNull RestrictionsSet getDevicePolicyLocalRestrictionsForTargetUserLR(
int targetUserId) {
RestrictionsSet result = mDevicePolicyLocalUserRestrictions.get(targetUserId);
if (result == null) {
result = new RestrictionsSet();
mDevicePolicyLocalUserRestrictions.put(targetUserId, result);
}
return result;
}
@GuardedBy("mRestrictionsLock")
private Bundle computeEffectiveUserRestrictionsLR(@UserIdInt int userId) {
final Bundle baseRestrictions =
UserRestrictionsUtils.nonNull(mBaseUserRestrictions.get(userId));
final Bundle global = UserRestrictionsUtils.mergeAll(mDevicePolicyGlobalUserRestrictions);
final Bundle local = mDevicePolicyLocalUserRestrictions.get(userId);
UserRestrictionsUtils.nonNull(mBaseUserRestrictions.getRestrictions(userId));
final Bundle global = mDevicePolicyGlobalUserRestrictions.mergeAll();
final RestrictionsSet local = getDevicePolicyLocalRestrictionsForTargetUserLR(userId);
if (UserRestrictionsUtils.isEmpty(global) && UserRestrictionsUtils.isEmpty(local)) {
if (UserRestrictionsUtils.isEmpty(global) && local.isEmpty()) {
// Common case first.
return baseRestrictions;
}
final Bundle effective = UserRestrictionsUtils.clone(baseRestrictions);
UserRestrictionsUtils.merge(effective, global);
UserRestrictionsUtils.merge(effective, local);
UserRestrictionsUtils.merge(effective, local.mergeAll());
return effective;
}
@@ -1814,10 +1893,10 @@ public class UserManagerService extends IUserManager.Stub {
private Bundle getEffectiveUserRestrictions(@UserIdInt int userId) {
synchronized (mRestrictionsLock) {
Bundle restrictions = mCachedEffectiveUserRestrictions.get(userId);
Bundle restrictions = mCachedEffectiveUserRestrictions.getRestrictions(userId);
if (restrictions == null) {
restrictions = computeEffectiveUserRestrictionsLR(userId);
mCachedEffectiveUserRestrictions.put(userId, restrictions);
mCachedEffectiveUserRestrictions.updateRestrictions(userId, restrictions);
}
return restrictions;
}
@@ -1920,31 +1999,17 @@ public class UserManagerService extends IUserManager.Stub {
}
synchronized (mRestrictionsLock) {
// Check if it is set by profile owner.
Bundle profileOwnerRestrictions = mDevicePolicyLocalUserRestrictions.get(userId);
if (UserRestrictionsUtils.contains(profileOwnerRestrictions, restrictionKey)) {
result.add(getEnforcingUserLocked(userId));
}
// Check if it is set as a local restriction.
result.addAll(getDevicePolicyLocalRestrictionsForTargetUserLR(userId).getEnforcingUsers(
restrictionKey, mDeviceOwnerUserId));
// Iterate over all users who enforce global restrictions.
for (int i = mDevicePolicyGlobalUserRestrictions.size() - 1; i >= 0; i--) {
Bundle globalRestrictions = mDevicePolicyGlobalUserRestrictions.valueAt(i);
int profileUserId = mDevicePolicyGlobalUserRestrictions.keyAt(i);
if (UserRestrictionsUtils.contains(globalRestrictions, restrictionKey)) {
result.add(getEnforcingUserLocked(profileUserId));
}
}
// Check if it is set as a global restriction.
result.addAll(mDevicePolicyGlobalUserRestrictions.getEnforcingUsers(restrictionKey,
mDeviceOwnerUserId));
}
return result;
}
@GuardedBy("mRestrictionsLock")
private EnforcingUser getEnforcingUserLocked(@UserIdInt int userId) {
int source = mDeviceOwnerUserId == userId ? UserManager.RESTRICTION_SOURCE_DEVICE_OWNER
: UserManager.RESTRICTION_SOURCE_PROFILE_OWNER;
return new EnforcingUser(userId, source);
}
/**
* @return UserRestrictions that are in effect currently. This always returns a new
* {@link Bundle}.
@@ -1962,7 +2027,7 @@ public class UserManagerService extends IUserManager.Stub {
return false;
}
synchronized (mRestrictionsLock) {
Bundle bundle = mBaseUserRestrictions.get(userId);
Bundle bundle = mBaseUserRestrictions.getRestrictions(userId);
return (bundle != null && bundle.getBoolean(restrictionKey, false));
}
}
@@ -1977,7 +2042,7 @@ public class UserManagerService extends IUserManager.Stub {
// Note we can't modify Bundles stored in mBaseUserRestrictions directly, so create
// a copy.
final Bundle newRestrictions = UserRestrictionsUtils.clone(
mBaseUserRestrictions.get(userId));
mBaseUserRestrictions.getRestrictions(userId));
newRestrictions.putBoolean(key, value);
updateUserRestrictionsInternalLR(newRestrictions, userId);
@@ -1996,25 +2061,25 @@ public class UserManagerService extends IUserManager.Stub {
private void updateUserRestrictionsInternalLR(
@Nullable Bundle newBaseRestrictions, @UserIdInt int userId) {
final Bundle prevAppliedRestrictions = UserRestrictionsUtils.nonNull(
mAppliedUserRestrictions.get(userId));
mAppliedUserRestrictions.getRestrictions(userId));
// Update base restrictions.
if (newBaseRestrictions != null) {
// If newBaseRestrictions == the current one, it's probably a bug.
final Bundle prevBaseRestrictions = mBaseUserRestrictions.get(userId);
final Bundle prevBaseRestrictions = mBaseUserRestrictions.getRestrictions(userId);
Preconditions.checkState(prevBaseRestrictions != newBaseRestrictions);
Preconditions.checkState(mCachedEffectiveUserRestrictions.get(userId)
Preconditions.checkState(mCachedEffectiveUserRestrictions.getRestrictions(userId)
!= newBaseRestrictions);
if (updateRestrictionsIfNeededLR(userId, newBaseRestrictions, mBaseUserRestrictions)) {
if (mBaseUserRestrictions.updateRestrictions(userId, newBaseRestrictions)) {
scheduleWriteUser(getUserDataNoChecks(userId));
}
}
final Bundle effective = computeEffectiveUserRestrictionsLR(userId);
mCachedEffectiveUserRestrictions.put(userId, effective);
mCachedEffectiveUserRestrictions.updateRestrictions(userId, effective);
// Apply the new restrictions.
if (DBG) {
@@ -2037,7 +2102,7 @@ public class UserManagerService extends IUserManager.Stub {
propagateUserRestrictionsLR(userId, effective, prevAppliedRestrictions);
mAppliedUserRestrictions.put(userId, new Bundle(effective));
mAppliedUserRestrictions.updateRestrictions(userId, new Bundle(effective));
}
private void propagateUserRestrictionsLR(final int userId,
@@ -2089,7 +2154,7 @@ public class UserManagerService extends IUserManager.Stub {
debug("applyUserRestrictionsForAllUsersLR");
}
// First, invalidate all cached values.
mCachedEffectiveUserRestrictions.clear();
mCachedEffectiveUserRestrictions.removeAllRestrictions();
// We don't want to call into ActivityManagerService while taking a lock, so we'll call
// it on a handler.
@@ -2565,7 +2630,7 @@ public class UserManagerService extends IUserManager.Stub {
synchronized (mRestrictionsLock) {
if (!UserRestrictionsUtils.isEmpty(oldGlobalUserRestrictions)
&& mDeviceOwnerUserId != UserHandle.USER_NULL) {
mDevicePolicyGlobalUserRestrictions.put(
mDevicePolicyGlobalUserRestrictions.updateRestrictions(
mDeviceOwnerUserId, oldGlobalUserRestrictions);
}
// ENSURE_VERIFY_APPS is now enforced globally even if put by profile owner, so move
@@ -2690,7 +2755,8 @@ public class UserManagerService extends IUserManager.Stub {
if (!restrictions.isEmpty()) {
synchronized (mRestrictionsLock) {
mBaseUserRestrictions.append(UserHandle.USER_SYSTEM, restrictions);
mBaseUserRestrictions.updateRestrictions(UserHandle.USER_SYSTEM,
restrictions);
}
}
@@ -2717,6 +2783,16 @@ public class UserManagerService extends IUserManager.Stub {
}
}
private void writeAllTargetUsersLP(int originatingUserId) {
for (int i = 0; i < mDevicePolicyLocalUserRestrictions.size(); i++) {
int targetUserId = mDevicePolicyLocalUserRestrictions.keyAt(i);
RestrictionsSet restrictionsSet = mDevicePolicyLocalUserRestrictions.valueAt(i);
if (restrictionsSet.containsKey(originatingUserId)) {
writeUserLP(getUserDataNoChecks(targetUserId));
}
}
}
private void writeUserLP(UserData userData) {
if (DBG) {
debug("writeUserLP " + userData);
@@ -2801,12 +2877,11 @@ public class UserManagerService extends IUserManager.Stub {
}
synchronized (mRestrictionsLock) {
UserRestrictionsUtils.writeRestrictions(serializer,
mBaseUserRestrictions.get(userInfo.id), TAG_RESTRICTIONS);
mBaseUserRestrictions.getRestrictions(userInfo.id), TAG_RESTRICTIONS);
getDevicePolicyLocalRestrictionsForTargetUserLR(userInfo.id).writeRestrictions(
serializer, TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS);
UserRestrictionsUtils.writeRestrictions(serializer,
mDevicePolicyLocalUserRestrictions.get(userInfo.id),
TAG_DEVICE_POLICY_RESTRICTIONS);
UserRestrictionsUtils.writeRestrictions(serializer,
mDevicePolicyGlobalUserRestrictions.get(userInfo.id),
mDevicePolicyGlobalUserRestrictions.getRestrictions(userInfo.id),
TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS);
}
@@ -2936,7 +3011,8 @@ public class UserManagerService extends IUserManager.Stub {
String seedAccountType = null;
PersistableBundle seedAccountOptions = null;
Bundle baseRestrictions = null;
Bundle localRestrictions = null;
Bundle legacyLocalRestrictions = null;
RestrictionsSet localRestrictions = null;
Bundle globalRestrictions = null;
XmlPullParser parser = Xml.newPullParser();
@@ -3006,7 +3082,10 @@ public class UserManagerService extends IUserManager.Stub {
} else if (TAG_RESTRICTIONS.equals(tag)) {
baseRestrictions = UserRestrictionsUtils.readRestrictions(parser);
} else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) {
localRestrictions = UserRestrictionsUtils.readRestrictions(parser);
legacyLocalRestrictions = UserRestrictionsUtils.readRestrictions(parser);
} else if (TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS.equals(tag)) {
localRestrictions = RestrictionsSet.readRestrictions(parser,
TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS);
} else if (TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS.equals(tag)) {
globalRestrictions = UserRestrictionsUtils.readRestrictions(parser);
} else if (TAG_ACCOUNT.equals(tag)) {
@@ -3051,13 +3130,20 @@ public class UserManagerService extends IUserManager.Stub {
synchronized (mRestrictionsLock) {
if (baseRestrictions != null) {
mBaseUserRestrictions.put(id, baseRestrictions);
mBaseUserRestrictions.updateRestrictions(id, baseRestrictions);
}
if (localRestrictions != null) {
mDevicePolicyLocalUserRestrictions.put(id, localRestrictions);
if (legacyLocalRestrictions != null) {
Slog.wtf(LOG_TAG, "Seeing both legacy and current local restrictions in xml");
}
} else if (legacyLocalRestrictions != null) {
mDevicePolicyLocalUserRestrictions.put(id,
new RestrictionsSet(id, legacyLocalRestrictions));
}
if (globalRestrictions != null) {
mDevicePolicyGlobalUserRestrictions.put(id, globalRestrictions);
mDevicePolicyGlobalUserRestrictions.updateRestrictions(id,
globalRestrictions);
}
}
return userData;
@@ -3373,7 +3459,7 @@ public class UserManagerService extends IUserManager.Stub {
userTypeDetails.addDefaultRestrictionsTo(restrictions);
}
synchronized (mRestrictionsLock) {
mBaseUserRestrictions.append(userId, restrictions);
mBaseUserRestrictions.updateRestrictions(userId, restrictions);
}
t.traceBegin("PM.onNewUserCreated-" + userId);
@@ -3844,9 +3930,17 @@ public class UserManagerService extends IUserManager.Stub {
mBaseUserRestrictions.remove(userId);
mAppliedUserRestrictions.remove(userId);
mCachedEffectiveUserRestrictions.remove(userId);
mDevicePolicyLocalUserRestrictions.remove(userId);
if (mDevicePolicyGlobalUserRestrictions.get(userId) != null) {
mDevicePolicyGlobalUserRestrictions.remove(userId);
// Remove local restrictions affecting user
mDevicePolicyLocalUserRestrictions.delete(userId);
// Remove local restrictions set by user
boolean changed = false;
for (int i = 0; i < mDevicePolicyLocalUserRestrictions.size(); i++) {
int targetUserId = mDevicePolicyLocalUserRestrictions.keyAt(i);
changed |= getDevicePolicyLocalRestrictionsForTargetUserLR(targetUserId)
.remove(userId);
}
changed |= mDevicePolicyGlobalUserRestrictions.remove(userId);
if (changed) {
applyUserRestrictionsForAllUsersLR();
}
}
@@ -4539,16 +4633,18 @@ public class UserManagerService extends IUserManager.Stub {
pw.println(" Restrictions:");
synchronized (mRestrictionsLock) {
UserRestrictionsUtils.dumpRestrictions(
pw, " ", mBaseUserRestrictions.get(userInfo.id));
pw, " ", mBaseUserRestrictions.getRestrictions(userInfo.id));
pw.println(" Device policy global restrictions:");
UserRestrictionsUtils.dumpRestrictions(
pw, " ", mDevicePolicyGlobalUserRestrictions.get(userInfo.id));
pw, " ",
mDevicePolicyGlobalUserRestrictions.getRestrictions(userInfo.id));
pw.println(" Device policy local restrictions:");
UserRestrictionsUtils.dumpRestrictions(
pw, " ", mDevicePolicyLocalUserRestrictions.get(userInfo.id));
getDevicePolicyLocalRestrictionsForTargetUserLR(
userInfo.id).dumpRestrictions(pw, " ");
pw.println(" Effective restrictions:");
UserRestrictionsUtils.dumpRestrictions(
pw, " ", mCachedEffectiveUserRestrictions.get(userInfo.id));
pw, " ",
mCachedEffectiveUserRestrictions.getRestrictions(userInfo.id));
}
if (userData.account != null) {
@@ -4664,7 +4760,7 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public Bundle getBaseUserRestrictions(@UserIdInt int userId) {
synchronized (mRestrictionsLock) {
return mBaseUserRestrictions.get(userId);
return mBaseUserRestrictions.getRestrictions(userId);
}
}
@@ -4672,8 +4768,8 @@ public class UserManagerService extends IUserManager.Stub {
public void setBaseUserRestrictionsByDpmsForMigration(
@UserIdInt int userId, Bundle baseRestrictions) {
synchronized (mRestrictionsLock) {
if (updateRestrictionsIfNeededLR(
userId, new Bundle(baseRestrictions), mBaseUserRestrictions)) {
if (mBaseUserRestrictions.updateRestrictions(userId,
new Bundle(baseRestrictions))) {
invalidateEffectiveUserRestrictionsLR(userId);
}
}

View File

@@ -394,22 +394,6 @@ public class UserRestrictionsUtils {
}
}
/**
* Merges a sparse array of restrictions bundles into one.
*/
@Nullable
public static Bundle mergeAll(SparseArray<Bundle> restrictions) {
if (restrictions.size() == 0) {
return null;
} else {
final Bundle result = new Bundle();
for (int i = 0; i < restrictions.size(); i++) {
merge(result, restrictions.valueAt(i));
}
return result;
}
}
/**
* @return true if a restriction is settable by device owner.
*/
@@ -864,27 +848,15 @@ public class UserRestrictionsUtils {
}
/**
* Moves a particular restriction from one array of bundles to another, e.g. for all users.
* Moves a particular restriction from one array of restrictions sets to a restriction set,
* e.g. for all users.
*/
public static void moveRestriction(String restrictionKey, SparseArray<Bundle> srcRestrictions,
SparseArray<Bundle> destRestrictions) {
for (int i = 0; i < srcRestrictions.size(); i++) {
final int key = srcRestrictions.keyAt(i);
final Bundle from = srcRestrictions.valueAt(i);
if (contains(from, restrictionKey)) {
from.remove(restrictionKey);
Bundle to = destRestrictions.get(key);
if (to == null) {
to = new Bundle();
destRestrictions.append(key, to);
}
to.putBoolean(restrictionKey, true);
// Don't keep empty bundles.
if (from.isEmpty()) {
srcRestrictions.removeAt(i);
i--;
}
}
public static void moveRestriction(String restrictionKey,
SparseArray<RestrictionsSet> sourceRestrictionsSets,
RestrictionsSet destRestrictionSet) {
for (int i = 0; i < sourceRestrictionsSets.size(); i++) {
final RestrictionsSet sourceRestrictionsSet = sourceRestrictionsSets.valueAt(i);
sourceRestrictionsSet.moveRestriction(destRestrictionSet, restrictionKey);
}
}

View File

@@ -25,6 +25,8 @@ import android.test.AndroidTestCase;
import android.util.Log;
import android.util.Printer;
import com.android.server.pm.RestrictionsSet;
import libcore.io.Streams;
import com.google.android.collect.Lists;
@@ -35,7 +37,6 @@ import org.junit.Assert;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
@@ -58,6 +59,13 @@ public class DpmTestUtils extends AndroidTestCase {
return list == null ? 0 : list.size();
}
public static RestrictionsSet newRestrictions(int userId, String... restrictions) {
Bundle localRestrictionsBundle = newRestrictions(restrictions);
RestrictionsSet localRestrictions = new RestrictionsSet();
localRestrictions.updateRestrictions(userId, localRestrictionsBundle);
return localRestrictions;
}
public static Bundle newRestrictions(String... restrictions) {
final Bundle ret = new Bundle();
for (String restriction : restrictions) {
@@ -66,6 +74,17 @@ public class DpmTestUtils extends AndroidTestCase {
return ret;
}
public static void assertRestrictions(RestrictionsSet expected, RestrictionsSet actual) {
assertEquals(expected.size(), actual.size());
for (int i = 0; i < expected.size(); i++) {
int originatingUserId = expected.keyAt(i);
Bundle actualRestrictions = actual.getRestrictions(originatingUserId);
assertFalse(actualRestrictions.isEmpty());
assertRestrictions(expected.getRestrictions(originatingUserId), actualRestrictions);
}
}
public static void assertRestrictions(Bundle expected, Bundle actual) {
final ArrayList<String> elist;
if (expected == null) {

View File

@@ -0,0 +1,191 @@
/*
* Copyright (C) 2020 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 static com.android.server.devicepolicy.DpmTestUtils.assertRestrictions;
import static com.android.server.devicepolicy.DpmTestUtils.newRestrictions;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
/** Test for {@link RestrictionsSet}. */
@RunWith(AndroidJUnit4.class)
public class RestrictionsSetTest {
private RestrictionsSet mRestrictionsSet = new RestrictionsSet();
private final int originatingUserId = 0;
@Test
public void testUpdateRestrictions_addRestrictions() {
Bundle restrictions = newRestrictions(UserManager.ENSURE_VERIFY_APPS);
assertTrue(mRestrictionsSet.updateRestrictions(originatingUserId, restrictions));
assertRestrictions(restrictions, mRestrictionsSet.getRestrictions(originatingUserId));
}
@Test
public void testUpdateRestrictions_removeRestrictions() {
Bundle restrictions = newRestrictions(UserManager.ENSURE_VERIFY_APPS);
mRestrictionsSet.updateRestrictions(originatingUserId, restrictions);
assertTrue(mRestrictionsSet.updateRestrictions(originatingUserId, new Bundle()));
assertNull(mRestrictionsSet.getRestrictions(originatingUserId));
}
@Test
public void testUpdateRestrictions_noChange() {
Bundle restrictions = newRestrictions(UserManager.ENSURE_VERIFY_APPS);
mRestrictionsSet.updateRestrictions(originatingUserId, restrictions);
assertFalse(mRestrictionsSet.updateRestrictions(originatingUserId, restrictions));
}
@Test
public void testMoveRestriction_containsRestriction() {
RestrictionsSet destRestrictionsSet = new RestrictionsSet();
String restriction = UserManager.DISALLOW_CONFIG_DATE_TIME;
mRestrictionsSet.updateRestrictions(originatingUserId,
newRestrictions(restriction));
mRestrictionsSet.moveRestriction(destRestrictionsSet, restriction);
assertNull(mRestrictionsSet.getRestrictions(originatingUserId));
assertNotNull(destRestrictionsSet.getRestrictions(originatingUserId));
assertRestrictions(newRestrictions(restriction),
destRestrictionsSet.getRestrictions(originatingUserId));
}
@Test
public void testMoveRestriction_doesNotContainRestriction() {
RestrictionsSet destRestrictionsSet = new RestrictionsSet();
mRestrictionsSet.updateRestrictions(originatingUserId,
newRestrictions(UserManager.ENSURE_VERIFY_APPS));
mRestrictionsSet.moveRestriction(destRestrictionsSet,
UserManager.DISALLOW_CONFIG_DATE_TIME);
assertRestrictions(newRestrictions(UserManager.ENSURE_VERIFY_APPS),
mRestrictionsSet.getRestrictions(originatingUserId));
assertNull(destRestrictionsSet.getRestrictions(originatingUserId));
}
@Test
public void testIsEmpty_noRestrictions() {
assertTrue(mRestrictionsSet.isEmpty());
}
@Test
public void testIsEmpty_hasRestrictions() {
mRestrictionsSet.updateRestrictions(originatingUserId,
newRestrictions(UserManager.ENSURE_VERIFY_APPS,
UserManager.DISALLOW_CONFIG_DATE_TIME));
assertFalse(mRestrictionsSet.isEmpty());
}
@Test
public void testMergeAll_noRestrictions() {
assertTrue(mRestrictionsSet.mergeAll().isEmpty());
}
@Test
public void testMergeAll_hasRestrictions() {
mRestrictionsSet.updateRestrictions(originatingUserId,
newRestrictions(UserManager.ENSURE_VERIFY_APPS,
UserManager.DISALLOW_CONFIG_DATE_TIME));
mRestrictionsSet.updateRestrictions(10,
newRestrictions(UserManager.DISALLOW_ADD_USER,
UserManager.DISALLOW_AIRPLANE_MODE));
Bundle actual = mRestrictionsSet.mergeAll();
assertRestrictions(newRestrictions(UserManager.ENSURE_VERIFY_APPS,
UserManager.DISALLOW_CONFIG_DATE_TIME, UserManager.DISALLOW_ADD_USER,
UserManager.DISALLOW_AIRPLANE_MODE), actual);
}
@Test
public void testGetEnforcingUsers_hasEnforcingUser() {
mRestrictionsSet.updateRestrictions(originatingUserId,
newRestrictions(UserManager.ENSURE_VERIFY_APPS));
mRestrictionsSet.updateRestrictions(10,
newRestrictions(UserManager.DISALLOW_ADD_USER));
List<UserManager.EnforcingUser> enforcingUsers = mRestrictionsSet.getEnforcingUsers(
UserManager.ENSURE_VERIFY_APPS, originatingUserId);
UserManager.EnforcingUser enforcingUser1 = enforcingUsers.get(0);
assertEquals(UserHandle.of(originatingUserId), enforcingUser1.getUserHandle());
assertEquals(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER,
enforcingUser1.getUserRestrictionSource());
}
@Test
public void testGetEnforcingUsers_hasMultipleEnforcingUsers() {
int originatingUserId2 = 10;
mRestrictionsSet.updateRestrictions(originatingUserId,
newRestrictions(UserManager.ENSURE_VERIFY_APPS));
mRestrictionsSet.updateRestrictions(originatingUserId2,
newRestrictions(UserManager.ENSURE_VERIFY_APPS));
List<UserManager.EnforcingUser> enforcingUsers = mRestrictionsSet.getEnforcingUsers(
UserManager.ENSURE_VERIFY_APPS, originatingUserId);
assertEquals(2, enforcingUsers.size());
for (UserManager.EnforcingUser enforcingUser : enforcingUsers) {
int userId = enforcingUser.getUserHandle().getIdentifier();
assertTrue((userId == originatingUserId) || (userId == originatingUserId2));
if (userId == originatingUserId) {
assertEquals(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER,
enforcingUser.getUserRestrictionSource());
}
if (userId == originatingUserId2) {
assertEquals(UserManager.RESTRICTION_SOURCE_PROFILE_OWNER,
enforcingUser.getUserRestrictionSource());
}
}
}
@Test
public void testGetEnforcingUsers_noEnforcingUsers() {
mRestrictionsSet.updateRestrictions(originatingUserId,
newRestrictions(UserManager.DISALLOW_USER_SWITCH));
List<UserManager.EnforcingUser> enforcingUsers = mRestrictionsSet.getEnforcingUsers(
UserManager.ENSURE_VERIFY_APPS, originatingUserId);
assertTrue(enforcingUsers.isEmpty());
}
}

View File

@@ -247,60 +247,43 @@ public class UserRestrictionsUtilsTest extends AndroidTestCase {
assertRestrictions(newRestrictions(UserManager.DISALLOW_CAMERA), local);
}
public void testMergeAll() {
SparseArray<Bundle> restrictions = new SparseArray<>();
assertNull(UserRestrictionsUtils.mergeAll(restrictions));
restrictions.put(0, newRestrictions(UserManager.DISALLOW_ADJUST_VOLUME));
restrictions.put(1, newRestrictions(UserManager.DISALLOW_USB_FILE_TRANSFER));
restrictions.put(2, newRestrictions(UserManager.DISALLOW_APPS_CONTROL));
Bundle result = UserRestrictionsUtils.mergeAll(restrictions);
assertRestrictions(
newRestrictions(
UserManager.DISALLOW_ADJUST_VOLUME,
UserManager.DISALLOW_USB_FILE_TRANSFER,
UserManager.DISALLOW_APPS_CONTROL),
result);
}
public void testMoveRestriction() {
SparseArray<Bundle> localRestrictions = new SparseArray<>();
SparseArray<Bundle> globalRestrictions = new SparseArray<>();
SparseArray<RestrictionsSet> localRestrictions = new SparseArray<>();
RestrictionsSet globalRestrictions = new RestrictionsSet();
// User 0 has only local restrictions, nothing should change.
localRestrictions.put(0, newRestrictions(UserManager.DISALLOW_ADJUST_VOLUME));
localRestrictions.put(0, newRestrictions(0, UserManager.DISALLOW_ADJUST_VOLUME));
// User 1 has a local restriction to be moved to global and some global already. Local
// restrictions should be removed for this user.
localRestrictions.put(1, newRestrictions(UserManager.ENSURE_VERIFY_APPS));
globalRestrictions.put(1, newRestrictions(UserManager.DISALLOW_ADD_USER));
localRestrictions.put(1, newRestrictions(1, UserManager.ENSURE_VERIFY_APPS));
globalRestrictions.updateRestrictions(1,
newRestrictions(UserManager.DISALLOW_ADD_USER));
// User 2 has a local restriction to be moved and one to leave local.
localRestrictions.put(2, newRestrictions(
UserManager.ENSURE_VERIFY_APPS,
UserManager.DISALLOW_CONFIG_VPN));
localRestrictions.put(2, newRestrictions(2,
UserManager.ENSURE_VERIFY_APPS, UserManager.DISALLOW_CONFIG_VPN));
UserRestrictionsUtils.moveRestriction(
UserManager.ENSURE_VERIFY_APPS, localRestrictions, globalRestrictions);
// Check user 0.
assertRestrictions(
newRestrictions(UserManager.DISALLOW_ADJUST_VOLUME),
newRestrictions(0, UserManager.DISALLOW_ADJUST_VOLUME),
localRestrictions.get(0));
assertNull(globalRestrictions.get(0));
assertNull(globalRestrictions.getRestrictions(0));
// Check user 1.
assertNull(localRestrictions.get(1));
assertTrue(localRestrictions.get(1).isEmpty());
assertRestrictions(
newRestrictions(UserManager.ENSURE_VERIFY_APPS, UserManager.DISALLOW_ADD_USER),
globalRestrictions.get(1));
globalRestrictions.getRestrictions(1));
// Check user 2.
assertRestrictions(
newRestrictions(UserManager.DISALLOW_CONFIG_VPN),
newRestrictions(2, UserManager.DISALLOW_CONFIG_VPN),
localRestrictions.get(2));
assertRestrictions(
newRestrictions(UserManager.ENSURE_VERIFY_APPS),
globalRestrictions.get(2));
globalRestrictions.getRestrictions(2));
}
public void testAreEqual() {