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

Commit e35cbd5d authored by Yu Shan's avatar Yu Shan Committed by Android (Google) Code Review
Browse files

Merge "Use batched property updates in IVehicleHardware." into main

parents 4d18fcae 863a1b2e
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
@@ -346,8 +346,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 {
@@ -2080,6 +2081,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) {
@@ -2099,11 +2175,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;
@@ -2111,12 +2182,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;
    }
}
@@ -2127,39 +2194,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