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.UserHandle; 24 import android.os.UserManager; 25 import android.util.IntArray; 26 import android.util.SparseArray; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.modules.utils.TypedXmlPullParser; 30 import com.android.modules.utils.TypedXmlSerializer; 31 import com.android.server.BundleUtils; 32 33 import org.xmlpull.v1.XmlPullParser; 34 import org.xmlpull.v1.XmlPullParserException; 35 36 import java.io.IOException; 37 import java.io.PrintWriter; 38 import java.util.ArrayList; 39 import java.util.List; 40 41 /** 42 * Data structure that contains the mapping of users to user restrictions. 43 * 44 * @hide 45 */ 46 public class RestrictionsSet { 47 48 private static final String USER_ID = "user_id"; 49 private static final String TAG_RESTRICTIONS = "restrictions"; 50 private static final String TAG_RESTRICTIONS_USER = "restrictions_user"; 51 52 /** 53 * Mapping of user restrictions. 54 * Only non-empty restriction bundles are stored. 55 * The key is the user id of the user. 56 * userId -> restrictionBundle 57 */ 58 private final SparseArray<Bundle> mUserRestrictions = new SparseArray<>(0); 59 RestrictionsSet()60 public RestrictionsSet() { 61 } 62 RestrictionsSet(@serIdInt int userId, @NonNull Bundle restrictions)63 public RestrictionsSet(@UserIdInt int userId, @NonNull Bundle restrictions) { 64 if (restrictions.isEmpty()) { 65 throw new IllegalArgumentException("empty restriction bundle cannot be added."); 66 } 67 mUserRestrictions.put(userId, restrictions); 68 UserManager.invalidateUserRestriction(); 69 } 70 71 /** 72 * Updates restriction bundle for a given user. 73 * If new bundle is empty, record is removed from the array. 74 * 75 * @return whether restrictions bundle is different from the old one. 76 */ updateRestrictions(@serIdInt int userId, @Nullable Bundle restrictions)77 public boolean updateRestrictions(@UserIdInt int userId, @Nullable Bundle restrictions) { 78 final boolean changed = 79 !UserRestrictionsUtils.areEqual(mUserRestrictions.get(userId), restrictions); 80 if (!changed) { 81 return false; 82 } 83 if (!BundleUtils.isEmpty(restrictions)) { 84 mUserRestrictions.put(userId, restrictions); 85 } else { 86 mUserRestrictions.delete(userId); 87 } 88 UserManager.invalidateUserRestriction(); 89 return true; 90 } 91 92 /** 93 * Removes a particular restriction for all users. 94 * 95 * @return whether the restriction was removed or not. 96 */ removeRestrictionsForAllUsers(String restriction)97 public boolean removeRestrictionsForAllUsers(String restriction) { 98 boolean removed = false; 99 for (int i = 0; i < mUserRestrictions.size(); i++) { 100 final Bundle restrictions = mUserRestrictions.valueAt(i); 101 102 if (UserRestrictionsUtils.contains(restrictions, restriction)) { 103 restrictions.remove(restriction); 104 removed = true; 105 } 106 } 107 if (removed) { 108 UserManager.invalidateUserRestriction(); 109 } 110 return removed; 111 } 112 113 /** 114 * Moves a particular restriction from one restriction set to another, e.g. for all users. 115 */ moveRestriction(@onNull RestrictionsSet destRestrictions, String restriction)116 public void moveRestriction(@NonNull RestrictionsSet destRestrictions, String restriction) { 117 for (int i = 0; i < mUserRestrictions.size(); i++) { 118 final int userId = mUserRestrictions.keyAt(i); 119 final Bundle from = mUserRestrictions.valueAt(i); 120 121 if (UserRestrictionsUtils.contains(from, restriction)) { 122 from.remove(restriction); 123 Bundle to = destRestrictions.getRestrictions(userId); 124 if (to == null) { 125 to = new Bundle(); 126 to.putBoolean(restriction, true); 127 destRestrictions.updateRestrictions(userId, to); 128 } else { 129 to.putBoolean(restriction, true); 130 } 131 // Don't keep empty bundles. 132 if (from.isEmpty()) { 133 mUserRestrictions.removeAt(i); 134 i--; 135 } 136 } 137 UserManager.invalidateUserRestriction(); 138 } 139 } 140 141 /** 142 * @return whether restrictions set has no restrictions. 143 */ isEmpty()144 public boolean isEmpty() { 145 return mUserRestrictions.size() == 0; 146 } 147 148 /** 149 * Merge all restrictions in restrictions set into one bundle. The original user restrictions 150 * set does not get modified, instead a new bundle is returned. 151 * 152 * @return restrictions bundle containing all user restrictions. 153 */ mergeAll()154 public @NonNull Bundle mergeAll() { 155 final Bundle result = new Bundle(); 156 for (int i = 0; i < mUserRestrictions.size(); i++) { 157 UserRestrictionsUtils.merge(result, mUserRestrictions.valueAt(i)); 158 } 159 return result; 160 } 161 162 /** 163 * @return list of enforcing users that enforce a particular restriction. 164 */ getEnforcingUsers(String restriction, @UserIdInt int userId)165 public @NonNull List<UserManager.EnforcingUser> getEnforcingUsers(String restriction, 166 @UserIdInt int userId) { 167 final List<UserManager.EnforcingUser> result = new ArrayList<>(); 168 if (getRestrictionsNonNull(userId).containsKey(restriction)) { 169 result.add(new UserManager.EnforcingUser(userId, 170 UserManager.RESTRICTION_SOURCE_PROFILE_OWNER)); 171 } 172 173 if (getRestrictionsNonNull(UserHandle.USER_ALL).containsKey(restriction)) { 174 result.add(new UserManager.EnforcingUser(UserHandle.USER_ALL, 175 UserManager.RESTRICTION_SOURCE_DEVICE_OWNER)); 176 } 177 178 return result; 179 } 180 181 /** 182 * @return list of user restrictions for a given user. Null is returned if the user does not 183 * have any restrictions. 184 */ getRestrictions(@serIdInt int userId)185 public @Nullable Bundle getRestrictions(@UserIdInt int userId) { 186 return mUserRestrictions.get(userId); 187 } 188 189 /** @return list of user restrictions for a given user that is not null. */ getRestrictionsNonNull(@serIdInt int userId)190 public @NonNull Bundle getRestrictionsNonNull(@UserIdInt int userId) { 191 return UserRestrictionsUtils.nonNull(mUserRestrictions.get(userId)); 192 } 193 194 /** 195 * Removes a given user from the restrictions set, returning true if the user has non-empty 196 * restrictions before removal. 197 */ remove(@serIdInt int userId)198 public boolean remove(@UserIdInt int userId) { 199 boolean hasUserRestriction = mUserRestrictions.contains(userId); 200 mUserRestrictions.remove(userId); 201 UserManager.invalidateUserRestriction(); 202 return hasUserRestriction; 203 } 204 205 /** 206 * Remove list of users and user restrictions. 207 */ removeAllRestrictions()208 public void removeAllRestrictions() { 209 mUserRestrictions.clear(); 210 UserManager.invalidateUserRestriction(); 211 } 212 213 /** 214 * Serialize a given {@link RestrictionsSet} to XML. 215 */ writeRestrictions(@onNull TypedXmlSerializer serializer, @NonNull String outerTag)216 public void writeRestrictions(@NonNull TypedXmlSerializer serializer, @NonNull String outerTag) 217 throws IOException { 218 serializer.startTag(null, outerTag); 219 for (int i = 0; i < mUserRestrictions.size(); i++) { 220 serializer.startTag(null, TAG_RESTRICTIONS_USER); 221 serializer.attributeInt(null, USER_ID, mUserRestrictions.keyAt(i)); 222 UserRestrictionsUtils.writeRestrictions(serializer, mUserRestrictions.valueAt(i), 223 TAG_RESTRICTIONS); 224 serializer.endTag(null, TAG_RESTRICTIONS_USER); 225 } 226 serializer.endTag(null, outerTag); 227 } 228 229 /** 230 * Read restrictions from XML. 231 */ readRestrictions(@onNull TypedXmlPullParser parser, @NonNull String outerTag)232 public static RestrictionsSet readRestrictions(@NonNull TypedXmlPullParser parser, 233 @NonNull String outerTag) throws IOException, XmlPullParserException { 234 RestrictionsSet restrictionsSet = new RestrictionsSet(); 235 int userId = 0; 236 int type; 237 238 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { 239 String tag = parser.getName(); 240 if (type == XmlPullParser.END_TAG && outerTag.equals(tag)) { 241 return restrictionsSet; 242 } else if (type == XmlPullParser.START_TAG && TAG_RESTRICTIONS_USER.equals(tag)) { 243 userId = parser.getAttributeInt(null, USER_ID); 244 } else if (type == XmlPullParser.START_TAG && TAG_RESTRICTIONS.equals(tag)) { 245 Bundle restrictions = UserRestrictionsUtils.readRestrictions(parser); 246 restrictionsSet.updateRestrictions(userId, restrictions); 247 } 248 } 249 throw new XmlPullParserException("restrictions cannot be read as xml is malformed."); 250 } 251 252 /** 253 * Dumps {@link RestrictionsSet}. 254 */ dumpRestrictions(PrintWriter pw, String prefix)255 public void dumpRestrictions(PrintWriter pw, String prefix) { 256 boolean noneSet = true; 257 for (int i = 0; i < mUserRestrictions.size(); i++) { 258 pw.println(prefix + "User Id: " + mUserRestrictions.keyAt(i)); 259 UserRestrictionsUtils.dumpRestrictions(pw, prefix + " ", mUserRestrictions.valueAt(i)); 260 noneSet = false; 261 } 262 if (noneSet) { 263 pw.println(prefix + "none"); 264 } 265 } 266 267 /** @return list of users in this restriction set. */ getUserIds()268 public IntArray getUserIds() { 269 IntArray userIds = new IntArray(mUserRestrictions.size()); 270 for (int i = 0; i < mUserRestrictions.size(); i++) { 271 userIds.add(mUserRestrictions.keyAt(i)); 272 } 273 return userIds; 274 } 275 containsKey(@serIdInt int userId)276 public boolean containsKey(@UserIdInt int userId) { 277 return mUserRestrictions.contains(userId); 278 } 279 280 @VisibleForTesting size()281 public int size() { 282 return mUserRestrictions.size(); 283 } 284 285 @VisibleForTesting keyAt(int index)286 public int keyAt(int index) { 287 return mUserRestrictions.keyAt(index); 288 } 289 290 @VisibleForTesting valueAt(int index)291 public Bundle valueAt(int index) { 292 return mUserRestrictions.valueAt(index); 293 } 294 295 } 296