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