blob: 645f56343cfe0325d29c2ba4ba560505a7b93942 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.hal;
import static android.car.VehiclePropertyIds.CREATE_USER;
import static android.car.VehiclePropertyIds.INITIAL_USER_INFO;
import static android.car.VehiclePropertyIds.REMOVE_USER;
import static android.car.VehiclePropertyIds.SWITCH_USER;
import static android.car.VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.car.hardware.property.CarPropertyManager;
import android.car.user.CarUserManager;
import android.car.userlib.HalCallback;
import android.car.userlib.UserHalHelper;
import android.car.userlib.UserHelper;
import android.hardware.automotive.vehicle.V2_0.CreateUserRequest;
import android.hardware.automotive.vehicle.V2_0.CreateUserResponse;
import android.hardware.automotive.vehicle.V2_0.CreateUserStatus;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
import android.hardware.automotive.vehicle.V2_0.RemoveUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserMessageType;
import android.hardware.automotive.vehicle.V2_0.SwitchUserRequest;
import android.hardware.automotive.vehicle.V2_0.SwitchUserResponse;
import android.hardware.automotive.vehicle.V2_0.SwitchUserStatus;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationAssociationType;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationGetRequest;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationResponse;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetRequest;
import android.hardware.automotive.vehicle.V2_0.UserInfo;
import android.hardware.automotive.vehicle.V2_0.UsersInfo;
import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
import android.os.Handler;
import android.os.Looper;
import android.os.ServiceSpecificException;
import android.sysprop.CarProperties;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.car.CarLocalServices;
import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.car.EventLogTags;
import com.android.internal.util.FunctionalUtils;
import com.android.internal.util.Preconditions;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* Service used to integrate the OEM's custom user management with Android's.
*/
public final class UserHalService extends HalServiceBase {
private static final String TAG = UserHalService.class.getSimpleName();
private static final String UNSUPPORTED_MSG = "Vehicle HAL does not support user management";
private static final int[] SUPPORTED_PROPERTIES = new int[]{
INITIAL_USER_INFO,
SWITCH_USER,
CREATE_USER,
USER_IDENTIFICATION_ASSOCIATION
};
private static final boolean DBG = false;
private final Object mLock = new Object();
private final VehicleHal mHal;
@GuardedBy("mLock")
@Nullable
private SparseArray<VehiclePropConfig> mProperties;
// This handler handles 2 types of messages:
// - "Anonymous" messages (what=0) containing runnables.
// - "Identifiable" messages used to check for timeouts (whose 'what' is the request id).
private final Handler mHandler;
/**
* Value used on the next request.
*/
@GuardedBy("mLock")
private int mNextRequestId = 1;
/**
* Map of callbacks by request id.
*/
@GuardedBy("mLock")
private final SparseArray<PendingRequest<?, ?>> mPendingRequests = new SparseArray<>();
public UserHalService(VehicleHal hal) {
this(hal, new Handler(Looper.getMainLooper()));
}
@VisibleForTesting
UserHalService(VehicleHal hal, Handler handler) {
mHal = hal;
mHandler = handler;
}
@Override
public void init() {
if (DBG) Log.d(TAG, "init()");
if (mProperties == null) {
return;
}
int size = mProperties.size();
for (int i = 0; i < size; i++) {
VehiclePropConfig config = mProperties.valueAt(i);
if (VehicleHal.isPropertySubscribable(config)) {
if (DBG) Log.d(TAG, "subscribing to property " + config.prop);
mHal.subscribeProperty(this, config.prop);
}
}
}
@Override
public void release() {
if (DBG) Log.d(TAG, "release()");
}
@Override
public void onHalEvents(List<VehiclePropValue> values) {
if (DBG) Log.d(TAG, "handleHalEvents(): " + values);
for (int i = 0; i < values.size(); i++) {
VehiclePropValue value = values.get(i);
switch (value.prop) {
case INITIAL_USER_INFO:
mHandler.sendMessage(obtainMessage(
UserHalService::handleOnInitialUserInfoResponse, this, value));
break;
case SWITCH_USER:
mHandler.sendMessage(obtainMessage(
UserHalService::handleOnSwitchUserResponse, this, value));
break;
case CREATE_USER:
mHandler.sendMessage(obtainMessage(
UserHalService::handleOnCreateUserResponse, this, value));
break;
case REMOVE_USER:
Log.w(TAG, "Received REMOVE_USER HAL event: " + value);
break;
case USER_IDENTIFICATION_ASSOCIATION:
mHandler.sendMessage(obtainMessage(
UserHalService::handleOnUserIdentificationAssociation, this, value));
break;
default:
Log.w(TAG, "received unsupported event from HAL: " + value);
}
}
}
@Override
public void onPropertySetError(int property, int area,
@CarPropertyManager.CarSetPropertyErrorCode int errorCode) {
if (DBG) Log.d(TAG, "handlePropertySetError(" + property + "/" + area + ")");
}
@Override
public int[] getAllSupportedProperties() {
return SUPPORTED_PROPERTIES;
}
@Override
public void takeProperties(Collection<VehiclePropConfig> properties) {
if (properties.isEmpty()) {
Log.w(TAG, UNSUPPORTED_MSG);
return;
}
SparseArray<VehiclePropConfig> supportedProperties = new SparseArray<>(5);
for (VehiclePropConfig config : properties) {
supportedProperties.put(config.prop, config);
}
synchronized (mLock) {
mProperties = supportedProperties;
}
}
/**
* Checks if the Vehicle HAL supports user management.
*/
public boolean isSupported() {
synchronized (mLock) {
return mProperties != null;
}
}
@GuardedBy("mLock")
private void checkSupportedLocked() {
Preconditions.checkState(isSupported(), UNSUPPORTED_MSG);
}
/**
* Calls HAL to asynchronously get info about the initial user.
*
* @param requestType type of request (as defined by
* {@link android.hardware.automotive.vehicle.V2_0.InitialUserInfoRequestType}).
* @param timeoutMs how long to wait (in ms) for the property change event.
* @param usersInfo current state of Android users.
* @param callback to handle the response.
*
* @throws IllegalStateException if the HAL does not support user management (callers should
* call {@link #isSupported()} first to avoid this exception).
*/
public void getInitialUserInfo(int requestType, int timeoutMs, @NonNull UsersInfo usersInfo,
@NonNull HalCallback<InitialUserInfoResponse> callback) {
if (DBG) Log.d(TAG, "getInitialInfo(" + requestType + ")");
Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
Objects.requireNonNull(usersInfo);
UserHalHelper.checkValid(usersInfo);
Objects.requireNonNull(callback);
VehiclePropValue propRequest;
int requestId;
synchronized (mLock) {
checkSupportedLocked();
if (hasPendingRequestLocked(InitialUserInfoResponse.class, callback)) return;
requestId = getNextRequestId();
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_INITIAL_USER_INFO_REQ, requestId,
requestType, timeoutMs);
propRequest = UserHalHelper.createPropRequest(INITIAL_USER_INFO, requestId,
requestType);
UserHalHelper.addUsersInfo(propRequest, usersInfo);
addPendingRequestLocked(requestId, InitialUserInfoResponse.class, callback);
}
sendHalRequest(requestId, timeoutMs, propRequest, callback);
}
private void sendHalRequest(int requestId, int timeoutMs, @NonNull VehiclePropValue request,
@NonNull HalCallback<?> callback) {
mHandler.sendMessageDelayed(obtainMessage(
UserHalService::handleCheckIfRequestTimedOut, this, requestId).setWhat(requestId),
timeoutMs);
try {
if (DBG) Log.d(TAG, "Calling hal.set(): " + request);
mHal.set(request);
} catch (ServiceSpecificException e) {
handleRemovePendingRequest(requestId);
Log.w(TAG, "Failed to set " + request, e);
callback.onResponse(HalCallback.STATUS_HAL_SET_TIMEOUT, null);
}
}
/**
* Calls HAL to asynchronously switch user.
*
* @param request metadata
* @param timeoutMs how long to wait (in ms) for the property change event.
* @param callback to handle the response.
*
* @throws IllegalStateException if the HAL does not support user management (callers should
* call {@link #isSupported()} first to avoid this exception).
*/
public void switchUser(@NonNull SwitchUserRequest request, int timeoutMs,
@NonNull HalCallback<SwitchUserResponse> callback) {
Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
Objects.requireNonNull(callback, "callback cannot be null");
if (DBG) Log.d(TAG, "switchUser(" + request + ")");
VehiclePropValue propRequest;
int requestId;
synchronized (mLock) {
checkSupportedLocked();
if (hasPendingRequestLocked(SwitchUserResponse.class, callback)) return;
requestId = getNextRequestId();
request.requestId = requestId;
request.messageType = SwitchUserMessageType.ANDROID_SWITCH;
propRequest = UserHalHelper.toVehiclePropValue(request);
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_REQ, requestId,
request.targetUser.userId, timeoutMs);
addPendingRequestLocked(requestId, SwitchUserResponse.class, callback);
}
sendHalRequest(requestId, timeoutMs, propRequest, callback);
}
/**
* Calls HAL to remove user.
*
* @throws IllegalStateException if the HAL does not support user management (callers should
* call {@link #isSupported()} first to avoid this exception).
*/
public void removeUser(@NonNull RemoveUserRequest request) {
Objects.requireNonNull(request, "request cannot be null");
if (DBG) Log.d(TAG, "removeUser(" + request.removedUserInfo.userId + ")");
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_REMOVE_USER_REQ,
request.removedUserInfo.userId, request.usersInfo.currentUser.userId);
VehiclePropValue propRequest;
synchronized (mLock) {
checkSupportedLocked();
request.requestId = getNextRequestId();
propRequest = UserHalHelper.toVehiclePropValue(request);
}
try {
if (DBG) Log.d(TAG, "Calling hal.set(): " + propRequest);
mHal.set(propRequest);
} catch (ServiceSpecificException e) {
Log.w(TAG, "Failed to set REMOVE USER", e);
}
}
/**
* Calls HAL to indicate an Android user was created.
*
* @param request info agout the created user.
* @param timeoutMs how long to wait (in ms) for the property change event.
* @param callback to handle the response.
*
* @throws IllegalStateException if the HAL does not support user management (callers should
* call {@link #isSupported()} first to avoid this exception).
*/
public void createUser(@NonNull CreateUserRequest request, int timeoutMs,
@NonNull HalCallback<CreateUserResponse> callback) {
if (DBG) Log.d(TAG, "createUser(): req=" + request + ", timeout=" + timeoutMs);
Objects.requireNonNull(request);
Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
Objects.requireNonNull(callback);
VehiclePropValue propRequest;
synchronized (mLock) {
checkSupportedLocked();
if (hasPendingRequestLocked(CreateUserResponse.class, callback)) return;
request.requestId = getNextRequestId();
propRequest = UserHalHelper.toVehiclePropValue(request);
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_CREATE_USER_REQ, request.requestId,
UserHelper.safeName(request.newUserName), request.newUserInfo.flags, timeoutMs);
addPendingRequestLocked(request.requestId, CreateUserResponse.class, callback);
}
sendHalRequest(request.requestId, timeoutMs, propRequest, callback);
}
/**
* Calls HAL after android user switch.
*/
public void postSwitchResponse(@NonNull SwitchUserRequest request) {
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_POST_SWITCH_USER_REQ, request.requestId,
request.targetUser.userId, request.usersInfo.currentUser.userId);
if (DBG) Log.d(TAG, "postSwitchResponse(" + request.targetUser.userId + ")");
VehiclePropValue propRequest;
synchronized (mLock) {
checkSupportedLocked();
request.messageType = SwitchUserMessageType.ANDROID_POST_SWITCH;
propRequest = UserHalHelper.toVehiclePropValue(request);
}
try {
if (DBG) Log.d(TAG, "Calling hal.set(): " + propRequest);
mHal.set(propRequest);
} catch (ServiceSpecificException e) {
Log.w(TAG, "Failed to set ANDROID POST SWITCH", e);
}
}
/**
* Calls HAL to switch user after legacy Android user switch. Legacy Android user switch means
* user switch is not requested by {@link CarUserManager} or OEM, and user switch is directly
* requested by {@link ActivityManager}
*/
public void legacyUserSwitch(@NonNull SwitchUserRequest request) {
if (DBG) Log.d(TAG, "userSwitchLegacy(" + request + ")");
VehiclePropValue propRequest;
synchronized (mLock) {
checkSupportedLocked();
int requestId = getNextRequestId();
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_LEGACY_SWITCH_USER_REQ, requestId,
request.targetUser.userId, request.usersInfo.currentUser.userId);
request.messageType = SwitchUserMessageType.LEGACY_ANDROID_SWITCH;
propRequest = UserHalHelper.toVehiclePropValue(request);
}
try {
if (DBG) Log.d(TAG, "Calling hal.set(): " + propRequest);
mHal.set(propRequest);
} catch (ServiceSpecificException e) {
Log.w(TAG, "Failed to set LEGACY ANDROID SWITCH", e);
}
}
// TODO(b/157699720): move to UserHalHelper
private static VehiclePropValue getPropRequestForSwitchUserLocked(int requestId,
int requestType, @NonNull UserInfo targetInfo, @NonNull UsersInfo usersInfo) {
VehiclePropValue propRequest =
UserHalHelper.createPropRequest(SWITCH_USER, requestId, requestType);
UserHalHelper.addUserInfo(propRequest, targetInfo);
UserHalHelper.addUsersInfo(propRequest, usersInfo);
return propRequest;
}
/**
* Calls HAL to get the value of the user identifications associated with the given user.
*
* @return HAL response or {@code null} if it was invalid (for example, mismatch on the
* requested number of associations).
*
* @throws IllegalArgumentException if request is invalid (mismatch on number of associations,
* duplicated association, invalid association type values, etc).
*/
@Nullable
public UserIdentificationResponse getUserAssociation(
@NonNull UserIdentificationGetRequest request) {
Objects.requireNonNull(request, "request cannot be null");
// Check that it doesn't have dupes
SparseBooleanArray types = new SparseBooleanArray(request.numberAssociationTypes);
for (int i = 0; i < request.numberAssociationTypes; i++) {
int type = request.associationTypes.get(i);
Preconditions.checkArgument(!types.get(type), "type %s found more than once on %s",
UserIdentificationAssociationType.toString(type), request);
types.put(type, true);
}
request.requestId = getNextRequestId();
if (DBG) Log.d(TAG, "getUserAssociation(): req=" + request);
VehiclePropValue requestAsPropValue = UserHalHelper.toVehiclePropValue(request);
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_GET_USER_AUTH_REQ,
requestAsPropValue.value.int32Values.toArray());
VehiclePropValue responseAsPropValue = mHal.get(requestAsPropValue);
if (responseAsPropValue == null) {
Log.w(TAG, "HAL returned null for request " + requestAsPropValue);
return null;
}
logEventWithErrorMessage(EventLogTags.CAR_USER_HAL_GET_USER_AUTH_RESP, responseAsPropValue);
if (DBG) Log.d(TAG, "getUserAssociation(): responseAsPropValue=" + responseAsPropValue);
UserIdentificationResponse response;
try {
response = UserHalHelper.toUserIdentificationResponse(responseAsPropValue);
} catch (IllegalArgumentException e) {
Log.w(TAG, "invalid response from HAL for " + requestAsPropValue, e);
return null;
}
if (DBG) Log.d(TAG, "getUserAssociation(): response=" + response);
// Validate the response according to the request
if (response.requestId != request.requestId) {
Log.w(TAG, "invalid request id (should be " + request.requestId + ") on HAL response: "
+ response);
return null;
}
if (response.numberAssociation != request.numberAssociationTypes) {
Log.w(TAG, "Wrong number of association types on HAL response (expected "
+ request.numberAssociationTypes + ") for request " + requestAsPropValue
+ ": " + response);
return null;
}
for (int i = 0; i < request.numberAssociationTypes; i++) {
int expectedType = request.associationTypes.get(i);
int actualType = response.associations.get(i).type;
if (actualType != expectedType) {
Log.w(TAG, "Wrong type on index " + i + " of HAL response (" + response + ") for "
+ "request " + requestAsPropValue + " : expected "
+ UserIdentificationAssociationType.toString(expectedType)
+ ", got " + UserIdentificationAssociationType.toString(actualType));
return null;
}
}
return response;
}
/**
* Calls HAL to set the value of the user identifications associated with the given user.
*
* @throws IllegalArgumentException if request is invalid (mismatch on number of associations,
* duplicated association, invalid association type values, etc).
*/
public void setUserAssociation(int timeoutMs, @NonNull UserIdentificationSetRequest request,
@NonNull HalCallback<UserIdentificationResponse> callback) {
if (DBG) Log.d(TAG, "setUserAssociation(" + request + ")");
Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
Objects.requireNonNull(request, "request cannot be null");
Objects.requireNonNull(callback, "callback cannot be null");
// Check that it doesn't have dupes
SparseBooleanArray types = new SparseBooleanArray(request.numberAssociations);
for (int i = 0; i < request.numberAssociations; i++) {
int type = request.associations.get(i).type;
Preconditions.checkArgument(!types.get(type), "type %s found more than once on %s",
UserIdentificationAssociationType.toString(type), request);
types.put(type, true);
}
VehiclePropValue propRequest;
int requestId;
synchronized (mLock) {
checkSupportedLocked();
if (hasPendingRequestLocked(UserIdentificationResponse.class, callback)) return;
requestId = request.requestId = getNextRequestId();
propRequest = UserHalHelper.toVehiclePropValue(request);
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SET_USER_AUTH_REQ,
propRequest.value.int32Values.toArray());
addPendingRequestLocked(requestId, UserIdentificationSetRequest.class,
UserIdentificationResponse.class, request, callback);
}
sendHalRequest(requestId, timeoutMs, propRequest, callback);
}
private void handleOnUserIdentificationAssociation(@NonNull VehiclePropValue value) {
logEventWithErrorMessage(EventLogTags.CAR_USER_HAL_SET_USER_AUTH_RESP, value);
if (DBG) Log.d(TAG, "handleOnUserIdentificationAssociation(): " + value);
int requestId = value.value.int32Values.get(0);
HalCallback<UserIdentificationResponse> callback = handleGetPendingCallback(requestId,
UserIdentificationResponse.class);
if (callback == null) {
Log.w(TAG, "no callback for requestId " + requestId + ": " + value);
return;
}
PendingRequest<?, ?> pendingRequest = handleRemovePendingRequest(requestId);
UserIdentificationResponse response;
try {
response = UserHalHelper.toUserIdentificationResponse(value);
} catch (RuntimeException e) {
Log.w(TAG, "error parsing UserIdentificationResponse (" + value + ")", e);
callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
return;
}
// Validate the response according to the request
UserIdentificationSetRequest request = PendingRequest.getRequest(pendingRequest,
UserIdentificationSetRequest.class, requestId);
if (request == null) {
// already logged on PendingRequest.getRequest
callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
}
if (response.numberAssociation != request.numberAssociations) {
Log.w(TAG, "Wrong number of association types on HAL response (expected "
+ request.numberAssociations + ") for request " + request
+ ": " + response);
callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
return;
}
for (int i = 0; i < request.numberAssociations; i++) {
int expectedType = request.associations.get(i).type;
int actualType = response.associations.get(i).type;
if (actualType != expectedType) {
Log.w(TAG, "Wrong type on index " + i + " of HAL response (" + response + ") for "
+ "request " + value + " : expected "
+ UserIdentificationAssociationType.toString(expectedType)
+ ", got " + UserIdentificationAssociationType.toString(actualType));
callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
return;
}
}
if (DBG) Log.d(TAG, "replying to request " + requestId + " with " + response);
callback.onResponse(HalCallback.STATUS_OK, response);
}
private static void logEventWithErrorMessage(int eventTag, @NonNull VehiclePropValue value) {
if (TextUtils.isEmpty(value.value.stringValue)) {
EventLog.writeEvent(eventTag, value.value.int32Values.toArray());
} else {
// Must manually append the error message to the array of values
int size = value.value.int32Values.size();
Object[] list = new Object[size + 1];
value.value.int32Values.toArray(list);
list[list.length - 1] = value.value.stringValue;
EventLog.writeEvent(eventTag, list);
}
}
@VisibleForTesting
int getNextRequestId() {
synchronized (mLock) {
return ++mNextRequestId;
}
}
@GuardedBy("mLock")
private <REQ, RESP> void addPendingRequestLocked(int requestId,
@NonNull Class<REQ> requestClass, @NonNull Class<RESP> responseClass,
@NonNull REQ request, @NonNull HalCallback<RESP> callback) {
PendingRequest<?, RESP> pendingRequest = new PendingRequest<>(responseClass, request,
callback);
if (DBG) {
Log.d(TAG, "adding pending request (" + pendingRequest + ") for requestId "
+ requestId);
}
mPendingRequests.put(requestId, pendingRequest);
}
@GuardedBy("mLock")
private <RESP> void addPendingRequestLocked(int requestId, @NonNull Class<RESP> responseClass,
@NonNull HalCallback<RESP> callback) {
addPendingRequestLocked(requestId, Void.class, responseClass, /* request= */ null,
callback);
}
/**
* Checks if there is a pending request of type {@code requestClass}, calling {@code callback}
* with {@link HalCallback#STATUS_CONCURRENT_OPERATION} when there is.
*/
@GuardedBy("mLock")
private boolean hasPendingRequestLocked(@NonNull Class<?> responseClass,
@NonNull HalCallback<?> callback) {
for (int i = 0; i < mPendingRequests.size(); i++) {
PendingRequest<?, ?> pendingRequest = mPendingRequests.valueAt(i);
if (pendingRequest.responseClass == responseClass) {
Log.w(TAG, "Already have pending request of type " + responseClass);
callback.onResponse(HalCallback.STATUS_CONCURRENT_OPERATION, null);
return true;
}
}
return false;
}
/**
* Removes the pending request and its timeout callback.
*/
@Nullable
private PendingRequest<?, ?> handleRemovePendingRequest(int requestId) {
if (DBG) Log.d(TAG, "Removing pending request #" + requestId);
mHandler.removeMessages(requestId);
PendingRequest<?, ?> pendingRequest;
synchronized (mLock) {
pendingRequest = mPendingRequests.get(requestId);
mPendingRequests.remove(requestId);
}
return pendingRequest;
}
private void handleCheckIfRequestTimedOut(int requestId) {
PendingRequest<?, ?> pendingRequest = getPendingRequest(requestId);
if (pendingRequest == null) return;
Log.w(TAG, "Request #" + requestId + " timed out");
handleRemovePendingRequest(requestId);
pendingRequest.callback.onResponse(HalCallback.STATUS_HAL_RESPONSE_TIMEOUT, null);
}
@Nullable
private PendingRequest<?, ?> getPendingRequest(int requestId) {
synchronized (mLock) {
return mPendingRequests.get(requestId);
}
}
private void handleOnInitialUserInfoResponse(VehiclePropValue value) {
int requestId = value.value.int32Values.get(0);
HalCallback<InitialUserInfoResponse> callback = handleGetPendingCallback(requestId,
InitialUserInfoResponse.class);
if (callback == null) {
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_INITIAL_USER_INFO_RESP, requestId,
HalCallback.STATUS_INVALID);
Log.w(TAG, "no callback for requestId " + requestId + ": " + value);
return;
}
handleRemovePendingRequest(requestId);
InitialUserInfoResponse response;
try {
response = UserHalHelper.toInitialUserInfoResponse(value);
} catch (RuntimeException e) {
Log.e(TAG, "invalid response (" + value + ") from HAL", e);
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_INITIAL_USER_INFO_RESP, requestId,
HalCallback.STATUS_WRONG_HAL_RESPONSE);
callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
return;
}
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_INITIAL_USER_INFO_RESP, requestId,
HalCallback.STATUS_OK, response.action,
response.userToSwitchOrCreate.userId, response.userToSwitchOrCreate.flags,
response.userNameToCreate, response.userLocales);
if (DBG) Log.d(TAG, "replying to request " + requestId + " with " + response);
callback.onResponse(HalCallback.STATUS_OK, response);
}
private void handleOnSwitchUserResponse(VehiclePropValue value) {
int requestId = value.value.int32Values.get(0);
int messageType = value.value.int32Values.get(1);
if (messageType == SwitchUserMessageType.VEHICLE_RESPONSE) {
handleOnSwitchUserVehicleResponse(value);
return;
}
if (messageType == SwitchUserMessageType.VEHICLE_REQUEST) {
handleOnSwitchUserVehicleRequest(value);
return;
}
Log.e(TAG, "handleOnSwitchUserResponse invalid message type (" + messageType
+ ") from HAL: " + value);
// check if a callback exists for the request ID
HalCallback<SwitchUserResponse> callback =
handleGetPendingCallback(requestId, SwitchUserResponse.class);
if (callback != null) {
handleRemovePendingRequest(requestId);
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_RESP, requestId,
HalCallback.STATUS_WRONG_HAL_RESPONSE);
callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
return;
}
}
private void handleOnSwitchUserVehicleRequest(VehiclePropValue value) {
int requestId = value.value.int32Values.get(0);
// Index 1 is message type, which is not required in this call.
int targetUserId = value.value.int32Values.get(2);
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_OEM_SWITCH_USER_REQ, requestId, targetUserId);
// HAL vehicle request should have negative request ID
if (requestId >= 0) {
Log.e(TAG, "handleVehicleRequest invalid requestId (" + requestId + ") from HAL: "
+ value);
return;
}
CarUserService userService = CarLocalServices.getService(CarUserService.class);
userService.switchAndroidUserFromHal(requestId, targetUserId);
}
private void handleOnSwitchUserVehicleResponse(VehiclePropValue value) {
int requestId = value.value.int32Values.get(0);
HalCallback<SwitchUserResponse> callback =
handleGetPendingCallback(requestId, SwitchUserResponse.class);
if (callback == null) {
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_RESP, requestId,
HalCallback.STATUS_INVALID);
Log.w(TAG, "no callback for requestId " + requestId + ": " + value);
return;
}
handleRemovePendingRequest(requestId);
SwitchUserResponse response = new SwitchUserResponse();
response.requestId = requestId;
response.messageType = value.value.int32Values.get(1);
response.status = value.value.int32Values.get(2);
response.errorMessage = value.value.stringValue;
if (response.status == SwitchUserStatus.SUCCESS
|| response.status == SwitchUserStatus.FAILURE) {
if (DBG) {
Log.d(TAG, "replying to request " + requestId + " with " + response);
}
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_RESP, requestId,
HalCallback.STATUS_OK, response.status, response.errorMessage);
callback.onResponse(HalCallback.STATUS_OK, response);
} else {
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_RESP, requestId,
HalCallback.STATUS_WRONG_HAL_RESPONSE, response.status, response.errorMessage);
Log.e(TAG, "invalid status (" + response.status + ") from HAL: " + value);
callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
}
}
private void handleOnCreateUserResponse(VehiclePropValue value) {
int requestId = value.value.int32Values.get(0);
HalCallback<CreateUserResponse> callback =
handleGetPendingCallback(requestId, CreateUserResponse.class);
if (callback == null) {
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_CREATE_USER_RESP, requestId,
HalCallback.STATUS_INVALID);
Log.w(TAG, "no callback for requestId " + requestId + ": " + value);
return;
}
handleRemovePendingRequest(requestId);
CreateUserResponse response = new CreateUserResponse();
response.requestId = requestId;
response.status = value.value.int32Values.get(1);
response.errorMessage = value.value.stringValue;
if (response.status == CreateUserStatus.SUCCESS
|| response.status == CreateUserStatus.FAILURE) {
if (DBG) {
Log.d(TAG, "replying to request " + requestId + " with " + response);
}
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_CREATE_USER_RESP, requestId,
HalCallback.STATUS_OK, response.status, response.errorMessage);
callback.onResponse(HalCallback.STATUS_OK, response);
} else {
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_CREATE_USER_RESP, requestId,
HalCallback.STATUS_WRONG_HAL_RESPONSE, response.status, response.errorMessage);
Log.e(TAG, "invalid status (" + response.status + ") from HAL: " + value);
callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
}
}
private <T> HalCallback<T> handleGetPendingCallback(int requestId, Class<T> clazz) {
PendingRequest<?, ?> pendingRequest = getPendingRequest(requestId);
if (pendingRequest == null) return null;
if (pendingRequest.responseClass != clazz) {
Log.e(TAG, "Invalid callback class for request " + requestId + ": expected" + clazz
+ ", but got is " + pendingRequest.responseClass);
// TODO(b/150413515): add unit test for this scenario once it supports other properties
return null;
}
@SuppressWarnings("unchecked")
HalCallback<T> callback = (HalCallback<T>) pendingRequest.callback;
return callback;
}
@Override
public void dump(PrintWriter writer) {
String indent = " ";
writer.printf("*User HAL*\n");
writer.printf("Relevant CarProperties\n");
dumpSystemProperty(writer, indent, "user_hal_enabled", CarProperties.user_hal_enabled());
dumpSystemProperty(writer, indent, "user_hal_timeout", CarProperties.user_hal_timeout());
synchronized (mLock) {
if (!isSupported()) {
writer.println(UNSUPPORTED_MSG);
return;
}
int numberProperties = mProperties.size();
writer.printf("%d supported properties\n", numberProperties);
for (int i = 0; i < numberProperties; i++) {
writer.printf("%s%s\n", indent, mProperties.valueAt(i));
}
writer.printf("next request id: %d\n", mNextRequestId);
int numberPendingCallbacks = mPendingRequests.size();
if (numberPendingCallbacks == 0) {
writer.println("no pending callbacks");
} else {
writer.printf("%d pending callbacks: %s\n", numberPendingCallbacks);
for (int i = 0; i < numberPendingCallbacks; i++) {
writer.print(indent);
mPendingRequests.valueAt(i).dump(writer);
writer.println();
}
}
}
}
private static void dumpSystemProperty(@NonNull PrintWriter writer, @NonNull String indent,
@NonNull String name, Optional<?> prop) {
String value = prop.isPresent() ? prop.get().toString() : "<NOT SET>";
writer.printf("%s%s=%s\n", indent, name, value);
}
private static final class PendingRequest<REQ, RESP> {
@NonNull
public final Class<RESP> responseClass;
@Nullable
public final REQ request;
@NonNull
public final HalCallback<RESP> callback;
PendingRequest(@NonNull Class<RESP> responseClass, @Nullable REQ request,
@NonNull HalCallback<RESP> callback) {
this.responseClass = responseClass;
this.request = request;
this.callback = callback;
}
/**
* Gets the safely cast request for a given pending request.
*/
@Nullable
private static <T> T getRequest(@Nullable PendingRequest<?, ?> pendingRequest,
@NonNull Class<T> clazz, int requestId) {
if (pendingRequest == null) {
Log.e(TAG, "No pending request for id " + requestId);
return null;
}
Object request = pendingRequest.request;
if (!clazz.isInstance(request)) {
Log.e(TAG, "Wrong pending request for id " + requestId + ": " + pendingRequest);
return null;
}
return clazz.cast(request);
}
public void dump(@NonNull PrintWriter pw) {
pw.printf("Class: %s Callback: %s", responseClass.getSimpleName(),
FunctionalUtils.getLambdaName(callback));
if (request != null) {
pw.printf(" Request: %s", request);
}
}
@Override
public String toString() {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.print("[PendingRequest: ");
dump(pw);
pw.print("]");
pw.flush();
return sw.toString();
}
}
}