blob: d3019a16cd857d1577312717c133bf816154383b [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.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.car.builtin.util.Slog;
import android.car.hardware.property.CarPropertyManager;
import android.car.user.CarUserManager;
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.InitialUserInfoRequestType;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponseAction;
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.UserIdentificationAssociation;
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.UserIdentificationSetAssociation;
import android.hardware.automotive.vehicle.V2_0.UserIdentificationSetRequest;
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.ServiceSpecificException;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.car.CarLocalServices;
import com.android.car.CarLog;
import com.android.car.CarServiceUtils;
import com.android.car.CarStatsLog;
import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.common.EventLogTags;
import com.android.car.internal.common.UserHelperLite;
import com.android.car.internal.os.CarSystemProperties;
import com.android.car.internal.util.FunctionalUtils;
import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
/**
* Service used to integrate the OEM's custom user management with Android's.
*/
public final class UserHalService extends HalServiceBase {
private static final String TAG = CarLog.tagFor(UserHalService.class);
private static final String UNSUPPORTED_MSG = "Vehicle HAL does not support user management";
private static final String USER_ASSOCIATION_UNSUPPORTED_MSG =
"Vehicle HAL does not support user association";
private static final int[] SUPPORTED_PROPERTIES = new int[]{
CREATE_USER,
INITIAL_USER_INFO,
REMOVE_USER,
SWITCH_USER,
USER_IDENTIFICATION_ASSOCIATION
};
private static final int[] CORE_PROPERTIES = new int[]{
CREATE_USER,
INITIAL_USER_INFO,
REMOVE_USER,
SWITCH_USER,
};
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;
/**
* Base requestID. RequestID logged for metrics will be mBaseRequestID + original
* requestID
*/
private final int mBaseRequestId;
/**
* Map of callbacks by request id.
*/
@GuardedBy("mLock")
private final SparseArray<PendingRequest<?, ?>> mPendingRequests = new SparseArray<>();
public UserHalService(VehicleHal hal) {
this(hal, new Handler(CarServiceUtils.getHandlerThread(
CarUserService.HANDLER_THREAD_NAME).getLooper()));
}
@VisibleForTesting
UserHalService(VehicleHal hal, Handler handler) {
mHal = hal;
mHandler = handler;
mBaseRequestId = ThreadLocalRandom.current().nextInt(0, Integer.MAX_VALUE);
}
@Override
public void init() {
if (DBG) Slog.d(TAG, "init()");
ArrayList<Integer> props = new ArrayList<>();
synchronized (mLock) {
if (mProperties == null) {
return;
}
int size = mProperties.size();
for (int i = 0; i < size; i++) {
VehiclePropConfig config = mProperties.valueAt(i);
if (VehicleHal.isPropertySubscribable(config)) {
props.add(config.prop);
}
}
}
for (int i = 0; i < props.size(); i++) {
int prop = props.get(i);
if (DBG) Slog.d(TAG, "subscribing to property " + prop);
mHal.subscribeProperty(this, prop);
}
}
@Override
public void release() {
if (DBG) Slog.d(TAG, "release()");
}
@Override
public void onHalEvents(List<VehiclePropValue> values) {
if (DBG) Slog.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.post(() -> handleOnInitialUserInfoResponse(value));
break;
case SWITCH_USER:
mHandler.post(() -> handleOnSwitchUserResponse(value));
break;
case CREATE_USER:
mHandler.post(() -> handleOnCreateUserResponse(value));
break;
case REMOVE_USER:
Slog.w(TAG, "Received REMOVE_USER HAL event: " + value);
break;
case USER_IDENTIFICATION_ASSOCIATION:
mHandler.post(() -> handleOnUserIdentificationAssociation(value));
break;
default:
Slog.w(TAG, "received unsupported event from HAL: " + value);
}
}
}
@Override
public void onPropertySetError(int property, int area,
@CarPropertyManager.CarSetPropertyErrorCode int errorCode) {
if (DBG) Slog.d(TAG, "handlePropertySetError(" + property + "/" + area + ")");
}
@Override
public int[] getAllSupportedProperties() {
return SUPPORTED_PROPERTIES;
}
@Override
public void takeProperties(Collection<VehiclePropConfig> properties) {
if (properties.isEmpty()) {
Slog.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 core user management actions.
*/
public boolean isSupported() {
synchronized (mLock) {
if (mProperties == null) return false;
for (int i = 0; i < CORE_PROPERTIES.length; i++) {
if (mProperties.get(CORE_PROPERTIES[i]) == null) {
return false;
}
}
return true;
}
}
/**
* Checks if the Vehicle HAL supports core user management actions.
*/
public boolean isUserAssociationSupported() {
synchronized (mLock) {
if (mProperties == null) return false;
if (mProperties.get(USER_IDENTIFICATION_ASSOCIATION) == null) return false;
return true;
}
}
private void checkSupported() {
Preconditions.checkState(isSupported(), UNSUPPORTED_MSG);
}
private void checkUserAssociationSupported() {
Preconditions.checkState(isUserAssociationSupported(), USER_ASSOCIATION_UNSUPPORTED_MSG);
}
// Returns mBaseRequestId + originalRequestID. If it overflows, then MOD by Integer.MAX_VALUE
// This request Id is used for logging data in statsd for metrics. As original request id
// starts with 1 after every restart, a random id is desired for co-relating metrics on the
// server side. mBaseRequestId is generated as a random id on each restart.
private int getRequestIdForStatsLog(int originalRequestId) {
if (Integer.MAX_VALUE - mBaseRequestId < originalRequestId) {
// overflow
return (mBaseRequestId - Integer.MAX_VALUE) + originalRequestId;
}
return mBaseRequestId + originalRequestId;
}
/**
* 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) Slog.d(TAG, "getInitialInfo(" + requestType + ")");
Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
Objects.requireNonNull(usersInfo);
UserHalHelper.checkValid(usersInfo);
Objects.requireNonNull(callback);
checkSupported();
int requestId = getNextRequestId();
VehiclePropValue propRequest = UserHalHelper.createPropRequest(INITIAL_USER_INFO, requestId,
requestType);
UserHalHelper.addUsersInfo(propRequest, usersInfo);
synchronized (mLock) {
if (hasPendingRequestLocked(InitialUserInfoResponse.class, callback)) return;
addPendingRequestLocked(requestId, InitialUserInfoResponse.class, callback);
}
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_INITIAL_USER_INFO_REQ, requestId,
requestType, timeoutMs);
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_INITIAL_USER_INFO_REQUEST_REPORTED,
getRequestIdForStatsLog(requestId),
getInitialUserInfoRequestTypeForStatsd(requestType), timeoutMs);
sendHalRequest(requestId, timeoutMs, propRequest, callback);
}
private static int getInitialUserInfoRequestTypeForStatsd(int requestType) {
// CHECKSTYLE:OFF IndentationCheck
switch (requestType) {
case InitialUserInfoRequestType.FIRST_BOOT:
return CarStatsLog
.CAR_USER_HAL_INITIAL_USER_INFO_REQUEST_REPORTED__REQUEST_TYPE__FIRST_BOOT;
case InitialUserInfoRequestType.FIRST_BOOT_AFTER_OTA:
return CarStatsLog
.CAR_USER_HAL_INITIAL_USER_INFO_REQUEST_REPORTED__REQUEST_TYPE__FIRST_BOOT_AFTER_OTA;
case InitialUserInfoRequestType.COLD_BOOT:
return CarStatsLog
.CAR_USER_HAL_INITIAL_USER_INFO_REQUEST_REPORTED__REQUEST_TYPE__COLD_BOOT;
case InitialUserInfoRequestType.RESUME:
return CarStatsLog
.CAR_USER_HAL_INITIAL_USER_INFO_REQUEST_REPORTED__REQUEST_TYPE__RESUME;
default:
return CarStatsLog
.CAR_USER_HAL_INITIAL_USER_INFO_REQUEST_REPORTED__REQUEST_TYPE__UNKNOWN;
}
// CHECKSTYLE:ON IndentationCheck
}
private void sendHalRequest(int requestId, int timeoutMs, @NonNull VehiclePropValue request,
@NonNull HalCallback<?> callback) {
mHandler.postDelayed(() -> handleCheckIfRequestTimedOut(requestId), requestId, timeoutMs);
try {
if (DBG) Slog.d(TAG, "Calling hal.set(): " + request);
mHal.set(request);
} catch (ServiceSpecificException e) {
handleRemovePendingRequest(requestId);
Slog.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");
Objects.requireNonNull(request, "request cannot be null");
if (DBG) Slog.d(TAG, "switchUser(" + request + ")");
checkSupported();
request.requestId = getNextRequestId();
request.messageType = SwitchUserMessageType.ANDROID_SWITCH;
VehiclePropValue propRequest = UserHalHelper.toVehiclePropValue(request);
synchronized (mLock) {
if (hasPendingRequestLocked(SwitchUserResponse.class, callback)) return;
addPendingRequestLocked(request.requestId, SwitchUserResponse.class, callback);
}
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_REQ, request.requestId,
request.targetUser.userId, timeoutMs);
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED,
getRequestIdForStatsLog(request.requestId),
CarStatsLog
.CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED__REQUEST_TYPE__SWITCH_REQUEST_ANDROID,
request.usersInfo.currentUser.userId, request.usersInfo.currentUser.flags,
request.targetUser.userId, request.targetUser.flags, timeoutMs);
sendHalRequest(request.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) Slog.d(TAG, "removeUser(" + request + ")");
checkSupported();
request.requestId = getNextRequestId();
VehiclePropValue propRequest = UserHalHelper.toVehiclePropValue(request);
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_REMOVE_USER_REQ,
request.removedUserInfo.userId, request.usersInfo.currentUser.userId);
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED,
getRequestIdForStatsLog(request.requestId),
CarStatsLog
.CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED__REQUEST_TYPE__REMOVE_REQUEST,
request.usersInfo.currentUser.userId, request.usersInfo.currentUser.flags,
request.removedUserInfo.userId, request.removedUserInfo.flags, /* timeout */ -1);
try {
if (DBG) Slog.d(TAG, "Calling hal.set(): " + propRequest);
mHal.set(propRequest);
} catch (ServiceSpecificException e) {
Slog.w(TAG, "Failed to set REMOVE USER", e);
}
}
/**
* Calls HAL to indicate an Android user was created.
*
* @param request info about 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) {
Objects.requireNonNull(request);
Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
Objects.requireNonNull(callback);
if (DBG) Slog.d(TAG, "createUser(): req=" + request + ", timeout=" + timeoutMs);
checkSupported();
request.requestId = getNextRequestId();
VehiclePropValue propRequest = UserHalHelper.toVehiclePropValue(request);
synchronized (mLock) {
if (hasPendingRequestLocked(CreateUserResponse.class, callback)) return;
addPendingRequestLocked(request.requestId, CreateUserResponse.class, callback);
}
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_CREATE_USER_REQ, request.requestId,
UserHelperLite.safeName(request.newUserName), request.newUserInfo.flags, timeoutMs);
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED,
getRequestIdForStatsLog(request.requestId),
CarStatsLog
.CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED__REQUEST_TYPE__CREATE_REQUEST,
request.usersInfo.currentUser.userId, request.usersInfo.currentUser.flags,
request.newUserInfo.userId, request.newUserInfo.flags, timeoutMs);
sendHalRequest(request.requestId, timeoutMs, propRequest, callback);
}
/**
* Calls HAL after android user switch.
*/
public void postSwitchResponse(@NonNull SwitchUserRequest request) {
Objects.requireNonNull(request, "request cannot be null");
if (DBG) Slog.d(TAG, "postSwitchResponse(" + request + ")");
checkSupported();
request.messageType = SwitchUserMessageType.ANDROID_POST_SWITCH;
VehiclePropValue propRequest = UserHalHelper.toVehiclePropValue(request);
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_POST_SWITCH_USER_REQ, request.requestId,
request.targetUser.userId, request.usersInfo.currentUser.userId);
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_POST_SWITCH_RESPONSE_REPORTED,
getRequestIdForStatsLog(request.requestId),
request.targetUser.userId == request.usersInfo.currentUser.userId
? CarStatsLog.CAR_USER_HAL_POST_SWITCH_RESPONSE_REPORTED__SWITCH_STATUS__SUCCESS
: CarStatsLog.CAR_USER_HAL_POST_SWITCH_RESPONSE_REPORTED__SWITCH_STATUS__FAILURE);
try {
if (DBG) Slog.d(TAG, "Calling hal.set(): " + propRequest);
mHal.set(propRequest);
} catch (ServiceSpecificException e) {
Slog.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) {
Objects.requireNonNull(request, "request cannot be null");
if (DBG) Slog.d(TAG, "userSwitchLegacy(" + request + ")");
checkSupported();
request.requestId = getNextRequestId();
request.messageType = SwitchUserMessageType.LEGACY_ANDROID_SWITCH;
VehiclePropValue propRequest = UserHalHelper.toVehiclePropValue(request);
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_LEGACY_SWITCH_USER_REQ, request.requestId,
request.targetUser.userId, request.usersInfo.currentUser.userId);
//CHECKSTYLE:OFF IndentationCheck
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED,
getRequestIdForStatsLog(request.requestId), CarStatsLog
.CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED__REQUEST_TYPE__SWITCH_REQUEST_LEGACY,
request.usersInfo.currentUser.userId, request.usersInfo.currentUser.flags,
request.targetUser.userId, request.targetUser.flags, /* timeout_ms= */ -1);
//CHECKSTYLE:ON IndentationCheck
try {
if (DBG) Slog.d(TAG, "Calling hal.set(): " + propRequest);
mHal.set(propRequest);
} catch (ServiceSpecificException e) {
Slog.w(TAG, "Failed to set LEGACY ANDROID SWITCH", e);
}
}
/**
* 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).
*/
@Nullable
public UserIdentificationResponse getUserAssociation(
@NonNull UserIdentificationGetRequest request) {
Objects.requireNonNull(request, "request cannot be null");
checkUserAssociationSupported();
// 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) Slog.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;
try {
responseAsPropValue = mHal.get(requestAsPropValue);
} catch (ServiceSpecificException e) {
Slog.w(TAG, "HAL returned error for request " + requestAsPropValue, e);
return null;
}
if (responseAsPropValue == null) {
Slog.w(TAG, "HAL returned null for request " + requestAsPropValue);
return null;
}
logEventWithErrorMessage(EventLogTags.CAR_USER_HAL_GET_USER_AUTH_RESP, responseAsPropValue);
if (DBG) Slog.d(TAG, "getUserAssociation(): responseAsPropValue=" + responseAsPropValue);
UserIdentificationResponse response;
try {
response = UserHalHelper.toUserIdentificationResponse(responseAsPropValue);
} catch (IllegalArgumentException e) {
Slog.w(TAG, "invalid response from HAL for " + requestAsPropValue, e);
return null;
}
if (DBG) Slog.d(TAG, "getUserAssociation(): response=" + response);
// Validate the response according to the request
if (response.requestId != request.requestId) {
Slog.w(TAG, "invalid request id (should be " + request.requestId + ") on HAL response: "
+ response);
return null;
}
if (response.numberAssociation != request.numberAssociationTypes) {
Slog.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) {
Slog.w(TAG, "Wrong type on index " + i + " of HAL response (" + response + ") for "
+ "request " + requestAsPropValue + " : expected "
+ UserIdentificationAssociationType.toString(expectedType)
+ ", got " + UserIdentificationAssociationType.toString(actualType));
return null;
}
}
// TODO(b/153900032): move this logic to a common helper
int[] associationTypes = new int[response.numberAssociation];
int[] associationValues = new int[response.numberAssociation];
for (int i = 0; i < response.numberAssociation; i++) {
UserIdentificationAssociation association = response.associations.get(i);
associationTypes[i] = association.type;
associationValues[i] = association.value;
}
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_USER_ASSOCIATION_REQUEST_REPORTED,
getRequestIdForStatsLog(request.requestId),
CarStatsLog.CAR_USER_HAL_USER_ASSOCIATION_REQUEST_REPORTED__REQUEST_TYPE__GET,
request.userInfo.userId,
request.userInfo.flags,
request.numberAssociationTypes,
Arrays.toString(associationTypes), Arrays.toString(associationValues));
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) {
Preconditions.checkArgumentPositive(timeoutMs, "timeout must be positive");
Objects.requireNonNull(request, "request cannot be null");
Objects.requireNonNull(callback, "callback cannot be null");
if (DBG) Slog.d(TAG, "setUserAssociation(" + request + ")");
// 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);
}
checkUserAssociationSupported();
request.requestId = getNextRequestId();
VehiclePropValue propRequest = UserHalHelper.toVehiclePropValue(request);
synchronized (mLock) {
if (hasPendingRequestLocked(UserIdentificationResponse.class, callback)) return;
addPendingRequestLocked(request.requestId, UserIdentificationResponse.class, request,
callback);
}
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SET_USER_AUTH_REQ,
propRequest.value.int32Values.toArray());
// TODO(b/153900032): move this logic to a common helper
int[] associationTypes = new int[request.numberAssociations];
int[] associationValues = new int[request.numberAssociations];
for (int i = 0; i < request.numberAssociations; i++) {
UserIdentificationSetAssociation association = request.associations.get(i);
associationTypes[i] = association.type;
associationValues[i] = association.value;
}
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_USER_ASSOCIATION_REQUEST_REPORTED,
getRequestIdForStatsLog(request.requestId),
CarStatsLog.CAR_USER_HAL_USER_ASSOCIATION_REQUEST_REPORTED__REQUEST_TYPE__SET,
request.userInfo.userId, request.userInfo.flags, request.numberAssociations,
Arrays.toString(associationTypes), Arrays.toString(associationValues));
sendHalRequest(request.requestId, timeoutMs, propRequest, callback);
}
private void handleOnUserIdentificationAssociation(@NonNull VehiclePropValue value) {
logEventWithErrorMessage(EventLogTags.CAR_USER_HAL_SET_USER_AUTH_RESP, value);
if (DBG) Slog.d(TAG, "handleOnUserIdentificationAssociation(): " + value);
int requestId = value.value.int32Values.get(0);
HalCallback<UserIdentificationResponse> callback = handleGetPendingCallback(requestId,
UserIdentificationResponse.class);
if (callback == null) {
Slog.w(TAG, "no callback for requestId " + requestId + ": " + value);
return;
}
PendingRequest<?, ?> pendingRequest = handleRemovePendingRequest(requestId);
UserIdentificationResponse response;
try {
response = UserHalHelper.toUserIdentificationResponse(value);
} catch (RuntimeException e) {
Slog.w(TAG, "error parsing UserIdentificationResponse (" + value + ")", e);
callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_SET_USER_ASSOCIATION_RESPONSE_REPORTED,
getRequestIdForStatsLog(requestId),
getHalCallbackStatusForStatsd(HalCallback.STATUS_WRONG_HAL_RESPONSE),
/* number_associations= */ 0, /* user_identification_association_types= */ "",
/* user_identification_association_values= */ "");
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);
logSetUserAssociationResponse(requestId, response,
HalCallback.STATUS_WRONG_HAL_RESPONSE);
return;
}
if (response.numberAssociation != request.numberAssociations) {
Slog.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);
logSetUserAssociationResponse(requestId, response,
HalCallback.STATUS_WRONG_HAL_RESPONSE);
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) {
Slog.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);
logSetUserAssociationResponse(requestId, response,
HalCallback.STATUS_WRONG_HAL_RESPONSE);
return;
}
}
if (DBG) Slog.d(TAG, "replying to request " + requestId + " with " + response);
callback.onResponse(HalCallback.STATUS_OK, response);
logSetUserAssociationResponse(requestId, response, HalCallback.STATUS_OK);
}
private void logSetUserAssociationResponse(int requestId, UserIdentificationResponse response,
int halCallbackStatus) {
// TODO(b/153900032): move this logic to a common helper
int[] associationTypes = new int[response.numberAssociation];
int[] associationValues = new int[response.numberAssociation];
for (int i = 0; i < response.numberAssociation; i++) {
UserIdentificationAssociation association = response.associations.get(i);
associationTypes[i] = association.type;
associationValues[i] = association.value;
}
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_SET_USER_ASSOCIATION_RESPONSE_REPORTED,
getRequestIdForStatsLog(requestId),
getHalCallbackStatusForStatsd(halCallbackStatus), response.numberAssociation,
Arrays.toString(associationTypes), Arrays.toString(associationValues));
}
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<RESP> responseClass, @NonNull REQ request,
@NonNull HalCallback<RESP> callback) {
PendingRequest<?, RESP> pendingRequest = new PendingRequest<>(responseClass, request,
callback);
if (DBG) {
Slog.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, 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) {
Slog.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) Slog.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;
Slog.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);
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_INITIAL_USER_INFO_RESPONSE_REPORTED,
getRequestIdForStatsLog(requestId),
getHalCallbackStatusForStatsd(HalCallback.STATUS_INVALID),
getInitialUserInfoResponseActionForStatsd(
InitialUserInfoResponseAction.DEFAULT),
/* user id= */ -1, /* flag= */ -1, /* user locales= */ "");
Slog.w(TAG, "no callback for requestId " + requestId + ": " + value);
return;
}
handleRemovePendingRequest(requestId);
InitialUserInfoResponse response;
try {
response = UserHalHelper.toInitialUserInfoResponse(value);
} catch (RuntimeException e) {
Slog.e(TAG, "invalid response (" + value + ") from HAL", e);
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_INITIAL_USER_INFO_RESP, requestId,
HalCallback.STATUS_WRONG_HAL_RESPONSE);
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_INITIAL_USER_INFO_RESPONSE_REPORTED,
getRequestIdForStatsLog(requestId),
getHalCallbackStatusForStatsd(HalCallback.STATUS_WRONG_HAL_RESPONSE),
getInitialUserInfoResponseActionForStatsd(
InitialUserInfoResponseAction.DEFAULT),
/* user id= */ -1, /* flag= */ -1, /* user locales= */ "");
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);
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_INITIAL_USER_INFO_RESPONSE_REPORTED,
getRequestIdForStatsLog(requestId),
getHalCallbackStatusForStatsd(HalCallback.STATUS_OK),
getInitialUserInfoResponseActionForStatsd(response.action),
response.userToSwitchOrCreate.userId, response.userToSwitchOrCreate.flags,
response.userLocales);
if (DBG) Slog.d(TAG, "replying to request " + requestId + " with " + response);
callback.onResponse(HalCallback.STATUS_OK, response);
}
private static int getInitialUserInfoResponseActionForStatsd(int action) {
switch (action) {
case InitialUserInfoResponseAction.CREATE:
return CarStatsLog
.CAR_USER_HAL_INITIAL_USER_INFO_RESPONSE_REPORTED__RESPONSE_ACTION__CREATE;
case InitialUserInfoResponseAction.SWITCH:
return CarStatsLog
.CAR_USER_HAL_INITIAL_USER_INFO_RESPONSE_REPORTED__RESPONSE_ACTION__SWITCH;
default:
return CarStatsLog
.CAR_USER_HAL_INITIAL_USER_INFO_RESPONSE_REPORTED__RESPONSE_ACTION__DEFAULT;
}
}
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;
}
Slog.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);
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED,
getRequestIdForStatsLog(requestId),
CarStatsLog
.CAR_USER_HAL_MODIFY_USER_REQUEST_REPORTED__REQUEST_TYPE__SWITCH_REQUEST_OEM,
/* current user id= */ -1, /* current user flag= */ -1, targetUserId,
/* target user flag= */ -1, /* timeout_ms= */ -1);
// HAL vehicle request should have negative request ID
if (requestId >= 0) {
Slog.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);
Slog.w(TAG, "no callback for requestId " + requestId + ": " + value);
logHalSwitchUserResponse(requestId, HalCallback.STATUS_WRONG_HAL_RESPONSE);
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) {
Slog.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);
logHalSwitchUserResponse(requestId, HalCallback.STATUS_OK, response.status);
} else {
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_RESP, requestId,
HalCallback.STATUS_WRONG_HAL_RESPONSE, response.status, response.errorMessage);
Slog.e(TAG, "invalid status (" + response.status + ") from HAL: " + value);
callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
logHalSwitchUserResponse(requestId, HalCallback.STATUS_WRONG_HAL_RESPONSE,
response.status);
}
}
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);
Slog.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) {
Slog.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);
logHalCreateUserResponse(requestId, HalCallback.STATUS_OK, response.status);
} else {
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_CREATE_USER_RESP, requestId,
HalCallback.STATUS_WRONG_HAL_RESPONSE, response.status, response.errorMessage);
Slog.e(TAG, "invalid status (" + response.status + ") from HAL: " + value);
callback.onResponse(HalCallback.STATUS_WRONG_HAL_RESPONSE, null);
logHalCreateUserResponse(requestId, HalCallback.STATUS_WRONG_HAL_RESPONSE);
}
}
private void logHalSwitchUserResponse(int requestId, int halCallbackStatus) {
//CHECKSTYLE:OFF IndentationCheck
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED,
getRequestIdForStatsLog(requestId),
getHalCallbackStatusForStatsd(halCallbackStatus),
CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__REQUEST_STATUS__UNSPECIFIED);
//CHECKSTYLE:ON IndentationCheck
}
private void logHalSwitchUserResponse(int requestId, int halCallbackStatus,
int userSwitchstatus) {
int userSwitchstatusForStatsd = userSwitchstatus == SwitchUserStatus.SUCCESS
? CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__REQUEST_STATUS__SUCCESS
: CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__REQUEST_STATUS__FAILURE;
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED,
getRequestIdForStatsLog(requestId),
getHalCallbackStatusForStatsd(halCallbackStatus), userSwitchstatusForStatsd);
}
private void logHalCreateUserResponse(int requestId, int halCallbackStatus) {
//CHECKSTYLE:OFF IndentationCheck
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED,
getRequestIdForStatsLog(requestId),
getHalCallbackStatusForStatsd(halCallbackStatus),
CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__REQUEST_STATUS__UNSPECIFIED);
//CHECKSTYLE:ON IndentationCheck
}
private void logHalCreateUserResponse(int requestId, int halCallbackStatus,
int userCreatestatus) {
int userCreatestatusForStatsd = userCreatestatus == CreateUserStatus.SUCCESS
? CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__REQUEST_STATUS__SUCCESS
: CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__REQUEST_STATUS__FAILURE;
CarStatsLog.write(CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED,
getRequestIdForStatsLog(requestId),
getHalCallbackStatusForStatsd(halCallbackStatus), userCreatestatusForStatsd);
}
private int getHalCallbackStatusForStatsd(int halCallbackStatus) {
// CHECKSTYLE:OFF IndentationCheck
switch (halCallbackStatus) {
case HalCallback.STATUS_OK:
return CarStatsLog.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__CALLBACK_STATUS__OK;
case HalCallback.STATUS_HAL_SET_TIMEOUT:
return CarStatsLog
.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__CALLBACK_STATUS__HAL_SET_TIMEOUT;
case HalCallback.STATUS_HAL_RESPONSE_TIMEOUT:
return CarStatsLog
.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__CALLBACK_STATUS__HAL_RESPONSE_TIMEOUT;
case HalCallback.STATUS_WRONG_HAL_RESPONSE:
return CarStatsLog
.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__CALLBACK_STATUS__WRONG_HAL_RESPONSE;
case HalCallback.STATUS_CONCURRENT_OPERATION:
return CarStatsLog
.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__CALLBACK_STATUS__CONCURRENT_OPERATION;
default:
return CarStatsLog
.CAR_USER_HAL_MODIFY_USER_RESPONSE_REPORTED__CALLBACK_STATUS__INVALID;
}
// CHECKSTYLE:ON IndentationCheck
}
private <T> HalCallback<T> handleGetPendingCallback(int requestId, Class<T> clazz) {
PendingRequest<?, ?> pendingRequest = getPendingRequest(requestId);
if (pendingRequest == null) return null;
if (pendingRequest.responseClass != clazz) {
Slog.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
@ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
public void dump(PrintWriter writer) {
String indent = " ";
writer.printf("*User HAL*\n");
writer.printf("Relevant CarSystemProperties\n");
dumpSystemProperty(writer, indent, "user_hal_timeout",
CarSystemProperties.getUserHalTimeout());
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("Base request id: %d\n", mBaseRequestId);
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) {
Slog.e(TAG, "No pending request for id " + requestId);
return null;
}
Object request = pendingRequest.request;
if (!clazz.isInstance(request)) {
Slog.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();
}
}
}