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

Commit 863a1b2e authored by Yu Shan's avatar Yu Shan
Browse files

Use batched property updates in IVehicleHardware.

This CL demonstrates how IVehicleHardware layer can batch property
update events for all continuous properties subscribed
at the same interval. For example, previously, 4 areas for tire
pressure, despite subscribed with the same interval, will generate
4 property update events through 4 binder calls. Since they are
updated at the same time, all the 4 events can be sent through one
binder call.

Note that this is different from the batching done at DefaultVehicleHal
side where the property events are put into a queue for batching. If
IVehicleHardware layer can provides batching, then we can avoid the
extra latency introduced by batching in DefaultVehicleHal layer.

In order to achieve batching, this CL adds several functions to
VehiclePropertyStore that operates on a list of values. Note that
VehiclePropertyStore is still backward compatible with this CL,
meaning that old APIs are not affected. But in order to achieve
better performance, caller can migrate to the newer APIs.

Test: atest FakeVehicleHardwareTest VehicleHalVehicleUtilsTest
Bug: 314850840
Change-Id: I94a13d3ed2b90aede4d627f73ce2f2828bb3e740
parent b2d54bf9
Loading
Loading
Loading
Loading
+22 −2
Original line number Diff line number Diff line
@@ -142,6 +142,16 @@ class FakeVehicleHardware : public IVehicleHardware {
        void handleRequestsOnce();
    };

    struct RefreshInfo {
        VehiclePropertyStore::EventMode eventMode;
        int64_t intervalInNanos;
    };

    struct ActionForInterval {
        std::unordered_set<PropIdAreaId, PropIdAreaIdHash> propIdAreaIdsToRefresh;
        std::shared_ptr<RecurrentTimer::Callback> recurrentAction;
    };

    const std::unique_ptr<obd2frame::FakeObd2Frame> mFakeObd2Frame;
    const std::unique_ptr<FakeUserHal> mFakeUserHal;
    // RecurrentTimer is thread-safe.
@@ -154,8 +164,9 @@ class FakeVehicleHardware : public IVehicleHardware {
    std::unique_ptr<const PropertySetErrorCallback> mOnPropertySetErrorCallback;

    std::mutex mLock;
    std::unordered_map<PropIdAreaId, std::shared_ptr<RecurrentTimer::Callback>, PropIdAreaIdHash>
            mRecurrentActions GUARDED_BY(mLock);
    std::unordered_map<PropIdAreaId, RefreshInfo, PropIdAreaIdHash> mRefreshInfoByPropIdAreaId
            GUARDED_BY(mLock);
    std::unordered_map<int64_t, ActionForInterval> mActionByIntervalInNanos GUARDED_BY(mLock);
    std::unordered_map<PropIdAreaId, VehiclePropValuePool::RecyclableType, PropIdAreaIdHash>
            mSavedProps GUARDED_BY(mLock);
    std::unordered_set<PropIdAreaId, PropIdAreaIdHash> mSubOnChangePropIdAreaIds GUARDED_BY(mLock);
@@ -183,6 +194,10 @@ class FakeVehicleHardware : public IVehicleHardware {
    void onValueChangeCallback(
            const aidl::android::hardware::automotive::vehicle::VehiclePropValue& value)
            EXCLUDES(mLock);
    // The callback that would be called when multiple vehicle property value changes happen.
    void onValuesChangeCallback(
            std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue> values)
            EXCLUDES(mLock);
    // Load the config files in format '*.json' from the directory and parse the config files
    // into a map from property ID to ConfigDeclarations.
    void loadPropConfigsFromDir(const std::string& dirPath,
@@ -276,6 +291,11 @@ class FakeVehicleHardware : public IVehicleHardware {
            const aidl::android::hardware::automotive::vehicle::VehiclePropConfig&
                    vehiclePropConfig) REQUIRES(mLock);

    void registerRefreshLocked(PropIdAreaId propIdAreaId, VehiclePropertyStore::EventMode eventMode,
                               float sampleRateHz) REQUIRES(mLock);
    void unregisterRefreshLocked(PropIdAreaId propIdAreaId) REQUIRES(mLock);
    void refreshTimeStampForInterval(int64_t intervalInNanos) EXCLUDES(mLock);

    static aidl::android::hardware::automotive::vehicle::VehiclePropValue createHwInputKeyProp(
            aidl::android::hardware::automotive::vehicle::VehicleHwKeyInputAction action,
            int32_t keyCode, int32_t targetDisplay);
+107 −32
Original line number Diff line number Diff line
@@ -313,8 +313,9 @@ void FakeVehicleHardware::init() {
        mFakeObd2Frame->initObd2FreezeFrame(maybeObd2FreezeFrame.value());
    }

    mServerSidePropStore->setOnValueChangeCallback(
            [this](const VehiclePropValue& value) { return onValueChangeCallback(value); });
    mServerSidePropStore->setOnValuesChangeCallback([this](std::vector<VehiclePropValue> values) {
        return onValuesChangeCallback(std::move(values));
    });
}

std::vector<VehiclePropConfig> FakeVehicleHardware::getAllPropertyConfigs() const {
@@ -2047,6 +2048,81 @@ bool FakeVehicleHardware::isVariableUpdateRateSupported(const VehiclePropConfig&
    return false;
}

void FakeVehicleHardware::refreshTimeStampForInterval(int64_t intervalInNanos) {
    std::unordered_map<PropIdAreaId, VehiclePropertyStore::EventMode, PropIdAreaIdHash>
            eventModeByPropIdAreaId;

    {
        std::scoped_lock<std::mutex> lockGuard(mLock);

        if (mActionByIntervalInNanos.find(intervalInNanos) == mActionByIntervalInNanos.end()) {
            ALOGE("No actions scheduled for the interval: %" PRId64 ", ignore the refresh request",
                  intervalInNanos);
            return;
        }

        ActionForInterval actionForInterval = mActionByIntervalInNanos[intervalInNanos];

        // Make a copy so that we don't hold the lock while trying to refresh the timestamp.
        // Refreshing the timestamp will inovke onValueChangeCallback which also requires lock, so
        // we must not hold lock.
        for (const PropIdAreaId& propIdAreaId : actionForInterval.propIdAreaIdsToRefresh) {
            const RefreshInfo& refreshInfo = mRefreshInfoByPropIdAreaId[propIdAreaId];
            eventModeByPropIdAreaId[propIdAreaId] = refreshInfo.eventMode;
        }
    }

    mServerSidePropStore->refreshTimestamps(eventModeByPropIdAreaId);
}

void FakeVehicleHardware::registerRefreshLocked(PropIdAreaId propIdAreaId,
                                                VehiclePropertyStore::EventMode eventMode,
                                                float sampleRateHz) {
    if (mRefreshInfoByPropIdAreaId.find(propIdAreaId) != mRefreshInfoByPropIdAreaId.end()) {
        unregisterRefreshLocked(propIdAreaId);
    }

    int64_t intervalInNanos = static_cast<int64_t>(1'000'000'000. / sampleRateHz);
    RefreshInfo refreshInfo = {
            .eventMode = eventMode,
            .intervalInNanos = intervalInNanos,
    };
    mRefreshInfoByPropIdAreaId[propIdAreaId] = refreshInfo;

    if (mActionByIntervalInNanos.find(intervalInNanos) != mActionByIntervalInNanos.end()) {
        // If we have already registered for this interval, then add the action info to the
        // actions list.
        mActionByIntervalInNanos[intervalInNanos].propIdAreaIdsToRefresh.insert(propIdAreaId);
        return;
    }

    // This is the first action for the interval, register a timer callback for that interval.
    auto action = std::make_shared<RecurrentTimer::Callback>(
            [this, intervalInNanos] { refreshTimeStampForInterval(intervalInNanos); });
    mActionByIntervalInNanos[intervalInNanos] = ActionForInterval{
            .propIdAreaIdsToRefresh = {propIdAreaId},
            .recurrentAction = action,
    };
    mRecurrentTimer->registerTimerCallback(intervalInNanos, action);
}

void FakeVehicleHardware::unregisterRefreshLocked(PropIdAreaId propIdAreaId) {
    if (mRefreshInfoByPropIdAreaId.find(propIdAreaId) == mRefreshInfoByPropIdAreaId.end()) {
        ALOGW("PropId: %" PRId32 ", areaId: %" PRId32 " was not registered for refresh, ignore",
              propIdAreaId.propId, propIdAreaId.areaId);
        return;
    }

    int64_t intervalInNanos = mRefreshInfoByPropIdAreaId[propIdAreaId].intervalInNanos;
    auto& actionForInterval = mActionByIntervalInNanos[intervalInNanos];
    actionForInterval.propIdAreaIdsToRefresh.erase(propIdAreaId);
    if (actionForInterval.propIdAreaIdsToRefresh.empty()) {
        mRecurrentTimer->unregisterTimerCallback(actionForInterval.recurrentAction);
        mActionByIntervalInNanos.erase(intervalInNanos);
    }
    mRefreshInfoByPropIdAreaId.erase(propIdAreaId);
}

StatusCode FakeVehicleHardware::subscribePropIdAreaIdLocked(
        int32_t propId, int32_t areaId, float sampleRateHz, bool enableVariableUpdateRate,
        const VehiclePropConfig& vehiclePropConfig) {
@@ -2066,11 +2142,6 @@ StatusCode FakeVehicleHardware::subscribePropIdAreaIdLocked(
                ALOGE("Must not use sample rate 0 for a continuous property");
                return StatusCode::INTERNAL_ERROR;
            }
            if (mRecurrentActions.find(propIdAreaId) != mRecurrentActions.end()) {
                mRecurrentTimer->unregisterTimerCallback(mRecurrentActions[propIdAreaId]);
            }
            int64_t intervalInNanos = static_cast<int64_t>(1'000'000'000. / sampleRateHz);

            // For continuous properties, we must generate a new onPropertyChange event
            // periodically according to the sample rate.
            auto eventMode = VehiclePropertyStore::EventMode::ALWAYS;
@@ -2078,12 +2149,8 @@ StatusCode FakeVehicleHardware::subscribePropIdAreaIdLocked(
                enableVariableUpdateRate) {
                eventMode = VehiclePropertyStore::EventMode::ON_VALUE_CHANGE;
            }
            auto action =
                    std::make_shared<RecurrentTimer::Callback>([this, propId, areaId, eventMode] {
                        mServerSidePropStore->refreshTimestamp(propId, areaId, eventMode);
                    });
            mRecurrentTimer->registerTimerCallback(intervalInNanos, action);
            mRecurrentActions[propIdAreaId] = action;

            registerRefreshLocked(propIdAreaId, eventMode, sampleRateHz);
            return StatusCode::OK;
    }
}
@@ -2094,39 +2161,47 @@ StatusCode FakeVehicleHardware::unsubscribe(int32_t propId, int32_t areaId) {
            .propId = propId,
            .areaId = areaId,
    };
    if (mRecurrentActions.find(propIdAreaId) != mRecurrentActions.end()) {
        mRecurrentTimer->unregisterTimerCallback(mRecurrentActions[propIdAreaId]);
        mRecurrentActions.erase(propIdAreaId);
    if (mRefreshInfoByPropIdAreaId.find(propIdAreaId) != mRefreshInfoByPropIdAreaId.end()) {
        unregisterRefreshLocked(propIdAreaId);
    }
    mSubOnChangePropIdAreaIds.erase(propIdAreaId);
    return StatusCode::OK;
}

void FakeVehicleHardware::onValueChangeCallback(const VehiclePropValue& value) {
    ATRACE_CALL();
    onValuesChangeCallback({value});
}

void FakeVehicleHardware::onValuesChangeCallback(std::vector<VehiclePropValue> values) {
    ATRACE_CALL();
    std::vector<VehiclePropValue> subscribedUpdatedValues;

    {
        std::scoped_lock<std::mutex> lockGuard(mLock);
        if (mOnPropertyChangeCallback == nullptr) {
            return;
        }

        for (const auto& value : values) {
            PropIdAreaId propIdAreaId{
                    .propId = value.prop,
                    .areaId = value.areaId,
            };

    {
        std::scoped_lock<std::mutex> lockGuard(mLock);
        if (mRecurrentActions.find(propIdAreaId) == mRecurrentActions.end() &&
            if (mRefreshInfoByPropIdAreaId.find(propIdAreaId) == mRefreshInfoByPropIdAreaId.end() &&
                mSubOnChangePropIdAreaIds.find(propIdAreaId) == mSubOnChangePropIdAreaIds.end()) {
                if (FAKE_VEHICLEHARDWARE_DEBUG) {
                    ALOGD("The updated property value: %s is not subscribed, ignore",
                          value.toString().c_str());
                }
            return;
                continue;
            }

            subscribedUpdatedValues.push_back(value);
        }
    }

    std::vector<VehiclePropValue> updatedValues;
    updatedValues.push_back(value);
    (*mOnPropertyChangeCallback)(std::move(updatedValues));
    (*mOnPropertyChangeCallback)(std::move(subscribedUpdatedValues));
}

void FakeVehicleHardware::loadPropConfigsFromDir(
+23 −5
Original line number Diff line number Diff line
@@ -48,21 +48,21 @@ class VehiclePropertyStore final {

    enum class EventMode : uint8_t {
        /**
         * Only invoke OnValueChangeCallback if the new property value (ignoring timestamp) is
         * different than the existing value.
         * Only invoke OnValueChangeCallback or OnValuesChangeCallback if the new property value
         * (ignoring timestamp) is different than the existing value.
         *
         * This should be used for regular cases.
         */
        ON_VALUE_CHANGE,
        /**
         * Always invoke OnValueChangeCallback.
         * Always invoke OnValueChangeCallback or OnValuesChangeCallback.
         *
         * This should be used for the special properties that are used for delivering event, e.g.
         * HW_KEY_INPUT.
         */
        ALWAYS,
        /**
         * Never invoke OnValueChangeCallback.
         * Never invoke OnValueChangeCallback or OnValuesChangeCalblack.
         *
         * This should be used for continuous property subscription when the sample rate for the
         * subscription is smaller than the refresh rate for the property. E.g., the vehicle speed
@@ -82,6 +82,10 @@ class VehiclePropertyStore final {
    using OnValueChangeCallback = std::function<void(
            const aidl::android::hardware::automotive::vehicle::VehiclePropValue&)>;

    // Callback when one or more property values have been updated or new values added.
    using OnValuesChangeCallback = std::function<void(
            std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>)>;

    // Function that used to calculate unique token for given VehiclePropValue.
    using TokenFunction = std::function<int64_t(
            const aidl::android::hardware::automotive::vehicle::VehiclePropValue& value)>;
@@ -99,7 +103,8 @@ class VehiclePropertyStore final {
    // 'status' would be initialized to {@code VehiclePropertyStatus::AVAILABLE}, if this is to
    // override an existing value, the status for the existing value would be used for the
    // overridden value.
    // 'EventMode' controls whether the 'OnValueChangeCallback' will be called for this operation.
    // 'EventMode' controls whether the 'OnValueChangeCallback' or 'OnValuesChangeCallback' will be
    // called for this operation.
    // If 'useCurrentTimestamp' is true, the property value will be set to the current timestamp.
    VhalResult<void> writeValue(VehiclePropValuePool::RecyclableType propValue,
                                bool updateStatus = false,
@@ -111,6 +116,11 @@ class VehiclePropertyStore final {
    // without generating event. This operation is atomic with other writeValue operations.
    void refreshTimestamp(int32_t propId, int32_t areaId, EventMode eventMode) EXCLUDES(mLock);

    // Refresh the timestamp for multiple [propId, areaId]s.
    void refreshTimestamps(
            std::unordered_map<PropIdAreaId, EventMode, PropIdAreaIdHash> eventModeByPropIdAreaId)
            EXCLUDES(mLock);

    // Remove a given property value from the property store. The 'propValue' would be used to
    // generate the key for the value to remove.
    void removeValue(
@@ -157,6 +167,13 @@ class VehiclePropertyStore final {
    // Set a callback that would be called when a property value has been updated.
    void setOnValueChangeCallback(const OnValueChangeCallback& callback) EXCLUDES(mLock);

    // Set a callback that would be called when one or more property values have been updated.
    // For backward compatibility, this is optional. If this is not set, then multiple property
    // updates will be delivered through multiple OnValueChangeCallback instead.
    // It is recommended to set this and batch the property update events for better performance.
    // If this is set, then OnValueChangeCallback will not be used.
    void setOnValuesChangeCallback(const OnValuesChangeCallback& callback) EXCLUDES(mLock);

    inline std::shared_ptr<VehiclePropValuePool> getValuePool() { return mValuePool; }

  private:
@@ -184,6 +201,7 @@ class VehiclePropertyStore final {
    mutable std::mutex mLock;
    std::unordered_map<int32_t, Record> mRecordsByPropId GUARDED_BY(mLock);
    OnValueChangeCallback mOnValueChangeCallback GUARDED_BY(mLock);
    OnValuesChangeCallback mOnValuesChangeCallback GUARDED_BY(mLock);

    const Record* getRecordLocked(int32_t propId) const;

+82 −27
Original line number Diff line number Diff line
@@ -113,6 +113,9 @@ VhalResult<void> VehiclePropertyStore::writeValue(VehiclePropValuePool::Recyclab
    bool valueUpdated = true;
    VehiclePropValue updatedValue;
    OnValueChangeCallback onValueChangeCallback = nullptr;
    OnValuesChangeCallback onValuesChangeCallback = nullptr;
    int32_t propId;
    int32_t areaId;
    {
        std::scoped_lock<std::mutex> g(mLock);

@@ -122,7 +125,8 @@ VhalResult<void> VehiclePropertyStore::writeValue(VehiclePropValuePool::Recyclab
            propValue->timestamp = elapsedRealtimeNano();
        }

        int32_t propId = propValue->prop;
        propId = propValue->prop;
        areaId = propValue->areaId;

        VehiclePropertyStore::Record* record = getRecordLocked(propId);
        if (record == nullptr) {
@@ -163,28 +167,56 @@ VhalResult<void> VehiclePropertyStore::writeValue(VehiclePropValuePool::Recyclab
            return {};
        }
        updatedValue = *(record->values[recId]);
        if (mOnValueChangeCallback == nullptr) {
            return {};
        }

        onValuesChangeCallback = mOnValuesChangeCallback;
        onValueChangeCallback = mOnValueChangeCallback;
    }

    if (onValuesChangeCallback == nullptr && onValueChangeCallback == nullptr) {
        ALOGW("No callback registered, ignoring property update for propId: %" PRId32
              ", area ID: %" PRId32,
              propId, areaId);
        return {};
    }

    // Invoke the callback outside the lock to prevent dead-lock.
    if (eventMode == EventMode::ALWAYS || valueUpdated) {
        if (onValuesChangeCallback != nullptr) {
            onValuesChangeCallback({updatedValue});
        } else {
            onValueChangeCallback(updatedValue);
        }
    }
    return {};
}

void VehiclePropertyStore::refreshTimestamp(int32_t propId, int32_t areaId, EventMode eventMode) {
    VehiclePropValue updatedValue;
    std::unordered_map<PropIdAreaId, EventMode, PropIdAreaIdHash> eventModeByPropIdAreaId;
    PropIdAreaId propIdAreaId = {
            .propId = propId,
            .areaId = areaId,
    };
    eventModeByPropIdAreaId[propIdAreaId] = eventMode;
    refreshTimestamps(eventModeByPropIdAreaId);
}

void VehiclePropertyStore::refreshTimestamps(
        std::unordered_map<PropIdAreaId, EventMode, PropIdAreaIdHash> eventModeByPropIdAreaId) {
    std::vector<VehiclePropValue> updatedValues;
    OnValuesChangeCallback onValuesChangeCallback = nullptr;
    OnValueChangeCallback onValueChangeCallback = nullptr;
    {
        std::scoped_lock<std::mutex> g(mLock);

        onValuesChangeCallback = mOnValuesChangeCallback;
        onValueChangeCallback = mOnValueChangeCallback;

        for (const auto& [propIdAreaId, eventMode] : eventModeByPropIdAreaId) {
            int32_t propId = propIdAreaId.propId;
            int32_t areaId = propIdAreaId.areaId;
            VehiclePropertyStore::Record* record = getRecordLocked(propId);
            if (record == nullptr) {
            return;
                continue;
            }

            VehiclePropValue propValue = {
@@ -196,19 +228,35 @@ void VehiclePropertyStore::refreshTimestamp(int32_t propId, int32_t areaId, Even
            VehiclePropertyStore::RecordId recId = getRecordIdLocked(propValue, *record);
            if (auto it = record->values.find(recId); it != record->values.end()) {
                it->second->timestamp = elapsedRealtimeNano();
            updatedValue = *(it->second);
                if (eventMode == EventMode::ALWAYS) {
                    updatedValues.push_back(*(it->second));
                }
            } else {
            return;
                continue;
            }
        if (!mOnValueChangeCallback) {
            return;
        }
        onValueChangeCallback = mOnValueChangeCallback;
    }

    // Invoke the callback outside the lock to prevent dead-lock.
    if (eventMode == EventMode::ALWAYS) {
        onValueChangeCallback(updatedValue);
    if (updatedValues.empty()) {
        return;
    }
    if (!onValuesChangeCallback && !onValueChangeCallback) {
        // If no callback is set, then we don't have to do anything.
        for (const auto& updateValue : updatedValues) {
            ALOGW("No callback registered, ignoring property update for propId: %" PRId32
                  ", area ID: %" PRId32,
                  updateValue.prop, updateValue.areaId);
        }
        return;
    }
    if (onValuesChangeCallback != nullptr) {
        onValuesChangeCallback(updatedValues);
    } else {
        // Fallback to use multiple onValueChangeCallback
        for (const auto& updateValue : updatedValues) {
            onValueChangeCallback(updateValue);
        }
    }
}

@@ -336,6 +384,13 @@ void VehiclePropertyStore::setOnValueChangeCallback(
    mOnValueChangeCallback = callback;
}

void VehiclePropertyStore::setOnValuesChangeCallback(
        const VehiclePropertyStore::OnValuesChangeCallback& callback) {
    std::scoped_lock<std::mutex> g(mLock);

    mOnValuesChangeCallback = callback;
}

}  // namespace vehicle
}  // namespace automotive
}  // namespace hardware
+128 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@
#include <VehicleUtils.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <utils/SystemClock.h>

namespace android {
namespace hardware {
@@ -527,6 +528,133 @@ TEST_F(VehiclePropertyStoreTest, testPropertyChangeCallbackUseVehiclePropertySto
    ASSERT_EQ(configs.size(), static_cast<size_t>(2));
}

TEST_F(VehiclePropertyStoreTest, testOnValuesChangeCallback) {
    std::vector<VehiclePropValue> updatedValues;
    VehiclePropValue fuelCapacity = {
            .prop = toInt(VehicleProperty::INFO_FUEL_CAPACITY),
            .value = {.floatValues = {1.0}},
    };
    ASSERT_RESULT_OK(mStore->writeValue(mValuePool->obtain(fuelCapacity)));

    mStore->setOnValuesChangeCallback(
            [&updatedValues](std::vector<VehiclePropValue> values) { updatedValues = values; });

    fuelCapacity.value.floatValues[0] = 2.0;
    fuelCapacity.timestamp = 1;

    ASSERT_RESULT_OK(mStore->writeValue(mValuePool->obtain(fuelCapacity)));

    ASSERT_THAT(updatedValues, ElementsAre(fuelCapacity));
}

TEST_F(VehiclePropertyStoreTest, testRefreshTimestamp) {
    std::vector<VehiclePropValue> updatedValues;
    mStore->setOnValuesChangeCallback(
            [&updatedValues](std::vector<VehiclePropValue> values) { updatedValues = values; });

    int64_t now = elapsedRealtimeNano();
    int propId = toInt(VehicleProperty::TIRE_PRESSURE);
    int areaId = WHEEL_FRONT_LEFT;
    VehiclePropValue tirePressure = {
            .prop = propId,
            .areaId = areaId,
            .value = {.floatValues = {1.0}},
    };
    ASSERT_RESULT_OK(mStore->writeValue(mValuePool->obtain(tirePressure)));
    updatedValues.clear();

    mStore->refreshTimestamp(propId, areaId, VehiclePropertyStore::EventMode::ALWAYS);

    ASSERT_EQ(updatedValues.size(), 1u);
    ASSERT_EQ(updatedValues[0].prop, propId);
    ASSERT_EQ(updatedValues[0].areaId, areaId);
    ASSERT_EQ(updatedValues[0].value.floatValues[0], 1.0);
    int64_t timestamp = updatedValues[0].timestamp;
    ASSERT_GE(timestamp, now);

    auto result = mStore->readValue(tirePressure);

    ASSERT_RESULT_OK(result);
    ASSERT_EQ((result.value())->timestamp, timestamp);
}

TEST_F(VehiclePropertyStoreTest, testRefreshTimestamp_eventModeOnValueChange) {
    std::vector<VehiclePropValue> updatedValues;
    mStore->setOnValuesChangeCallback(
            [&updatedValues](std::vector<VehiclePropValue> values) { updatedValues = values; });

    int64_t now = elapsedRealtimeNano();
    int propId = toInt(VehicleProperty::TIRE_PRESSURE);
    int areaId = WHEEL_FRONT_LEFT;
    VehiclePropValue tirePressure = {
            .prop = propId,
            .areaId = areaId,
            .value = {.floatValues = {1.0}},
    };
    ASSERT_RESULT_OK(mStore->writeValue(mValuePool->obtain(tirePressure)));
    updatedValues.clear();

    mStore->refreshTimestamp(propId, areaId, VehiclePropertyStore::EventMode::ON_VALUE_CHANGE);

    ASSERT_EQ(updatedValues.size(), 0u)
            << "Must generate no property update events if only the timestamp is refreshed";

    auto result = mStore->readValue(tirePressure);

    ASSERT_RESULT_OK(result);
    ASSERT_GE((result.value())->timestamp, now)
            << "Even though event mode is on value change, the store timestamp must be updated";
}

TEST_F(VehiclePropertyStoreTest, testRefreshTimestamps) {
    std::vector<VehiclePropValue> updatedValues;
    mStore->setOnValuesChangeCallback(
            [&updatedValues](std::vector<VehiclePropValue> values) { updatedValues = values; });

    int64_t now = elapsedRealtimeNano();
    int propId1 = toInt(VehicleProperty::INFO_FUEL_CAPACITY);
    int areaId1 = 0;
    VehiclePropValue fuelCapacity = {
            .prop = propId1,
            .areaId = areaId1,
            .value = {.floatValues = {1.0}},
    };
    ASSERT_RESULT_OK(mStore->writeValue(mValuePool->obtain(fuelCapacity)));

    int propId2 = toInt(VehicleProperty::TIRE_PRESSURE);
    int areaId2 = WHEEL_FRONT_LEFT;
    VehiclePropValue tirePressure = {
            .prop = propId2,
            .areaId = areaId2,
            .value = {.floatValues = {2.0}},
    };
    ASSERT_RESULT_OK(mStore->writeValue(mValuePool->obtain(tirePressure)));
    updatedValues.clear();

    std::unordered_map<PropIdAreaId, VehiclePropertyStore::EventMode, PropIdAreaIdHash>
            eventModeByPropIdAreaId;
    eventModeByPropIdAreaId[PropIdAreaId{
            .propId = propId1,
            .areaId = areaId1,
    }] = VehiclePropertyStore::EventMode::ALWAYS;
    eventModeByPropIdAreaId[PropIdAreaId{
            .propId = propId2,
            .areaId = areaId2,
    }] = VehiclePropertyStore::EventMode::ALWAYS;

    mStore->refreshTimestamps(eventModeByPropIdAreaId);

    ASSERT_EQ(updatedValues.size(), 2u);
    ASSERT_EQ(updatedValues[0].prop, propId1);
    ASSERT_EQ(updatedValues[0].areaId, areaId1);
    ASSERT_EQ(updatedValues[0].value.floatValues[0], 1.0);
    ASSERT_GE(updatedValues[0].timestamp, now);
    ASSERT_EQ(updatedValues[1].prop, propId2);
    ASSERT_EQ(updatedValues[1].areaId, areaId2);
    ASSERT_EQ(updatedValues[1].value.floatValues[0], 2.0);
    ASSERT_GE(updatedValues[1].timestamp, now);
}

}  // namespace vehicle
}  // namespace automotive
}  // namespace hardware
Loading