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