从车窗升降一探 Android 车机的重要 API:车辆属性 CarProperty

前言

前面我们介绍过 Android 车机 Automotive OS 的几块重要内容:

本篇文章我们聚焦 Android 车机上最重要、最常用的接口:即车辆属性 CarPropertyManager

并结合车窗升降这种典型的场景来探究它的完整链路。

实现车窗升降

CarPropertyManager 通常针对某个 Property 发起读写,这些属性有很多,从车窗到空调、油量到续航等等。

想要控制它们,得需要知道它的唯一标识,并和系统定义的 ID 保持一致。那么车窗对应的 ID 为 VehiclePropertyIds 中的 WINDOW_POS,其要求 app 拥有专用的权限:

android.car.Car.PERMISSION_CONTROL_CAR_WINDOWS

属性监听

当目标属性发生变化,可以通过 CarPropertyEventCallback 通知到请求 App,为了满足各种场景,系统提供了设置通知频次的可能。

总共有如下几种:

通知频次类型 频次(HZ)
SENSOR_RATE_ONCHANGE -
SENSOR_RATE_FASTEST 100
SENSOR_RATE_FAST 10
SENSOR_RATE_NORMAL 1
SENSOR_RATE_UI 5

对于车窗、入座这些即时信号,采用 SENSOR_RATE_ONCHANGE 类型即可,意味着只在变化的时候通知。当然,注册的时候会立即回调一次以通知当前的数值。

代码很简单,构建 CarPropertyEventCallback 实例,并传递目标 Property ID 和上述的通知类型,即可完成该属性的监听。

kotlin 复制代码
java
复制代码
     class CarEventCallBack: CarPropertyManager.CarPropertyEventCallback {
         override fun onChangeEvent(value: CarPropertyValue<*>?) { }
     }
     ​
     val car = Car.createCar(context)
     val carPropertyManager =
         car?.getCarManager(Car.PROPERTY_SERVICE) as CarPropertyManager
     ​
     carPropertyManager.registerCallback(
         CarEventCallBack(),
         VehiclePropertyIds.WINDOW_POS,
         CarPropertyManager.SENSOR_RATE_ONCHANGE
     )

属性读写

对于车窗硬件来说,用户关心的是其升降的状况,系统用 0~100 来进行定义,继而决定了它的值为 Int 型。

那么读取的 API 为 getIntProperty(),参数:

  • prop:希望读取的属性 ID,比如上面的车窗 Property ID:WINDOW_POS
  • area :希望读取属性的位置信息 zone,对应到 VehicleAreaWindow 类型中常量

注意:该方法是同步的,而且因为车窗等属性的操作耗时,建议在子线程 invoke。

写入的 API 为 setIntProperty(),参数:

  • prop:希望改写的属性 ID,
  • areaId:该属性对应的位置薪资
  • val:Value to set,比如车窗即 0 ~ 100,对应着完全关闭到完全打开

和 getIntProperty() 一样,set 一样耗时,需要同样运行在子线程中。

系统预设的和 Window 相关的 zone areaId 如下,比如前排、驾驶侧、副驾驶侧、乘客侧、天窗、挡风玻璃等。

java 复制代码
java
复制代码
     package android.hardware.automotive.vehicle;
     ​
     public @interface VehicleAreaWindow {
       public static final int FRONT_WINDSHIELD = 1;
       public static final int REAR_WINDSHIELD = 2;
       public static final int ROW_1_LEFT = 16;
       public static final int ROW_1_RIGHT = 64;
       public static final int ROW_2_LEFT = 256;
       public static final int ROW_2_RIGHT = 1024;
       public static final int ROW_3_LEFT = 4096;
       public static final int ROW_3_RIGHT = 16384;
       public static final int ROOF_TOP_1 = 65536;
       public static final int ROOF_TOP_2 = 131072;
     }

如下代码展示如何了完全打开驾驶位车窗

scss 复制代码
java
复制代码
 Thread().run {
     carPropertyManager.setIntProperty(
         VehiclePropertyIds.WINDOW_POS,
         VehicleAreaWindow.WINDOW_ROW_1_LEFT,
         0
    )
 }

工作原理

首先,车窗相关的 area 在 HAL 层有相应的定义:

ini 复制代码
java
复制代码
 // android/hardware/automotive/vehicle/2.0/types.h 
 
 /**
  * Various windshields/windows in the car.
  */
 enum class VehicleAreaWindow : int32_t {
     FRONT_WINDSHIELD = 1 /* 0x00000001 */,
     REAR_WINDSHIELD = 2 /* 0x00000002 */,
     ROW_1_LEFT = 16 /* 0x00000010 */,
     ROW_1_RIGHT = 64 /* 0x00000040 */,
     ROW_2_LEFT = 256 /* 0x00000100 */,
     ROW_2_RIGHT = 1024 /* 0x00000400 */,
     ROW_3_LEFT = 4096 /* 0x00001000 */,
     ROW_3_RIGHT = 16384 /* 0x00004000 */,
     ROOF_TOP_1 = 65536 /* 0x00010000 */,
     ROOF_TOP_2 = 131072 /* 0x00020000 */,
 };

读取

直接看 getIntProperty(),首先调用 checkSupportedProperty() 检查是否支持该属性,当不支持的话抛出:

IllegalArgumentException: "Unsupported property:xxx"

接着调用 getProperty(),不过指定了返回的数据类型。

scala 复制代码
java
复制代码
     public class CarPropertyManager extends CarManagerBase {
         public int getIntProperty(int prop, int area) {
             checkSupportedProperty(prop);
             CarPropertyValue<Integer> carProp = getProperty(Integer.class, prop, area);
             return handleNullAndPropertyStatus(carProp, area, 0);
         }
     ​
         private void checkSupportedProperty(int propId) {
             switch (propId) {
                 case VehiclePropertyIds.INITIAL_USER_INFO:
                 case VehiclePropertyIds.SWITCH_USER:
                 case VehiclePropertyIds.CREATE_USER:
                 case VehiclePropertyIds.REMOVE_USER:
                 case VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION:
                     throw new IllegalArgumentException("Unsupported property: "
                             + VehiclePropertyIds.toString(propId) + " (" + propId + ")");
             }
         }
         ...
     }

getProperty() 的实现在于 CarPropertyService

scala 复制代码
java
复制代码
     public class CarPropertyManager extends CarManagerBase {
         public <E> CarPropertyValue<E> getProperty(@NonNull Class<E> clazz, int propId, int areaId) {
             checkSupportedProperty(propId);
     ​
             try {
                 CarPropertyValue<E> propVal = mService.getProperty(propId, areaId);
                 if (propVal != null && propVal.getValue() != null) {
                     Class<?> actualClass = propVal.getValue().getClass();
                 }
                 return propVal;
             }
             ...
         }
         ...
     }

CarPropertyService 按照如下步骤进行:

  1. 先到存放所有 Property ID 的 SparseArray 中检查是否确实存在该 Property,如果不存在的话打印 error 提醒并结束

  2. 获取该 Property 的 permission 配置,如果不存在的话,抛出:

    SecurityException: Platform does not have permission to read value for property Id: 0x...

  3. assertPermission() 检查当前 CarService 是否确实被授予了如上 permission

  4. 最后调用持有的 PropertyHalService 继续发出读取的调用

scala 复制代码
java
复制代码

     public class CarPropertyService extends ICarProperty.Stub
             implements CarServiceBase, PropertyHalService.PropertyHalListener {
         @Override
         public CarPropertyValue getProperty(int prop, int zone) ... {
             synchronized (mLock) {
                 if (mConfigs.get(prop) == null) {
                     // Do not attempt to register an invalid propId
                     Slogf.e(TAG, "getProperty: propId is not in config list:0x" + toHexString(prop));
                     return null;
                 }
             }
     ​
             // Checks if android has permission to read property.
             String permission = mHal.getReadPermission(prop);
             if (permission == null) {
                 throw new SecurityException("Platform does not have permission to read value for "
                         + "property Id: 0x" + Integer.toHexString(prop));
             }
             CarServiceUtils.assertPermission(mContext, permission);
             return runSyncOperationCheckLimit(() -> {
                 return mHal.getProperty(prop, zone);
             });
         }
         ...
     }

PropertyHalService 首先调用 managerToHalPropId() 将 Property ID 转为 HAL 中该 ID 的定义,并再度检查该 HAL ID 是否确实存在。如果不存在的话亦抛出:

IllegalArgumentException:Invalid property Id : 0x...

接着,通过 VehicleHal 传递 HAL 中 ID 继续读取得到 HalPropValue,当读取的 value 存在的话,首先得获取该 Property 在 HAL 层和上层定义的 HalPropConfig 规则。

最后依据 config 将 value 解析成 CarPropertyValue 类型返回。

scala 复制代码
java
复制代码
     public class PropertyHalService extends HalServiceBase {
     '/ '    ...
         public CarPropertyValue getProperty(int mgrPropId, int areaId)
                 throws IllegalArgumentException, ServiceSpecificException {
             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.
             HalPropValue value = mVehicleHal.get(halPropId, areaId);
             if (value == null) {
                 return null;
             }
             HalPropConfig propConfig;
             synchronized (mLock) {
                 propConfig = mHalPropIdToPropConfig.get(halPropId);
             }
             return value.toCarPropertyValue(mgrPropId, propConfig);
         }
         ...
     }

其实 VehicleHal 并未做太多处理就直接交给了 HalClient 来处理。

java 复制代码
java
复制代码
     public class VehicleHal implements HalClientCallback {
         ...
         public HalPropValue get(int propertyId)
                 throws IllegalArgumentException, ServiceSpecificException {
             return get(propertyId, NO_AREA);
         }
         ...
         public HalPropValue get(int propertyId, int areaId)
                 throws IllegalArgumentException, ServiceSpecificException {
             return mHalClient.getValue(mPropValueBuilder.build(propertyId, areaId));
         }
         ...
     }

HalClient 通过 invokeRetriable() 进行超时为 50ms 的 internalGet() 调用:如果结果是 TRY_AGAIN 并且尚未超时的话,再次调用;反之已经超时或者结果成功获取到的话,即结束。

后续会再次检查该 Result 中的 status,是否是不合法的、空的值等等,通过检查的话则返回 HalPropValue 出去。

ini 复制代码
java
复制代码
     final class HalClient {
         ...
         private static final int SLEEP_BETWEEN_RETRIABLE_INVOKES_MS = 50;
     ​
         HalPropValue getValue(HalPropValue requestedPropValue)
                 throws IllegalArgumentException, ServiceSpecificException {
             ObjectWrapper<ValueResult> resultWrapper = new ObjectWrapper<>();
             resultWrapper.object = new ValueResult();
             int status = invokeRetriable(() -> {
                 resultWrapper.object = internalGet(requestedPropValue);
                 return resultWrapper.object.status;
             }, mWaitCapMs, mSleepMs);
     ​
             ValueResult result = resultWrapper.object;
     ​
             if (StatusCode.INVALID_ARG == status) {
                 throw new IllegalArgumentException(
                         getValueErrorMessage("get", requestedPropValue, result.errorMsg));
             }
     ​
             if (StatusCode.OK != status || result.propValue == null) {
                 if (StatusCode.OK == status) {
                     status = StatusCode.NOT_AVAILABLE;
                 }
                 throw new ServiceSpecificException(
                         status, getValueErrorMessage("get", requestedPropValue, result.errorMsg));
             }
     ​
             return result.propValue;
         }
     ​
         private ValueResult internalGet(HalPropValue requestedPropValue) {
             final ValueResult result = new ValueResult();
             try {
                 result.propValue = mVehicle.get(requestedPropValue);
                 result.status = StatusCode.OK;
                 result.errorMsg = new String();
             }
             ...
             return result;
         }
         ...
     }

internalGet() 的实现由持有的 VehicleStub 实例的 get 方法完成,其实现对应于依据 HIDL 的配置调用 HAL 侧获取相应数据。

java 复制代码
java
复制代码
     public abstract class VehicleStub {
         ...
         @Nullable
         public abstract HalPropValue get(HalPropValue requestedPropValue)
                 throws RemoteException, ServiceSpecificException;
         ...
     }

写入

set 写入的链路和 get 大同小异,主要区别是:

  1. 事先构建待写入的属性实例 CarPropertyValue 并传入
  2. 传入属性变化时 callback 用的 CarPropertyEventListenerToService 实例
scala 复制代码
java
复制代码

     public class CarPropertyManager extends CarManagerBase {
         public void setIntProperty(int prop, int areaId, int val) {
             setProperty(Integer.class, prop, areaId, val);
         }
     ​
         public <E> void setProperty(@NonNull Class<E> clazz, int propId, int areaId, @NonNull E val) {
             checkSupportedProperty(propId);
             try {
                 runSyncOperation(() -> {
                     mService.setProperty(new CarPropertyValue<>(propId, areaId, val),
                             mCarPropertyEventToService);
                     return null;
                 });
             }
             ...
         }
     }

下一层 CarPropertyService 的实现也是通过 PropertyHalService 进行。

传入的 CarPropertyEventListenerToService 其实是 ICarPropertyEventListener AIDL 代理,这里会将其转为 Binder 对象,按照调用的源头 client 缓存起来,在属性变化的时候用。

ini 复制代码
java
复制代码
     public class CarPropertyService extends ICarProperty.Stub
             implements CarServiceBase, PropertyHalService.PropertyHalListener {
         public void setProperty(CarPropertyValue prop, ICarPropertyEventListener listener)
                 throws IllegalArgumentException, ServiceSpecificException {
             int propId = prop.getPropertyId();
             ...
             runSyncOperationCheckLimit(() -> {
                 mHal.setProperty(prop);
                 return null;
             });
             IBinder listenerBinder = listener.asBinder();
             synchronized (mLock) {
                 Client client = mClientMap.get(listenerBinder);
                 if (client == null) {
                     client = new Client(listener);
                 }
                 if (client.isDead()) {
                     Slogf.w(TAG, "the ICarPropertyEventListener is already dead");
                     return;
                 }
                 mClientMap.put(listenerBinder, client);
                 updateSetOperationRecorderLocked(propId, prop.getAreaId(), client);
             }
         }
         ...
     }

继续分发到 VehicleHal 侧。

scala 复制代码
java
复制代码
     public class PropertyHalService extends HalServiceBase {
         public void setProperty(CarPropertyValue prop)
                 throws IllegalArgumentException, ServiceSpecificException {
             int halPropId = managerToHalPropId(prop.getPropertyId());
             ...
             HalPropValue halPropValue = mPropValueBuilder.build(prop, halPropId, propConfig);
             // CarPropertyManager catches and rethrows exception, no need to handle here.
             mVehicleHal.set(halPropValue);
         }
         ...
     }

后续一样的是通过 VehicleHalHalClient,再到 VehicleStub,最后抵达 HAL。

java 复制代码
java
复制代码
     public class VehicleHal implements HalClientCallback {
         ...
         public void set(HalPropValue propValue)
                 throws IllegalArgumentException, ServiceSpecificException {
             mHalClient.setValue(propValue);
         }
     }
     ​
     final class HalClient {
         ...
         public void setValue(HalPropValue propValue)
                 throws IllegalArgumentException, ServiceSpecificException {
             ObjectWrapper<String> errorMsgWrapper = new ObjectWrapper<>();
             errorMsgWrapper.object = new String();
     ​
             int status = invokeRetriable(() -> {
                 try {
                     mVehicle.set(propValue);
                     errorMsgWrapper.object = new String();
                     return StatusCode.OK;
                 }
                 ...
             }, mWaitCapMs, mSleepMs);
             ...
         }
         ...
     }
     ​
     public abstract class VehicleStub {
         ...
         public abstract void set(HalPropValue propValue)
                 throws RemoteException, ServiceSpecificException;
         ...
     }

结语

结合一张图回顾下整个过程:

  1. App 先通过 Car lib 拿到 CarServiceCar 实例,CarService 会初始化所有 Car 相关的实现,比如其中车辆属性的化,会初始化 CarPropertyServicePropertyHalService
  2. 接着,App 会从 Car 实例获取车辆某个接口的实例,比如控制车辆属性的话,需要获取 CarPropertyManager,CarService 则会从初始化完成的 map 里返回已准备好的对应对象
  3. App 的属性读写会通过 AIDL 接口抵达直接负责的 CarPropertyService,然后到与 HAL 中车辆属性模块交互的 PropertyHalService,再到综合的 VehicleHal,最后通过 HIDL 接口抵达以及更下面的 Hal,并按照定义的数据类型更改 ECU 的相关属性

希望本文能言简意赅地带你了解车辆属性的大体全貌,感谢阅读。

推荐阅读

参考资料

作者:TechMerger

链接:juejin.cn/post/729200...

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

相关推荐
丘狸尾21 分钟前
[cisco 模拟器] ftp服务器配置
android·运维·服务器
van叶~2 小时前
探索未来编程:仓颉语言的优雅设计与无限可能
android·java·数据库·仓颉
Crossoads6 小时前
【汇编语言】端口 —— 「从端口到时间:一文了解CMOS RAM与汇编指令的交汇」
android·java·汇编·深度学习·网络协议·机器学习·汇编语言
li_liuliu7 小时前
Android4.4 在系统中添加自己的System Service
android
C4rpeDime9 小时前
自建MD5解密平台-续
android
鲤籽鲲11 小时前
C# Random 随机数 全面解析
android·java·c#
m0_5485147715 小时前
2024.12.10——攻防世界Web_php_include
android·前端·php
凤邪摩羯15 小时前
Android-性能优化-03-启动优化-启动耗时
android
凤邪摩羯15 小时前
Android-性能优化-02-内存优化-LeakCanary原理解析
android
喀什酱豆腐16 小时前
Handle
android