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