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 android.car.VehiclePropertyIds.CREATE_USER;
19 import static android.car.VehiclePropertyIds.INITIAL_USER_INFO;
20 import static android.car.VehiclePropertyIds.REMOVE_USER;
21 import static android.car.VehiclePropertyIds.SWITCH_USER;
22 import static android.car.VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION;
23 
24 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.app.ActivityManager;
29 import android.car.hardware.property.CarPropertyManager;
30 import android.car.user.CarUserManager;
31 import android.car.userlib.HalCallback;
32 import android.car.userlib.UserHalHelper;
33 import android.car.userlib.UserHelper;
34 import android.hardware.automotive.vehicle.V2_0.CreateUserRequest;
35 import android.hardware.automotive.vehicle.V2_0.CreateUserResponse;
36 import android.hardware.automotive.vehicle.V2_0.CreateUserStatus;
37 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType;
38 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
39 import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
40 import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
41 import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
42 import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
43 import android.hardware.automotive.vehicle.V2_0.SwitchUserResponse;
44 import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
45 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociation;
46 import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType;
47 import android.hardware.automotive.vehicle.V2_0.UserIdentificationGetRequest;
48 import android.hardware.automotive.vehicle.V2_0.UserIdentificationResponse;
49 import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetAssociation;
50 import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetRequest;
51 import android.hardware.automotive.vehicle.V2_0.UsersInfo;
52 import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
53 import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
54 import android.os.Handler;
55 import android.os.Looper;
56 import android.os.ServiceSpecificException;
57 import android.sysprop.CarProperties;
58 import android.text.TextUtils;
59 import android.util.EventLog;
60 import android.util.Log;
61 import android.util.SparseArray;
62 import android.util.SparseBooleanArray;
63 
64 import com.android.car.CarLocalServices;
65 import com.android.car.CarStatsLog;
66 import com.android.car.user.CarUserService;
67 import com.android.internal.annotations.GuardedBy;
68 import com.android.internal.annotations.VisibleForTesting;
69 import com.android.internal.car.EventLogTags;
70 import com.android.internal.util.FunctionalUtils;
71 import com.android.internal.util.Preconditions;
72 
73 import java.io.PrintWriter;
74 import java.io.StringWriter;
75 import java.util.Arrays;
76 import java.util.Collection;
77 import java.util.List;
78 import java.util.Objects;
79 import java.util.Optional;
80 
81 /**
82  * Service used to integrate the OEM's custom user management with Android's.
83  */
84 public final class UserHalService extends HalServiceBase {
85 
86     private static final String TAG = UserHalService.class.getSimpleName();
87 
88     private static final String UNSUPPORTED_MSG = "Vehicle HAL does not support user management";
89     private static final String USER_ASSOCIATION_UNSUPPORTED_MSG =
90             "Vehicle HAL does not support user association";
91 
92     private static final int[] SUPPORTED_PROPERTIES = new int[]{
93             CREATE_USER,
94             INITIAL_USER_INFO,
95             REMOVE_USER,
96             SWITCH_USER,
97             USER_IDENTIFICATION_ASSOCIATION
98     };
99 
100     private static final int[] CORE_PROPERTIES = new int[]{
101             CREATE_USER,
102             INITIAL_USER_INFO,
103             REMOVE_USER,
104             SWITCH_USER,
105     };
106 
107     private static final boolean DBG = false;
108 
109     private final Object mLock = new Object();
110 
111     private final VehicleHal mHal;
112 
113     @GuardedBy("mLock")
114     @Nullable
115     private SparseArray<VehiclePropConfig> mProperties;
116 
117     // This handler handles 2 types of messages:
118     // - "Anonymous" messages (what=0) containing runnables.
119     // - "Identifiable" messages used to check for timeouts (whose 'what' is the request id).
120     private final Handler mHandler;
121 
122     /**
123      * Value used on the next request.
124      */
125     @GuardedBy("mLock")
126     private int mNextRequestId = 1;
127 
128     /**
129      * Map of callbacks by request id.
130      */
131     @GuardedBy("mLock")
132     private final SparseArray<PendingRequest<?, ?>> mPendingRequests = new SparseArray<>();
133 
UserHalService(VehicleHal hal)134     public UserHalService(VehicleHal hal) {
135         this(hal, new Handler(Looper.getMainLooper()));
136     }
137 
138     @VisibleForTesting
UserHalService(VehicleHal hal, Handler handler)139     UserHalService(VehicleHal hal, Handler handler) {
140         mHal = hal;
141         mHandler = handler;
142     }
143 
144     @Override
init()145     public void init() {
146         if (DBG) Log.d(TAG, "init()");
147 
148         if (mProperties == null) {
149             return;
150         }
151 
152         int size = mProperties.size();
153         for (int i = 0; i < size; i++) {
154             VehiclePropConfig config = mProperties.valueAt(i);
155             if (VehicleHal.isPropertySubscribable(config)) {
156                 if (DBG) Log.d(TAG, "subscribing to property " + config.prop);
157                 mHal.subscribeProperty(this, config.prop);
158             }
159         }
160     }
161 
162     @Override
release()163     public void release() {
164         if (DBG) Log.d(TAG, "release()");
165     }
166 
167     @Override
onHalEvents(List<VehiclePropValue> values)168     public void onHalEvents(List<VehiclePropValue> values) {
169         if (DBG) Log.d(TAG, "handleHalEvents(): " + values);
170 
171         for (int i = 0; i < values.size(); i++) {
172             VehiclePropValue value = values.get(i);
173             switch (value.prop) {
174                 case INITIAL_USER_INFO:
175                     mHandler.sendMessage(obtainMessage(
176                             UserHalService::handleOnInitialUserInfoResponse, this, value));
177                     break;
178                 case SWITCH_USER:
179                     mHandler.sendMessage(obtainMessage(
180                             UserHalService::handleOnSwitchUserResponse, this, value));
181                     break;
182                 case CREATE_USER:
183                     mHandler.sendMessage(obtainMessage(
184                             UserHalService::handleOnCreateUserResponse, this, value));
185                     break;
186                 case REMOVE_USER:
187                     Log.w(TAG, "Received REMOVE_USER HAL event: " + value);
188                     break;
189                 case USER_IDENTIFICATION_ASSOCIATION:
190                     mHandler.sendMessage(obtainMessage(
191                             UserHalService::handleOnUserIdentificationAssociation, this, value));
192                     break;
193                 default:
194                     Log.w(TAG, "received unsupported event from HAL: " + value);
195             }
196         }
197     }
198 
199     @Override
onPropertySetError(int property, int area, @CarPropertyManager.CarSetPropertyErrorCode int errorCode)200     public void onPropertySetError(int property, int area,
201             @CarPropertyManager.CarSetPropertyErrorCode int errorCode) {
202         if (DBG) Log.d(TAG, "handlePropertySetError(" + property + "/" + area + ")");
203     }
204 
205     @Override
getAllSupportedProperties()206     public int[] getAllSupportedProperties() {
207         return SUPPORTED_PROPERTIES;
208     }
209 
210     @Override
takeProperties(Collection<VehiclePropConfig> properties)211     public void takeProperties(Collection<VehiclePropConfig> properties) {
212         if (properties.isEmpty()) {
213             Log.w(TAG, UNSUPPORTED_MSG);
214             return;
215         }
216         SparseArray<VehiclePropConfig> supportedProperties = new SparseArray<>(5);
217         for (VehiclePropConfig config : properties) {
218             supportedProperties.put(config.prop, config);
219         }
220         synchronized (mLock) {
221             mProperties = supportedProperties;
222         }
223     }
224 
225     /**
226      * Checks if the Vehicle HAL supports core user management actions.
227      */
isSupported()228     public boolean isSupported() {
229         synchronized (mLock) {
230             if (mProperties == null) return false;
231 
232             for (int i = 0; i < CORE_PROPERTIES.length; i++) {
233                 if (mProperties.get(CORE_PROPERTIES[i]) == null) {
234                     return false;
235                 }
236             }
237             return true;
238         }
239     }
240 
241     /**
242      * Checks if the Vehicle HAL supports core user management actions.
243      */
isUserAssociationSupported()244     public boolean isUserAssociationSupported() {
245         synchronized (mLock) {
246             if (mProperties == null) return false;
247             if (mProperties.get(USER_IDENTIFICATION_ASSOCIATION) == null) return false;
248             return true;
249         }
250     }
251 
252     @GuardedBy("mLock")
checkSupported()253     private void checkSupported() {
254         Preconditions.checkState(isSupported(), UNSUPPORTED_MSG);
255     }
256 
257     @GuardedBy("mLock")
checkUserAssociationSupported()258     private void checkUserAssociationSupported() {
259         Preconditions.checkState(isUserAssociationSupported(), USER_ASSOCIATION_UNSUPPORTED_MSG);
260     }
261 
262 
263     /**
264      * Calls HAL to asynchronously get info about the initial user.
265      *
266      * @param requestType type of request (as defined by
267      * {@link android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType}).
268      * @param timeoutMs how long to wait (in ms) for the property change event.
269      * @param usersInfo current state of Android users.
270      * @param callback to handle the response.
271      *
272      * @throws IllegalStateException if the HAL does not support user management (callers should
273      * call {@link #isSupported()} first to avoid this exception).
274      */
getInitialUserInfo(int requestType, int timeoutMs, @NonNull UsersInfo usersInfo, @NonNull HalCallback<InitialUserInfoResponse> callback)275     public void getInitialUserInfo(int requestType, int timeoutMs, @NonNull UsersInfo usersInfo,
276             @NonNull HalCallback<InitialUserInfoResponse> callback) {
277         if (DBG) Log.d(TAG, "getInitialInfo(" + requestType + ")");
278         Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
279         Objects.requireNonNull(usersInfo);
280         UserHalHelper.checkValid(usersInfo);
281         Objects.requireNonNull(callback);
282         checkSupported();
283 
284         int requestId = getNextRequestId();
285         VehiclePropValue propRequest = UserHalHelper.createPropRequest(INITIAL_USER_INFO, requestId,
286                 requestType);
287         UserHalHelper.addUsersInfo(propRequest, usersInfo);
288 
289         synchronized (mLock) {
290             if (hasPendingRequestLocked(InitialUserInfoResponse.class, callback)) return;
291             addPendingRequestLocked(requestId, InitialUserInfoResponse.class, callback);
292         }
293 
294         EventLog.writeEvent(EventLogTags.CAR_USER_HAL_INITIAL_USER_INFO_REQ, requestId,
295                 requestType, timeoutMs);
296         CarStatsLog.write(CarStatsLog.CAR_USER_HAL_INITIAL_USER_INFO_REQUEST_REPORTED,
297                 requestId, getInitialUserInfoRequestTypeForStatsd(requestType), timeoutMs);
298 
299         sendHalRequest(requestId, timeoutMs, propRequest, callback);
300     }
301 
getInitialUserInfoRequestTypeForStatsd(int requestType)302     private static int getInitialUserInfoRequestTypeForStatsd(int requestType) {
303         // CHECKSTYLE:OFF IndentationCheck
304         switch (requestType) {
305             case InitialUserInfoRequestType.FIRST_BOOT:
306                 return CarStatsLog
307                         .CAR_USER_HAL_INITIAL_USER_INFO_REQUEST_REPORTED__REQUEST_TYPE__FIRST_BOOT;
308             case InitialUserInfoRequestType.FIRST_BOOT_AFTER_OTA:
309                 return CarStatsLog
310                .CAR_USER_HAL_INITIAL_USER_INFO_REQUEST_REPORTED__REQUEST_TYPE__FIRST_BOOT_AFTER_OTA;
311             case InitialUserInfoRequestType.COLD_BOOT:
312                 return CarStatsLog
313                         .CAR_USER_HAL_INITIAL_USER_INFO_REQUEST_REPORTED__REQUEST_TYPE__COLD_BOOT;
314             case InitialUserInfoRequestType.RESUME:
315                 return CarStatsLog
316                         .CAR_USER_HAL_INITIAL_USER_INFO_REQUEST_REPORTED__REQUEST_TYPE__RESUME;
317             default:
318                 return CarStatsLog
319                         .CAR_USER_HAL_INITIAL_USER_INFO_REQUEST_REPORTED__REQUEST_TYPE__UNKNOWN;
320         }
321         // CHECKSTYLE:ON IndentationCheck
322     }
323 
sendHalRequest(int requestId, int timeoutMs, @NonNull VehiclePropValue request, @NonNull HalCallback<?> callback)324     private void sendHalRequest(int requestId, int timeoutMs, @NonNull VehiclePropValue request,
325             @NonNull HalCallback<?> callback) {
326         mHandler.sendMessageDelayed(obtainMessage(
327                 UserHalService::handleCheckIfRequestTimedOut, this, requestId).setWhat(requestId),
328                 timeoutMs);
329         try {
330             if (DBG) Log.d(TAG, "Calling hal.set(): " + request);
331             mHal.set(request);
332         } catch (ServiceSpecificException e) {
333             handleRemovePendingRequest(requestId);
334             Log.w(TAG, "Failed to set " + request, e);
335             callback.onResponse(HalCallback.STATUS_HAL_SET_TIMEOUT, null);
336         }
337     }
338 
339     /**
340      * Calls HAL to asynchronously switch user.
341      *
342      * @param request metadata
343      * @param timeoutMs how long to wait (in ms) for the property change event.
344      * @param callback to handle the response.
345      *
346      * @throws IllegalStateException if the HAL does not support user management (callers should
347      * call {@link #isSupported()} first to avoid this exception).
348      */
switchUser(@onNull SwitchUserRequest request, int timeoutMs, @NonNull HalCallback<SwitchUserResponse> callback)349     public void switchUser(@NonNull SwitchUserRequest request, int timeoutMs,
350             @NonNull HalCallback<SwitchUserResponse> callback) {
351         Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
352         Objects.requireNonNull(callback, "callback cannot be null");
353         Objects.requireNonNull(request, "request cannot be null");
354         if (DBG) Log.d(TAG, "switchUser(" + request + ")");
355 
356         checkSupported();
357         request.requestId = getNextRequestId();
358         request.messageType = SwitchUserMessageType.ANDROID_SWITCH;
359         VehiclePropValue propRequest = UserHalHelper.toVehiclePropValue(request);
360 
361         synchronized (mLock) {
362             if (hasPendingRequestLocked(SwitchUserResponse.class, callback)) return;
363             addPendingRequestLocked(request.requestId, SwitchUserResponse.class, callback);
364         }
365 
366         EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_REQ, request.requestId,
367                 request.targetUser.userId, timeoutMs);
368         CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED, request.requestId,
369                 CarStatsLog
370                 .CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED__REQUEST_TYPE__SWITCH_REQUEST_ANDROID,
371                 request.usersInfo.currentUser.userId, request.usersInfo.currentUser.flags,
372                 request.targetUser.userId, request.targetUser.flags, timeoutMs);
373 
374         sendHalRequest(request.requestId, timeoutMs, propRequest, callback);
375     }
376 
377     /**
378      * Calls HAL to remove user.
379      *
380      * @throws IllegalStateException if the HAL does not support user management (callers should
381      * call {@link #isSupported()} first to avoid this exception).
382      */
removeUser(@onNull RemoveUserRequest request)383     public void removeUser(@NonNull RemoveUserRequest request) {
384         Objects.requireNonNull(request, "request cannot be null");
385         if (DBG) Log.d(TAG, "removeUser(" + request + ")");
386 
387         checkSupported();
388         request.requestId = getNextRequestId();
389         VehiclePropValue propRequest = UserHalHelper.toVehiclePropValue(request);
390 
391         EventLog.writeEvent(EventLogTags.CAR_USER_HAL_REMOVE_USER_REQ,
392                 request.removedUserInfo.userId, request.usersInfo.currentUser.userId);
393         CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED, request.requestId,
394                 CarStatsLog
395                 .CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED__REQUEST_TYPE__REMOVE_REQUEST,
396                 request.usersInfo.currentUser.userId, request.usersInfo.currentUser.flags,
397                 request.removedUserInfo.userId, request.removedUserInfo.flags, /* timeout */ -1);
398 
399         try {
400             if (DBG) Log.d(TAG, "Calling hal.set(): " + propRequest);
401             mHal.set(propRequest);
402         } catch (ServiceSpecificException e) {
403             Log.w(TAG, "Failed to set REMOVE USER", e);
404         }
405     }
406 
407     /**
408      * Calls HAL to indicate an Android user was created.
409      *
410      * @param request info about the created user.
411      * @param timeoutMs how long to wait (in ms) for the property change event.
412      * @param callback to handle the response.
413      *
414      * @throws IllegalStateException if the HAL does not support user management (callers should
415      * call {@link #isSupported()} first to avoid this exception).
416      */
createUser(@onNull CreateUserRequest request, int timeoutMs, @NonNull HalCallback<CreateUserResponse> callback)417     public void createUser(@NonNull CreateUserRequest request, int timeoutMs,
418             @NonNull HalCallback<CreateUserResponse> callback) {
419         Objects.requireNonNull(request);
420         Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
421         Objects.requireNonNull(callback);
422         if (DBG) Log.d(TAG, "createUser(): req=" + request + ", timeout=" + timeoutMs);
423 
424         checkSupported();
425         request.requestId = getNextRequestId();
426         VehiclePropValue propRequest = UserHalHelper.toVehiclePropValue(request);
427 
428         synchronized (mLock) {
429             if (hasPendingRequestLocked(CreateUserResponse.class, callback)) return;
430             addPendingRequestLocked(request.requestId, CreateUserResponse.class, callback);
431         }
432 
433         EventLog.writeEvent(EventLogTags.CAR_USER_HAL_CREATE_USER_REQ, request.requestId,
434                 UserHelper.safeName(request.newUserName), request.newUserInfo.flags, timeoutMs);
435         CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED, request.requestId,
436                 CarStatsLog
437                 .CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED__REQUEST_TYPE__CREATE_REQUEST,
438                 request.usersInfo.currentUser.userId, request.usersInfo.currentUser.flags,
439                 request.newUserInfo.userId, request.newUserInfo.flags, timeoutMs);
440 
441         sendHalRequest(request.requestId, timeoutMs, propRequest, callback);
442     }
443 
444     /**
445      * Calls HAL after android user switch.
446      */
postSwitchResponse(@onNull SwitchUserRequest request)447     public void postSwitchResponse(@NonNull SwitchUserRequest request) {
448         Objects.requireNonNull(request, "request cannot be null");
449         if (DBG) Log.d(TAG, "postSwitchResponse(" + request + ")");
450 
451         checkSupported();
452         request.messageType = SwitchUserMessageType.ANDROID_POST_SWITCH;
453         VehiclePropValue propRequest = UserHalHelper.toVehiclePropValue(request);
454 
455         EventLog.writeEvent(EventLogTags.CAR_USER_HAL_POST_SWITCH_USER_REQ, request.requestId,
456                 request.targetUser.userId, request.usersInfo.currentUser.userId);
457         CarStatsLog.write(CarStatsLog.CAR_USER_HAL_POST_SWITCH_RESPONSE_REPORTED, request.requestId,
458                 request.targetUser.userId == request.usersInfo.currentUser.userId
459                 ? CarStatsLog.CAR_USER_HAL_POST_SWITCH_RESPONSE_REPORTED__SWITCH_STATUS__SUCCESS
460                 : CarStatsLog.CAR_USER_HAL_POST_SWITCH_RESPONSE_REPORTED__SWITCH_STATUS__FAILURE);
461 
462         try {
463             if (DBG) Log.d(TAG, "Calling hal.set(): " + propRequest);
464             mHal.set(propRequest);
465         } catch (ServiceSpecificException e) {
466             Log.w(TAG, "Failed to set ANDROID POST SWITCH", e);
467         }
468     }
469 
470     /**
471      * Calls HAL to switch user after legacy Android user switch. Legacy Android user switch means
472      * user switch is not requested by {@link CarUserManager} or OEM, and user switch is directly
473      * requested by {@link ActivityManager}
474      */
legacyUserSwitch(@onNull SwitchUserRequest request)475     public void legacyUserSwitch(@NonNull SwitchUserRequest request) {
476         Objects.requireNonNull(request, "request cannot be null");
477         if (DBG) Log.d(TAG, "userSwitchLegacy(" + request + ")");
478 
479         checkSupported();
480         request.requestId = getNextRequestId();
481         request.messageType = SwitchUserMessageType.LEGACY_ANDROID_SWITCH;
482         VehiclePropValue propRequest = UserHalHelper.toVehiclePropValue(request);
483 
484         EventLog.writeEvent(EventLogTags.CAR_USER_HAL_LEGACY_SWITCH_USER_REQ, request.requestId,
485                 request.targetUser.userId, request.usersInfo.currentUser.userId);
486         //CHECKSTYLE:OFF IndentationCheck
487         CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED,
488                 request.requestId, CarStatsLog
489                 .CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED__REQUEST_TYPE__SWITCH_REQUEST_LEGACY,
490                 request.usersInfo.currentUser.userId, request.usersInfo.currentUser.flags,
491                 request.targetUser.userId, request.targetUser.flags, /* timeout_ms= */ -1);
492         //CHECKSTYLE:ON IndentationCheck
493 
494         try {
495             if (DBG) Log.d(TAG, "Calling hal.set(): " + propRequest);
496             mHal.set(propRequest);
497         } catch (ServiceSpecificException e) {
498             Log.w(TAG, "Failed to set LEGACY ANDROID SWITCH", e);
499         }
500     }
501 
502     /**
503      * Calls HAL to get the value of the user identifications associated with the given user.
504      *
505      * @return HAL response or {@code null} if it was invalid (for example, mismatch on the
506      * requested number of associations).
507      *
508      * @throws IllegalArgumentException if request is invalid (mismatch on number of associations,
509      *   duplicated association, invalid association type values, etc).
510      */
511     @Nullable
getUserAssociation( @onNull UserIdentificationGetRequest request)512     public UserIdentificationResponse getUserAssociation(
513             @NonNull UserIdentificationGetRequest request) {
514         Objects.requireNonNull(request, "request cannot be null");
515         checkUserAssociationSupported();
516 
517         // Check that it doesn't have dupes
518         SparseBooleanArray types = new SparseBooleanArray(request.numberAssociationTypes);
519         for (int i = 0; i < request.numberAssociationTypes; i++) {
520             int type = request.associationTypes.get(i);
521             Preconditions.checkArgument(!types.get(type), "type %s found more than once on %s",
522                     UserIdentificationAssociationType.toString(type), request);
523             types.put(type, true);
524         }
525 
526         request.requestId = getNextRequestId();
527 
528         if (DBG) Log.d(TAG, "getUserAssociation(): req=" + request);
529 
530         VehiclePropValue requestAsPropValue = UserHalHelper.toVehiclePropValue(request);
531 
532         EventLog.writeEvent(EventLogTags.CAR_USER_HAL_GET_USER_AUTH_REQ,
533                 requestAsPropValue.value.int32Values.toArray());
534 
535         VehiclePropValue responseAsPropValue = mHal.get(requestAsPropValue);
536         if (responseAsPropValue == null) {
537             Log.w(TAG, "HAL returned null for request " + requestAsPropValue);
538             return null;
539         }
540 
541         logEventWithErrorMessage(EventLogTags.CAR_USER_HAL_GET_USER_AUTH_RESP, responseAsPropValue);
542         if (DBG) Log.d(TAG, "getUserAssociation(): responseAsPropValue=" + responseAsPropValue);
543 
544         UserIdentificationResponse response;
545         try {
546             response = UserHalHelper.toUserIdentificationResponse(responseAsPropValue);
547         } catch (IllegalArgumentException e) {
548             Log.w(TAG, "invalid response from HAL for " + requestAsPropValue, e);
549             return null;
550         }
551         if (DBG) Log.d(TAG, "getUserAssociation(): response=" + response);
552 
553         // Validate the response according to the request
554         if (response.requestId != request.requestId) {
555             Log.w(TAG, "invalid request id (should be " + request.requestId + ") on HAL response: "
556                     + response);
557             return null;
558         }
559         if (response.numberAssociation != request.numberAssociationTypes) {
560             Log.w(TAG, "Wrong number of association types on HAL response (expected "
561                     + request.numberAssociationTypes + ") for request " + requestAsPropValue
562                     + ": " + response);
563             return null;
564         }
565         for (int i = 0; i < request.numberAssociationTypes; i++) {
566             int expectedType = request.associationTypes.get(i);
567             int actualType = response.associations.get(i).type;
568             if (actualType != expectedType) {
569                 Log.w(TAG, "Wrong type on index " + i + " of HAL response (" + response + ") for "
570                         + "request " + requestAsPropValue + " : expected "
571                         + UserIdentificationAssociationType.toString(expectedType)
572                         + ", got " + UserIdentificationAssociationType.toString(actualType));
573                 return null;
574             }
575         }
576 
577         // TODO(b/153900032): move this logic to a common helper
578         int[] associationTypes = new int[response.numberAssociation];
579         int[] associationValues = new int[response.numberAssociation];
580         for (int i = 0; i < response.numberAssociation; i++) {
581             UserIdentificationAssociation association = response.associations.get(i);
582             associationTypes[i] = association.type;
583             associationValues[i] = association.value;
584         }
585 
586         CarStatsLog.write(CarStatsLog.CAR_USER_HAL_USER_ASSOCIATION_REQUEST_REPORTED,
587                 request.requestId,
588                 CarStatsLog.CAR_USER_HAL_USER_ASSOCIATION_REQUEST_REPORTED__REQUEST_TYPE__GET,
589                 request.userInfo.userId,
590                 request.userInfo.flags,
591                 request.numberAssociationTypes,
592                 Arrays.toString(associationTypes), Arrays.toString(associationValues));
593 
594         return response;
595     }
596 
597     /**
598      * Calls HAL to set the value of the user identifications associated with the given user.
599      *
600      * @throws IllegalArgumentException if request is invalid (mismatch on number of associations,
601      *   duplicated association, invalid association type values, etc).
602      */
setUserAssociation(int timeoutMs, @NonNull UserIdentificationSetRequest request, @NonNull HalCallback<UserIdentificationResponse> callback)603     public void setUserAssociation(int timeoutMs, @NonNull UserIdentificationSetRequest request,
604             @NonNull HalCallback<UserIdentificationResponse> callback) {
605         Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
606         Objects.requireNonNull(request, "request cannot be null");
607         Objects.requireNonNull(callback, "callback cannot be null");
608         if (DBG) Log.d(TAG, "setUserAssociation(" + request + ")");
609 
610         // Check that it doesn't have dupes
611         SparseBooleanArray types = new SparseBooleanArray(request.numberAssociations);
612         for (int i = 0; i < request.numberAssociations; i++) {
613             int type = request.associations.get(i).type;
614             Preconditions.checkArgument(!types.get(type), "type %s found more than once on %s",
615                     UserIdentificationAssociationType.toString(type), request);
616             types.put(type, true);
617         }
618 
619         checkUserAssociationSupported();
620         request.requestId = getNextRequestId();
621         VehiclePropValue propRequest = UserHalHelper.toVehiclePropValue(request);
622 
623         synchronized (mLock) {
624             if (hasPendingRequestLocked(UserIdentificationResponse.class, callback)) return;
625             addPendingRequestLocked(request.requestId, UserIdentificationResponse.class, request,
626                     callback);
627         }
628 
629         EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SET_USER_AUTH_REQ,
630                 propRequest.value.int32Values.toArray());
631         // TODO(b/153900032): move this logic to a common helper
632         int[] associationTypes = new int[request.numberAssociations];
633         int[] associationValues = new int[request.numberAssociations];
634         for (int i = 0; i < request.numberAssociations; i++) {
635             UserIdentificationSetAssociation association = request.associations.get(i);
636             associationTypes[i] = association.type;
637             associationValues[i] = association.value;
638         }
639         CarStatsLog.write(CarStatsLog.CAR_USER_HAL_USER_ASSOCIATION_REQUEST_REPORTED,
640                 request.requestId,
641                 CarStatsLog.CAR_USER_HAL_USER_ASSOCIATION_REQUEST_REPORTED__REQUEST_TYPE__SET,
642                 request.userInfo.userId, request.userInfo.flags, request.numberAssociations,
643                 Arrays.toString(associationTypes), Arrays.toString(associationValues));
644         sendHalRequest(request.requestId, timeoutMs, propRequest, callback);
645     }
646 
handleOnUserIdentificationAssociation(@onNull VehiclePropValue value)647     private void handleOnUserIdentificationAssociation(@NonNull VehiclePropValue value) {
648         logEventWithErrorMessage(EventLogTags.CAR_USER_HAL_SET_USER_AUTH_RESP, value);
649         if (DBG) Log.d(TAG, "handleOnUserIdentificationAssociation(): " + value);
650 
651         int requestId = value.value.int32Values.get(0);
652         HalCallback<UserIdentificationResponse> callback = handleGetPendingCallback(requestId,
653                 UserIdentificationResponse.class);
654         if (callback == null) {
655             Log.w(TAG, "no callback for requestId " + requestId + ": " + value);
656             return;
657         }
658         PendingRequest<?, ?> pendingRequest = handleRemovePendingRequest(requestId);
659         UserIdentificationResponse response;
660         try {
661             response = UserHalHelper.toUserIdentificationResponse(value);
662         } catch (RuntimeException e) {
663             Log.w(TAG, "error parsing UserIdentificationResponse (" + value + ")", e);
664             callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
665             CarStatsLog.write(CarStatsLog.CAR_USER_HAL_SET_USER_ASSOCIATION_RESPONSE_REPORTED,
666                     getHalCallbackStatusForStatsd(HalCallback.STATUS_WRONG_HAL_RESPONSE), requestId,
667                     /* number_associations= */ 0, /* user_identification_association_types= */ "",
668                     /* user_identification_association_values= */ "");
669             return;
670         }
671 
672         // Validate the response according to the request
673         UserIdentificationSetRequest request = PendingRequest.getRequest(pendingRequest,
674                 UserIdentificationSetRequest.class, requestId);
675 
676         if (request == null) {
677             // already logged on PendingRequest.getRequest
678             callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
679             logSetUserAssociationResponse(requestId, response,
680                     HalCallback.STATUS_WRONG_HAL_RESPONSE);
681             return;
682         }
683 
684         if (response.numberAssociation != request.numberAssociations) {
685             Log.w(TAG, "Wrong number of association types on HAL response (expected "
686                     + request.numberAssociations + ") for request " + request
687                     + ": " + response);
688             callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
689             logSetUserAssociationResponse(requestId, response,
690                     HalCallback.STATUS_WRONG_HAL_RESPONSE);
691             return;
692         }
693 
694         for (int i = 0; i < request.numberAssociations; i++) {
695             int expectedType = request.associations.get(i).type;
696             int actualType = response.associations.get(i).type;
697             if (actualType != expectedType) {
698                 Log.w(TAG, "Wrong type on index " + i + " of HAL response (" + response + ") for "
699                         + "request " + value + " : expected "
700                         + UserIdentificationAssociationType.toString(expectedType)
701                         + ", got " + UserIdentificationAssociationType.toString(actualType));
702                 callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
703                 logSetUserAssociationResponse(requestId, response,
704                         HalCallback.STATUS_WRONG_HAL_RESPONSE);
705                 return;
706             }
707         }
708 
709         if (DBG) Log.d(TAG, "replying to request " + requestId + " with " + response);
710         callback.onResponse(HalCallback.STATUS_OK, response);
711         logSetUserAssociationResponse(requestId, response, HalCallback.STATUS_OK);
712     }
713 
logSetUserAssociationResponse(int requestId, UserIdentificationResponse response, int halCallbackStatus)714     private void logSetUserAssociationResponse(int requestId, UserIdentificationResponse response,
715             int halCallbackStatus) {
716         // TODO(b/153900032): move this logic to a common helper
717         int[] associationTypes = new int[response.numberAssociation];
718         int[] associationValues = new int[response.numberAssociation];
719         for (int i = 0; i < response.numberAssociation; i++) {
720             UserIdentificationAssociation association = response.associations.get(i);
721             associationTypes[i] = association.type;
722             associationValues[i] = association.value;
723         }
724         CarStatsLog.write(CarStatsLog.CAR_USER_HAL_SET_USER_ASSOCIATION_RESPONSE_REPORTED,
725                 getHalCallbackStatusForStatsd(halCallbackStatus), requestId,
726                 response.numberAssociation, Arrays.toString(associationTypes),
727                 Arrays.toString(associationValues));
728     }
729 
logEventWithErrorMessage(int eventTag, @NonNull VehiclePropValue value)730     private static void logEventWithErrorMessage(int eventTag, @NonNull VehiclePropValue value) {
731         if (TextUtils.isEmpty(value.value.stringValue)) {
732             EventLog.writeEvent(eventTag, value.value.int32Values.toArray());
733         } else {
734             // Must manually append the error message to the array of values
735             int size = value.value.int32Values.size();
736             Object[] list = new Object[size + 1];
737             value.value.int32Values.toArray(list);
738             list[list.length - 1] = value.value.stringValue;
739             EventLog.writeEvent(eventTag, list);
740         }
741     }
742 
743     @VisibleForTesting
getNextRequestId()744     int getNextRequestId() {
745         synchronized (mLock) {
746             return mNextRequestId++;
747         }
748     }
749 
750     @GuardedBy("mLock")
addPendingRequestLocked(int requestId, @NonNull Class<RESP> responseClass, @NonNull REQ request, @NonNull HalCallback<RESP> callback)751     private <REQ, RESP> void addPendingRequestLocked(int requestId,
752             @NonNull Class<RESP> responseClass, @NonNull REQ request,
753             @NonNull HalCallback<RESP> callback) {
754         PendingRequest<?, RESP> pendingRequest = new PendingRequest<>(responseClass, request,
755                 callback);
756         if (DBG) {
757             Log.d(TAG, "adding pending request (" + pendingRequest + ") for requestId "
758                     + requestId);
759         }
760         mPendingRequests.put(requestId, pendingRequest);
761     }
762 
763     @GuardedBy("mLock")
addPendingRequestLocked(int requestId, @NonNull Class<RESP> responseClass, @NonNull HalCallback<RESP> callback)764     private <RESP> void addPendingRequestLocked(int requestId, @NonNull Class<RESP> responseClass,
765             @NonNull HalCallback<RESP> callback) {
766         addPendingRequestLocked(requestId, responseClass, /* request= */ null,
767                 callback);
768     }
769 
770     /**
771      * Checks if there is a pending request of type {@code requestClass}, calling {@code callback}
772      * with {@link HalCallback#STATUS_CONCURRENT_OPERATION} when there is.
773      */
774     @GuardedBy("mLock")
hasPendingRequestLocked(@onNull Class<?> responseClass, @NonNull HalCallback<?> callback)775     private boolean hasPendingRequestLocked(@NonNull Class<?> responseClass,
776             @NonNull HalCallback<?> callback) {
777         for (int i = 0; i < mPendingRequests.size(); i++) {
778             PendingRequest<?, ?> pendingRequest = mPendingRequests.valueAt(i);
779             if (pendingRequest.responseClass == responseClass) {
780                 Log.w(TAG, "Already have pending request of type " + responseClass);
781                 callback.onResponse(HalCallback.STATUS_CONCURRENT_OPERATION, null);
782                 return true;
783             }
784         }
785         return false;
786     }
787 
788     /**
789      * Removes the pending request and its timeout callback.
790      */
791     @Nullable
handleRemovePendingRequest(int requestId)792     private PendingRequest<?, ?> handleRemovePendingRequest(int requestId) {
793         if (DBG) Log.d(TAG, "Removing pending request #" + requestId);
794         mHandler.removeMessages(requestId);
795         PendingRequest<?, ?> pendingRequest;
796         synchronized (mLock) {
797             pendingRequest = mPendingRequests.get(requestId);
798             mPendingRequests.remove(requestId);
799         }
800         return pendingRequest;
801     }
802 
handleCheckIfRequestTimedOut(int requestId)803     private void handleCheckIfRequestTimedOut(int requestId) {
804         PendingRequest<?, ?> pendingRequest = getPendingRequest(requestId);
805         if (pendingRequest == null) return;
806 
807         Log.w(TAG, "Request #" + requestId + " timed out");
808         handleRemovePendingRequest(requestId);
809         pendingRequest.callback.onResponse(HalCallback.STATUS_HAL_RESPONSE_TIMEOUT, null);
810     }
811 
812     @Nullable
getPendingRequest(int requestId)813     private PendingRequest<?, ?> getPendingRequest(int requestId) {
814         synchronized (mLock) {
815             return mPendingRequests.get(requestId);
816         }
817     }
818 
handleOnInitialUserInfoResponse(VehiclePropValue value)819     private void handleOnInitialUserInfoResponse(VehiclePropValue value) {
820         int requestId = value.value.int32Values.get(0);
821         HalCallback<InitialUserInfoResponse> callback = handleGetPendingCallback(requestId,
822                 InitialUserInfoResponse.class);
823         if (callback == null) {
824             EventLog.writeEvent(EventLogTags.CAR_USER_HAL_INITIAL_USER_INFO_RESP, requestId,
825                     HalCallback.STATUS_INVALID);
826             CarStatsLog.write(CarStatsLog.CAR_USER_HAL_INITIAL_USER_INFO_RESPONSE_REPORTED,
827                     requestId, getHalCallbackStatusForStatsd(HalCallback.STATUS_INVALID),
828                     getInitialUserInfoResponseActionForStatsd(
829                             InitialUserInfoResponseAction.DEFAULT), /* user id= */ -1,
830                     /* flag= */ -1, /* user locales= */ "");
831 
832             Log.w(TAG, "no callback for requestId " + requestId + ": " + value);
833             return;
834         }
835         handleRemovePendingRequest(requestId);
836 
837         InitialUserInfoResponse response;
838         try {
839             response = UserHalHelper.toInitialUserInfoResponse(value);
840         } catch (RuntimeException e) {
841             Log.e(TAG, "invalid response (" + value + ") from HAL", e);
842             EventLog.writeEvent(EventLogTags.CAR_USER_HAL_INITIAL_USER_INFO_RESP, requestId,
843                     HalCallback.STATUS_WRONG_HAL_RESPONSE);
844             CarStatsLog.write(CarStatsLog.CAR_USER_HAL_INITIAL_USER_INFO_RESPONSE_REPORTED,
845                     requestId, getHalCallbackStatusForStatsd(HalCallback.STATUS_WRONG_HAL_RESPONSE),
846                     getInitialUserInfoResponseActionForStatsd(
847                             InitialUserInfoResponseAction.DEFAULT), /* user id= */ -1,
848                     /* flag= */ -1, /* user locales= */ "");
849 
850             callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
851             return;
852         }
853 
854         EventLog.writeEvent(EventLogTags.CAR_USER_HAL_INITIAL_USER_INFO_RESP, requestId,
855                 HalCallback.STATUS_OK, response.action,
856                 response.userToSwitchOrCreate.userId, response.userToSwitchOrCreate.flags,
857                 response.userNameToCreate, response.userLocales);
858         CarStatsLog.write(CarStatsLog.CAR_USER_HAL_INITIAL_USER_INFO_RESPONSE_REPORTED,
859                 requestId, getHalCallbackStatusForStatsd(HalCallback.STATUS_OK),
860                 getInitialUserInfoResponseActionForStatsd(response.action),
861                 response.userToSwitchOrCreate.userId, response.userToSwitchOrCreate.flags,
862                 response.userLocales);
863 
864         if (DBG) Log.d(TAG, "replying to request " + requestId + " with " + response);
865         callback.onResponse(HalCallback.STATUS_OK, response);
866     }
867 
getInitialUserInfoResponseActionForStatsd(int action)868     private static int getInitialUserInfoResponseActionForStatsd(int action) {
869         switch (action) {
870             case InitialUserInfoResponseAction.CREATE:
871                 return CarStatsLog
872                         .CAR_USER_HAL_INITIAL_USER_INFO_RESPONSE_REPORTED__RESPONSE_ACTION__CREATE;
873             case InitialUserInfoResponseAction.SWITCH:
874                 return CarStatsLog
875                         .CAR_USER_HAL_INITIAL_USER_INFO_RESPONSE_REPORTED__RESPONSE_ACTION__SWITCH;
876             default:
877                 return CarStatsLog
878                         .CAR_USER_HAL_INITIAL_USER_INFO_RESPONSE_REPORTED__RESPONSE_ACTION__DEFAULT;
879         }
880     }
881 
handleOnSwitchUserResponse(VehiclePropValue value)882     private void handleOnSwitchUserResponse(VehiclePropValue value) {
883         int requestId = value.value.int32Values.get(0);
884         int messageType = value.value.int32Values.get(1);
885 
886         if (messageType == SwitchUserMessageType.VEHICLE_RESPONSE) {
887             handleOnSwitchUserVehicleResponse(value);
888             return;
889         }
890 
891         if (messageType == SwitchUserMessageType.VEHICLE_REQUEST) {
892             handleOnSwitchUserVehicleRequest(value);
893             return;
894         }
895 
896         Log.e(TAG, "handleOnSwitchUserResponse invalid message type (" + messageType
897                 + ") from HAL: " + value);
898 
899         // check if a callback exists for the request ID
900         HalCallback<SwitchUserResponse> callback =
901                 handleGetPendingCallback(requestId, SwitchUserResponse.class);
902         if (callback != null) {
903             handleRemovePendingRequest(requestId);
904             EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_RESP, requestId,
905                     HalCallback.STATUS_WRONG_HAL_RESPONSE);
906             callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
907             return;
908         }
909     }
910 
handleOnSwitchUserVehicleRequest(VehiclePropValue value)911     private void handleOnSwitchUserVehicleRequest(VehiclePropValue value) {
912         int requestId = value.value.int32Values.get(0);
913         // Index 1 is message type, which is not required in this call.
914         int targetUserId = value.value.int32Values.get(2);
915         EventLog.writeEvent(EventLogTags.CAR_USER_HAL_OEM_SWITCH_USER_REQ, requestId, targetUserId);
916         CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED, requestId,
917                 CarStatsLog
918                     .CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED__REQUEST_TYPE__SWITCH_REQUEST_OEM,
919                     /* current user id= */ -1, /* current user flag= */ -1, targetUserId,
920                     /* target user flag= */ -1, /* timeout_ms= */ -1);
921 
922         // HAL vehicle request should have negative request ID
923         if (requestId >= 0) {
924             Log.e(TAG, "handleVehicleRequest invalid requestId (" + requestId + ") from HAL: "
925                     + value);
926             return;
927         }
928 
929         CarUserService userService = CarLocalServices.getService(CarUserService.class);
930         userService.switchAndroidUserFromHal(requestId, targetUserId);
931     }
932 
handleOnSwitchUserVehicleResponse(VehiclePropValue value)933     private void handleOnSwitchUserVehicleResponse(VehiclePropValue value) {
934         int requestId = value.value.int32Values.get(0);
935         HalCallback<SwitchUserResponse> callback =
936                 handleGetPendingCallback(requestId, SwitchUserResponse.class);
937         if (callback == null) {
938             EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_RESP, requestId,
939                     HalCallback.STATUS_INVALID);
940             Log.w(TAG, "no callback for requestId " + requestId + ": " + value);
941             logHalSwitchUserResponse(requestId, HalCallback.STATUS_WRONG_HAL_RESPONSE);
942             return;
943         }
944         handleRemovePendingRequest(requestId);
945         SwitchUserResponse response = new SwitchUserResponse();
946         response.requestId = requestId;
947         response.messageType = value.value.int32Values.get(1);
948         response.status = value.value.int32Values.get(2);
949         response.errorMessage = value.value.stringValue;
950         if (response.status == SwitchUserStatus.SUCCESS
951                 || response.status == SwitchUserStatus.FAILURE) {
952             if (DBG) {
953                 Log.d(TAG, "replying to request " + requestId + " with " + response);
954             }
955             EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_RESP, requestId,
956                     HalCallback.STATUS_OK, response.status, response.errorMessage);
957             callback.onResponse(HalCallback.STATUS_OK, response);
958             logHalSwitchUserResponse(requestId, HalCallback.STATUS_OK, response.status);
959         } else {
960             EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_RESP, requestId,
961                     HalCallback.STATUS_WRONG_HAL_RESPONSE, response.status, response.errorMessage);
962             Log.e(TAG, "invalid status (" + response.status + ") from HAL: " + value);
963             callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
964             logHalSwitchUserResponse(requestId, HalCallback.STATUS_WRONG_HAL_RESPONSE,
965                     response.status);
966         }
967     }
968 
handleOnCreateUserResponse(VehiclePropValue value)969     private void handleOnCreateUserResponse(VehiclePropValue value) {
970         int requestId = value.value.int32Values.get(0);
971         HalCallback<CreateUserResponse> callback =
972                 handleGetPendingCallback(requestId, CreateUserResponse.class);
973         if (callback == null) {
974             EventLog.writeEvent(EventLogTags.CAR_USER_HAL_CREATE_USER_RESP, requestId,
975                     HalCallback.STATUS_INVALID);
976             Log.w(TAG, "no callback for requestId " + requestId + ": " + value);
977             return;
978         }
979         handleRemovePendingRequest(requestId);
980         CreateUserResponse response = new CreateUserResponse();
981         response.requestId = requestId;
982         response.status = value.value.int32Values.get(1);
983         response.errorMessage = value.value.stringValue;
984         if (response.status == CreateUserStatus.SUCCESS
985                 || response.status == CreateUserStatus.FAILURE) {
986             if (DBG) {
987                 Log.d(TAG, "replying to request " + requestId + " with " + response);
988             }
989             EventLog.writeEvent(EventLogTags.CAR_USER_HAL_CREATE_USER_RESP, requestId,
990                     HalCallback.STATUS_OK, response.status, response.errorMessage);
991             callback.onResponse(HalCallback.STATUS_OK, response);
992             logHalCreateUserResponse(requestId, HalCallback.STATUS_OK, response.status);
993         } else {
994             EventLog.writeEvent(EventLogTags.CAR_USER_HAL_CREATE_USER_RESP, requestId,
995                     HalCallback.STATUS_WRONG_HAL_RESPONSE, response.status, response.errorMessage);
996             Log.e(TAG, "invalid status (" + response.status + ") from HAL: " + value);
997             callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
998             logHalCreateUserResponse(requestId, HalCallback.STATUS_WRONG_HAL_RESPONSE);
999         }
1000     }
1001 
logHalSwitchUserResponse(int requestId, int halCallbackStatus)1002     private void logHalSwitchUserResponse(int requestId, int halCallbackStatus) {
1003         //CHECKSTYLE:OFF IndentationCheck
1004         CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED, requestId,
1005                 getHalCallbackStatusForStatsd(halCallbackStatus),
1006                CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__REQUEST_STATUS__UNSPECIFIED);
1007         //CHECKSTYLE:ON IndentationCheck
1008     }
1009 
logHalSwitchUserResponse(int requestId, int halCallbackStatus, int userSwitchstatus)1010     private void logHalSwitchUserResponse(int requestId, int halCallbackStatus,
1011             int userSwitchstatus) {
1012         int userSwitchstatusForStatsd = userSwitchstatus == SwitchUserStatus.SUCCESS
1013                 ? CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__REQUEST_STATUS__SUCCESS
1014                 : CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__REQUEST_STATUS__FAILURE;
1015         CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED, requestId,
1016                 getHalCallbackStatusForStatsd(halCallbackStatus), userSwitchstatusForStatsd);
1017     }
1018 
logHalCreateUserResponse(int requestId, int halCallbackStatus)1019     private void logHalCreateUserResponse(int requestId, int halCallbackStatus) {
1020         //CHECKSTYLE:OFF IndentationCheck
1021         CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED, requestId,
1022                 getHalCallbackStatusForStatsd(halCallbackStatus),
1023                CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__REQUEST_STATUS__UNSPECIFIED);
1024         //CHECKSTYLE:ON IndentationCheck
1025     }
1026 
logHalCreateUserResponse(int requestId, int halCallbackStatus, int userCreatestatus)1027     private void logHalCreateUserResponse(int requestId, int halCallbackStatus,
1028             int userCreatestatus) {
1029         int userCreatestatusForStatsd = userCreatestatus == CreateUserStatus.SUCCESS
1030                 ? CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__REQUEST_STATUS__SUCCESS
1031                 : CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__REQUEST_STATUS__FAILURE;
1032         CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED, requestId,
1033                 getHalCallbackStatusForStatsd(halCallbackStatus), userCreatestatusForStatsd);
1034     }
1035 
getHalCallbackStatusForStatsd(int halCallbackStatus)1036     private int getHalCallbackStatusForStatsd(int halCallbackStatus) {
1037         // CHECKSTYLE:OFF IndentationCheck
1038         switch (halCallbackStatus) {
1039             case HalCallback.STATUS_OK:
1040                 return CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__CALLBACK_STATUS__OK;
1041             case HalCallback.STATUS_HAL_SET_TIMEOUT:
1042                 return CarStatsLog
1043                       .CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__CALLBACK_STATUS__HAL_SET_TIMEOUT;
1044             case HalCallback.STATUS_HAL_RESPONSE_TIMEOUT:
1045                 return CarStatsLog
1046                  .CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__CALLBACK_STATUS__HAL_RESPONSE_TIMEOUT;
1047             case HalCallback.STATUS_WRONG_HAL_RESPONSE:
1048                 return CarStatsLog
1049                    .CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__CALLBACK_STATUS__WRONG_HAL_RESPONSE;
1050             case HalCallback.STATUS_CONCURRENT_OPERATION:
1051                 return CarStatsLog
1052                  .CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__CALLBACK_STATUS__CONCURRENT_OPERATION;
1053             default:
1054                 return CarStatsLog
1055                         .CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__CALLBACK_STATUS__INVALID;
1056         }
1057         // CHECKSTYLE:ON IndentationCheck
1058     }
1059 
handleGetPendingCallback(int requestId, Class<T> clazz)1060     private <T> HalCallback<T> handleGetPendingCallback(int requestId, Class<T> clazz) {
1061         PendingRequest<?, ?> pendingRequest = getPendingRequest(requestId);
1062         if (pendingRequest == null) return null;
1063 
1064         if (pendingRequest.responseClass != clazz) {
1065             Log.e(TAG, "Invalid callback class for request " + requestId + ": expected" + clazz
1066                     + ", but got is " + pendingRequest.responseClass);
1067             // TODO(b/150413515): add unit test for this scenario once it supports other properties
1068             return null;
1069         }
1070         @SuppressWarnings("unchecked")
1071         HalCallback<T> callback = (HalCallback<T>) pendingRequest.callback;
1072         return callback;
1073     }
1074 
1075     @Override
dump(PrintWriter writer)1076     public void dump(PrintWriter writer) {
1077         String indent = "  ";
1078         writer.printf("*User HAL*\n");
1079 
1080         writer.printf("Relevant CarProperties\n");
1081         dumpSystemProperty(writer, indent, "user_hal_enabled", CarProperties.user_hal_enabled());
1082         dumpSystemProperty(writer, indent, "user_hal_timeout", CarProperties.user_hal_timeout());
1083 
1084         synchronized (mLock) {
1085             if (!isSupported()) {
1086                 writer.println(UNSUPPORTED_MSG);
1087                 return;
1088             }
1089             int numberProperties = mProperties.size();
1090             writer.printf("%d supported properties\n", numberProperties);
1091             for (int i = 0; i < numberProperties; i++) {
1092                 writer.printf("%s%s\n", indent, mProperties.valueAt(i));
1093             }
1094             writer.printf("next request id: %d\n", mNextRequestId);
1095 
1096             int numberPendingCallbacks = mPendingRequests.size();
1097             if (numberPendingCallbacks == 0) {
1098                 writer.println("no pending callbacks");
1099             } else {
1100                 writer.printf("%d pending callbacks: %s\n", numberPendingCallbacks);
1101                 for (int i = 0; i < numberPendingCallbacks; i++) {
1102                     writer.print(indent);
1103                     mPendingRequests.valueAt(i).dump(writer);
1104                     writer.println();
1105                 }
1106             }
1107         }
1108     }
1109 
dumpSystemProperty(@onNull PrintWriter writer, @NonNull String indent, @NonNull String name, Optional<?> prop)1110     private static void dumpSystemProperty(@NonNull PrintWriter writer, @NonNull String indent,
1111             @NonNull String name, Optional<?> prop) {
1112         String value = prop.isPresent() ? prop.get().toString() : "<NOT SET>";
1113         writer.printf("%s%s=%s\n", indent, name, value);
1114     }
1115 
1116     private static final class PendingRequest<REQ, RESP> {
1117         @NonNull
1118         public final Class<RESP> responseClass;
1119 
1120         @Nullable
1121         public final REQ request;
1122 
1123         @NonNull
1124         public final HalCallback<RESP> callback;
1125 
PendingRequest(@onNull Class<RESP> responseClass, @Nullable REQ request, @NonNull HalCallback<RESP> callback)1126         PendingRequest(@NonNull Class<RESP> responseClass, @Nullable REQ request,
1127                 @NonNull HalCallback<RESP> callback) {
1128             this.responseClass = responseClass;
1129             this.request = request;
1130             this.callback = callback;
1131         }
1132 
1133         /**
1134          * Gets the safely cast request for a given pending request.
1135          */
1136         @Nullable
getRequest(@ullable PendingRequest<?, ?> pendingRequest, @NonNull Class<T> clazz, int requestId)1137         private static <T> T getRequest(@Nullable PendingRequest<?, ?> pendingRequest,
1138                 @NonNull Class<T> clazz, int requestId) {
1139             if (pendingRequest == null) {
1140                 Log.e(TAG, "No pending request for id " + requestId);
1141                 return null;
1142 
1143             }
1144             Object request = pendingRequest.request;
1145             if (!clazz.isInstance(request)) {
1146                 Log.e(TAG, "Wrong pending request for id " + requestId + ": " + pendingRequest);
1147                 return null;
1148             }
1149             return clazz.cast(request);
1150         }
1151 
dump(@onNull PrintWriter pw)1152         public void dump(@NonNull PrintWriter pw) {
1153             pw.printf("Class: %s Callback: %s", responseClass.getSimpleName(),
1154                     FunctionalUtils.getLambdaName(callback));
1155             if (request != null) {
1156                 pw.printf(" Request: %s", request);
1157             }
1158         }
1159 
1160         @Override
toString()1161         public String toString() {
1162             StringWriter sw = new StringWriter();
1163             PrintWriter pw = new PrintWriter(sw);
1164             pw.print("[PendingRequest: ");
1165             dump(pw);
1166             pw.print("]");
1167             pw.flush();
1168             return sw.toString();
1169         }
1170     }
1171 }
1172