Android Automotive

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提供对各属性的操作。

15.HIDL

一次使用HIDL实现简单的HAL通信过程

16.HAL

HAL简介

HAL开发步骤

17.Treble & HIDL

讲解Treble & HIDL源代码及相关实现过程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,667评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,361评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,700评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,027评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,988评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,230评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,705评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,366评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,496评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,405评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,453评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,725评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,803评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,015评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,514评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,111评论 2 341