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

news2024/11/17 21:23:53

在这里插入图片描述

前言

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

  • 一文了解 Android 车机如何处理中控的旋钮输入
  • 从实体按键看 Android 车机的自定义事件机制
  • 深度入门 Android 车机核心 CarService 的构成和链路

本篇文章我们聚焦 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_FASTEST100
SENSOR_RATE_FAST10
SENSOR_RATE_NORMAL1
SENSOR_RATE_UI5

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

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

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 如下,比如前排、驾驶侧、副驾驶侧、乘客侧、天窗、挡风玻璃等。

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;
}

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

Thread().run {
    carPropertyManager.setIntProperty(
        VehiclePropertyIds.WINDOW_POS,
        VehicleAreaWindow.WINDOW_ROW_1_LEFT,
        0
   )
}

工作原理

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

// 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(),不过指定了返回的数据类型。

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

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 继续发出读取的调用

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 类型返回。

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 来处理。

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 出去。

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 侧获取相应数据。

public abstract class VehicleStub {
    ...
    @Nullable
    public abstract HalPropValue get(HalPropValue requestedPropValue)
            throws RemoteException, ServiceSpecificException;
    ...
}

写入

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

  1. 事先构建待写入的属性实例 CarPropertyValue 并传入
  2. 传入属性变化时 callback 用的 CarPropertyEventListenerToService 实例
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 缓存起来,在属性变化的时候用。

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 侧。

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。

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 的相关属性

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

推荐阅读

  • 一文了解 Android 车机如何处理中控的旋钮输入
  • 从实体按键看 Android 车机的自定义事件机制
  • 深度入门 Android 车机核心 CarService 的构成和链路
  • Android 车机初体验:Auto,Automotive 傻傻分不清楚?

参考资料

  • CarPropertyManager

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1121203.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode--180 连续出现的数字

文章目录 1 题目描述2 结果示例3 解题思路3.1 MySQL 代码 4 知识拓展 1 题目描述 Logs表: ---------------------- | Column Name | Type | ---------------------- | id | int | | num | varchar | ----------------------在 SQL 中&#xff0c;id …

Mybatis的SqlRunner执行流程

Mybatis的SqlRunner执行流程 SqlRunner exec new SqlRunner(connection); Map<String, Object> row exec.selectOne("SELECT * FROM PRODUCT WHERE PRODUCTID ?", "FI-SW-01");connection.close();assertEquals("FI-SW-01", row.ge…

YOLOv5算法改进(19)— 手把手教你去更换NMS(DIoU-NMS/CIoU-NMS/EIoU-NMS/GIoU-NMS/SIoU-NMS)

前言:Hello大家好,我是小哥谈。YOLOv5中的NMS指非极大值抑制(Non-Maximum Suppression),它是一种用于目标检测算法中的后处理技术。在检测到多个重叠的边界框时,NMS可以帮助选择最佳的边界框。NMS的工作原理是首先根据预测边界框的置信度对它们进行排序,然后从置信度最高…

iOS逆向工程之Theos

如果你对iOS逆向工程有所了解&#xff0c;那么你对Tweak并不陌生。那么由Tweak我们又会引出Theos, 那么什么是Theos呢&#xff0c;简单一句话&#xff0c;Theos是一个越狱开发工具包&#xff0c;Theos是越狱开发工具的首先&#xff0c;因为其最大的特点就是简单。大道至简&…

python中多行注释与取消注释

在小白学习python编程的过程中&#xff0c;我们经常会发现很多同学们喜欢问的一个问题&#xff1a; 怎么多行注释呢&#xff1f; 怎么取消多行注释呢&#xff1f; 以上种种问题我相信来到这里都会得到相应答案 那我们接下来开始吧&#xff01; 文章目录 单行注释多行注释取消多…

c初阶检测题

选择题 1.局部变量的作用域是&#xff1a;&#xff08;D&#xff09; A.main函数内部 B.整个程序 C.main函数之前 D.局部变量所在的局部范围 2.字符串的结束标志是&#xff1a;&#xff08;A&#xff09; 作业内容 A.是’0’ B.是EOF C. 是’\0’ D.是空格 3.下面那个不是…

精选六个大学生可以做的兼职副业,学习赚钱两不误

在当今的社会中&#xff0c;大学生们面临着学业压力和生活开销。为了减轻家庭的负担&#xff0c;同时也为了更好地拓宽自己的视野和实践能力&#xff0c;许多大学生开始寻找一种既可以学习又能赚钱的副业。这样的选择&#xff0c;不仅可以弥补金钱上的不足&#xff0c;还可以提…

【java计算机毕设】博客管理系统 javaweb springboot vue mysql

目录 1.项目功能截图​ 2.项目简介 3.源码下载地址 1.项目功能截图 2.项目简介 博客管理系统 idea mysql5.7/8 vue html jdk1.8 系统功能&#xff1a; 前台功能&#xff1a;文章信息&#xff0c;相册信息&#xff0c;留言板&#xff0c;个人中心&#xff0c;后台管理 后…

闭包(函数)

把内部函数通过return扔出去 必要条件

靶机 DC-2

DC_2 信息搜集 存活检测 详细扫描 添加 host 后台网页扫描 网页信息搜集 主页面 找到 flag1&#xff0c;提示使用 cewl 密码生成工具 wp 登陆界面 使用 wpscan 扫描用户 wpscan --url http://dc-2/ --enumerate u 扫描出用户 tom、jerry、admin 将三个用户名写入文件中…

一文解密网络背后的秘密

一文解密网络背后的秘密 网络的发展史从键入网址到网页进行显示期间法上了什么&#xff1f;首先我们需要了解一下URLDNS解析建立连接什么是三次握手&#xff0c;可不可以两次呢&#xff1f; 本地发送请求&#xff0c;服务器处理请求断开连接 实践一下 相信大家对网络都不陌生了…

《认知觉醒:开启自我改变的原动力》

目录 第一章 大脑 — 一切问题的起源 第二章 潜意识 — 生命留给我们的彩蛋 第三章 元认知 — 人类的终极能能力 第四章 专注力——情绪和智慧的交叉地带 第五章 学习力——学习不是一味地努力 第六章 行动力 — 没有行动世界只是个概念 第七章 情绪力 — 情绪是多角度看…

【JavaScript】深入讲解浏览器渲染原理

一. 浏览器是如何渲染页面的&#xff1f; 当浏览器的网络线程收到 HTML 文档后&#xff0c;会产生一个渲染任务&#xff0c;并将其传递给渲染主线程的消息队列。 在事件循环机制的作用下&#xff0c;渲染主线程取出消息队列中的渲染任务&#xff0c;开启渲染流程。 渲染&…

Python数据分析实战-使用numpy.where方法基于条件替换某列的值(附源码和实现效果)

实现功能 在Pandas中&#xff0c;replace方法默认是基于精确匹配进行替换&#xff0c;而不是基于条件匹配。要实现基于条件的替换&#xff0c;可以使用numpy.where函数。将DataFrame中某一列的指定的两个值分别替换为0和1&#xff0c;其他值替换为2 实现代码 import pandas …

【tg】3:线程模型:4个主要线程

media :调用主线程worker therad 、network thread 是webrtc要求的module thread 也是webrtc的moudle需要的代码分布 G:\CDN\P2P-DEV\tdesktop-offical\Telegram\ThirdParty\tgcalls\tgcalls\StaticThreads.hgetThreads 触发创建线程 分别获取,看起来是分别只有1个的 Threads…

不愧是疑问解决神器(二)!你强任你强

不愧是疑问解决神器(二)&#xff01;你强任你强&#x1f44d;&#x1f44d;&#x1f44d; 第3章 代码的坏味道 1.神秘命令(Mysterious Name)? 整洁代码中最重要的一环就是有一个好名字&#xff0c;使他们能够清晰地表明自己的功能和用法。但正因为如此&#xff0c;命名就成了…

17.Tensor Product Spaces

同样&#xff0c;本文仍采用非标准 的符号。 在之前的文章里&#xff0c;已经展示了&#xff1a; 使用张量积将向量和协向量组合在一起可以为我们提供线性映射&#xff0c;这个线性映射的系数实际上只是一个数组的条目。 还展示了&#xff1a;使用张量积组合两个协向量&#x…

Linux系统之部署WBO在线协作白板

Linux系统之部署WBO在线协作白板 一、WBO白板工具介绍1.1 WBO白板简介1.2 WBO白板特点 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、检查本地环境3.1 检查本地操作系统版本3.2 检查系统内核版本 四、部署Node.js环境4.1 下载Node.js安装包4.2 解压Node.js安装包4.3 …

进制转换(二进制、八进制、十进制、十六进制)

目录 一&#xff1a;十进制转换为二进制、八进制、十六进制 &#xff08;1&#xff09;整数转换 &#xff08;2&#xff09;小数转换 1&#xff09;十进制转二进制 2&#xff09;十进制转八进制 3&#xff09;十进制转十六进制 二&#xff1a;二进制、八进制、十六进制转…

数据库实验二:图书信息管理系统数据查询与数据更新

实验项目名称&#xff1a;图书信息管理系统数据查询与数据更新 实验目的与要求实验原理与内容1&#xff0e; 实验原理&#xff1a;2 . 实验内容&#xff1a;2. 数据查询 实验设备与软件环境实验过程与结果1. 数据更新图书信息表t_book新增馆藏可借图书c_book借书证表t_libraryC…