blob: 15038cd069259037e79967b1570a20e1f3e55132 [file] [log] [blame]
/*
* Copyright (C) 2018 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 com.android.car.hal.CarPropertyUtils.toCarPropertyValue;
import static com.android.car.hal.CarPropertyUtils.toMixedCarPropertyValue;
import static com.android.car.hal.CarPropertyUtils.toMixedVehiclePropValue;
import static com.android.car.hal.CarPropertyUtils.toVehiclePropValue;
import static java.lang.Integer.toHexString;
import android.annotation.Nullable;
import android.car.VehiclePropertyIds;
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyValue;
import android.car.hardware.property.CarPropertyEvent;
import android.car.hardware.property.CarPropertyManager;
import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
import android.os.Build;
import android.util.Log;
import android.util.SparseArray;
import com.android.car.CarServiceUtils;
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Common interface for HAL services that send Vehicle Properties back and forth via ICarProperty.
* Services that communicate by passing vehicle properties back and forth via ICarProperty should
* extend this class.
*/
public class PropertyHalService extends HalServiceBase {
private final boolean mDbg = true;
private final LinkedList<CarPropertyEvent> mEventsToDispatch = new LinkedList<>();
@GuardedBy("mLock")
private final Map<Integer, CarPropertyConfig<?>> mMgrPropIdToCarPropConfig = new HashMap<>();
@GuardedBy("mLock")
private final SparseArray<VehiclePropConfig> mHalPropIdToVehiclePropConfig =
new SparseArray<>();
// Only contains propId if the property Id is different in HAL and manager
private static final Map<Integer, Integer> PROPERTY_ID_HAL_TO_MANAGER = Map.of(
VehicleProperty.VEHICLE_SPEED_DISPLAY_UNITS,
VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS);
// Only contains propId if the property Id is different in HAL and manager
private static final Map<Integer, Integer> PROPERTY_ID_MANAGER_TO_HAL = Map.of(
VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS,
VehicleProperty.VEHICLE_SPEED_DISPLAY_UNITS);
private static final String TAG = "PropertyHalService";
private final VehicleHal mVehicleHal;
private final PropertyHalServiceIds mPropIds;
@GuardedBy("mLock")
private PropertyHalListener mListener;
@GuardedBy("mLock")
private Set<Integer> mSubscribedHalPropIds;
private final Object mLock = new Object();
/**
* Converts manager property ID to Vehicle HAL property ID.
*/
private int managerToHalPropId(int mgrPropId) {
return PROPERTY_ID_MANAGER_TO_HAL.getOrDefault(mgrPropId, mgrPropId);
}
/**
* Converts Vehicle HAL property ID to manager property ID.
*/
private int halToManagerPropId(int halPropId) {
return PROPERTY_ID_HAL_TO_MANAGER.getOrDefault(halPropId, halPropId);
}
// Checks if the property exists in this VHAL before calling methods in IVehicle.
private boolean isPropertySupportedInVehicle(int halPropId) {
return mHalPropIdToVehiclePropConfig.contains(halPropId);
}
/**
* PropertyHalListener used to send events to CarPropertyService
*/
public interface PropertyHalListener {
/**
* This event is sent whenever the property value is updated
* @param events
*/
void onPropertyChange(List<CarPropertyEvent> events);
/**
* This event is sent when the set property call fails
* @param property
* @param area
*/
void onPropertySetError(int property, int area,
@CarPropertyManager.CarSetPropertyErrorCode int errorCode);
}
public PropertyHalService(VehicleHal vehicleHal) {
mPropIds = new PropertyHalServiceIds();
mSubscribedHalPropIds = new HashSet<Integer>();
mVehicleHal = vehicleHal;
if (mDbg) {
Log.d(TAG, "started PropertyHalService");
}
}
/**
* Set the listener for the HAL service
* @param listener
*/
public void setListener(PropertyHalListener listener) {
synchronized (mLock) {
mListener = listener;
}
}
/**
*
* @return List<CarPropertyConfig> List of configs available.
*/
public Map<Integer, CarPropertyConfig<?>> getPropertyList() {
if (mDbg) {
Log.d(TAG, "getPropertyList");
}
synchronized (mLock) {
if (mMgrPropIdToCarPropConfig.size() == 0) {
for (int i = 0; i < mHalPropIdToVehiclePropConfig.size(); i++) {
VehiclePropConfig p = mHalPropIdToVehiclePropConfig.valueAt(i);
int mgrPropId = halToManagerPropId(p.prop);
CarPropertyConfig config = CarPropertyUtils.toCarPropertyConfig(p, mgrPropId);
mMgrPropIdToCarPropConfig.put(mgrPropId, config);
}
}
}
return mMgrPropIdToCarPropConfig;
}
/**
* Returns property or null if property is not ready yet.
* @param mgrPropId
* @param areaId
*/
@Nullable
public CarPropertyValue getProperty(int mgrPropId, int areaId) {
int halPropId = managerToHalPropId(mgrPropId);
if (!isPropertySupportedInVehicle(halPropId)) {
throw new IllegalArgumentException("Invalid property Id : 0x" + toHexString(mgrPropId));
}
// CarPropertyManager catches and rethrows exception, no need to handle here.
VehiclePropValue value = mVehicleHal.get(halPropId, areaId);
if (isMixedTypeProperty(halPropId)) {
VehiclePropConfig propConfig;
synchronized (mLock) {
propConfig = mHalPropIdToVehiclePropConfig.get(halPropId);
}
boolean containBooleanType = propConfig.configArray.get(1) == 1;
return value == null ? null : toMixedCarPropertyValue(value,
mgrPropId, containBooleanType);
}
return value == null ? null : toCarPropertyValue(value, mgrPropId);
}
/**
* Returns sample rate for the property
* @param mgrPropId
*/
public float getSampleRate(int mgrPropId) {
int halPropId = managerToHalPropId(mgrPropId);
if (!isPropertySupportedInVehicle(halPropId)) {
throw new IllegalArgumentException("Invalid property Id : 0x" + toHexString(mgrPropId));
}
return mVehicleHal.getSampleRate(halPropId);
}
/**
* Get the read permission string for the property.
* @param mgrPropId
*/
@Nullable
public String getReadPermission(int mgrPropId) {
int halPropId = managerToHalPropId(mgrPropId);
return mPropIds.getReadPermission(halPropId);
}
/**
* Get the write permission string for the property.
* @param mgrPropId
*/
@Nullable
public String getWritePermission(int mgrPropId) {
int halPropId = managerToHalPropId(mgrPropId);
return mPropIds.getWritePermission(halPropId);
}
/**
* Return true if property is a display_units property
* @param mgrPropId
*/
public boolean isDisplayUnitsProperty(int mgrPropId) {
int halPropId = managerToHalPropId(mgrPropId);
return mPropIds.isPropertyToChangeUnits(halPropId);
}
/**
* Set the property value.
* @param prop
*/
public void setProperty(CarPropertyValue prop) {
int halPropId = managerToHalPropId(prop.getPropertyId());
if (!isPropertySupportedInVehicle(halPropId)) {
throw new IllegalArgumentException("Invalid property Id : 0x"
+ toHexString(prop.getPropertyId()));
}
VehiclePropValue halProp;
if (isMixedTypeProperty(halPropId)) {
// parse mixed type property value.
VehiclePropConfig propConfig;
synchronized (mLock) {
propConfig = mHalPropIdToVehiclePropConfig.get(prop.getPropertyId());
}
int[] configArray = propConfig.configArray.stream().mapToInt(i->i).toArray();
halProp = toMixedVehiclePropValue(prop, halPropId, configArray);
} else {
halProp = toVehiclePropValue(prop, halPropId);
}
// CarPropertyManager catches and rethrows exception, no need to handle here.
mVehicleHal.set(halProp);
}
/**
* Subscribe to this property at the specified update rate.
* @param mgrPropId
* @param rate
*/
public void subscribeProperty(int mgrPropId, float rate) {
if (mDbg) {
Log.d(TAG, "subscribeProperty propId=0x" + toHexString(mgrPropId) + ", rate=" + rate);
}
int halPropId = managerToHalPropId(mgrPropId);
if (!isPropertySupportedInVehicle(halPropId)) {
throw new IllegalArgumentException("Invalid property Id : 0x"
+ toHexString(mgrPropId));
}
synchronized (mLock) {
VehiclePropConfig cfg = mHalPropIdToVehiclePropConfig.get(halPropId);
if (rate > cfg.maxSampleRate) {
rate = cfg.maxSampleRate;
} else if (rate < cfg.minSampleRate) {
rate = cfg.minSampleRate;
}
mSubscribedHalPropIds.add(halPropId);
}
mVehicleHal.subscribeProperty(this, halPropId, rate);
}
/**
* Unsubscribe the property and turn off update events for it.
* @param mgrPropId
*/
public void unsubscribeProperty(int mgrPropId) {
if (mDbg) {
Log.d(TAG, "unsubscribeProperty propId=0x" + toHexString(mgrPropId));
}
int halPropId = managerToHalPropId(mgrPropId);
if (!isPropertySupportedInVehicle(halPropId)) {
throw new IllegalArgumentException("Invalid property Id : 0x"
+ toHexString(mgrPropId));
}
synchronized (mLock) {
if (mSubscribedHalPropIds.contains(halPropId)) {
mSubscribedHalPropIds.remove(halPropId);
mVehicleHal.unsubscribeProperty(this, halPropId);
}
}
}
@Override
public void init() {
if (mDbg) {
Log.d(TAG, "init()");
}
}
@Override
public void release() {
if (mDbg) {
Log.d(TAG, "release()");
}
synchronized (mLock) {
for (Integer halProp : mSubscribedHalPropIds) {
mVehicleHal.unsubscribeProperty(this, halProp);
}
mSubscribedHalPropIds.clear();
mHalPropIdToVehiclePropConfig.clear();
mMgrPropIdToCarPropConfig.clear();
mListener = null;
}
}
@Override
public boolean isSupportedProperty(int propId) {
return mPropIds.isSupportedProperty(propId);
}
@Override
public int[] getAllSupportedProperties() {
return CarServiceUtils.EMPTY_INT_ARRAY;
}
@Override
public void takeProperties(Collection<VehiclePropConfig> allProperties) {
for (VehiclePropConfig p : allProperties) {
if (mPropIds.isSupportedProperty(p.prop)) {
synchronized (mLock) {
mHalPropIdToVehiclePropConfig.put(p.prop, p);
}
if (mDbg) {
Log.d(TAG, "takeSupportedProperties: " + toHexString(p.prop));
}
}
}
if (mDbg) {
Log.d(TAG, "takeSupportedProperties() took " + allProperties.size()
+ " properties");
}
// If vehicle hal support to select permission for vendor properties.
VehiclePropConfig customizePermission;
synchronized (mLock) {
customizePermission = mHalPropIdToVehiclePropConfig.get(
VehicleProperty.SUPPORT_CUSTOMIZE_VENDOR_PERMISSION);
}
if (customizePermission != null) {
mPropIds.customizeVendorPermission(customizePermission.configArray);
}
}
@Override
public void onHalEvents(List<VehiclePropValue> values) {
PropertyHalListener listener;
synchronized (mLock) {
listener = mListener;
}
if (listener != null) {
for (VehiclePropValue v : values) {
if (v == null) {
continue;
}
if (!isPropertySupportedInVehicle(v.prop)) {
Log.e(TAG, "Property is not supported: 0x" + toHexString(v.prop));
continue;
}
// Check payload if it is a userdebug build.
if (Build.IS_DEBUGGABLE && !mPropIds.checkPayload(v)) {
Log.e(TAG, "Drop event for property: " + v + " because it is failed "
+ "in payload checking.");
continue;
}
int mgrPropId = halToManagerPropId(v.prop);
CarPropertyValue<?> propVal;
if (isMixedTypeProperty(v.prop)) {
// parse mixed type property value.
VehiclePropConfig propConfig;
synchronized (mLock) {
propConfig = mHalPropIdToVehiclePropConfig.get(v.prop);
}
boolean containBooleanType = propConfig.configArray.get(1) == 1;
propVal = toMixedCarPropertyValue(v, mgrPropId, containBooleanType);
} else {
propVal = toCarPropertyValue(v, mgrPropId);
}
CarPropertyEvent event = new CarPropertyEvent(
CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE, propVal);
mEventsToDispatch.add(event);
}
listener.onPropertyChange(mEventsToDispatch);
mEventsToDispatch.clear();
}
}
@Override
public void onPropertySetError(int halPropId, int area,
@CarPropertyManager.CarSetPropertyErrorCode int errorCode) {
PropertyHalListener listener;
synchronized (mLock) {
listener = mListener;
}
if (listener != null) {
int mgrPropId = halToManagerPropId(halPropId);
listener.onPropertySetError(mgrPropId, area, errorCode);
}
}
@Override
public void dump(PrintWriter writer) {
writer.println(TAG);
writer.println(" Properties available:");
synchronized (mLock) {
for (int i = 0; i < mHalPropIdToVehiclePropConfig.size(); i++) {
VehiclePropConfig p = mHalPropIdToVehiclePropConfig.valueAt(i);
writer.println(" " + p);
}
}
}
private static boolean isMixedTypeProperty(int halPropId) {
return (halPropId & VehiclePropertyType.MASK) == VehiclePropertyType.MIXED;
}
}