• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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