Automotive——Vehicle添加新的property示例
本文以添加Car Air purifier空气净化器为例,介绍如何在Automotive里添加新的属性,并在模拟器里验证
一、Hal层
1.1 type.hal
路径hardware\interfaces\automotive\vehicle\2.0\type.hal
首先要在type.hal中定义新的property CAP_POWER_ON CAP_LEVEL CAP_MODE三个新的属性,其中CAP_MODE里会使用自定义的属性类型,提前给他加上。
其中0x0601 为自定义ID值,前面4位有特殊含义:
第一位 参考 enum VehiclePropertyGroup 属性组 SYSTEM 1 VENDOR 2
第二位 参考 enum VehicleArea 属性作用域 GLOBAL 01 SEAT 05
第三、四位 参考 enum VehiclePropertyType 属性值类型 BOOLEAN 20 INT32 40
// CAP feature
/**
* car air purifier power on
* @change_mode VehiclePropertyChangeMode:ON_CHANGE
* @access VehiclePropertyAccess:READ_WRITE
*/
CAP_POWER_ON =(
0x0601
| VehiclePropertyGroup:SYSTEM
| VehiclePropertyType:BOOLEAN
| VehicleArea:GLOBAL),
/**
* car air purifier level
* @change_mode VehiclePropertyChangeMode:ON_CHANGE
* @access VehiclePropertyAccess:READ_WRITE
*/
CAP_LEVEL =(
0x0602
| VehiclePropertyGroup:SYSTEM
| VehiclePropertyType:INT32
| VehicleArea:SEAT),
/**
* car air purifier MODE
* @change_mode VehiclePropertyChangeMode:ON_CHANGE
* @access VehiclePropertyAccess:READ_WRITE
*/
CAP_MODE =(
0x0603
| VehiclePropertyGroup:SYSTEM
| VehiclePropertyType:INT32
| VehicleArea:GLOBAL),
// CAP feature
用于CAP_MODE的类型 AUTO自动模式 STERILIZE除菌模式 DEODORISER除臭模式
//CAP feature
enum VehicleCapMode : int32_t {
AUTO = 0x1,
STERILIZE = 0x2,
DEODORISER = 0x3,
};
//CAP feature
1.2 DefaultConfig.h
路径hardware\interfaces\automotive\vehicle\2.0\default\impl\vhal_v2_0\DefaultConfig.h
自定义cap的作用位置
//CAP feature
constexpr int CAP_1_LEFT = (int)VehicleAreaSeat::ROW_1_LEFT;
constexpr int CAP_1_RIGHT = (int)VehicleAreaSeat::ROW_1_RIGHT;
constexpr int CAP_2_LEFT = (int)VehicleAreaSeat::ROW_2_LEFT;
constexpr int CAP_2_RIGHT = (int)VehicleAreaSeat::ROW_2_RIGHT;
//CAP feature
添加三个属性的具体设置和初始化值
// CAP feature
{.config = {.prop = toInt(VehicleProperty::CAP_POWER_ON),
.access = VehiclePropertyAccess::READ_WRITE,
.changeMode = VehiclePropertyChangeMode::ON_CHANGE,
.areaConfigs = {VehicleAreaConfig{.areaId = (0)}}},
.initialValue = {.int32Values = {0}}},
{.config = {.prop = toInt(VehicleProperty::CAP_LEVEL),
.access = VehiclePropertyAccess::READ_WRITE,
.changeMode = VehiclePropertyChangeMode::ON_CHANGE,
// 引用之前定义的位置 并设置最大最小值
.areaConfigs = {VehicleAreaConfig{
.areaId = CAP_1_LEFT, .minInt32Value = 0, .maxInt32Value = 5,
},
VehicleAreaConfig{
.areaId = CAP_1_RIGHT, .minInt32Value = 0, .maxInt32Value = 5,
},
VehicleAreaConfig{
.areaId = CAP_2_LEFT, .minInt32Value = 0, .maxInt32Value = 5,
},
VehicleAreaConfig{
.areaId = CAP_2_RIGHT, .minInt32Value = 0, .maxInt32Value = 5,
}}},
// 给个初始值
.initialAreaValues = {{CAP_1_LEFT, {.int32Values = {2}}},
{CAP_1_RIGHT, {.int32Values = {3}}},
{CAP_2_LEFT, {.int32Values = {4}}},
{CAP_2_RIGHT, {.int32Values = {5}}}}},
{.config = {.prop = toInt(VehicleProperty::CAP_MODE),
.access = VehiclePropertyAccess::READ_WRITE,
.changeMode = VehiclePropertyChangeMode::ON_CHANGE,
.areaConfigs = {VehicleAreaConfig{.areaId = (0)}}},
.initialValue = {.int32Values = {toInt(VehicleCapMode::AUTO)}}},
// CAP feature
1.3 EmulatedVehicleHal.cpp
路径hardware\interfaces\automotive\vehicle\2.0\default\impl\vhal_v2_0\EmulatedVehicleHal.cpp
为了测试方便加个假的回调 在set方法里调用doHalEvent逻辑。
StatusCode EmulatedVehicleHal::set(const VehiclePropValue& propValue) {
······
//mock hal callback event
ALOGI("%s propId: 0x%x, areaId: 0x%x", __func__, propValue.prop, propValue.areaId);
doHalEvent(getValuePool()->obtain(propValue));
//mock hal callback event
······
getEmulatorOrDie()->doSetValueFromClient(propValue);
return StatusCode::OK;
}
1.4 Android.bp
路径hardware\interfaces\automotive\vehicle\2.0\Android.bp
将之前定义的VehicleCapMode 类型加入
types: [
//CAP feature
"VehicleCapMode",
//CAP feature
]
二、car-lib
2.1 CarApManager.java
路径 packages\services\Car\car-lib\src\android\car\hardware\cap\CarApManager.java
package android.car.hardware.cap;
import android.car.CarManagerBase;
import android.car.CarNotConnectedException;
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyValue;
import android.car.hardware.property.CarPropertyManager;
import android.content.Context;
import android.os.Handler;
import android.os.IBinder;
import android.util.ArraySet;
import android.util.Log;
import android.annotation.IntDef;
import android.annotation.SystemApi;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* @author : GW00175635
* @date : 2019/11/13 14:33
* desc :
* version: 1.0
*/
public final class CarApManager implements CarManagerBase {
private final static boolean DBG = false;
private final String TAG = "CarApManager";
private final CarPropertyManager mCarPropertyMgr;
private final ArraySet<CarApEventCallback> mCallbacks = new ArraySet<>();
private CarPropertyEventListenerToBase mListenerToBase = null;
//hal层定义的三个ID值
public static final int ID_CAP_POWER_ON = 0x11200601;
public static final int ID_CAP_LEVEL = 0x15400602;
public static final int ID_CAP_MODE = 0x11400603;
//hal层定义的三个Mode值
public static final int AUTO_MODE = 0x1;
public static final int STERILIZE_MODE = 0x2;
public static final int DEODORISER_MODE = 0x3;
//注解ArraySet 用于检测是否为本类可执行的ID
/** @hide */
@IntDef({
ID_CAP_POWER_ON,
ID_CAP_LEVEL,
ID_CAP_MODE
})
@Retention(RetentionPolicy.SOURCE)
public @interface CapPropertyId {}
private final ArraySet<Integer> mCapPropertyIds = new ArraySet<>(Arrays.asList(new Integer [] {
ID_CAP_POWER_ON,
ID_CAP_LEVEL,
ID_CAP_MODE
}));
//CarApEventCallback 上层回调接口 hal的 doHalEvent 最后回调到这
public interface CarApEventCallback {
/**
* Called when a property is updated
* @param value Property that has been updated.
*/
void onChangeEvent(CarPropertyValue value);
/**
* Called when an error is detected with a property
* @param propertyId
* @param zone
*/
void onErrorEvent(@CapPropertyId int propertyId, int zone);
}
private static class CarPropertyEventListenerToBase implements
CarPropertyManager.CarPropertyEventListener {
private final WeakReference<CarApManager> mManager;
public CarPropertyEventListenerToBase(CarApManager manager) {
mManager = new WeakReference<>(manager);
}
@Override
public void onChangeEvent(CarPropertyValue value) {
CarApManager manager = mManager.get();
if (manager != null) {
manager.handleOnChangeEvent(value);
}
}
@Override
public void onErrorEvent(int propertyId, int zone) {
CarApManager manager = mManager.get();
if (manager != null) {
manager.handleOnErrorEvent(propertyId, zone);
}
}
}
private void handleOnChangeEvent(CarPropertyValue value) {
Collection<CarApEventCallback> callbacks;
synchronized (this) {
callbacks = new ArraySet<>(mCallbacks);
}
if (!callbacks.isEmpty()) {
for (CarApEventCallback l: callbacks) {
l.onChangeEvent(value);
}
}
}
private void handleOnErrorEvent(int propertyId, int zone) {
Collection<CarApEventCallback> callbacks;
synchronized (this) {
callbacks = new ArraySet<>(mCallbacks);
}
if (!callbacks.isEmpty()) {
for (CarApEventCallback l: callbacks) {
l.onErrorEvent(propertyId, zone);
}
}
}
public CarApManager(IBinder service, Context context, Handler handler){
mCarPropertyMgr = new CarPropertyManager(service, handler, DBG, TAG);
}
public synchronized void registerCallback(CarApEventCallback callback)
throws CarNotConnectedException {
if (mCallbacks.isEmpty()) {
mListenerToBase = new CarPropertyEventListenerToBase(this);
}
List<CarPropertyConfig> configs = getPropertyList();
for (CarPropertyConfig c : configs) {
// Register each individual propertyId
mCarPropertyMgr.registerListener(mListenerToBase, c.getPropertyId(), 0);
}
mCallbacks.add(callback);
}
public synchronized void unregisterCallback(CarApEventCallback callback) {
mCallbacks.remove(callback);
try {
List<CarPropertyConfig> configs = getPropertyList();
for (CarPropertyConfig c : configs) {
// Register each individual propertyId
mCarPropertyMgr.unregisterListener(mListenerToBase, c.getPropertyId());
}
} catch (Exception e) {
Log.e(TAG, "getPropertyList exception ", e);
}
if (mCallbacks.isEmpty()) {
mCarPropertyMgr.unregisterListener(mListenerToBase);
mListenerToBase = null;
}
}
// 获取propertylist 从可用的propertylist(可用是通过当前APP的权限进行判定)挑出匹配到ArraySet的property
public List<CarPropertyConfig> getPropertyList() throws CarNotConnectedException {
return mCarPropertyMgr.getPropertyList(mCapPropertyIds);
}
public boolean isPropertyAvailable(@CapPropertyId int propertyId, int area)
throws CarNotConnectedException {
return mCarPropertyMgr.isPropertyAvailable(propertyId, area);
}
public boolean getBooleanProperty(@CapPropertyId int propertyId, int area)
throws CarNotConnectedException {
return mCarPropertyMgr.getBooleanProperty(propertyId, area);
}
public float getFloatProperty(@CapPropertyId int propertyId, int area)
throws CarNotConnectedException {
return mCarPropertyMgr.getFloatProperty(propertyId, area);
}
public int getIntProperty(@CapPropertyId int propertyId, int area)
throws CarNotConnectedException {
return mCarPropertyMgr.getIntProperty(propertyId, area);
}
public void setBooleanProperty(@CapPropertyId int propertyId, int area, boolean val)
throws CarNotConnectedException {
if (mCapPropertyIds.contains(propertyId)) {
mCarPropertyMgr.setBooleanProperty(propertyId, area, val);
}
}
public void setFloatProperty(@CapPropertyId int propertyId, int area, float val)
throws CarNotConnectedException {
if (mCapPropertyIds.contains(propertyId)) {
mCarPropertyMgr.setFloatProperty(propertyId, area, val);
}
}
public void setIntProperty(@CapPropertyId int propertyId, int area, int val)
throws CarNotConnectedException {
if (mCapPropertyIds.contains(propertyId)) {
mCarPropertyMgr.setIntProperty(propertyId, area, val);
}
}
/** @hide */
@Override
public void onCarDisconnected() {
mCarPropertyMgr.onCarDisconnected();
}
}
2.2 Car.java
路径packages\services\Car\car-lib\src\android\car\Car.java
在Car中添加CarApManager的专属权限,并初始化CarApManager。
import android.car.hardware.cap.CarApManager;
//CAP feature
/**
* @hide
*/
@SystemApi
public static final String CAP_SERVICE = "cap";
//CAP feature
//Cap feature
/**
* Permission necessary to access Car AP APIs.
* @hide
*/
@SystemApi
public static final String PERMISSION_CONTROL_CAR_AP =
"android.car.permission.CONTROL_CAR_AP";
//Cap feature
private CarManagerBase createCarManager(String serviceName, IBinder binder)
throws CarNotConnectedException {
CarManagerBase manager = null;
switch (serviceName) {
case AUDIO_SERVICE:
manager = new CarAudioManager(binder, mContext, mEventHandler);
break;
case HVAC_SERVICE:
manager = new CarHvacManager(binder, mContext, mEventHandler);
break;
//CAP feature
case CAP_SERVICE:
manager = new CarApManager(binder,mContext,mEventHandler);
break;
//CAP feature
case POWER_SERVICE:
manager = new CarPowerManager(binder, mContext, mEventHandler);
break;
case CAR_CONFIGURATION_SERVICE:
manager = new CarConfigurationManager(binder);
break;
default:
break;
}
return manager;
}
2.3 VehiclePropertyIds.java
路径packages\services\Car\car-lib\src\android\car\VehiclePropertyIds.java
添加新的ID
//Cap feature
public static final int CAP_POWER_ON = 287311361;
public static final int CAP_LEVEL = 356517378;
public static final int CAP_MODE = 289408515;
//Cap feature
public static String toString(int o) {
//Cap feature
if (o == CAP_POWER_ON) {
return "CAP_POWER_ON";
}
if (o == CAP_LEVEL) {
return "CAP_LEVEL";
}
if (o == CAP_MODE) {
return "CAP_MODE";
}
//Cap feature
}
2.4 system-current.txt
路径packages\services\Car\car-lib\api\system-current.txt
将car-lib的改动添加到这里
public final class Car {
····
field public static final java.lang.String CAP_SERVICE = "cap";
field public static final java.lang.String PERMISSION_CONTROL_CAR_AP = "android.car.permission.CONTROL_CAR_AP";
····
}
package android.car.hardware.cap {
public final class CarApManager {
method public boolean getBooleanProperty(int, int) throws android.car.CarNotConnectedException;
method public float getFloatProperty(int, int) throws android.car.CarNotConnectedException;
method public int getIntProperty(int, int) throws android.car.CarNotConnectedException;
method public java.util.List<android.car.hardware.CarPropertyConfig> getPropertyList() throws android.car.CarNotConnectedException;
method public static boolean isZonedProperty(int);
method public synchronized void registerCallback(android.car.hardware.cap.CarApManager.CarApEventCallback) throws android.car.CarNotConnectedException;
method public void setBooleanProperty(int, int, boolean) throws android.car.CarNotConnectedException;
method public void setFloatProperty(int, int, float) throws android.car.CarNotConnectedException;
method public void setIntProperty(int, int, int) throws android.car.CarNotConnectedException;
method public synchronized void unregisterCallback(android.car.hardware.cap.CarApManager.CarApEventCallback);
field public static final int ID_CAP_POWER_ON = 287311361; // 0x11200601
field public static final int ID_CAP_LEVEL = 356517378; // 0x15400602
field public static final int ID_CAP_MODE = 289408515; // 0x11400603
}
public static abstract interface CarApManager.CarApEventCallback {
method public abstract void onChangeEvent(android.car.hardware.CarPropertyValue);
method public abstract void onErrorEvent(int, int);
}
}
三、carService
car-lib的真正实现者,对接hal的so库
3.1 ICarImpl.java
路径packages\services\Car\service\src\com\android\car\ ICarImpl.java
CarApManager是对CarPropertyManager的封装
@Override
public IBinder getCarService(String serviceName) {
switch (serviceName) {
·····
case Car.CABIN_SERVICE:
case Car.HVAC_SERVICE:
//Cap feature
case Car.CAP_SERVICE:
//Cap feature
case Car.INFO_SERVICE:
case Car.PROPERTY_SERVICE:
case Car.SENSOR_SERVICE:
case Car.VENDOR_EXTENSION_SERVICE:
return mCarPropertyService;
·····
default:
Log.w(CarLog.TAG_SERVICE, "getCarService for unknown service:" + serviceName);
return null;
}
}
3.2 PropertyHalServiceIds.java
路径packages\services\Car\service\src\com\android\car\hal\PropertyHalServiceIds.java
将权限和property对应放入map里
public PropertyHalServiceIds() {
`````
//Cap feature
mProps.put(VehicleProperty.CAP_POWER_ON, new Pair<>(
Car.PERMISSION_CONTROL_CAR_AP,
Car.PERMISSION_CONTROL_CAR_AP));
mProps.put(VehicleProperty.CAP_LEVEL, new Pair<>(
Car.PERMISSION_CONTROL_CAR_AP,
Car.PERMISSION_CONTROL_CAR_AP));
mProps.put(VehicleProperty.CAP_MODE, new Pair<>(
Car.PERMISSION_CONTROL_CAR_AP,
Car.PERMISSION_CONTROL_CAR_AP));
//Cap feature
`````
}
3.3 AndroidManifest.xml
路径packages\services\Car\service\AndroidManifest.xml
在service里加入car ap权限
<permission
android:name="android.car.permission.CONTROL_CAR_AP"
android:protectionLevel="system|signature"
android:label="@string/car_permission_label_ap"
android:description="@string/car_permission_desc_ap" />
四、调用和测试
app AndroidManifest 里添加android.car.permission.CONTROL_CAR_AP 权限
private void init(Car car){
mCarApManager = (CarApManager) car.getCarManager(Car.CAP_SERVICE);
List<CarPropertyConfig> carPropertyConfigs = mCarApManager.getPropertyList();
}
截图
log
11-16 05:10:39.560 D 6153 6153 BaseCarTestFragment_ApFragment: onStartTrackingTouch:
11-16 05:10:39.560 D 6153 6153 BaseCarTestFragment_ApFragment: onProgressChanged: isFloatValue false value 1
11-16 05:10:39.653 D 6153 6153 BaseCarTestFragment_ApFragment: onStopTrackingTouch:
11-16 05:10:39.673 D 6153 6153 CapCarViewModel: handleMessage: propertyId = 356517378 areaId = 1 type = 1 value = 1
11-16 05:10:39.674 I 1614 5898 DefaultVehicleHal_v2_0: set propId: 0x15400602, areaId: 0x1
11-16 05:10:39.688 D 6153 6153 CapCarViewModel: onChangeEvent: propertyId = 356517378 areaId = 1 value = 1
11-16 05:10:39.688 D 6153 6153 CapCarViewModel: onChangeEvent: CarPropertyValue{mPropertyId=0x15400602, mAreaId=0x1, mStatus=0, mTimestamp=0, mValue=1}
11-16 05:10:39.688 D 6153 6153 CapCarViewModel: onChangeEvent: find and update list ID_CAP_LEVEL
11-16 05:10:39.689 D 6153 6153 ApFragment: onChanged: 2 0 [BaseCarProperty{propertyId=356517378, propertyName=ID_CAP_LEVEL, access=3, changeMode=1, configString=, configArray=[], maxSampleRate=0.0, minSampleRate=0.0, global=false, type=class java.lang.Integer, description=unDefineDes, areaCount=4, areaIds=[1, 4, 16, 64], mCarPropertyConfig=CarPropertyConfig{mPropertyId=356517378, mAccess=3, mAreaType=3, mChangeMode=1, mConfigArray=[], mConfigString=, mMaxSampleRate=0.0, mMinSampleRate=0.0, mSupportedAreas={1=CarAreaConfig{mMinValue=0, mMaxValue=5}, 4=CarAreaConfig{mMinValue=0, mMaxValue=5}, 16=CarAreaConfig{mMinValue=0, mMaxValue=5}, 64=CarAreaConfig{mMinValue=0, mMaxValue=5}}, mType=class java.lang.Integer}}, BaseCarProperty{propertyId=289408515, propertyName=ID_CAP_MODE, access=3, changeMode=1, configString=, configArray=[], maxSampleRate=0.0, minSampleRate=0.0, global=true, type=class java.lang.Integer, description=unDefineDes, areaCount=1, areaIds=[0], mCarPropertyConfig=CarPropertyConfig{mPropertyId=289408515, mAccess=3, mAreaType=0, mChangeMode=1, mConfigArray=[], mConfigString=, mMaxSampleRate=0.0, mMinSampleRate=0.0, mSupportedAreas={0=CarAreaConfig{mMinValue=0, mMaxValue=0}}, mType=class java.lang.Integer}}, BaseCarProperty{propertyId=287311361, propertyName=ID_CAP_POWER_ON, access=3, changeMode=1, configString=, configArray=[], maxSampleRate=0.0, minSampleRate=0.0, global=true, type=class java.lang.Boolean, description=unDefineDes, areaCount=1, areaIds=[0], mCarPropertyConfig=CarPropertyConfig{mPropertyId=287311361, mAccess=3, mAreaType=0, mChangeMode=1, mConfigArray=[], mConfigString=, mMaxSampleRate=0.0, mMinSampleRate=0.0, mSupportedAreas={0=null}, mType=class java.lang.Boolean}}]
五、问题bug
模拟器设置里直接点击ID设值,模拟器会崩溃,原因未知,后续解决