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 package com.android.car.hal; 17 18 import static com.android.car.CarServiceUtils.toIntArray; 19 import static com.android.internal.util.Preconditions.checkArgument; 20 21 import android.annotation.UserIdInt; 22 import android.app.ActivityManager; 23 import android.car.builtin.os.UserManagerHelper; 24 import android.hardware.automotive.vehicle.CreateUserRequest; 25 import android.hardware.automotive.vehicle.InitialUserInfoRequestType; 26 import android.hardware.automotive.vehicle.InitialUserInfoResponse; 27 import android.hardware.automotive.vehicle.InitialUserInfoResponseAction; 28 import android.hardware.automotive.vehicle.RemoveUserRequest; 29 import android.hardware.automotive.vehicle.SwitchUserRequest; 30 import android.hardware.automotive.vehicle.UserIdentificationAssociation; 31 import android.hardware.automotive.vehicle.UserIdentificationAssociationSetValue; 32 import android.hardware.automotive.vehicle.UserIdentificationAssociationType; 33 import android.hardware.automotive.vehicle.UserIdentificationAssociationValue; 34 import android.hardware.automotive.vehicle.UserIdentificationGetRequest; 35 import android.hardware.automotive.vehicle.UserIdentificationResponse; 36 import android.hardware.automotive.vehicle.UserIdentificationSetAssociation; 37 import android.hardware.automotive.vehicle.UserIdentificationSetRequest; 38 import android.hardware.automotive.vehicle.UserInfo; 39 import android.hardware.automotive.vehicle.UsersInfo; 40 import android.hardware.automotive.vehicle.VehiclePropertyStatus; 41 import android.os.SystemClock; 42 import android.os.UserHandle; 43 import android.os.UserManager; 44 import android.text.TextUtils; 45 import android.util.Log; 46 47 import com.android.car.hal.HalCallback.HalCallbackStatus; 48 import com.android.car.internal.util.DebugUtils; 49 import com.android.car.user.UserHandleHelper; 50 import com.android.internal.annotations.VisibleForTesting; 51 import com.android.internal.util.Preconditions; 52 53 import java.util.ArrayList; 54 import java.util.Arrays; 55 import java.util.List; 56 import java.util.Objects; 57 58 /** 59 * Provides utility methods for User HAL related functionalities. 60 */ 61 public final class UserHalHelper { 62 63 @VisibleForTesting 64 static final String TAG = UserHalHelper.class.getSimpleName(); 65 66 public static final int INITIAL_USER_INFO_PROPERTY = 299896583; 67 public static final int SWITCH_USER_PROPERTY = 299896584; 68 public static final int CREATE_USER_PROPERTY = 299896585; 69 public static final int REMOVE_USER_PROPERTY = 299896586; 70 public static final int USER_IDENTIFICATION_ASSOCIATION_PROPERTY = 299896587; 71 72 private static final boolean DEBUG = false; 73 private static final String STRING_SEPARATOR = "\\|\\|"; 74 75 /** 76 * Gets user-friendly representation of the status. 77 */ halCallbackStatusToString(@alCallbackStatus int status)78 public static String halCallbackStatusToString(@HalCallbackStatus int status) { 79 switch (status) { 80 case HalCallback.STATUS_OK: 81 return "OK"; 82 case HalCallback.STATUS_HAL_SET_TIMEOUT: 83 return "HAL_SET_TIMEOUT"; 84 case HalCallback.STATUS_HAL_RESPONSE_TIMEOUT: 85 return "HAL_RESPONSE_TIMEOUT"; 86 case HalCallback.STATUS_WRONG_HAL_RESPONSE: 87 return "WRONG_HAL_RESPONSE"; 88 case HalCallback.STATUS_CONCURRENT_OPERATION: 89 return "CONCURRENT_OPERATION"; 90 default: 91 return "UNKNOWN-" + status; 92 } 93 } 94 95 /** 96 * Converts a string to a {@link InitialUserInfoRequestType}. 97 * 98 * @return valid type or numeric value if passed "as is" 99 * 100 * @throws IllegalArgumentException if type is not valid neither a number 101 */ parseInitialUserInfoRequestType(String type)102 public static int parseInitialUserInfoRequestType(String type) { 103 switch(type) { 104 case "FIRST_BOOT": 105 return InitialUserInfoRequestType.FIRST_BOOT; 106 case "FIRST_BOOT_AFTER_OTA": 107 return InitialUserInfoRequestType.FIRST_BOOT_AFTER_OTA; 108 case "COLD_BOOT": 109 return InitialUserInfoRequestType.COLD_BOOT; 110 case "RESUME": 111 return InitialUserInfoRequestType.RESUME; 112 default: 113 try { 114 return Integer.parseInt(type); 115 } catch (NumberFormatException e) { 116 throw new IllegalArgumentException("invalid type: " + type); 117 } 118 } 119 } 120 121 /** 122 * Converts Android user flags to HALs. 123 */ convertFlags(UserHandleHelper userHandleHelper, UserHandle user)124 public static int convertFlags(UserHandleHelper userHandleHelper, UserHandle user) { 125 checkArgument(user != null, "user cannot be null"); 126 127 int flags = 0; 128 if (user.getIdentifier() == UserHandle.SYSTEM.getIdentifier()) { 129 flags |= UserInfo.USER_FLAG_SYSTEM; 130 } 131 if (userHandleHelper.isAdminUser(user)) { 132 flags |= UserInfo.USER_FLAG_ADMIN; 133 } 134 if (userHandleHelper.isGuestUser(user)) { 135 flags |= UserInfo.USER_FLAG_GUEST; 136 } 137 if (userHandleHelper.isEphemeralUser(user)) { 138 flags |= UserInfo.USER_FLAG_EPHEMERAL; 139 } 140 if (!userHandleHelper.isEnabledUser(user)) { 141 flags |= UserInfo.USER_FLAG_DISABLED; 142 } 143 if (userHandleHelper.isProfileUser(user)) { 144 flags |= UserInfo.USER_FLAG_PROFILE; 145 } 146 147 return flags; 148 } 149 150 151 /** 152 * Converts Android user flags to HALs. 153 */ getFlags(UserHandleHelper userHandleHelper, @UserIdInt int userId)154 public static int getFlags(UserHandleHelper userHandleHelper, @UserIdInt int userId) { 155 Preconditions.checkArgument(userHandleHelper != null, "UserManager cannot be null"); 156 UserHandle user = userHandleHelper.getExistingUserHandle(userId); 157 Preconditions.checkArgument(user != null, "No user with id %d", userId); 158 return convertFlags(userHandleHelper, user); 159 } 160 161 /** Checks if a HAL flag contains {@link UserInfo#USER_FLAG_SYSTEM}. */ isSystem(int flags)162 public static boolean isSystem(int flags) { 163 return (flags & UserInfo.USER_FLAG_SYSTEM) != 0; 164 } 165 166 /** Checks if a HAL flag contains {@link UserInfo#USER_FLAG_GUEST}. */ isGuest(int flags)167 public static boolean isGuest(int flags) { 168 return (flags & UserInfo.USER_FLAG_GUEST) != 0; 169 } 170 171 /** Checks if a HAL flag contains {@link UserInfo#USER_FLAG_EPHEMERAL}. */ isEphemeral(int flags)172 public static boolean isEphemeral(int flags) { 173 return (flags & UserInfo.USER_FLAG_EPHEMERAL) != 0; 174 } 175 176 /** Checks if a HAL flag contains {@link UserInfo#USER_FLAG_ADMIN}. */ isAdmin(int flags)177 public static boolean isAdmin(int flags) { 178 return (flags & UserInfo.USER_FLAG_ADMIN) != 0; 179 } 180 181 /** Checks if a HAL flag contains {@link UserInfo#USER_FLAG_DISABLED}. */ isDisabled(int flags)182 public static boolean isDisabled(int flags) { 183 return (flags & UserInfo.USER_FLAG_DISABLED) != 0; 184 } 185 186 /** Checks if a HAL flag contains {@link UserInfo#USER_FLAG_PROFILE}. */ isProfile(int flags)187 public static boolean isProfile(int flags) { 188 return (flags & UserInfo.USER_FLAG_PROFILE) != 0; 189 } 190 191 /** 192 * Converts HAL flags to Android's. 193 */ toUserInfoFlags(int halFlags)194 public static int toUserInfoFlags(int halFlags) { 195 int flags = 0; 196 if (isEphemeral(halFlags)) { 197 flags |= UserManagerHelper.FLAG_EPHEMERAL; 198 } 199 if (isAdmin(halFlags)) { 200 flags |= UserManagerHelper.FLAG_ADMIN; 201 } 202 return flags; 203 } 204 205 /** 206 * Gets a user-friendly representation of the user flags. 207 */ userFlagsToString(int flags)208 public static String userFlagsToString(int flags) { 209 return DebugUtils.flagsToString(UserInfo.class, /* prefix= */ "", flags); 210 } 211 212 /** 213 * Adds users information to the property's integer values. 214 * 215 * <p><b>NOTE: </b>it does not validate the semantics of {@link UsersInfo} content (for example, 216 * if the current user is present in the list of users or if the flags are valid), only the 217 * basic correctness (like number of users matching existing users list size). Use 218 * {@link #checkValid(UsersInfo)} for a full check. 219 */ addUsersInfo(List<Integer> intValues, UsersInfo usersInfo)220 public static void addUsersInfo(List<Integer> intValues, UsersInfo usersInfo) { 221 Objects.requireNonNull(usersInfo.currentUser, "Current user cannot be null"); 222 checkArgument(usersInfo.numberUsers == usersInfo.existingUsers.length, 223 "Number of existing users info does not match numberUsers, got %d, want %d", 224 usersInfo.numberUsers, usersInfo.existingUsers.length); 225 226 addUserInfo(intValues, usersInfo.currentUser); 227 intValues.add(usersInfo.numberUsers); 228 for (int i = 0; i < usersInfo.numberUsers; i++) { 229 UserInfo userInfo = usersInfo.existingUsers[i]; 230 addUserInfo(intValues, userInfo); 231 } 232 } 233 234 /** Adds user information to the property's integer values. */ addUserInfo(List<Integer> intValues, UserInfo userInfo)235 public static void addUserInfo(List<Integer> intValues, UserInfo userInfo) { 236 Objects.requireNonNull(userInfo, "UserInfo cannot be null"); 237 238 intValues.add(userInfo.userId); 239 intValues.add(userInfo.flags); 240 } 241 242 /** 243 * Checks if the given {@code value} is a valid {@link UserIdentificationAssociationType}. 244 */ isValidUserIdentificationAssociationType(int type)245 public static boolean isValidUserIdentificationAssociationType(int type) { 246 switch(type) { 247 case UserIdentificationAssociationType.KEY_FOB: 248 case UserIdentificationAssociationType.CUSTOM_1: 249 case UserIdentificationAssociationType.CUSTOM_2: 250 case UserIdentificationAssociationType.CUSTOM_3: 251 case UserIdentificationAssociationType.CUSTOM_4: 252 return true; 253 } 254 return false; 255 } 256 257 /** 258 * Checks if the given {@code value} is a valid {@link UserIdentificationAssociationValue}. 259 */ isValidUserIdentificationAssociationValue(int value)260 public static boolean isValidUserIdentificationAssociationValue(int value) { 261 switch(value) { 262 case UserIdentificationAssociationValue.ASSOCIATED_ANOTHER_USER: 263 case UserIdentificationAssociationValue.ASSOCIATED_CURRENT_USER: 264 case UserIdentificationAssociationValue.NOT_ASSOCIATED_ANY_USER: 265 case UserIdentificationAssociationValue.UNKNOWN: 266 return true; 267 } 268 return false; 269 } 270 271 /** 272 * Checks if the given {@code value} is a valid {@link UserIdentificationAssociationSetValue}. 273 */ isValidUserIdentificationAssociationSetValue(int value)274 public static boolean isValidUserIdentificationAssociationSetValue(int value) { 275 switch(value) { 276 case UserIdentificationAssociationSetValue.ASSOCIATE_CURRENT_USER: 277 case UserIdentificationAssociationSetValue.DISASSOCIATE_CURRENT_USER: 278 case UserIdentificationAssociationSetValue.DISASSOCIATE_ALL_USERS: 279 return true; 280 } 281 return false; 282 } 283 284 /** 285 * Creates a {@link UserIdentificationResponse} from a generic {@link HalPropValue} sent by 286 * HAL. 287 * 288 * @throws IllegalArgumentException if the HAL property doesn't have the proper format. 289 */ toUserIdentificationResponse(HalPropValue prop)290 public static UserIdentificationResponse toUserIdentificationResponse(HalPropValue prop) { 291 Objects.requireNonNull(prop, "prop cannot be null"); 292 checkArgument(prop.getPropId() == USER_IDENTIFICATION_ASSOCIATION_PROPERTY, 293 "invalid prop on %s", prop); 294 // need at least 4: request_id, number associations, type1, value1 295 assertMinimumSize(prop, 4); 296 297 int requestId = prop.getInt32Value(0); 298 checkArgument(requestId > 0, "invalid request id (%d) on %s", requestId, prop); 299 300 int numberAssociations = prop.getInt32Value(1); 301 checkArgument(numberAssociations >= 1, "invalid number of items on %s", prop); 302 int numberOfNonItems = 2; // requestId and size 303 int numberItems = prop.getInt32ValuesSize() - numberOfNonItems; 304 checkArgument(numberItems == numberAssociations * 2, "number of items mismatch on %s", 305 prop); 306 307 UserIdentificationResponse response = new UserIdentificationResponse(); 308 response.requestId = requestId; 309 response.errorMessage = prop.getStringValue(); 310 311 response.numberAssociation = numberAssociations; 312 int i = numberOfNonItems; 313 ArrayList<UserIdentificationAssociation> associations = new ArrayList<>(numberAssociations); 314 for (int a = 0; a < numberAssociations; a++) { 315 int index; 316 UserIdentificationAssociation association = new UserIdentificationAssociation(); 317 index = i++; 318 association.type = prop.getInt32Value(index); 319 checkArgument(isValidUserIdentificationAssociationType(association.type), 320 "invalid type at index %d on %s", index, prop); 321 index = i++; 322 association.value = prop.getInt32Value(index); 323 checkArgument(isValidUserIdentificationAssociationValue(association.value), 324 "invalid value at index %d on %s", index, prop); 325 associations.add(association); 326 } 327 328 response.associations = associations.toArray( 329 new UserIdentificationAssociation[associations.size()]); 330 331 return response; 332 } 333 334 /** 335 * Creates a {@link InitialUserInfoResponse} from a generic {@link HalPropValue} sent by 336 * HAL. 337 * 338 * @throws IllegalArgumentException if the HAL property doesn't have the proper format. 339 */ toInitialUserInfoResponse(HalPropValue prop)340 public static InitialUserInfoResponse toInitialUserInfoResponse(HalPropValue prop) { 341 if (DEBUG) Log.d(TAG, "toInitialUserInfoResponse(): " + prop); 342 Objects.requireNonNull(prop, "prop cannot be null"); 343 checkArgument(prop.getPropId() == INITIAL_USER_INFO_PROPERTY, "invalid prop on %s", prop); 344 345 // need at least 2: request_id, action_type 346 assertMinimumSize(prop, 2); 347 348 int requestId = prop.getInt32Value(0); 349 checkArgument(requestId > 0, "invalid request id (%d) on %s", requestId, prop); 350 351 InitialUserInfoResponse response = new InitialUserInfoResponse(); 352 response.userToSwitchOrCreate = new UserInfo(); 353 response.userLocales = ""; 354 response.userNameToCreate = ""; 355 response.requestId = requestId; 356 response.action = prop.getInt32Value(1); 357 358 String[] stringValues = null; 359 if (!TextUtils.isEmpty(prop.getStringValue())) { 360 stringValues = TextUtils.split(prop.getStringValue(), STRING_SEPARATOR); 361 if (DEBUG) { 362 Log.d(TAG, "toInitialUserInfoResponse(): values=" + Arrays.toString(stringValues) 363 + " length: " + stringValues.length); 364 } 365 } 366 if (stringValues != null && stringValues.length > 0) { 367 response.userLocales = stringValues[0]; 368 } 369 370 switch (response.action) { 371 case InitialUserInfoResponseAction.DEFAULT: 372 response.userToSwitchOrCreate.userId = UserManagerHelper.USER_NULL; 373 break; 374 case InitialUserInfoResponseAction.SWITCH: 375 assertMinimumSize(prop, 3); // request_id, action_type, user_id 376 response.userToSwitchOrCreate.userId = prop.getInt32Value(2); 377 break; 378 case InitialUserInfoResponseAction.CREATE: 379 assertMinimumSize(prop, 4); // request_id, action_type, user_id, user_flags 380 // user id is set at index 2, but it's ignored 381 response.userToSwitchOrCreate.userId = UserManagerHelper.USER_NULL; 382 response.userToSwitchOrCreate.flags = prop.getInt32Value(3); 383 if (stringValues.length > 1) { 384 response.userNameToCreate = stringValues[1]; 385 } 386 break; 387 default: 388 throw new IllegalArgumentException("Invalid response action (" + response.action 389 + " on " + prop); 390 } 391 392 if (DEBUG) Log.d(TAG, "returning : " + response); 393 394 return response; 395 } 396 397 /** 398 * Creates a generic {@link HalPropValue} (that can be sent to HAL) from a 399 * {@link UserIdentificationGetRequest}. 400 * 401 * @throws IllegalArgumentException if the request doesn't have the proper format. 402 */ toHalPropValue(HalPropValueBuilder builder, UserIdentificationGetRequest request)403 public static HalPropValue toHalPropValue(HalPropValueBuilder builder, 404 UserIdentificationGetRequest request) { 405 Objects.requireNonNull(request, "request cannot be null"); 406 Objects.requireNonNull(request.associationTypes, "associationTypes must not be null"); 407 checkArgument(request.numberAssociationTypes > 0, 408 "invalid number of association types mismatch on %s", request); 409 checkArgument(request.numberAssociationTypes == request.associationTypes.length, 410 "number of association types mismatch on %s", request); 411 checkArgument(request.requestId > 0, "invalid requestId on %s", request); 412 413 List<Integer> intValues = new ArrayList<>(request.numberAssociationTypes + 2); 414 intValues.add(request.requestId); 415 addUserInfo(intValues, request.userInfo); 416 intValues.add(request.numberAssociationTypes); 417 418 for (int i = 0; i < request.numberAssociationTypes; i++) { 419 int type = request.associationTypes[i]; 420 checkArgument(isValidUserIdentificationAssociationType(type), 421 "invalid type at index %d on %s", i, request); 422 intValues.add(type); 423 } 424 425 HalPropValue propValue = builder.build(USER_IDENTIFICATION_ASSOCIATION_PROPERTY, 426 /* areaId= */ 0, SystemClock.elapsedRealtime(), 427 VehiclePropertyStatus.AVAILABLE, 428 toIntArray(intValues)); 429 430 return propValue; 431 } 432 433 /** 434 * Creates a generic {@link HalPropValue} (that can be sent to HAL) from a 435 * {@link UserIdentificationSetRequest}. 436 * 437 * @throws IllegalArgumentException if the request doesn't have the proper format. 438 */ toHalPropValue(HalPropValueBuilder builder, UserIdentificationSetRequest request)439 public static HalPropValue toHalPropValue(HalPropValueBuilder builder, 440 UserIdentificationSetRequest request) { 441 Objects.requireNonNull(request, "request cannot be null"); 442 Objects.requireNonNull(request.associations, "associations must not be null"); 443 checkArgument(request.numberAssociations > 0, 444 "invalid number of associations mismatch on %s", request); 445 checkArgument(request.numberAssociations == request.associations.length, 446 "number of associations mismatch on %s", request); 447 checkArgument(request.requestId > 0, "invalid requestId on %s", request); 448 449 List<Integer> intValues = new ArrayList<>(2); 450 intValues.add(request.requestId); 451 addUserInfo(intValues, request.userInfo); 452 intValues.add(request.numberAssociations); 453 454 for (int i = 0; i < request.numberAssociations; i++) { 455 UserIdentificationSetAssociation association = request.associations[i]; 456 checkArgument(isValidUserIdentificationAssociationType(association.type), 457 "invalid type at index %d on %s", i, request); 458 intValues.add(association.type); 459 checkArgument(isValidUserIdentificationAssociationSetValue(association.value), 460 "invalid value at index %d on %s", i, request); 461 intValues.add(association.value); 462 } 463 464 HalPropValue propValue = builder.build(USER_IDENTIFICATION_ASSOCIATION_PROPERTY, 465 /* areaId= */ 0, SystemClock.elapsedRealtime(), 466 VehiclePropertyStatus.AVAILABLE, 467 toIntArray(intValues)); 468 469 return propValue; 470 } 471 472 /** 473 * Creates a generic {@link HalPropValue} (that can be sent to HAL) from a 474 * {@link CreateUserRequest}. 475 * 476 * @throws IllegalArgumentException if the request doesn't have the proper format. 477 */ toHalPropValue(HalPropValueBuilder builder, CreateUserRequest request)478 public static HalPropValue toHalPropValue(HalPropValueBuilder builder, 479 CreateUserRequest request) { 480 Objects.requireNonNull(request, "request cannot be null"); 481 Objects.requireNonNull(request.newUserInfo, "NewUserInfo cannot be null"); 482 checkArgument(request.requestId > 0, "invalid requestId on %s", request); 483 checkValid(request.usersInfo); 484 checkArgument(request.newUserName != null, "newUserName cannot be null (should be empty " 485 + "instead) on %s", request); 486 487 boolean hasNewUser = false; 488 int newUserFlags = 0; 489 for (int i = 0; i < request.usersInfo.existingUsers.length; i++) { 490 UserInfo user = request.usersInfo.existingUsers[i]; 491 if (user.userId == request.newUserInfo.userId) { 492 hasNewUser = true; 493 newUserFlags = user.flags; 494 break; 495 } 496 } 497 Preconditions.checkArgument(hasNewUser, 498 "new user's id not present on existing users on request %s", request); 499 Preconditions.checkArgument(request.newUserInfo.flags == newUserFlags, 500 "new user flags mismatch on existing users on %s", request); 501 502 List<Integer> intValues = new ArrayList<>(2); 503 intValues.add(request.requestId); 504 addUserInfo(intValues, request.newUserInfo); 505 addUsersInfo(intValues, request.usersInfo); 506 507 HalPropValue propValue = builder.build(CREATE_USER_PROPERTY, /* areaId= */ 0, 508 SystemClock.elapsedRealtime(), VehiclePropertyStatus.AVAILABLE, 509 toIntArray(intValues), new float[0], new long[0], request.newUserName, new byte[0]); 510 511 return propValue; 512 } 513 514 /** 515 * Creates a generic {@link HalPropValue} (that can be sent to HAL) from a 516 * {@link SwitchUserRequest}. 517 * 518 * @throws IllegalArgumentException if the request doesn't have the proper format. 519 */ toHalPropValue(HalPropValueBuilder builder, SwitchUserRequest request)520 public static HalPropValue toHalPropValue(HalPropValueBuilder builder, 521 SwitchUserRequest request) { 522 Objects.requireNonNull(request, "request cannot be null"); 523 checkArgument(request.messageType > 0, "invalid messageType on %s", request); 524 UserInfo targetInfo = request.targetUser; 525 UsersInfo usersInfo = request.usersInfo; 526 Objects.requireNonNull(targetInfo); 527 checkValid(usersInfo); 528 529 List<Integer> intValues = new ArrayList<>(2); 530 intValues.add(request.requestId); 531 intValues.add(request.messageType); 532 533 addUserInfo(intValues, targetInfo); 534 addUsersInfo(intValues, usersInfo); 535 536 HalPropValue propValue = builder.build(SWITCH_USER_PROPERTY, /* areaId= */ 0, 537 SystemClock.elapsedRealtime(), VehiclePropertyStatus.AVAILABLE, 538 toIntArray(intValues)); 539 540 return propValue; 541 } 542 543 /** 544 * Creates a generic {@link HalPropValue} (that can be sent to HAL) from a 545 * {@link RemoveUserRequest}. 546 * 547 * @throws IllegalArgumentException if the request doesn't have the proper format. 548 */ toHalPropValue(HalPropValueBuilder builder, RemoveUserRequest request)549 public static HalPropValue toHalPropValue(HalPropValueBuilder builder, 550 RemoveUserRequest request) { 551 checkArgument(request.requestId > 0, "invalid requestId on %s", request); 552 UserInfo removedUserInfo = request.removedUserInfo; 553 Objects.requireNonNull(removedUserInfo); 554 UsersInfo usersInfo = request.usersInfo; 555 checkValid(usersInfo); 556 557 List<Integer> intValues = new ArrayList<>(1); 558 intValues.add(request.requestId); 559 560 addUserInfo(intValues, removedUserInfo); 561 addUsersInfo(intValues, usersInfo); 562 563 HalPropValue propValue = builder.build(REMOVE_USER_PROPERTY, /* areaId= */ 0, 564 SystemClock.elapsedRealtime(), VehiclePropertyStatus.AVAILABLE, 565 toIntArray(intValues)); 566 return propValue; 567 } 568 569 /** 570 * Creates a {@link UsersInfo} instance populated with the current users, using 571 * {@link ActivityManager#getCurrentUser()} as the current user. 572 */ newUsersInfo(UserManager um, UserHandleHelper userHandleHelper)573 public static UsersInfo newUsersInfo(UserManager um, UserHandleHelper userHandleHelper) { 574 return newUsersInfo(um, userHandleHelper, ActivityManager.getCurrentUser()); 575 } 576 577 /** 578 * Creates a {@link UsersInfo} instance populated with the current users, using 579 * {@code userId} as the current user. 580 */ newUsersInfo(UserManager um, UserHandleHelper userHandleHelper, @UserIdInt int userId)581 public static UsersInfo newUsersInfo(UserManager um, UserHandleHelper userHandleHelper, 582 @UserIdInt int userId) { 583 Preconditions.checkArgument(um != null, "UserManager cannot be null"); 584 Preconditions.checkArgument(userHandleHelper != null, "UserHandleHelper cannot be null"); 585 586 List<UserHandle> users = UserManagerHelper.getUserHandles(um, /* excludePartial= */ false, 587 /* excludeDying= */ false, /* excludePreCreated= */ true); 588 589 if (users == null || users.isEmpty()) { 590 Log.w(TAG, "newUsersInfo(): no users"); 591 return emptyUsersInfo(); 592 } 593 594 UsersInfo usersInfo = emptyUsersInfo(); 595 usersInfo.currentUser.userId = userId; 596 UserHandle currentUser = null; 597 int allUsersSize = users.size(); 598 ArrayList<UserInfo> halUsers = new ArrayList<>(allUsersSize); 599 for (int i = 0; i < allUsersSize; i++) { 600 UserHandle user = users.get(i); 601 try { 602 if (user.getIdentifier() == usersInfo.currentUser.userId) { 603 currentUser = user; 604 } 605 UserInfo halUser = new UserInfo(); 606 halUser.userId = user.getIdentifier(); 607 halUser.flags = convertFlags(userHandleHelper, user); 608 halUsers.add(halUser); 609 } catch (Exception e) { 610 // Most likely the user was removed 611 Log.w(TAG, "newUsersInfo(): ignoring user " + user + " due to exception", e); 612 } 613 } 614 int existingUsersSize = halUsers.size(); 615 usersInfo.numberUsers = existingUsersSize; 616 usersInfo.existingUsers = halUsers.toArray(new UserInfo[existingUsersSize]); 617 618 if (currentUser != null) { 619 usersInfo.currentUser.flags = convertFlags(userHandleHelper, currentUser); 620 } else { 621 // This should not happen. 622 Log.wtf(TAG, "Current user is not part of existing users. usersInfo: " + usersInfo); 623 } 624 625 return usersInfo; 626 } 627 628 /** 629 * Checks if the given {@code usersInfo} is valid. 630 * 631 * @throws IllegalArgumentException if it isn't. 632 */ checkValid(UsersInfo usersInfo)633 public static void checkValid(UsersInfo usersInfo) { 634 Preconditions.checkArgument(usersInfo != null); 635 Preconditions.checkArgument(usersInfo.existingUsers != null); 636 Preconditions.checkArgument(usersInfo.currentUser != null); 637 Preconditions.checkArgument(usersInfo.numberUsers == usersInfo.existingUsers.length, 638 "sizes mismatch: numberUsers=%d, existingUsers.size=%d", usersInfo.numberUsers, 639 usersInfo.existingUsers.length); 640 boolean hasCurrentUser = false; 641 int currentUserFlags = 0; 642 for (int i = 0; i < usersInfo.numberUsers; i++) { 643 UserInfo user = usersInfo.existingUsers[i]; 644 if (user.userId == usersInfo.currentUser.userId) { 645 hasCurrentUser = true; 646 currentUserFlags = user.flags; 647 break; 648 } 649 } 650 Preconditions.checkArgument(hasCurrentUser, 651 "current user not found on existing users on %s", usersInfo); 652 Preconditions.checkArgument(usersInfo.currentUser.flags == currentUserFlags, 653 "current user flags mismatch on existing users on %s", usersInfo); 654 } 655 656 /** 657 * Gets an empty CreateUserRequest with fields initialized to valid empty values (not 658 * {@code null}). 659 * 660 * @return An empty {@link CreateUserRequest}. 661 */ emptyCreateUserRequest()662 public static CreateUserRequest emptyCreateUserRequest() { 663 CreateUserRequest request = new CreateUserRequest(); 664 request.newUserInfo = new UserInfo(); 665 request.usersInfo = emptyUsersInfo(); 666 request.newUserName = ""; 667 return request; 668 } 669 670 /** 671 * Gets an empty SwitchUserRequest with fields initialized to valid empty values (not 672 * {@code null} 673 * 674 * @return An empty {@link SwitchUserRequest}. 675 */ emptySwitchUserRequest()676 public static SwitchUserRequest emptySwitchUserRequest() { 677 SwitchUserRequest request = new SwitchUserRequest(); 678 request.targetUser = new UserInfo(); 679 request.usersInfo = emptyUsersInfo(); 680 return request; 681 } 682 683 /** 684 * Gets an empty RemoveUserRequest with fields initialized to valid empty values (not 685 * {@code null} 686 * 687 * @return An empty {@link RemoveUserRequest}. 688 */ emptyRemoveUserRequest()689 public static RemoveUserRequest emptyRemoveUserRequest() { 690 RemoveUserRequest request = new RemoveUserRequest(); 691 request.removedUserInfo = new UserInfo(); 692 request.usersInfo = emptyUsersInfo(); 693 return request; 694 } 695 696 /** 697 * Gets an empty UserIdentificationGetRequest with fields initialized to valid empty values 698 * (not {@code null}). 699 * 700 * @return An empty {@link UserIdentificationGetRequest}. 701 */ emptyUserIdentificationGetRequest()702 public static UserIdentificationGetRequest emptyUserIdentificationGetRequest() { 703 UserIdentificationGetRequest request = new UserIdentificationGetRequest(); 704 request.userInfo = new UserInfo(); 705 request.associationTypes = new int[0]; 706 return request; 707 } 708 709 /** 710 * Gets an empty UserIdentificationSetRequest with fields initialized to valid empty values 711 * (not {@code null}). 712 * 713 * @return An empty {@link UserIdentificationSetRequest}. 714 */ emptyUserIdentificationSetRequest()715 public static UserIdentificationSetRequest emptyUserIdentificationSetRequest() { 716 UserIdentificationSetRequest request = new UserIdentificationSetRequest(); 717 request.userInfo = new UserInfo(); 718 request.associations = new UserIdentificationSetAssociation[0]; 719 return request; 720 } 721 722 /** 723 * Gets an empty UsersInfo with fields initialized to valid empty values (not {@code null}). 724 * 725 * @return An empty {@link UsersInfo}. 726 */ emptyUsersInfo()727 public static UsersInfo emptyUsersInfo() { 728 UsersInfo usersInfo = new UsersInfo(); 729 usersInfo.currentUser = new UserInfo(); 730 usersInfo.existingUsers = new UserInfo[0]; 731 usersInfo.currentUser.userId = UserManagerHelper.USER_NULL; 732 return usersInfo; 733 } 734 assertMinimumSize(HalPropValue prop, int minSize)735 private static void assertMinimumSize(HalPropValue prop, int minSize) { 736 checkArgument(prop.getInt32ValuesSize() >= minSize, 737 "not enough int32Values (minimum is %d) on %s", minSize, prop); 738 } 739 UserHalHelper()740 private UserHalHelper() { 741 throw new UnsupportedOperationException("contains only static methods"); 742 } 743 } 744