1.Hvac
Hvac:供暖通风与空气调节(Heating Ventilation and Air Conditioning)
在Android Automotive中,Hvac是作为控制汽车供暖通风与空气调节的系统应用,如下为Hvac源码目录结构及相关说明:
│ BootCompleteReceiver.java 用于开机启动HvacUiService
│ DataStore.java 存储管理Hvac属性数据
│ HvacController.java Hvac属性控制类
│ HvacPolicy.java Hvac属性规则类
│ HvacUiService.java Hvac界面服务
│ LocalHvacPropertyService.java Demo模式使用的模拟服务
├─controllers
│ FanDirectionButtonsController.java 风扇方向控制
│ FanSpeedBarController.java 风扇速度控制
│ HvacPanelController.java Hvac界面布局及相关逻辑
│ SeatWarmerController.java 座椅温度控制
│ TemperatureController.java 温度控制
└─ui
FanDirectionButtons.java
FanSpeedBar.java
FanSpeedBarSegment.java
HvacPanelRow.java
PressAndHoldTouchListener.java
SeatWarmerButton.java
TemperatureBarOverlay.java
ToggleButton.java
2.Hvac Manifest文件
在Hvac的Manifest文件中,一共注册了三个组件
服务HvacController:用于设置及获取Hvac相关属性
服务HvacUiService:用于显示Hvac的界面
广播接收器BootCompleteReceiver:接收开机启动广播并启动HvacUiService服务
3.Hvac启动
应用在Manifest文件中注册了开机广播,所以在开机完成后会收到开机完成的广播,然后再广播接收器里面会启动HvacUiService。
BootCompleteReceiver.java
@Override
public void onReceive(Context context, Intent intent) {
Intent hvacUiService = new Intent(context, HvacUiService.class);
context.startService(hvacUiService);
}
在HvacUiService启动的时候实例化HvacPanelController并绑定HvacController服务。
HvacUiService服务通过WindowManager将View添加Window中,并且给HvacPanelController传入了布局信息,然后在HvacPanelController中进行view的layout操作。并且在HvacPanelController包含了controller包下的FanSpeedBarController,SeatWarmerController等几个类,使得HvacPanelController可以作为Hvac的界面统一控制类。
HvacUiService.java
mHvacPanelController = new HvacPanelController(this /* context */, mContainer,
mWindowManager, mDriverTemperatureBar, mPassengerTemperatureBar,
mDriverTemperatureBarCollapsed, mPassengerTemperatureBarCollapsed
);
Intent bindIntent = new Intent(this /* context */, HvacController.class);
if (!bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE)) {
Log.e(TAG, "Failed to connect to HvacController.");
}
当HvacUiService成功绑定HvacController时,HvacPanelController会通过HvacController获取Hvac属性进行界面更新,并且HvacController也会从系统中读取各属性值,并存取在DateStore中。
HvacUiService.java
final Runnable r = () -> {
// Once the hvac controller has refreshed its values from the vehicle,
// bind all the values.
mHvacPanelController.updateHvacController(mHvacController);// 更新界面
};
if (mHvacController != null) {
mHvacController.requestRefresh(r, new Handler(context.getMainLooper()));// 存储数据
}
4.Hvac属性存储
DataStore用于存储HvacController从系统中获取的属性值。
我们可以从两个位置进行更新Hvac相关属性,即用户界面和硬件按钮。这两种不同的更新方式都是从不同的线程更新到当前状态。此外,在某些情况下,暖通空调系统可能会发送虚假的更新,因此这个类将所有内容更新管理合并,从而确保在用户看来应用程序的界面是正常的。
如风扇速度,DateStore会在收到系统事件是通过shouldPropagateFanSpeedUpdate方法判断是否需要更新,判断是否要更新的规则也很简单,即最近两秒内是否更新过该属性值,如果更新过,那么从系统回调的属性值将不会被Hvac应用处理。
DataStore.java
public int getFanSpeed() {
synchronized (mFanSpeed) {
return mFanSpeed;
}
}
public void setFanSpeed(int speed) {
synchronized (mFanSpeed) {
mFanSpeed = speed;
mLastFanSpeedSet = SystemClock.uptimeMillis();
}
}
public boolean shouldPropagateFanSpeedUpdate(int zone, int speed) {
// TODO: We ignore fan speed zones for now because we dont have a multi zone car.
synchronized (mFanSpeed) {
if (SystemClock.uptimeMillis() - mLastFanSpeedSet < COALESCE_TIME_MS) {
return false;
}
mFanSpeed = speed;
}
return true;
}
比如用户通过界面设置风扇速度
此时记录了最近设置风扇速度的时间mLastFanSpeedSet,而如果Hvac在两秒内收到系统的回调,可能是系统的错误信息也可能是通过风扇实体按钮改变的回调信息,那么这样的信息是不会显示在界面上的(这点有待验证)。
HvacController.java
public void setFanSpeed(final int fanSpeed) {
mDataStore.setFanSpeed(fanSpeed);
final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
int newFanSpeed;
protected Void doInBackground(Void... unused) {
if (mHvacManager != null) {
int zone = SEAT_ALL; // Car specific workaround.
try {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Setting fanspeed to: " + fanSpeed);
}
mHvacManager.setIntProperty(
CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone, fanSpeed);
newFanSpeed = mHvacManager.getIntProperty(
CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone);
} catch (android.car.CarNotConnectedException e) {
Log.e(TAG, "Car not connected in setFanSpeed");
}
}
return null;
}
@Override
protected void onPostExecute(final Void result) {
Log.e(TAG, "postExecute new fanSpeed: " + newFanSpeed);
}
};
task.execute();
}
5.HvacController
HvacController作为Hvac应用与系统的信息传输控制器。
在Hvac中的设置及获取操作都是通过HvacController进行的,在HvacController启动时会获取一个Car实例,并通过connect方法连接CarService。
HvacController.java
mCarApiClient = Car.createCar(this, mCarConnectionCallback);
mCarApiClient.connect();
当成功连接CarService时,会进行初始化CarHvacManager并通过CarHvacManager获取车辆支持的属性列表,然后通知HvacPanelController已经成连接CarService并更新相关属性存储到DataStore中。
HvacController.java
@Override
public void onConnected(Car car) {
synchronized (mHvacManagerReady) {
try {
initHvacManager((CarHvacManager) mCarApiClient.getCarManager(
android.car.Car.HVAC_SERVICE));
mHvacManagerReady.notifyAll();
} catch (CarNotConnectedException e) {
Log.e(TAG, "Car not connected in onServiceConnected");
}
}
}
private void initHvacManager(CarHvacManager carHvacManager) {
mHvacManager = carHvacManager;
List<CarPropertyConfig> properties = null;
try {
properties = mHvacManager.getPropertyList();
mPolicy = new HvacPolicy(HvacController.this, properties);
mHvacManager.registerCallback(mHardwareCallback);
} catch (android.car.CarNotConnectedException e) {
Log.e(TAG, "Car not connected in HVAC");
}
}
在HvacController中有两个重要的对象,Car和CarHvacManager,HvacController正是通过这两个对象与CarService进行通信。
(1)Car
Car作为汽车平台最高等级的API为外界提供汽车所有服务和数据的访问。
通过createCar方法可以新建一个Car实例,通过connect方法连接CarService。当成功连接时可以通过getCarManager方法获取一个一个相关的manager,比如Hvac通过getCarManager方法获取了一个CarHvacManager,当获取到manager后就可以进行相关操作了。
(2)CarHvacManager
CarHvacManager作为控制汽车Hvac系统的API为外界提供数据访问。
CarHvacManager实现了CarManagerBase接口,并且只要是作为Car*Manager, 都需要实现CarManagerBase接口,如CarCabinManager,CarSensorManager等都实现了该接口。
CarHvacManager的控制操作是通过CarPropertyManager来完成的,CarPropertyManager统一控制汽车属性相关的操作。CarHvacManager只是控制与Hvac相关的操作,在汽车中还有很多属性控制的Manager,如传感器,座舱等属性的控制,他们都是通过CarPropertyManager进行属性操作,通过在操作时传入的属性ID,属性区域以及属性值,在CarPropertyManager中会将这些参数转化为一个CarPropertyValue对象继续往下层传递。
CarHvacManager控制的属性有:
// 全局属性,只有一个
ID_MIRROR_DEFROSTER_ON //视镜除雾
ID_STEERING_WHEEL_HEAT //方向盘温度
ID_OUTSIDE_AIR_TEMP //室外温度
ID_TEMPERATURE_DISPLAY_UNITS //在使用的温度
// 区域属性,可在不同区域设置
ID_ZONED_TEMP_SETPOINT //用户设置的温度
ID_ZONED_TEMP_ACTUAL //区域实际温度
ID_ZONED_HVAC_POWER_ON //HVAC系统电源开关
ID_ZONED_FAN_SPEED_SETPOINT //风扇设置的速度
ID_ZONED_FAN_SPEED_RPM //风扇实际的速度
ID_ZONED_FAN_DIRECTION_AVAILABLE //风扇可设置的方向
ID_ZONED_FAN_DIRECTION //现在风扇设置的方向
ID_ZONED_SEAT_TEMP //座椅温度
ID_ZONED_AC_ON //空调开关
ID_ZONED_AUTOMATIC_MODE_ON //HVAC自动模式开关
ID_ZONED_AIR_RECIRCULATION_ON //空气循环开关
ID_ZONED_MAX_AC_ON //空调最大速度开关
ID_ZONED_DUAL_ZONE_ON //双区模式开关
ID_ZONED_MAX_DEFROST_ON //最大除雾开关
ID_ZONED_HVAC_AUTO_RECIRC_ON //自动循环模式开关
ID_WINDOW_DEFROSTER_ON //除雾模式开关
6.一次设置过程
现在我们跟踪一次风扇速度的设置过程。
点击Hvac界面中的风扇速度最大按钮时,通过在HvacPanelController中实例化时传入的mHvacController对象来设置最大速度。
FanSpeedBarController.java
private static final int MAX_FAN_SPEED = 6;
private FanSpeedBar.FanSpeedButtonClickListener mClickListener
= new FanSpeedBar.FanSpeedButtonClickListener() {
@Override
public void onMaxButtonClicked() {
mHvacController.setFanSpeed(MAX_FAN_SPEED);
}
……
};
HvacController收到传入的为值为MAX_FAN_SPEED = 6;
首先会将这个值存储到DataStore中,然后通过CarHvacManager的setInProperty方法设置
传入属性ID为ID_ZONED_FAN_SPEED_SETPOINT,属性区域为SEAT_ALL,属性值为要设置的风扇速度。
HvacController.java
// Hardware specific value for the front seats
public static final int SEAT_ALL = VehicleAreaSeat.SEAT_ROW_1_LEFT |
VehicleAreaSeat.SEAT_ROW_1_RIGHT | VehicleAreaSeat.SEAT_ROW_2_LEFT |
VehicleAreaSeat.SEAT_ROW_2_CENTER | VehicleAreaSeat.SEAT_ROW_2_RIGHT;
public void setFanSpeed(final int fanSpeed) {
mDataStore.setFanSpeed(fanSpeed);
final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
int newFanSpeed;
protected Void doInBackground(Void... unused) {
if (mHvacManager != null) {
int zone = SEAT_ALL; // Car specific workaround.
try {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Setting fanspeed to: " + fanSpeed);
}
mHvacManager.setIntProperty(
CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone, fanSpeed);
newFanSpeed = mHvacManager.getIntProperty(
CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone);
} catch (android.car.CarNotConnectedException e) {
Log.e(TAG, "Car not connected in setFanSpeed");
}
}
return null;
}
@Override
protected void onPostExecute(final Void result) {
Log.e(TAG, "postExecute new fanSpeed: " + newFanSpeed);
}
};
task.execute();
}
在CarHvacManager中进行了属性ID合法性的检查,判断这个属性是否为Hvac相关的属性。然后通过CarPropertyManager进行设置。
CarHvacManager.java
public void setIntProperty(@PropertyId int propertyId, int area, int val)
throws CarNotConnectedException {
if (mHvacPropertyIds.contains(propertyId)) {
mCarPropertyMgr.setIntProperty(propertyId, area, val);
}
}
在CarPropertyManager中继续进行属性设置,最后是通过CarPropertyService进行操作的,
而CarPropertyService是Car调用getCarManager时通过AIDL绑定的Service,所以可能会出现RemoteException,而出现这个异常时,意味着CarService不可用,需要抛出去让用户知道。
CarPropertyManager.java
/** Set int value of property*/
public void setIntProperty(int prop, int area, int val) throws CarNotConnectedException {
setProperty(Integer.class, prop, area, val);
}
/** Set CarPropertyValue */
public <E> void setProperty(Class<E> clazz, int propId, int area, E val)
throws CarNotConnectedException {
if (mDbg) {
Log.d(mTag, "setProperty, propId: 0x" + toHexString(propId)
+ ", area: 0x" + toHexString(area) + ", class: " + clazz + ", val: " + val);
}
try {
mService.setProperty(new CarPropertyValue<>(propId, area, val));
} catch (RemoteException e) {
Log.e(mTag, "setProperty failed with " + e.toString(), e);
throw new CarNotConnectedException(e);
}
}
在CarPropertyService中,会再次对属性ID合法性进行判断,并判断要修改该属性的应用是否有权限,最后通过PropertyHalService进行设置。
CarPropertyService.java
@Override
public void setProperty(CarPropertyValue prop) {
int propId = prop.getPropertyId();
if (mConfigs.get(propId) == null) {
// Do not attempt to register an invalid propId
Log.e(TAG, "setProperty: propId is not in config list:0x" + toHexString(propId));
return;
}
ICarImpl.assertPermission(mContext, mHal.getWritePermission(propId));
mHal.setProperty(prop);
}
在PropertyHalService中,首先将Car属性值对象转换为Vehicle的Hal属性值对象,并判断此车机是否支持该属性,然后通过VehicleHal进行设置,VehiclePropValue是在Hal层定义的一个数据类型。
PropertyHalService.java
public void setProperty(CarPropertyValue prop) {
int halPropId = managerToHalPropId(prop.getPropertyId());
if (halPropId == NOT_SUPPORTED_PROPERTY) {
throw new IllegalArgumentException("Invalid property Id : 0x"
+ toHexString(prop.getPropertyId()));
}
VehiclePropValue halProp = toVehiclePropValue(prop, halPropId);
try {
mVehicleHal.set(halProp);
} catch (PropertyTimeoutException e) {
Log.e(CarLog.TAG_PROPERTY, "set, property not ready 0x" + toHexString(halPropId), e);
throw new RuntimeException(e);
}
}
types.hal
/**
* Encapsulates the property name and the associated value. It
* is used across various API calls to set values, get values or to register for
* events.
*/
struct VehiclePropValue {
/** Time is elapsed nanoseconds since boot */
int64_t timestamp;
/**
* Area type(s) for non-global property it must be one of the value from
* VehicleArea* enums or 0 for global properties.
*/
int32_t areaId;
/** Property identifier */
int32_t prop;
/** Status of the property */
VehiclePropertyStatus status;
/**
* Contains value for a single property. Depending on property data type of
* this property (VehiclePropetyType) one field of this structure must be filled in.
*/
struct RawValue {
/**
* This is used for properties of types VehiclePropertyType#INT
* and VehiclePropertyType#INT_VEC
*/
vec<int32_t> int32Values;
/**
* This is used for properties of types VehiclePropertyType#FLOAT
* and VehiclePropertyType#FLOAT_VEC
*/
vec<float> floatValues;
/** This is used for properties of type VehiclePropertyType#INT64 */
vec<int64_t> int64Values;
/** This is used for properties of type VehiclePropertyType#BYTES */
vec<uint8_t> bytes;
/** This is used for properties of type VehiclePropertyType#STRING */
string stringValue;
};
RawValue value;
};
VehicleHal为车辆HAL的抽象。此类处理与本机HAL的接口信息,并对接收到的数据进行基本分析(类型检查)。
在这里的set方法中,使用的是HalClient的setValue方法。
VehicleHal.java
void set(VehiclePropValue propValue) throws PropertyTimeoutException {
mHalClient.setValue(propValue);
}
HalClient为直接与车辆HAL接口交互的类。
到此时,已经是与Hal层通过HIDL进行交互了,在HalClient实例化时,会传入一个Ivehicle对象,这个对象是在CarService创建时通过HIDL获取的对象,与Hal层进行数据传输。
HalClient.java
public void setValue(VehiclePropValue propValue) throws PropertyTimeoutException {
int status = invokeRetriable(() -> {
try {
return mVehicle.set(propValue);
} catch (RemoteException e) {
Log.e(CarLog.TAG_HAL, "Failed to set value", e);
return StatusCode.TRY_AGAIN;
}
}, WAIT_CAP_FOR_RETRIABLE_RESULT_MS, SLEEP_BETWEEN_RETRIABLE_INVOKES_MS);
……
}
如下方法为CarService通过HIDL的方式获取Hal层服务对象,获取的为Hal层VehicleService注册的服务。
CarService.java
@Nullable
private static IVehicle getVehicle() {
try {
return android.hardware.automotive.vehicle.V2_0.IVehicle.getService();
} catch (RemoteException e) {
Log.e(CarLog.TAG_SERVICE, "Failed to get IVehicle service", e);
} catch (NoSuchElementException e) {
Log.e(CarLog.TAG_SERVICE, "IVehicle service not registered yet");
}
return null;
}
VehicleService.cpp
ALOGI("Registering as service...");
status_t status = service->registerAsService();
在HalClient中调用了IVehicle的set方法,这个set方法的真正实现在EmulatedVehicleHal.cpp中
IVehicle.hal
/**
* Set a vehicle property value.
*
* Timestamp of data must be ignored for set operation.
*
* Setting some properties require having initial state available. If initial
* data is not available yet this call must return StatusCode::TRY_AGAIN.
* For a property with separate power control this call must return
* StatusCode::NOT_AVAILABLE error if property is not powered on.
*/
set(VehiclePropValue propValue) generates (StatusCode status);
EmulatedVehicleHal.cpp
StatusCode EmulatedVehicleHal::set(const VehiclePropValue& propValue) {
static constexpr bool shouldUpdateStatus = false;
if (propValue.prop == kGenerateFakeDataControllingProperty) {
StatusCode status = handleGenerateFakeDataRequest(propValue);
if (status != StatusCode::OK) {
return status;
}
} else if (mHvacPowerProps.count(propValue.prop)) {
auto hvacPowerOn = mPropStore->readValueOrNull(
toInt(VehicleProperty::HVAC_POWER_ON),
(VehicleAreaSeat::ROW_1_LEFT | VehicleAreaSeat::ROW_1_RIGHT |
VehicleAreaSeat::ROW_2_LEFT | VehicleAreaSeat::ROW_2_CENTER |
VehicleAreaSeat::ROW_2_RIGHT));
if (hvacPowerOn && hvacPowerOn->value.int32Values.size() == 1
&& hvacPowerOn->value.int32Values[0] == 0) {
return StatusCode::NOT_AVAILABLE;
}
} else {
// Handle property specific code
switch (propValue.prop) {
case OBD2_FREEZE_FRAME_CLEAR:
return clearObd2FreezeFrames(propValue);
case VEHICLE_MAP_SERVICE:
// Placeholder for future implementation of VMS property in the default hal. For
// now, just returns OK; otherwise, hal clients crash with property not supported.
return StatusCode::OK;
case AP_POWER_STATE_REPORT:
// This property has different behavior between get/set. When it is set, the value
// goes to the vehicle but is NOT updated in the property store back to Android.
// Commented out for now, because it may mess up automated testing that use the
// emulator interface.
// getEmulatorOrDie()->doSetValueFromClient(propValue);
return StatusCode::OK;
}
}
if (propValue.status != VehiclePropertyStatus::AVAILABLE) {
// Android side cannot set property status - this value is the
// purview of the HAL implementation to reflect the state of
// its underlying hardware
return StatusCode::INVALID_ARG;
}
auto currentPropValue = mPropStore->readValueOrNull(propValue);
if (currentPropValue == nullptr) {
return StatusCode::INVALID_ARG;
}
if (currentPropValue->status != VehiclePropertyStatus::AVAILABLE) {
// do not allow Android side to set() a disabled/error property
return StatusCode::NOT_AVAILABLE;
}
if (!mPropStore->writeValue(propValue, shouldUpdateStatus)) {
return StatusCode::INVALID_ARG;
}
getEmulatorOrDie()->doSetValueFromClient(propValue);
return StatusCode::OK;
}
7.Hvac注册及监听属性信息
在hardware的vehicle目录下,总共定义了3个hal文件:Ivehicle.hal, IvehicleCallback.hal和types.hal文件。在上面的一次设置过程中,我们可以看到在最后与hal层进行交互时调用的正是Ivehicle.hal中的set方法,而我们仅仅是通过CarService中以HIDL方式获取的Ivehicle服务来调用他,其中复杂的调用过程都由HIDL来完成,最终会调用到服务端实现该接口的方法中去,即EmulatedVehicleHal.cpp中的set方法。
这三个文件对应的功能为:
IVehicle.hal: 定义了服务端给客户端调用的的接口
IVehicleCallback.hal: 定义了服务端回调客户端的接口
types.hal: 定义了需要使用的数据结构
在Hvac应用中,通过HvacController注册了CarHvacManager.CarHvacEventCallback,我们用这个回调函数来接收Hvac相关的属性改变。
在CarHvacManager的registerCallback中,首先会通过传入的属性ID获取当前支持的属性列表(如果不支持,那么就在返回列表中就不会存在该属性),传输的ID列表mHvacPropertyIds为Hvac相关的属性ID,然后通过一个for循环,将可支持的属性ID都进行注册监听,并通过CarPropertyEventListenerToBase来接收属性变化。
CarHvacManager.java
public synchronized void registerCallback(CarHvacEventCallback 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 List<CarPropertyConfig> getPropertyList() throws CarNotConnectedException {
return mCarPropertyMgr.getPropertyList(mHvacPropertyIds);
}
在CarPropertyManager中进行简单的判断后调用CarPropertyService中的registerListener方法;
CarPropertyManager.java
public boolean registerListener(CarPropertyEventListener listener, int propertyId, float rate)
throws CarNotConnectedException {
synchronized (mActivePropertyListener) {
if (mCarPropertyEventToService == null) {
mCarPropertyEventToService = new CarPropertyEventListenerToService(this);
}
boolean needsServerUpdate = false;
CarPropertyListeners listeners;
listeners = mActivePropertyListener.get(propertyId);
if (listeners == null) {
listeners = new CarPropertyListeners(rate);
mActivePropertyListener.put(propertyId, listeners);
needsServerUpdate = true;
}
if (listeners.addAndUpdateRate(listener, rate)) {
needsServerUpdate = true;
}
if (needsServerUpdate) {
if (!registerOrUpdatePropertyListener(propertyId, rate)) {
return false;
}
}
}
return true;
}
private boolean registerOrUpdatePropertyListener(int propertyId, float rate)
throws CarNotConnectedException {
try {
mService.registerListener(propertyId, rate, mCarPropertyEventToService);
} catch (IllegalStateException e) {
CarApiUtil.checkCarNotConnectedExceptionFromCarService(e);
} catch (RemoteException e) {
throw new CarNotConnectedException(e);
}
return true;
}
在CarPropertyService的registerListener方法中,会判断当前要监听的属性是否已经监听,如果没有监听则会调用PropertyHalService中的subscribeProperty方法来监听该属性的变化;
CarPropertyService.java
@Override
public void registerListener(int propId, float rate, ICarPropertyEventListener listener) {
……
IBinder listenerBinder = listener.asBinder();
synchronized (mLock) {
// Get the client for this listener
Client client = mClientMap.get(listenerBinder);
if (client == null) {
client = new Client(listener);
}
client.addProperty(propId, rate);
// Insert the client into the propId --> clients map
List<Client> clients = mPropIdClientMap.get(propId);
if (clients == null) {
clients = new CopyOnWriteArrayList<Client>();
mPropIdClientMap.put(propId, clients);
}
if (!clients.contains(client)) {
clients.add(client);
}
// Set the HAL listener if necessary
if (!mListenerIsSet) {
mHal.setListener(this);
}
// Set the new rate
if (rate > mHal.getSampleRate(propId)) {
mHal.subscribeProperty(propId, rate);
}
}
……
}
在PropertyHalService中,进行简单处理进入到VehicleHal的subscribePropert的方法;
PropertyHalService.java
public void subscribeProperty(int propId, float rate) {
……
synchronized (mSubscribedPropIds) {
mSubscribedPropIds.add(halPropId);
}
mVehicleHal.subscribeProperty(this, halPropId, rate);
}
在VehicleHal的subscribeProperty方法中,判断当前要注册监听的属性是否是可注册的(即该属性是否可读或者是可更改的),然后进入HalClient的subscribe方法进行注册;
VehicleHal.java
public void subscribeProperty(HalServiceBase service, int property,
float samplingRateHz, int flags) throws IllegalArgumentException {
……
if (config == null) {
throw new IllegalArgumentException("subscribe error: config is null for property 0x" +
toHexString(property));
} else if (isPropertySubscribable(config)) {
SubscribeOptions opts = new SubscribeOptions();
opts.propId = property;
opts.sampleRate = samplingRateHz;
opts.flags = flags;
synchronized (this) {
assertServiceOwnerLocked(service, property);
mSubscribedProperties.put(property, opts);
}
try {
mHalClient.subscribe(opts);
} catch (RemoteException e) {
Log.e(CarLog.TAG_HAL, "Failed to subscribe to property: 0x" + property, e);
}
} else {
Log.e(CarLog.TAG_HAL, "Cannot subscribe to property: " + property);
}
}
在HalClient中,属性合法性问题都在之前进行了处理,这里直接通过Ivehicle获得的服务端方法进行注册,之后会通过HIDL调用到EmulatedVehicleHal的subscribe方法,在该方法中除了传入需要监听的属性之外,还传入了一个回调对象VehicleCallback。VehicleCallback继承自IVehicleCallback.Stub并实现了该类中的相关方法,当hal服务端属性信息变化时,会通过该回调把信息传回来,客户端会做出相应的处理。
HalClient.java
public void subscribe(SubscribeOptions... options) throws RemoteException {
mVehicle.subscribe(mInternalCallback, new ArrayList<>(Arrays.asList(options)));
}
至此,Car的属性注册及监听过程结束。
8.Car获取CarHvacManager
Car获取Car相关的manager都是通过传入要获取manager的名称,类似于getSystemService的方法,然后会返回相关的manager
获取manager是通过CarServiceLoaderEmbedded中的getCarManager方法
Car.java
public Object getCarManager(String serviceName)
throws CarNotConnectedException {
Object manager = null;
synchronized (mCarManagerLock) {
manager = mServiceMap.get(serviceName);
if (manager == null) {
manager = mCarServiceLoader.getCarManager(serviceName);
}
// do not store if it is not CarManagerBase. This can happen when system version
// is retrieved from this call.
if (manager != null && manager instanceof CarManagerBase) {
mServiceMap.put(serviceName, (CarManagerBase) manager);
}
}
return manager;
}
这里的mEmbeddedCar为包android.car下面的Car,而我们之前使用的Car为android.support.car下面的Car;
CarServiceLoaderEmbedded.java
public Object getCarManager(String serviceName) throws CarNotConnectedException {
Object manager;
try {
manager = mEmbeddedCar.getCarManager(serviceName);
} catch (android.car.CarNotConnectedException e) {
throw new CarNotConnectedException(e);
}
……
default:
return manager;
}
}
通过传入的manager名称来获取服务,然后为该服务创建一个manager并返回给请求者
其中ICar的getCarService的实现在ICarImpl中;
Car.java
public Object getCarManager(String serviceName) throws CarNotConnectedException {
CarManagerBase manager;
ICar service = getICarOrThrow();
synchronized (mCarManagerLock) {
manager = mServiceMap.get(serviceName);
if (manager == null) {
try {
IBinder binder = service.getCarService(serviceName);
……
manager = createCarManager(serviceName, binder);
……
mServiceMap.put(serviceName, manager);
} catch (RemoteException e) {
handleRemoteException(e);
}
}
}
return manager;
}
通过获取的服务新建manager实例,然后返回;
Car.java
private CarManagerBase createCarManager(String serviceName, IBinder binder)
throws CarNotConnectedException {
CarManagerBase manager = null;
switch (serviceName) {
……
case HVAC_SERVICE:
manager = new CarHvacManager(binder, mContext, mEventHandler);
break;
……
default:
break;
}
return manager;
}
可以看到,在请求舱室,暖通空调,信息,属性,传感器以及三方拓展服务是返回的都是CarPropertyService服务
IcarImpl.java
@Override
public IBinder getCarService(String serviceName) {
switch (serviceName) {
case Car.AUDIO_SERVICE:
return mCarAudioService;
……
case Car.CABIN_SERVICE:
case Car.HVAC_SERVICE:
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;
}
}
至此,获取manager的过程结束。
9.Car获取CarServie
这里的Car为android.car包下的Car
在CarService中定义了action
Car.java
public static final String CAR_SERVICE_INTERFACE_NAME = "android.car.ICar";
private void startCarService() {
Intent intent = new Intent();
intent.setPackage(CAR_SERVICE_PACKAGE);
intent.setAction(Car.CAR_SERVICE_INTERFACE_NAME);
boolean bound = mContext.bindServiceAsUser(intent, mServiceConnectionListener,
Context.BIND_AUTO_CREATE, UserHandle.CURRENT_OR_SELF);
……
}
AndroidManifest.xml
<service android:name=".CarService"
android:singleUser="true">
<intent-filter>
<action android:name="android.car.ICar" />
</intent-filter>
</service>
10.与Hal交互的数据类型
在Hal层定义了一系列的属性值,都放在了types.hal文件中。
如座椅属性与Hvac中传入的VehicleAreaSeat.java中的座椅属性,两者的值是相同的
types.hal
/**
* Various Seats in the car.
*/
enum VehicleAreaSeat : int32_t {
ROW_1_LEFT = 0x0001,
ROW_1_CENTER = 0x0002,
ROW_1_RIGHT = 0x0004,
ROW_2_LEFT = 0x0010,
ROW_2_CENTER = 0x0020,
ROW_2_RIGHT = 0x0040,
ROW_3_LEFT = 0x0100,
ROW_3_CENTER = 0x0200,
ROW_3_RIGHT = 0x0400
};
VehicleAreaSeat.java
public static final int SEAT_ROW_1_LEFT = 0x0001;
public static final int SEAT_ROW_1_CENTER = 0x0002;
public static final int SEAT_ROW_1_RIGHT = 0x0004;
public static final int SEAT_ROW_2_LEFT = 0x0010;
public static final int SEAT_ROW_2_CENTER = 0x0020;
public static final int SEAT_ROW_2_RIGHT = 0x0040;
public static final int SEAT_ROW_3_LEFT = 0x0100;
public static final int SEAT_ROW_3_CENTER = 0x0200;
public static final int SEAT_ROW_3_RIGHT = 0x0400;
11.车机属性与权限
在设置Hvac时,用到了暖通空调相关的属性。
在车机中,与PropertyHalService有关的属性可分为4种舱室属性(车门,车镜,座椅等),HVAC属性(空调,风扇等),信息属性(制造商,型号,ID等)以及传感器属性(车速,油量等),与修改这些属性需要的相关权限都被定义在PropertyHalServiceIds文件中。
PropertyHalServiceIds.java
// Index (key is propertyId, and the value is readPermission, writePermission
private final SparseArray<Pair<String, String>> mProps;
// HVAC properties
mProps.put(VehicleProperty.HVAC_FAN_SPEED, new Pair<>(
Car.PERMISSION_CONTROL_CAR_CLIMATE,
Car.PERMISSION_CONTROL_CAR_CLIMATE));
mProps.put(VehicleProperty.HVAC_FAN_DIRECTION, new Pair<>(
Car.PERMISSION_CONTROL_CAR_CLIMATE,
Car.PERMISSION_CONTROL_CAR_CLIMATE));
mProps.put(VehicleProperty.HVAC_TEMPERATURE_CURRENT, new Pair<>(
Car.PERMISSION_CONTROL_CAR_CLIMATE,
Car.PERMISSION_CONTROL_CAR_CLIMATE));
mProps.put(VehicleProperty.HVAC_TEMPERATURE_SET, new Pair<>(
Car.PERMISSION_CONTROL_CAR_CLIMATE,
Car.PERMISSION_CONTROL_CAR_CLIMATE));
mProps.put(VehicleProperty.HVAC_DEFROSTER, new Pair<>(
Car.PERMISSION_CONTROL_CAR_CLIMATE,
Car.PERMISSION_CONTROL_CAR_CLIMATE));
12.CarInfoManager
用于获取车辆固定属性信息的工具,这些信息都是不可变的。
获取这些信息都是通过CarPropertyService获得的
信息有:
BASIC_INFO_KEY_MANUFACTURER //制造商
BASIC_INFO_KEY_MODEL //型号
BASIC_INFO_KEY_MODEL_YEAR //
BASIC_INFO_KEY_VEHICLE_ID //车机ID
INFO_KEY_PRODUCT_CONFIGURATION //配置相关
BASIC_INFO_FUEL_CAPACITY //油箱容量
BASIC_INFO_FUEL_TYPES //燃油类型
BASIC_INFO_EV_BATTERY_CAPACITY //电源容量
BASIC_INFO_EV_CONNECTOR_TYPES //电源连接类型
13.CarPropertyService
CarPropertyService 实现了IcarProperty.aidl接口,控制各manager对车机属性的注册,监听以及读写。当属性发生变化时,回调给注册监听了该属性的manager,然后继续向上传递,使得各属性manager方便的处理车机相关属性。该类对车机属性的处理是通过PropertyHalService进行的,而在PropertyHalService中的操作为VehicleHal。
14.PropertyHalService
PropertyHalService是汽车HAL用来传输车辆属性的服务。PropertyHalService是继承自HalServiceBase,继承HalServiceBase的还有InputHalService, SensorHalService,PowerHalService以及VmsHalService。
PropertyHalService为CarPropertyService提供对各属性的操作。
网友评论