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