blob: 5aff6bbe002e634d4162dd3b348027888fd6d48a [file] [log] [blame]
/*
* 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);
}
}