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