1 /*
2  * Copyright (C) 2019 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 static android.content.pm.UserInfo.FLAG_DEMO;
20 import static android.content.pm.UserInfo.FLAG_EPHEMERAL;
21 import static android.content.pm.UserInfo.FLAG_FULL;
22 import static android.content.pm.UserInfo.FLAG_GUEST;
23 import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
24 import static android.content.pm.UserInfo.FLAG_PROFILE;
25 import static android.content.pm.UserInfo.FLAG_RESTRICTED;
26 import static android.content.pm.UserInfo.FLAG_SYSTEM;
27 import static android.os.UserManager.USER_TYPE_FULL_DEMO;
28 import static android.os.UserManager.USER_TYPE_FULL_GUEST;
29 import static android.os.UserManager.USER_TYPE_FULL_RESTRICTED;
30 import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
31 import static android.os.UserManager.USER_TYPE_FULL_SYSTEM;
32 import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
33 import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS;
34 
35 import static com.android.server.pm.UserTypeDetails.UNLIMITED_NUMBER_OF_USERS;
36 
37 import android.content.pm.UserInfo;
38 import android.content.res.Resources;
39 import android.content.res.XmlResourceParser;
40 import android.os.Bundle;
41 import android.os.UserManager;
42 import android.util.ArrayMap;
43 import android.util.Slog;
44 
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.internal.util.XmlUtils;
47 
48 import org.xmlpull.v1.XmlPullParserException;
49 
50 import java.io.IOException;
51 import java.util.ArrayList;
52 import java.util.function.Consumer;
53 
54 /**
55  * Class for creating all {@link UserTypeDetails} on the device.
56  *
57  * This class is responsible both for defining the AOSP use types, as well as reading in customized
58  * user types from {@link com.android.internal.R.xml#config_user_types}.
59  *
60  * Tests are located in UserManagerServiceUserTypeTest.java.
61  * @hide
62  */
63 public final class UserTypeFactory {
64 
65     private static final String LOG_TAG = "UserTypeFactory";
66 
67     /** This is a utility class, so no instantiable constructor. */
UserTypeFactory()68     private UserTypeFactory() {}
69 
70     /**
71      * Obtains the user types (built-in and customized) for this device.
72      *
73      * @return mapping from the name of each user type to its {@link UserTypeDetails} object
74      */
getUserTypes()75     public static ArrayMap<String, UserTypeDetails> getUserTypes() {
76         final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
77         builders.put(USER_TYPE_PROFILE_MANAGED, getDefaultTypeProfileManaged());
78         builders.put(USER_TYPE_FULL_SYSTEM, getDefaultTypeFullSystem());
79         builders.put(USER_TYPE_FULL_SECONDARY, getDefaultTypeFullSecondary());
80         builders.put(USER_TYPE_FULL_GUEST, getDefaultTypeFullGuest());
81         builders.put(USER_TYPE_FULL_DEMO, getDefaultTypeFullDemo());
82         builders.put(USER_TYPE_FULL_RESTRICTED, getDefaultTypeFullRestricted());
83         builders.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless());
84 
85         try (XmlResourceParser parser =
86                      Resources.getSystem().getXml(com.android.internal.R.xml.config_user_types)) {
87             customizeBuilders(builders, parser);
88         }
89 
90         final ArrayMap<String, UserTypeDetails> types = new ArrayMap<>(builders.size());
91         for (int i = 0; i < builders.size(); i++) {
92             types.put(builders.keyAt(i), builders.valueAt(i).createUserTypeDetails());
93         }
94         return types;
95     }
96 
97     /**
98      * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_MANAGED}
99      * configuration.
100      */
getDefaultTypeProfileManaged()101     private static UserTypeDetails.Builder getDefaultTypeProfileManaged() {
102         return new UserTypeDetails.Builder()
103                 .setName(USER_TYPE_PROFILE_MANAGED)
104                 .setBaseType(FLAG_PROFILE)
105                 .setDefaultUserInfoPropertyFlags(FLAG_MANAGED_PROFILE)
106                 .setMaxAllowedPerParent(1)
107                 .setLabel(0)
108                 .setIconBadge(com.android.internal.R.drawable.ic_corp_icon_badge_case)
109                 .setBadgePlain(com.android.internal.R.drawable.ic_corp_badge_case)
110                 .setBadgeNoBackground(com.android.internal.R.drawable.ic_corp_badge_no_background)
111                 .setBadgeLabels(
112                         com.android.internal.R.string.managed_profile_label_badge,
113                         com.android.internal.R.string.managed_profile_label_badge_2,
114                         com.android.internal.R.string.managed_profile_label_badge_3)
115                 .setBadgeColors(
116                         com.android.internal.R.color.profile_badge_1,
117                         com.android.internal.R.color.profile_badge_2,
118                         com.android.internal.R.color.profile_badge_3)
119                 .setDarkThemeBadgeColors(
120                         com.android.internal.R.color.profile_badge_1_dark,
121                         com.android.internal.R.color.profile_badge_2_dark,
122                         com.android.internal.R.color.profile_badge_3_dark)
123                 .setDefaultRestrictions(null);
124     }
125 
126     /**
127      * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_SECONDARY}
128      * configuration.
129      */
getDefaultTypeFullSecondary()130     private static UserTypeDetails.Builder getDefaultTypeFullSecondary() {
131         return new UserTypeDetails.Builder()
132                 .setName(USER_TYPE_FULL_SECONDARY)
133                 .setBaseType(FLAG_FULL)
134                 .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS)
135                 .setDefaultRestrictions(getDefaultSecondaryUserRestrictions());
136     }
137 
138     /**
139      * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_GUEST} configuration.
140      */
getDefaultTypeFullGuest()141     private static UserTypeDetails.Builder getDefaultTypeFullGuest() {
142         final boolean ephemeralGuests = Resources.getSystem()
143                 .getBoolean(com.android.internal.R.bool.config_guestUserEphemeral);
144         final int flags = FLAG_GUEST | (ephemeralGuests ? FLAG_EPHEMERAL : 0);
145 
146         return new UserTypeDetails.Builder()
147                 .setName(USER_TYPE_FULL_GUEST)
148                 .setBaseType(FLAG_FULL)
149                 .setDefaultUserInfoPropertyFlags(flags)
150                 .setMaxAllowed(1)
151                 .setDefaultRestrictions(getDefaultGuestUserRestrictions());
152     }
153 
154     /**
155      * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_DEMO} configuration.
156      */
getDefaultTypeFullDemo()157     private static UserTypeDetails.Builder getDefaultTypeFullDemo() {
158         return new UserTypeDetails.Builder()
159                 .setName(USER_TYPE_FULL_DEMO)
160                 .setBaseType(FLAG_FULL)
161                 .setDefaultUserInfoPropertyFlags(FLAG_DEMO)
162                 .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS)
163                 .setDefaultRestrictions(null);
164     }
165 
166     /**
167      * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_RESTRICTED}
168      * configuration.
169      */
getDefaultTypeFullRestricted()170     private static UserTypeDetails.Builder getDefaultTypeFullRestricted() {
171         return new UserTypeDetails.Builder()
172                 .setName(USER_TYPE_FULL_RESTRICTED)
173                 .setBaseType(FLAG_FULL)
174                 .setDefaultUserInfoPropertyFlags(FLAG_RESTRICTED)
175                 .setMaxAllowed(UNLIMITED_NUMBER_OF_USERS)
176                 // NB: UserManagerService.createRestrictedProfile() applies hardcoded restrictions.
177                 .setDefaultRestrictions(null);
178     }
179 
180     /**
181      * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_SYSTEM} configuration.
182      */
getDefaultTypeFullSystem()183     private static UserTypeDetails.Builder getDefaultTypeFullSystem() {
184         return new UserTypeDetails.Builder()
185                 .setName(USER_TYPE_FULL_SYSTEM)
186                 .setBaseType(FLAG_SYSTEM | FLAG_FULL);
187     }
188 
189     /**
190      * Returns the Builder for the default {@link UserManager#USER_TYPE_SYSTEM_HEADLESS}
191      * configuration.
192      */
getDefaultTypeSystemHeadless()193     private static UserTypeDetails.Builder getDefaultTypeSystemHeadless() {
194         return new UserTypeDetails.Builder()
195                 .setName(USER_TYPE_SYSTEM_HEADLESS)
196                 .setBaseType(FLAG_SYSTEM);
197     }
198 
getDefaultSecondaryUserRestrictions()199     private static Bundle getDefaultSecondaryUserRestrictions() {
200         final Bundle restrictions = new Bundle();
201         restrictions.putBoolean(UserManager.DISALLOW_OUTGOING_CALLS, true);
202         restrictions.putBoolean(UserManager.DISALLOW_SMS, true);
203         return restrictions;
204     }
205 
getDefaultGuestUserRestrictions()206     private static Bundle getDefaultGuestUserRestrictions() {
207         // Guest inherits the secondary user's restrictions, plus has some extra ones.
208         final Bundle restrictions = getDefaultSecondaryUserRestrictions();
209         restrictions.putBoolean(UserManager.DISALLOW_CONFIG_WIFI, true);
210         restrictions.putBoolean(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true);
211         return restrictions;
212     }
213 
214     /**
215      * Reads the given xml parser to obtain device user-type customization, and updates the given
216      * map of {@link UserTypeDetails.Builder}s accordingly.
217      * <p>
218      * The xml file can specify the attributes according to the set... methods below.
219      */
220     @VisibleForTesting
customizeBuilders(ArrayMap<String, UserTypeDetails.Builder> builders, XmlResourceParser parser)221     static void customizeBuilders(ArrayMap<String, UserTypeDetails.Builder> builders,
222             XmlResourceParser parser) {
223         try {
224             XmlUtils.beginDocument(parser, "user-types");
225             for (XmlUtils.nextElement(parser);
226                     parser.getEventType() != XmlResourceParser.END_DOCUMENT;
227                     XmlUtils.nextElement(parser)) {
228                 final boolean isProfile;
229                 final String elementName = parser.getName();
230                 if ("profile-type".equals(elementName)) {
231                     isProfile = true;
232                 } else if ("full-type".equals(elementName)) {
233                     isProfile = false;
234                 } else {
235                     Slog.w(LOG_TAG, "Skipping unknown element " + elementName + " in "
236                                 + parser.getPositionDescription());
237                     XmlUtils.skipCurrentTag(parser);
238                     continue;
239                 }
240 
241                 String typeName = parser.getAttributeValue(null, "name");
242                 if (typeName == null) {
243                     Slog.w(LOG_TAG, "Skipping user type with no name in "
244                             + parser.getPositionDescription());
245                     XmlUtils.skipCurrentTag(parser);
246                     continue;
247                 }
248                 typeName.intern();
249 
250                 UserTypeDetails.Builder builder;
251                 if (typeName.startsWith("android.")) {
252                     // typeName refers to a AOSP-defined type which we are modifying.
253                     Slog.i(LOG_TAG, "Customizing user type " + typeName);
254                     builder = builders.get(typeName);
255                     if (builder == null) {
256                         throw new IllegalArgumentException("Illegal custom user type name "
257                                 + typeName + ": Non-AOSP user types cannot start with 'android.'");
258                     }
259                     final boolean isValid =
260                             (isProfile && builder.getBaseType() == UserInfo.FLAG_PROFILE)
261                             || (!isProfile && builder.getBaseType() == UserInfo.FLAG_FULL);
262                     if (!isValid) {
263                         throw new IllegalArgumentException("Wrong base type to customize user type "
264                                 + "(" + typeName + "), which is type "
265                                 + UserInfo.flagsToString(builder.getBaseType()));
266                     }
267                 } else if (isProfile) {
268                     // typeName refers to a new OEM-defined profile type which we are defining.
269                     Slog.i(LOG_TAG, "Creating custom user type " + typeName);
270                     builder = new UserTypeDetails.Builder();
271                     builder.setName(typeName);
272                     builder.setBaseType(FLAG_PROFILE);
273                     builders.put(typeName, builder);
274                 } else {
275                     throw new IllegalArgumentException("Creation of non-profile user type "
276                             + "(" + typeName + ") is not currently supported.");
277                 }
278 
279                 // Process the attributes (other than name).
280                 if (isProfile) {
281                     setIntAttribute(parser, "max-allowed-per-parent",
282                             builder::setMaxAllowedPerParent);
283                     setResAttribute(parser, "icon-badge", builder::setIconBadge);
284                     setResAttribute(parser, "badge-plain", builder::setBadgePlain);
285                     setResAttribute(parser, "badge-no-background", builder::setBadgeNoBackground);
286                 }
287 
288                 // Process child elements.
289                 final int depth = parser.getDepth();
290                 while (XmlUtils.nextElementWithin(parser, depth)) {
291                     final String childName = parser.getName();
292                     if ("default-restrictions".equals(childName)) {
293                         final Bundle restrictions = UserRestrictionsUtils.readRestrictions(parser);
294                         builder.setDefaultRestrictions(restrictions);
295                     } else if (isProfile && "badge-labels".equals(childName)) {
296                         setResAttributeArray(parser, builder::setBadgeLabels);
297                     } else if (isProfile && "badge-colors".equals(childName)) {
298                         setResAttributeArray(parser, builder::setBadgeColors);
299                     } else if (isProfile && "badge-colors-dark".equals(childName)) {
300                         setResAttributeArray(parser, builder::setDarkThemeBadgeColors);
301                     } else {
302                         Slog.w(LOG_TAG, "Unrecognized tag " + childName + " in "
303                                 + parser.getPositionDescription());
304                     }
305                 }
306             }
307         } catch (XmlPullParserException | IOException e) {
308             Slog.w(LOG_TAG, "Cannot read user type configuration file.", e);
309         }
310     }
311 
312     /**
313      * If the given attribute exists, gets the int stored in it and performs the given fcn using it.
314      * The stored value must be an int or NumberFormatException will be thrown.
315      *
316      * @param parser xml parser from which to read the attribute
317      * @param attributeName name of the attribute
318      * @param fcn one-int-argument function,
319      *            like {@link UserTypeDetails.Builder#setMaxAllowedPerParent(int)}
320      */
setIntAttribute(XmlResourceParser parser, String attributeName, Consumer<Integer> fcn)321     private static void setIntAttribute(XmlResourceParser parser, String attributeName,
322             Consumer<Integer> fcn) {
323         final String intValue = parser.getAttributeValue(null, attributeName);
324         if (intValue == null) {
325             return;
326         }
327         try {
328             fcn.accept(Integer.parseInt(intValue));
329         } catch (NumberFormatException e) {
330             Slog.e(LOG_TAG, "Cannot parse value of '" + intValue + "' for " + attributeName
331                     + " in " + parser.getPositionDescription(), e);
332             throw e;
333         }
334     }
335 
336     /**
337      * If the given attribute exists, gets the resId stored in it (or 0 if it is not a valid resId)
338      * and performs the given fcn using it.
339      *
340      * @param parser xml parser from which to read the attribute
341      * @param attributeName name of the attribute
342      * @param fcn one-argument function, like {@link UserTypeDetails.Builder#setIconBadge(int)}
343      */
setResAttribute(XmlResourceParser parser, String attributeName, Consumer<Integer> fcn)344     private static void setResAttribute(XmlResourceParser parser, String attributeName,
345             Consumer<Integer> fcn) {
346         if (parser.getAttributeValue(null, attributeName) == null) {
347             // Attribute is not present, i.e. use the default value.
348             return;
349         }
350         final int resId = parser.getAttributeResourceValue(null, attributeName, Resources.ID_NULL);
351         fcn.accept(resId);
352     }
353 
354     /**
355      * Gets the resIds stored in "item" elements (in their "res" attribute) at the current depth.
356      * Then performs the given fcn using the int[] array of these resIds.
357      * <p>
358      * Each xml element is expected to be of the form {@code <item res="someResValue" />}.
359      *
360      * @param parser xml parser from which to read the elements and their attributes
361      * @param fcn function, like {@link UserTypeDetails.Builder#setBadgeColors(int...)}
362      */
setResAttributeArray(XmlResourceParser parser, Consumer<int[]> fcn)363     private static void setResAttributeArray(XmlResourceParser parser, Consumer<int[]> fcn)
364             throws IOException, XmlPullParserException {
365 
366         ArrayList<Integer> resList = new ArrayList<>();
367         final int depth = parser.getDepth();
368         while (XmlUtils.nextElementWithin(parser, depth)) {
369             final String elementName = parser.getName();
370             if (!"item".equals(elementName)) {
371                 Slog.w(LOG_TAG, "Skipping unknown child element " + elementName + " in "
372                         + parser.getPositionDescription());
373                 XmlUtils.skipCurrentTag(parser);
374                 continue;
375             }
376             final int resId = parser.getAttributeResourceValue(null, "res", -1);
377             if (resId == -1) {
378                 continue;
379             }
380             resList.add(resId);
381         }
382 
383         int[] result = new int[resList.size()];
384         for (int i = 0; i < resList.size(); i++) {
385             result[i] = resList.get(i);
386         }
387         fcn.accept(result);
388     }
389 }
390