Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cb9d6e81 authored by Yu Shan's avatar Yu Shan
Browse files

Implement VUR in SubscriptionManager.

SubscriptionManager combines the enableVUR option for all the VHAL
clients and sends the request to IVehicleHardware. If some of
the clients enables VUR while others disable VUR, SubscriptionManager
will apply filtering for the clients that enable it.

Test: atest DefaultVehicleHalTest
Bug: 306748801
Change-Id: I1a375ab2c00c7f37e432ed039818367dd0465b19
parent 605faf68
Loading
Loading
Loading
Loading
+41 −15
Original line number Diff line number Diff line
@@ -117,18 +117,53 @@ class IVehicleHardware {
        return std::chrono::nanoseconds(0);
    }

    // A [propId, areaId] is newly subscribed or the update rate is changed.
    // A [propId, areaId] is newly subscribed or the subscribe options are changed.
    //
    // The 'options' contains the property ID, area ID and sample rate in Hz.
    // The subscribe options contain sample rate in Hz or enable/disable variable update rate.
    //
    // For continuous property, the sample rate is never 0 and indicates the new sample rate (or
    // the initial sample rate if this property was not subscribed before).
    // For continuous properties:
    //
    // For on-change property, the sample rate is always 0 and must be ignored.
    // The sample rate is never 0 and indicates the desired polling rate for this property. The
    // sample rate is guaranteed to be within supported {@code minSampleRate} and
    // {@code maxSampleRate} as specified in {@code VehiclePropConfig}.
    //
    // If the specified sample rate is not supported, e.g. vehicle bus only supports 5hz and 10hz
    // polling rate but the sample rate is 8hz, impl must choose the higher polling rate (10hz).
    //
    // Whether variable update rate is enabled is specified by {@code enableVariableUpdateRate} in
    // {@code SubscribeOptions}. If variable update rate is not supported for the
    // [propId, areaId], impl must ignore this option and always treat it as disabled.
    //
    // If variable update rate is disabled/not supported, impl must report all the property events
    // for this [propId, areaId] through {@code propertyChangeCallback} according to the sample
    // rate. E.g. a sample rate of 10hz must generate at least 10 property change events per second.
    //
    // If variable update rate is enabled AND supported, impl must only report property events
    // when the [propId, areaId]'s value or status changes (a.k.a same as on-change property).
    // The sample rate still guides the polling rate, but duplicate property events must be dropped
    // and not reported via {@code propertyChangeCallback}.
    //
    // Async property set error events are not affected by variable update rate and must always
    // be reported.
    //
    // If the impl is always polling at {@code maxSampleRate} for all continuous [propId, areaId]s,
    // and do not support variable update rate for any [propId, areaId], then this function can be a
    // no-op.
    //
    // For on-change properties:
    //
    // The sample rate is always 0 and must be ignored. If the impl is always subscribing to all
    // on-change properties, then this function can be no-op.
    //
    // For all properties:
    //
    // It is recommended to only deliver the subscribed property events to DefaultVehicleHal to
    // improve performance. However, even if unsubscribed property events are delivered, they
    // will be filtered out by DefaultVehicleHal.
    //
    // A subscription from VHAL client might not necessarily trigger this function.
    // DefaultVehicleHal will aggregate all the subscriptions from all the clients and notify
    // IVehicleHardware if new subscriptions are required or sample rate is updated.
    // IVehicleHardware if new subscriptions are required or subscribe options are updated.
    //
    // For example:
    // 1. VHAL initially have no subscriber for speed.
@@ -144,15 +179,6 @@ class IVehicleHardware {
    // 5. The second subscriber is removed, 'unsubscribe' is called.
    //    The impl can optionally disable the polling for vehicle speed.
    //
    // It is recommended to only deliver the subscribed property events to DefaultVehicleHal to
    // improve performance. However, even if unsubscribed property events are delivered, they
    // will be filtered out by DefaultVehicleHal.
    //
    // For continuous property, if the impl is always polling at {@code maxSampleRate} as specified
    // in config, then this function can be a no-op.
    //
    // For on-change property, if the impl is always subscribing to all on-change properties, then
    // this function can be no-op.
    virtual aidl::android::hardware::automotive::vehicle::StatusCode subscribe(
            [[maybe_unused]] aidl::android::hardware::automotive::vehicle::SubscribeOptions
                    options) {
+39 −9
Original line number Diff line number Diff line
@@ -36,20 +36,29 @@ namespace hardware {
namespace automotive {
namespace vehicle {

// A structure to represent subscription config for one subscription client.
struct SubConfig {
    float sampleRateHz;
    bool enableVur;
};

// A class to represent all the subscription configs for a continuous [propId, areaId].
class ContSubConfigs final {
  public:
    using ClientIdType = const AIBinder*;

    void addClient(const ClientIdType& clientId, float sampleRateHz);
    void addClient(const ClientIdType& clientId, float sampleRateHz, bool enableVur);
    void removeClient(const ClientIdType& clientId);
    float getMaxSampleRateHz() const;
    bool isVurEnabled() const;
    bool isVurEnabledForClient(const ClientIdType& clientId);

  private:
    float mMaxSampleRateHz = 0.;
    std::unordered_map<ClientIdType, float> mSampleRateHzByClient;
    bool mEnableVur;
    std::unordered_map<ClientIdType, SubConfig> mConfigByClient;

    void refreshMaxSampleRateHz();
    void refreshCombinedConfig();
};

// A thread-safe subscription manager that manages all VHAL subscriptions.
@@ -58,6 +67,7 @@ class SubscriptionManager final {
    using ClientIdType = const AIBinder*;
    using CallbackType =
            std::shared_ptr<aidl::android::hardware::automotive::vehicle::IVehicleCallback>;
    using VehiclePropValue = aidl::android::hardware::automotive::vehicle::VehiclePropValue;

    explicit SubscriptionManager(IVehicleHardware* vehicleHardware);
    ~SubscriptionManager();
@@ -92,11 +102,8 @@ class SubscriptionManager final {
    // For a list of updated properties, returns a map that maps clients subscribing to
    // the updated properties to a list of updated values. This would only return on-change property
    // clients that should be informed for the given updated values.
    std::unordered_map<CallbackType,
                       std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>>
    getSubscribedClients(
            std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>&&
                    updatedValues);
    std::unordered_map<CallbackType, std::vector<VehiclePropValue>> getSubscribedClients(
            std::vector<VehiclePropValue>&& updatedValues);

    // For a list of set property error events, returns a map that maps clients subscribing to the
    // properties to a list of errors for each client.
@@ -116,6 +123,21 @@ class SubscriptionManager final {

    IVehicleHardware* mVehicleHardware;

    struct VehiclePropValueHashPropIdAreaId {
        inline size_t operator()(const VehiclePropValue& vehiclePropValue) const {
            size_t res = 0;
            hashCombine(res, vehiclePropValue.prop);
            hashCombine(res, vehiclePropValue.areaId);
            return res;
        }
    };

    struct VehiclePropValueEqualPropIdAreaId {
        inline bool operator()(const VehiclePropValue& left, const VehiclePropValue& right) const {
            return left.prop == right.prop && left.areaId == right.areaId;
        }
    };

    mutable std::mutex mLock;
    std::unordered_map<PropIdAreaId, std::unordered_map<ClientIdType, CallbackType>,
                       PropIdAreaIdHash>
@@ -124,10 +146,15 @@ class SubscriptionManager final {
            mSubscribedPropsByClient GUARDED_BY(mLock);
    std::unordered_map<PropIdAreaId, ContSubConfigs, PropIdAreaIdHash> mContSubConfigsByPropIdArea
            GUARDED_BY(mLock);
    std::unordered_map<CallbackType,
                       std::unordered_set<VehiclePropValue, VehiclePropValueHashPropIdAreaId,
                                          VehiclePropValueEqualPropIdAreaId>>
            mContSubValuesByCallback GUARDED_BY(mLock);

    VhalResult<void> addContinuousSubscriberLocked(const ClientIdType& clientId,
                                                   const PropIdAreaId& propIdAreaId,
                                                   float sampleRateHz) REQUIRES(mLock);
                                                   float sampleRateHz, bool enableVur)
            REQUIRES(mLock);
    VhalResult<void> addOnChangeSubscriberLocked(const PropIdAreaId& propIdAreaId) REQUIRES(mLock);
    // Removes the subscription client for the continuous [propId, areaId].
    VhalResult<void> removeContinuousSubscriberLocked(const ClientIdType& clientId,
@@ -147,6 +174,9 @@ class SubscriptionManager final {
    // Checks whether the manager is empty. For testing purpose.
    bool isEmpty();

    bool isValueUpdatedLocked(const CallbackType& callback, const VehiclePropValue& value)
            REQUIRES(mLock);

    // Get the interval in nanoseconds accroding to sample rate.
    static android::base::Result<int64_t> getIntervalNanos(float sampleRateHz);
};
+2 −0
Original line number Diff line number Diff line
@@ -695,6 +695,8 @@ ScopedAStatus DefaultVehicleHal::subscribe(const CallbackType& callback,
        if (config.changeMode == VehiclePropertyChangeMode::CONTINUOUS) {
            optionCopy.sampleRate = getDefaultSampleRateHz(
                    optionCopy.sampleRate, config.minSampleRate, config.maxSampleRate);
            // TODO: set this to false if VUR is not supported for the [propId, areaId].
            optionCopy.enableVariableUpdateRate = false;
            continuousSubscriptions.push_back(std::move(optionCopy));
        } else {
            onChangeSubscriptions.push_back(std::move(optionCopy));
+90 −27
Original line number Diff line number Diff line
@@ -42,11 +42,13 @@ using ::ndk::ScopedAStatus;

constexpr float ONE_SECOND_IN_NANOS = 1'000'000'000.;

SubscribeOptions newSubscribeOptions(int32_t propId, int32_t areaId, float sampleRateHz) {
SubscribeOptions newSubscribeOptions(int32_t propId, int32_t areaId, float sampleRateHz,
                                     bool enableVur) {
    SubscribeOptions subscribedOptions;
    subscribedOptions.propId = propId;
    subscribedOptions.areaIds = {areaId};
    subscribedOptions.sampleRate = sampleRateHz;
    subscribedOptions.enableVariableUpdateRate = enableVur;

    return subscribedOptions;
}
@@ -79,32 +81,50 @@ Result<int64_t> SubscriptionManager::getIntervalNanos(float sampleRateHz) {
    return intervalNanos;
}

void ContSubConfigs::refreshMaxSampleRateHz() {
void ContSubConfigs::refreshCombinedConfig() {
    float maxSampleRateHz = 0.;
    bool enableVur = true;
    // This is not called frequently so a brute-focre is okay. More efficient way exists but this
    // is simpler.
    for (const auto& [_, sampleRateHz] : mSampleRateHzByClient) {
        if (sampleRateHz > maxSampleRateHz) {
            maxSampleRateHz = sampleRateHz;
    for (const auto& [_, subConfig] : mConfigByClient) {
        if (subConfig.sampleRateHz > maxSampleRateHz) {
            maxSampleRateHz = subConfig.sampleRateHz;
        }
        if (!subConfig.enableVur) {
            // If one client does not enable variable update rate, we cannot enable variable update
            // rate in IVehicleHardware.
            enableVur = false;
        }
    }
    mMaxSampleRateHz = maxSampleRateHz;
    mEnableVur = enableVur;
}

void ContSubConfigs::addClient(const ClientIdType& clientId, float sampleRateHz) {
    mSampleRateHzByClient[clientId] = sampleRateHz;
    refreshMaxSampleRateHz();
void ContSubConfigs::addClient(const ClientIdType& clientId, float sampleRateHz, bool enableVur) {
    mConfigByClient[clientId] = {
            .sampleRateHz = sampleRateHz,
            .enableVur = enableVur,
    };
    refreshCombinedConfig();
}

void ContSubConfigs::removeClient(const ClientIdType& clientId) {
    mSampleRateHzByClient.erase(clientId);
    refreshMaxSampleRateHz();
    mConfigByClient.erase(clientId);
    refreshCombinedConfig();
}

float ContSubConfigs::getMaxSampleRateHz() const {
    return mMaxSampleRateHz;
}

bool ContSubConfigs::isVurEnabled() const {
    return mEnableVur;
}

bool ContSubConfigs::isVurEnabledForClient(const ClientIdType& clientId) {
    return mConfigByClient[clientId].enableVur;
}

VhalResult<void> SubscriptionManager::addOnChangeSubscriberLocked(
        const PropIdAreaId& propIdAreaId) {
    if (mClientsByPropIdAreaId.find(propIdAreaId) != mClientsByPropIdAreaId.end()) {
@@ -115,7 +135,7 @@ VhalResult<void> SubscriptionManager::addOnChangeSubscriberLocked(
    int32_t propId = propIdAreaId.propId;
    int32_t areaId = propIdAreaId.areaId;
    if (auto status = mVehicleHardware->subscribe(
                newSubscribeOptions(propId, areaId, /*updateRateHz=*/0));
                newSubscribeOptions(propId, areaId, /*updateRateHz=*/0, /*enableVur*/ false));
        status != StatusCode::OK) {
        return StatusError(status)
               << StringPrintf("failed subscribe for prop: %s, areaId: %" PRId32,
@@ -125,10 +145,11 @@ VhalResult<void> SubscriptionManager::addOnChangeSubscriberLocked(
}

VhalResult<void> SubscriptionManager::addContinuousSubscriberLocked(
        const ClientIdType& clientId, const PropIdAreaId& propIdAreaId, float sampleRateHz) {
        const ClientIdType& clientId, const PropIdAreaId& propIdAreaId, float sampleRateHz,
        bool enableVur) {
    // Make a copy so that we don't modify 'mContSubConfigsByPropIdArea' on failure cases.
    ContSubConfigs newConfig = mContSubConfigsByPropIdArea[propIdAreaId];
    newConfig.addClient(clientId, sampleRateHz);
    newConfig.addClient(clientId, sampleRateHz, enableVur);
    return updateContSubConfigsLocked(propIdAreaId, newConfig);
}

@@ -159,14 +180,16 @@ VhalResult<void> SubscriptionManager::removeOnChangeSubscriberLocked(

VhalResult<void> SubscriptionManager::updateContSubConfigsLocked(const PropIdAreaId& propIdAreaId,
                                                                 const ContSubConfigs& newConfig) {
    if (newConfig.getMaxSampleRateHz() ==
        mContSubConfigsByPropIdArea[propIdAreaId].getMaxSampleRateHz()) {
    const auto& oldConfig = mContSubConfigsByPropIdArea[propIdAreaId];
    float newRateHz = newConfig.getMaxSampleRateHz();
    float oldRateHz = oldConfig.getMaxSampleRateHz();
    if (newRateHz == oldRateHz && newConfig.isVurEnabled() == oldConfig.isVurEnabled()) {
        mContSubConfigsByPropIdArea[propIdAreaId] = newConfig;
        return {};
    }
    float newRateHz = newConfig.getMaxSampleRateHz();
    int32_t propId = propIdAreaId.propId;
    int32_t areaId = propIdAreaId.areaId;
    if (newRateHz != oldRateHz) {
        if (auto status = mVehicleHardware->updateSampleRate(propId, areaId, newRateHz);
            status != StatusCode::OK) {
            return StatusError(status)
@@ -174,9 +197,10 @@ VhalResult<void> SubscriptionManager::updateContSubConfigsLocked(const PropIdAre
                                   ", sample rate: %f HZ",
                                   propIdToString(propId).c_str(), areaId, newRateHz);
        }
    }
    if (newRateHz != 0) {
        if (auto status =
                    mVehicleHardware->subscribe(newSubscribeOptions(propId, areaId, newRateHz));
        if (auto status = mVehicleHardware->subscribe(
                    newSubscribeOptions(propId, areaId, newRateHz, newConfig.isVurEnabled()));
            status != StatusCode::OK) {
            return StatusError(status) << StringPrintf(
                           "failed subscribe for prop: %s, areaId"
@@ -228,7 +252,8 @@ VhalResult<void> SubscriptionManager::subscribe(const std::shared_ptr<IVehicleCa
            };
            VhalResult<void> result;
            if (isContinuousProperty) {
                result = addContinuousSubscriberLocked(clientId, propIdAreaId, option.sampleRate);
                result = addContinuousSubscriberLocked(clientId, propIdAreaId, option.sampleRate,
                                                       option.enableVariableUpdateRate);
            } else {
                result = addOnChangeSubscriberLocked(propIdAreaId);
            }
@@ -324,6 +349,34 @@ VhalResult<void> SubscriptionManager::unsubscribe(SubscriptionManager::ClientIdT
    return {};
}

bool SubscriptionManager::isValueUpdatedLocked(const std::shared_ptr<IVehicleCallback>& callback,
                                               const VehiclePropValue& value) {
    const auto& it = mContSubValuesByCallback[callback].find(value);
    if (it == mContSubValuesByCallback[callback].end()) {
        mContSubValuesByCallback[callback].insert(value);
        return true;
    }

    if (it->timestamp > value.timestamp) {
        ALOGE("The updated property value: %s is outdated, ignored", value.toString().c_str());
        return false;
    }

    if (it->value == value.value && it->status == value.status) {
        // Even though the property value is the same, we need to store the new property event to
        // update the timestamp.
        mContSubValuesByCallback[callback].insert(value);
        ALOGD("The updated property value for propId: %" PRId32 ", areaId: %" PRId32
              " has the "
              "same value and status, ignored if VUR is enabled",
              it->prop, it->areaId);
        return false;
    }

    mContSubValuesByCallback[callback].insert(value);
    return true;
}

std::unordered_map<std::shared_ptr<IVehicleCallback>, std::vector<VehiclePropValue>>
SubscriptionManager::getSubscribedClients(std::vector<VehiclePropValue>&& updatedValues) {
    std::scoped_lock<std::mutex> lockGuard(mLock);
@@ -338,8 +391,18 @@ SubscriptionManager::getSubscribedClients(std::vector<VehiclePropValue>&& update
            continue;
        }

        for (const auto& [_, client] : mClientsByPropIdAreaId[propIdAreaId]) {
            clients[client].push_back(value);
        for (const auto& [client, callback] : mClientsByPropIdAreaId[propIdAreaId]) {
            auto& subConfigs = mContSubConfigsByPropIdArea[propIdAreaId];
            // If client wants VUR (and VUR is supported as checked in DefaultVehicleHal), it is
            // possible that VUR is not enabled in IVehicleHardware because another client does not
            // enable VUR. We will implement VUR filtering here for the client that enables it.
            if (subConfigs.isVurEnabledForClient(client) && !subConfigs.isVurEnabled()) {
                if (isValueUpdatedLocked(callback, value)) {
                    clients[callback].push_back(value);
                }
            } else {
                clients[callback].push_back(value);
            }
        }
    }
    return clients;
+14 −0
Original line number Diff line number Diff line
@@ -90,6 +90,10 @@ StatusCode MockVehicleHardware::checkHealth() {
}

StatusCode MockVehicleHardware::subscribe(SubscribeOptions options) {
    {
        std::scoped_lock<std::mutex> lockGuard(mLock);
        mSubscribeOptions.push_back(options);
    }
    for (int32_t areaId : options.areaIds) {
        if (auto status = subscribePropIdAreaId(options.propId, areaId, options.sampleRate);
            status != StatusCode::OK) {
@@ -99,6 +103,16 @@ StatusCode MockVehicleHardware::subscribe(SubscribeOptions options) {
    return StatusCode::OK;
}

std::vector<SubscribeOptions> MockVehicleHardware::getSubscribeOptions() {
    std::scoped_lock<std::mutex> lockGuard(mLock);
    return mSubscribeOptions;
}

void MockVehicleHardware::clearSubscribeOptions() {
    std::scoped_lock<std::mutex> lockGuard(mLock);
    mSubscribeOptions.clear();
}

StatusCode MockVehicleHardware::subscribePropIdAreaId(int32_t propId, int32_t areaId,
                                                      float sampleRateHz) {
    if (sampleRateHz == 0) {
Loading