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