1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.pm; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.UserIdInt; 22 import android.os.Bundle; 23 import android.os.UserManager; 24 import android.util.SparseArray; 25 26 import com.android.internal.annotations.VisibleForTesting; 27 28 import org.xmlpull.v1.XmlPullParser; 29 import org.xmlpull.v1.XmlPullParserException; 30 import org.xmlpull.v1.XmlSerializer; 31 32 import java.io.IOException; 33 import java.io.PrintWriter; 34 import java.util.ArrayList; 35 import java.util.List; 36 37 /** 38 * Data structure that contains the mapping of users to user restrictions (either the user 39 * restrictions that apply to them, or the user restrictions that they set, depending on the 40 * circumstances). 41 * 42 * @hide 43 */ 44 public class RestrictionsSet { 45 46 private static final String USER_ID = "user_id"; 47 private static final String TAG_RESTRICTIONS = "restrictions"; 48 private static final String TAG_RESTRICTIONS_USER = "restrictions_user"; 49 50 /** 51 * Mapping of user restrictions. 52 * Only non-empty restriction bundles are stored. 53 * The key is the user id of the user. 54 * userId -> restrictionBundle 55 */ 56 private final SparseArray<Bundle> mUserRestrictions = new SparseArray<>(0); 57 RestrictionsSet()58 public RestrictionsSet() { 59 } 60 RestrictionsSet(@serIdInt int userId, @NonNull Bundle restrictions)61 public RestrictionsSet(@UserIdInt int userId, @NonNull Bundle restrictions) { 62 if (restrictions.isEmpty()) { 63 throw new IllegalArgumentException("empty restriction bundle cannot be added."); 64 } 65 mUserRestrictions.put(userId, restrictions); 66 } 67 68 /** 69 * Updates restriction bundle for a given user. 70 * If new bundle is empty, record is removed from the array. 71 * 72 * @return whether restrictions bundle is different from the old one. 73 */ updateRestrictions(@serIdInt int userId, @Nullable Bundle restrictions)74 public boolean updateRestrictions(@UserIdInt int userId, @Nullable Bundle restrictions) { 75 final boolean changed = 76 !UserRestrictionsUtils.areEqual(mUserRestrictions.get(userId), restrictions); 77 if (!changed) { 78 return false; 79 } 80 if (!UserRestrictionsUtils.isEmpty(restrictions)) { 81 mUserRestrictions.put(userId, restrictions); 82 } else { 83 mUserRestrictions.delete(userId); 84 } 85 return true; 86 } 87 88 /** 89 * Moves a particular restriction from one restriction set to another, e.g. for all users. 90 */ moveRestriction(@onNull RestrictionsSet destRestrictions, String restriction)91 public void moveRestriction(@NonNull RestrictionsSet destRestrictions, String restriction) { 92 for (int i = 0; i < mUserRestrictions.size(); i++) { 93 final int userId = mUserRestrictions.keyAt(i); 94 final Bundle from = mUserRestrictions.valueAt(i); 95 96 if (UserRestrictionsUtils.contains(from, restriction)) { 97 from.remove(restriction); 98 Bundle to = destRestrictions.getRestrictions(userId); 99 if (to == null) { 100 to = new Bundle(); 101 to.putBoolean(restriction, true); 102 destRestrictions.updateRestrictions(userId, to); 103 } else { 104 to.putBoolean(restriction, true); 105 } 106 // Don't keep empty bundles. 107 if (from.isEmpty()) { 108 mUserRestrictions.removeAt(i); 109 i--; 110 } 111 } 112 } 113 } 114 115 /** 116 * @return whether restrictions set has no restrictions. 117 */ isEmpty()118 public boolean isEmpty() { 119 return mUserRestrictions.size() == 0; 120 } 121 122 /** 123 * Merge all restrictions in restrictions set into one bundle. The original user restrictions 124 * set does not get modified, instead a new bundle is returned. 125 * 126 * @return restrictions bundle containing all user restrictions. 127 */ mergeAll()128 public @NonNull Bundle mergeAll() { 129 final Bundle result = new Bundle(); 130 for (int i = 0; i < mUserRestrictions.size(); i++) { 131 UserRestrictionsUtils.merge(result, mUserRestrictions.valueAt(i)); 132 } 133 return result; 134 } 135 136 /** 137 * @return list of enforcing users that enforce a particular restriction. 138 */ getEnforcingUsers(String restriction, @UserIdInt int deviceOwnerUserId)139 public @NonNull List<UserManager.EnforcingUser> getEnforcingUsers(String restriction, 140 @UserIdInt int deviceOwnerUserId) { 141 final List<UserManager.EnforcingUser> result = new ArrayList<>(); 142 for (int i = 0; i < mUserRestrictions.size(); i++) { 143 if (UserRestrictionsUtils.contains(mUserRestrictions.valueAt(i), restriction)) { 144 result.add(getEnforcingUser(mUserRestrictions.keyAt(i), deviceOwnerUserId)); 145 } 146 } 147 return result; 148 } 149 getEnforcingUser(@serIdInt int userId, @UserIdInt int deviceOwnerUserId)150 private UserManager.EnforcingUser getEnforcingUser(@UserIdInt int userId, 151 @UserIdInt int deviceOwnerUserId) { 152 int source = deviceOwnerUserId == userId 153 ? UserManager.RESTRICTION_SOURCE_DEVICE_OWNER 154 : UserManager.RESTRICTION_SOURCE_PROFILE_OWNER; 155 return new UserManager.EnforcingUser(userId, source); 156 } 157 158 /** 159 * @return list of user restrictions for a given user. Null is returned if the user does not 160 * have any restrictions. 161 */ getRestrictions(@serIdInt int userId)162 public @Nullable Bundle getRestrictions(@UserIdInt int userId) { 163 return mUserRestrictions.get(userId); 164 } 165 166 /** 167 * Removes a given user from the restrictions set, returning true if the user has non-empty 168 * restrictions before removal. 169 */ remove(@serIdInt int userId)170 public boolean remove(@UserIdInt int userId) { 171 boolean hasUserRestriction = mUserRestrictions.contains(userId); 172 mUserRestrictions.remove(userId); 173 return hasUserRestriction; 174 } 175 176 /** 177 * Remove list of users and user restrictions. 178 */ removeAllRestrictions()179 public void removeAllRestrictions() { 180 mUserRestrictions.clear(); 181 } 182 183 /** 184 * Serialize a given {@link RestrictionsSet} to XML. 185 */ writeRestrictions(@onNull XmlSerializer serializer, @NonNull String outerTag)186 public void writeRestrictions(@NonNull XmlSerializer serializer, @NonNull String outerTag) 187 throws IOException { 188 serializer.startTag(null, outerTag); 189 for (int i = 0; i < mUserRestrictions.size(); i++) { 190 serializer.startTag(null, TAG_RESTRICTIONS_USER); 191 serializer.attribute(null, USER_ID, String.valueOf(mUserRestrictions.keyAt(i))); 192 UserRestrictionsUtils.writeRestrictions(serializer, mUserRestrictions.valueAt(i), 193 TAG_RESTRICTIONS); 194 serializer.endTag(null, TAG_RESTRICTIONS_USER); 195 } 196 serializer.endTag(null, outerTag); 197 } 198 199 /** 200 * Read restrictions from XML. 201 */ readRestrictions(@onNull XmlPullParser parser, @NonNull String outerTag)202 public static RestrictionsSet readRestrictions(@NonNull XmlPullParser parser, 203 @NonNull String outerTag) throws IOException, XmlPullParserException { 204 RestrictionsSet restrictionsSet = new RestrictionsSet(); 205 int userId = 0; 206 int type; 207 208 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 209 String tag = parser.getName(); 210 if (type == XmlPullParser.END_TAG && outerTag.equals(tag)) { 211 return restrictionsSet; 212 } else if (type == XmlPullParser.START_TAG && TAG_RESTRICTIONS_USER.equals(tag)) { 213 userId = Integer.parseInt(parser.getAttributeValue(null, USER_ID)); 214 } else if (type == XmlPullParser.START_TAG && TAG_RESTRICTIONS.equals(tag)) { 215 Bundle restrictions = UserRestrictionsUtils.readRestrictions(parser); 216 restrictionsSet.updateRestrictions(userId, restrictions); 217 } 218 } 219 throw new XmlPullParserException("restrictions cannot be read as xml is malformed."); 220 } 221 222 /** 223 * Dumps {@link RestrictionsSet}. 224 */ dumpRestrictions(PrintWriter pw, String prefix)225 public void dumpRestrictions(PrintWriter pw, String prefix) { 226 boolean noneSet = true; 227 for (int i = 0; i < mUserRestrictions.size(); i++) { 228 pw.println(prefix + "User Id: " + mUserRestrictions.keyAt(i)); 229 UserRestrictionsUtils.dumpRestrictions(pw, prefix + " ", mUserRestrictions.valueAt(i)); 230 noneSet = false; 231 } 232 if (noneSet) { 233 pw.println(prefix + "none"); 234 } 235 } 236 containsKey(@serIdInt int userId)237 public boolean containsKey(@UserIdInt int userId) { 238 return mUserRestrictions.contains(userId); 239 } 240 241 @VisibleForTesting size()242 public int size() { 243 return mUserRestrictions.size(); 244 } 245 246 @VisibleForTesting keyAt(int index)247 public int keyAt(int index) { 248 return mUserRestrictions.keyAt(index); 249 } 250 251 @VisibleForTesting valueAt(int index)252 public Bundle valueAt(int index) { 253 return mUserRestrictions.valueAt(index); 254 } 255 256 } 257