Loading automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h +22 −2 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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); Loading Loading @@ -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, Loading Loading @@ -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); Loading automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp +107 −32 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -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) { Loading @@ -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; Loading @@ -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; } } Loading @@ -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( Loading automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h +23 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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)>; Loading @@ -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, Loading @@ -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( Loading Loading @@ -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: Loading Loading @@ -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; Loading automotive/vehicle/aidl/impl/utils/common/src/VehiclePropertyStore.cpp +82 −27 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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) { Loading Loading @@ -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 = { Loading @@ -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); } } } Loading Loading @@ -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 Loading automotive/vehicle/aidl/impl/utils/common/test/VehiclePropertyStoreTest.cpp +128 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ #include <VehicleUtils.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include <utils/SystemClock.h> namespace android { namespace hardware { Loading Loading @@ -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 Loading
automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h +22 −2 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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); Loading Loading @@ -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, Loading Loading @@ -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); Loading
automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp +107 −32 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -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) { Loading @@ -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; Loading @@ -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; } } Loading @@ -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( Loading
automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h +23 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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)>; Loading @@ -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, Loading @@ -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( Loading Loading @@ -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: Loading Loading @@ -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; Loading
automotive/vehicle/aidl/impl/utils/common/src/VehiclePropertyStore.cpp +82 −27 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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) { Loading Loading @@ -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 = { Loading @@ -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); } } } Loading Loading @@ -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 Loading
automotive/vehicle/aidl/impl/utils/common/test/VehiclePropertyStoreTest.cpp +128 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ #include <VehicleUtils.h> #include <gmock/gmock.h> #include <gtest/gtest.h> #include <utils/SystemClock.h> namespace android { namespace hardware { Loading Loading @@ -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