Loading automotive/vehicle/aidl/impl/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -32,5 +32,6 @@ cc_defaults { "-Wall", "-Wextra", "-Werror", "-Wthread-safety", ], } automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h 0 → 100644 +136 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef android_hardware_automotive_vehicle_aidl_impl_utils_common_include_VehiclePropertyStore_H_ #define android_hardware_automotive_vehicle_aidl_impl_utils_common_include_VehiclePropertyStore_H_ #include <cstdint> #include <map> #include <memory> #include <mutex> #include <unordered_map> #include <VehicleHalTypes.h> #include <android-base/result.h> #include <android-base/thread_annotations.h> namespace android { namespace hardware { namespace automotive { namespace vehicle { // Encapsulates work related to storing and accessing configuration, storing and modifying // vehicle property values. // // VehiclePropertyValues stored in a sorted map thus it makes easier to get range of values, e.g. // to get value for all areas for particular property. // // This class is thread-safe, however it uses blocking synchronization across all methods. class VehiclePropertyStore { public: // Function that used to calculate unique token for given VehiclePropValue. using TokenFunction = ::std::function<int64_t( const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value)>; // Register the given property according to the config. A property has to be registered first // before write/read. If tokenFunc is not nullptr, it would be used to generate a unique // property token to act as the key the property store. Otherwise, {propertyID, areaID} would be // used as the key. void registerProperty( const ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig& config, TokenFunction tokenFunc = nullptr); // Stores provided value. Returns true if value was written returns false if config wasn't // registered. ::android::base::Result<void> writeValue( const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue); // 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( const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue); // Remove all the values for the property. void removeValuesForProperty(int32_t propId); // Read all the stored values. std::vector<::aidl::android::hardware::automotive::vehicle::VehiclePropValue> readAllValues() const; // Read all the values for the property. ::android::base::Result< std::vector<::aidl::android::hardware::automotive::vehicle::VehiclePropValue>> readValuesForProperty(int32_t propId) const; // Read the value for the requested property. ::android::base::Result< std::unique_ptr<::aidl::android::hardware::automotive::vehicle::VehiclePropValue>> readValue( const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& request) const; // Read the value for the requested property. ::android::base::Result< std::unique_ptr<::aidl::android::hardware::automotive::vehicle::VehiclePropValue>> readValue(int32_t prop, int32_t area = 0, int64_t token = 0) const; // Get all property configs. std::vector<::aidl::android::hardware::automotive::vehicle::VehiclePropConfig> getAllConfigs() const; // Get the property config for the requested property. ::android::base::Result< const ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig*> getConfig(int32_t propId) const; private: struct RecordId { int32_t area; int64_t token; bool operator==(const RecordId& other) const; bool operator<(const RecordId& other) const; std::string toString() const; }; struct Record { ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig propConfig; TokenFunction tokenFunction; std::map<RecordId, ::aidl::android::hardware::automotive::vehicle::VehiclePropValue> values; }; mutable std::mutex mLock; std::unordered_map<int32_t, Record> mRecordsByPropId GUARDED_BY(mLock); const Record* getRecordLocked(int32_t propId) const; Record* getRecordLocked(int32_t propId); RecordId getRecordIdLocked( const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue, const Record& record) const; ::android::base::Result< std::unique_ptr<::aidl::android::hardware::automotive::vehicle::VehiclePropValue>> readValueLocked(const RecordId& recId, const Record& record) const; }; } // namespace vehicle } // namespace automotive } // namespace hardware } // namespace android #endif // android_hardware_automotive_vehicle_aidl_impl_utils_common_include_VehiclePropertyStore_H_ automotive/vehicle/aidl/impl/utils/common/src/VehiclePropertyStore.cpp 0 → 100644 +231 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "VehiclePropertyStore" #include <utils/Log.h> #include "VehiclePropertyStore.h" #include <VehicleUtils.h> #include <android-base/format.h> namespace android { namespace hardware { namespace automotive { namespace vehicle { using ::aidl::android::hardware::automotive::vehicle::VehicleAreaConfig; using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig; using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue; using ::android::base::Result; bool VehiclePropertyStore::RecordId::operator==(const VehiclePropertyStore::RecordId& other) const { return area == other.area && token == other.token; } bool VehiclePropertyStore::RecordId::operator<(const VehiclePropertyStore::RecordId& other) const { return area < other.area || (area == other.area && token < other.token); } std::string VehiclePropertyStore::RecordId::toString() const { return ::fmt::format("RecordID{{.areaId={:d}, .token={:d}}}", area, token); } const VehiclePropertyStore::Record* VehiclePropertyStore::getRecordLocked(int32_t propId) const REQUIRES(mLock) { auto RecordIt = mRecordsByPropId.find(propId); return RecordIt == mRecordsByPropId.end() ? nullptr : &RecordIt->second; } VehiclePropertyStore::Record* VehiclePropertyStore::getRecordLocked(int32_t propId) REQUIRES(mLock) { auto RecordIt = mRecordsByPropId.find(propId); return RecordIt == mRecordsByPropId.end() ? nullptr : &RecordIt->second; } VehiclePropertyStore::RecordId VehiclePropertyStore::getRecordIdLocked( const VehiclePropValue& propValue, const VehiclePropertyStore::Record& record) const REQUIRES(mLock) { VehiclePropertyStore::RecordId recId{ .area = isGlobalProp(propValue.prop) ? 0 : propValue.areaId, .token = 0}; if (record.tokenFunction != nullptr) { recId.token = record.tokenFunction(propValue); } return recId; } Result<std::unique_ptr<VehiclePropValue>> VehiclePropertyStore::readValueLocked( const RecordId& recId, const Record& record) const REQUIRES(mLock) { auto it = record.values.find(recId); if (it == record.values.end()) { return Errorf("Record ID: {} is not found", recId.toString()); } return std::make_unique<VehiclePropValue>(it->second); } void VehiclePropertyStore::registerProperty(const VehiclePropConfig& config, VehiclePropertyStore::TokenFunction tokenFunc) { std::lock_guard<std::mutex> g(mLock); mRecordsByPropId[config.prop] = Record{ .propConfig = config, .tokenFunction = tokenFunc, }; } Result<void> VehiclePropertyStore::writeValue(const VehiclePropValue& propValue) { std::lock_guard<std::mutex> g(mLock); VehiclePropertyStore::Record* record = getRecordLocked(propValue.prop); if (record == nullptr) { return Errorf("property: {:d} not registered", propValue.prop); } if (!isGlobalProp(propValue.prop) && getAreaConfig(propValue, record->propConfig) == nullptr) { return Errorf("no config for property: {:d} area: {:d}", propValue.prop, propValue.areaId); } VehiclePropertyStore::RecordId recId = getRecordIdLocked(propValue, *record); auto it = record->values.find(recId); if (it == record->values.end()) { record->values[recId] = propValue; return {}; } VehiclePropValue* valueToUpdate = &(it->second); // propValue is outdated and drops it. if (valueToUpdate->timestamp > propValue.timestamp) { return Errorf("outdated timestamp: {:d}", propValue.timestamp); } // Update the propertyValue. // The timestamp in propertyStore should only be updated by the server side. It indicates // the time when the event is generated by the server. valueToUpdate->timestamp = propValue.timestamp; valueToUpdate->value = propValue.value; valueToUpdate->status = propValue.status; return {}; } void VehiclePropertyStore::removeValue(const VehiclePropValue& propValue) { std::lock_guard<std::mutex> g(mLock); VehiclePropertyStore::Record* record = getRecordLocked(propValue.prop); if (record == nullptr) { return; } VehiclePropertyStore::RecordId recId = getRecordIdLocked(propValue, *record); if (auto it = record->values.find(recId); it != record->values.end()) { record->values.erase(it); } } void VehiclePropertyStore::removeValuesForProperty(int32_t propId) { std::lock_guard<std::mutex> g(mLock); VehiclePropertyStore::Record* record = getRecordLocked(propId); if (record == nullptr) { return; } record->values.clear(); } std::vector<VehiclePropValue> VehiclePropertyStore::readAllValues() const { std::lock_guard<std::mutex> g(mLock); std::vector<VehiclePropValue> allValues; for (auto const& [_, record] : mRecordsByPropId) { for (auto const& [_, value] : record.values) { allValues.push_back(value); } } return allValues; } Result<std::vector<VehiclePropValue>> VehiclePropertyStore::readValuesForProperty( int32_t propId) const { std::lock_guard<std::mutex> g(mLock); std::vector<VehiclePropValue> values; const VehiclePropertyStore::Record* record = getRecordLocked(propId); if (record == nullptr) { return Errorf("property: {:d} not registered", propId); } for (auto const& [_, value] : record->values) { values.push_back(value); } return values; } Result<std::unique_ptr<VehiclePropValue>> VehiclePropertyStore::readValue( const VehiclePropValue& propValue) const { std::lock_guard<std::mutex> g(mLock); const VehiclePropertyStore::Record* record = getRecordLocked(propValue.prop); if (record == nullptr) { return Errorf("property: {:d} not registered", propValue.prop); } VehiclePropertyStore::RecordId recId = getRecordIdLocked(propValue, *record); return readValueLocked(recId, *record); } Result<std::unique_ptr<VehiclePropValue>> VehiclePropertyStore::readValue(int32_t propId, int32_t areaId, int64_t token) const { std::lock_guard<std::mutex> g(mLock); const VehiclePropertyStore::Record* record = getRecordLocked(propId); if (record == nullptr) { return Errorf("property: {:d} not registered", propId); } VehiclePropertyStore::RecordId recId{.area = isGlobalProp(propId) ? 0 : areaId, .token = token}; return readValueLocked(recId, *record); } std::vector<VehiclePropConfig> VehiclePropertyStore::getAllConfigs() const { std::lock_guard<std::mutex> g(mLock); std::vector<VehiclePropConfig> configs; configs.reserve(mRecordsByPropId.size()); for (auto& [_, config] : mRecordsByPropId) { configs.push_back(config.propConfig); } return configs; } Result<const VehiclePropConfig*> VehiclePropertyStore::getConfig(int32_t propId) const { std::lock_guard<std::mutex> g(mLock); const VehiclePropertyStore::Record* record = getRecordLocked(propId); if (record == nullptr) { return Errorf("property: {:d} not registered", propId); } return &record->propConfig; } } // namespace vehicle } // namespace automotive } // namespace hardware } // namespace android automotive/vehicle/aidl/impl/utils/common/test/Android.bp +5 −1 Original line number Diff line number Diff line Loading @@ -22,7 +22,11 @@ cc_test { name: "VehicleHalVehicleUtilsTest", srcs: ["*.cpp"], vendor: true, static_libs: ["VehicleHalUtils"], static_libs: [ "VehicleHalUtils", "libgtest", "libgmock", ], defaults: ["VehicleHalDefaults"], test_suites: ["general-tests"], } automotive/vehicle/aidl/impl/utils/common/test/VehiclePropertyStoreTest.cpp 0 → 100644 +314 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <PropertyUtils.h> #include <VehiclePropertyStore.h> #include <VehicleUtils.h> #include <gmock/gmock.h> #include <gtest/gtest.h> namespace android { namespace hardware { namespace automotive { namespace vehicle { namespace { using ::aidl::android::hardware::automotive::vehicle::VehicleAreaConfig; using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig; using ::aidl::android::hardware::automotive::vehicle::VehicleProperty; using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyAccess; using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyChangeMode; using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue; using ::android::base::Result; using ::testing::ElementsAre; using ::testing::Eq; using ::testing::WhenSortedBy; constexpr int INVALID_PROP_ID = 0; struct PropValueCmp { bool operator()(const VehiclePropValue& a, const VehiclePropValue& b) const { return (a.prop < b.prop) || ((a.prop == b.prop) && (a.value < b.value)) || ((a.prop == b.prop) && (a.value == b.value) && (a.areaId < b.areaId)); } } propValueCmp; int64_t timestampToken(const VehiclePropValue& value) { return value.timestamp; } } // namespace class VehiclePropertyStoreTest : public ::testing::Test { protected: void SetUp() override { mConfigFuelCapacity = { .prop = toInt(VehicleProperty::INFO_FUEL_CAPACITY), .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::STATIC, }; VehiclePropConfig configTirePressure = { .prop = toInt(VehicleProperty::TIRE_PRESSURE), .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::CONTINUOUS, .areaConfigs = {VehicleAreaConfig{.areaId = WHEEL_FRONT_LEFT}, VehicleAreaConfig{.areaId = WHEEL_FRONT_RIGHT}, VehicleAreaConfig{.areaId = WHEEL_REAR_LEFT}, VehicleAreaConfig{.areaId = WHEEL_REAR_RIGHT}}, }; mStore.registerProperty(mConfigFuelCapacity); mStore.registerProperty(configTirePressure); } VehiclePropertyStore mStore; VehiclePropConfig mConfigFuelCapacity; }; TEST_F(VehiclePropertyStoreTest, testGetAllConfigs) { std::vector<VehiclePropConfig> configs = mStore.getAllConfigs(); ASSERT_EQ(configs.size(), static_cast<size_t>(2)); } TEST_F(VehiclePropertyStoreTest, testGetConfig) { Result<const VehiclePropConfig*> result = mStore.getConfig(toInt(VehicleProperty::INFO_FUEL_CAPACITY)); ASSERT_RESULT_OK(result); ASSERT_EQ(*(result.value()), mConfigFuelCapacity); } TEST_F(VehiclePropertyStoreTest, testGetConfigWithInvalidPropId) { Result<const VehiclePropConfig*> result = mStore.getConfig(INVALID_PROP_ID); ASSERT_FALSE(result.ok()) << "expect error when getting a config for an invalid property ID"; } std::vector<VehiclePropValue> getTestPropValues() { VehiclePropValue fuelCapacity = { .prop = toInt(VehicleProperty::INFO_FUEL_CAPACITY), .value = {.floatValues = {1.0}}, }; VehiclePropValue leftTirePressure = { .prop = toInt(VehicleProperty::TIRE_PRESSURE), .value = {.floatValues = {170.0}}, .areaId = WHEEL_FRONT_LEFT, }; VehiclePropValue rightTirePressure = { .prop = toInt(VehicleProperty::TIRE_PRESSURE), .value = {.floatValues = {180.0}}, .areaId = WHEEL_FRONT_RIGHT, }; return {fuelCapacity, leftTirePressure, rightTirePressure}; } TEST_F(VehiclePropertyStoreTest, testWriteValueOk) { auto values = getTestPropValues(); ASSERT_RESULT_OK(mStore.writeValue(values[0])); } TEST_F(VehiclePropertyStoreTest, testReadAllValues) { auto values = getTestPropValues(); for (const auto& value : values) { ASSERT_RESULT_OK(mStore.writeValue(value)); } auto gotValues = mStore.readAllValues(); ASSERT_THAT(gotValues, WhenSortedBy(propValueCmp, Eq(values))); } TEST_F(VehiclePropertyStoreTest, testReadValuesForPropertyOneValue) { auto values = getTestPropValues(); for (const auto& value : values) { ASSERT_RESULT_OK(mStore.writeValue(value)); } auto result = mStore.readValuesForProperty(toInt(VehicleProperty::INFO_FUEL_CAPACITY)); ASSERT_RESULT_OK(result); ASSERT_THAT(result.value(), ElementsAre(values[0])); } TEST_F(VehiclePropertyStoreTest, testReadValuesForPropertyMultipleValues) { auto values = getTestPropValues(); for (const auto& value : values) { ASSERT_RESULT_OK(mStore.writeValue(value)); } auto result = mStore.readValuesForProperty(toInt(VehicleProperty::TIRE_PRESSURE)); ASSERT_RESULT_OK(result); ASSERT_THAT(result.value(), WhenSortedBy(propValueCmp, ElementsAre(values[1], values[2]))); } TEST_F(VehiclePropertyStoreTest, testReadValuesForPropertyError) { auto result = mStore.readValuesForProperty(INVALID_PROP_ID); ASSERT_FALSE(result.ok()) << "expect error when reading values for an invalid property"; } TEST_F(VehiclePropertyStoreTest, testReadValueOk) { auto values = getTestPropValues(); for (const auto& value : values) { ASSERT_RESULT_OK(mStore.writeValue(value)); } VehiclePropValue requestValue = { .prop = toInt(VehicleProperty::TIRE_PRESSURE), .areaId = WHEEL_FRONT_LEFT, }; auto result = mStore.readValue(requestValue); ASSERT_RESULT_OK(result); ASSERT_EQ(*(result.value()), values[1]); } TEST_F(VehiclePropertyStoreTest, testReadValueByPropIdOk) { auto values = getTestPropValues(); for (const auto& value : values) { ASSERT_RESULT_OK(mStore.writeValue(value)); } auto result = mStore.readValue(toInt(VehicleProperty::TIRE_PRESSURE), WHEEL_FRONT_RIGHT); ASSERT_EQ(*(result.value()), values[2]); } TEST_F(VehiclePropertyStoreTest, testReadValueError) { auto values = getTestPropValues(); for (const auto& value : values) { ASSERT_RESULT_OK(mStore.writeValue(value)); } auto result = mStore.readValue(toInt(VehicleProperty::TIRE_PRESSURE), WHEEL_REAR_LEFT); ASSERT_FALSE(result.ok()) << "expect error when reading a value that has not been written"; } TEST_F(VehiclePropertyStoreTest, testWriteValueError) { ASSERT_FALSE(mStore.writeValue({ .prop = INVALID_PROP_ID, .value = {.floatValues = {1.0}}, }) .ok()) << "expect error when writing value for an invalid property ID"; } TEST_F(VehiclePropertyStoreTest, testWriteValueNoAreaConfig) { ASSERT_FALSE(mStore.writeValue({ .prop = toInt(VehicleProperty::TIRE_PRESSURE), .value = {.floatValues = {180.0}}, // There is no config for ALL_WHEELS. .areaId = ALL_WHEELS, }) .ok()) << "expect error when writing value for an area without config"; } TEST_F(VehiclePropertyStoreTest, testWriteOutdatedValue) { ASSERT_RESULT_OK(mStore.writeValue({ .timestamp = 1, .prop = toInt(VehicleProperty::TIRE_PRESSURE), .value = {.floatValues = {180.0}}, .areaId = WHEEL_FRONT_LEFT, })); // Write an older value. ASSERT_FALSE(mStore.writeValue({ .timestamp = 0, .prop = toInt(VehicleProperty::TIRE_PRESSURE), .value = {.floatValues = {180.0}}, .areaId = WHEEL_FRONT_LEFT, }) .ok()) << "expect error when writing an outdated value"; } TEST_F(VehiclePropertyStoreTest, testToken) { int propId = toInt(VehicleProperty::INFO_FUEL_CAPACITY); VehiclePropConfig config = { .prop = propId, }; // Replace existing config. mStore.registerProperty(config, timestampToken); VehiclePropValue fuelCapacityValueToken1 = { .timestamp = 1, .prop = propId, .value = {.floatValues = {1.0}}, }; VehiclePropValue fuelCapacityValueToken2 = { .timestamp = 2, .prop = propId, .value = {.floatValues = {2.0}}, }; ASSERT_RESULT_OK(mStore.writeValue(fuelCapacityValueToken1)); ASSERT_RESULT_OK(mStore.writeValue(fuelCapacityValueToken2)); auto result = mStore.readValuesForProperty(propId); ASSERT_RESULT_OK(result); ASSERT_EQ(result.value().size(), static_cast<size_t>(2)); auto tokenResult = mStore.readValue(propId, /*areaId=*/0, /*token=*/2); ASSERT_RESULT_OK(tokenResult); ASSERT_EQ(*(tokenResult.value()), fuelCapacityValueToken2); } TEST_F(VehiclePropertyStoreTest, testRemoveValue) { auto values = getTestPropValues(); for (const auto& value : values) { ASSERT_RESULT_OK(mStore.writeValue(value)); } mStore.removeValue(values[0]); ASSERT_FALSE(mStore.readValue(values[0]).ok()) << "expect error when reading a removed value"; auto leftTirePressureResult = mStore.readValue(values[1]); ASSERT_RESULT_OK(leftTirePressureResult); ASSERT_EQ(*(leftTirePressureResult.value()), values[1]); } TEST_F(VehiclePropertyStoreTest, testRemoveValuesForProperty) { auto values = getTestPropValues(); for (const auto& value : values) { ASSERT_RESULT_OK(mStore.writeValue(value)); } mStore.removeValuesForProperty(toInt(VehicleProperty::INFO_FUEL_CAPACITY)); mStore.removeValuesForProperty(toInt(VehicleProperty::TIRE_PRESSURE)); auto gotValues = mStore.readAllValues(); ASSERT_TRUE(gotValues.empty()); } } // namespace vehicle } // namespace automotive } // namespace hardware } // namespace android Loading
automotive/vehicle/aidl/impl/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -32,5 +32,6 @@ cc_defaults { "-Wall", "-Wextra", "-Werror", "-Wthread-safety", ], }
automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h 0 → 100644 +136 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef android_hardware_automotive_vehicle_aidl_impl_utils_common_include_VehiclePropertyStore_H_ #define android_hardware_automotive_vehicle_aidl_impl_utils_common_include_VehiclePropertyStore_H_ #include <cstdint> #include <map> #include <memory> #include <mutex> #include <unordered_map> #include <VehicleHalTypes.h> #include <android-base/result.h> #include <android-base/thread_annotations.h> namespace android { namespace hardware { namespace automotive { namespace vehicle { // Encapsulates work related to storing and accessing configuration, storing and modifying // vehicle property values. // // VehiclePropertyValues stored in a sorted map thus it makes easier to get range of values, e.g. // to get value for all areas for particular property. // // This class is thread-safe, however it uses blocking synchronization across all methods. class VehiclePropertyStore { public: // Function that used to calculate unique token for given VehiclePropValue. using TokenFunction = ::std::function<int64_t( const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& value)>; // Register the given property according to the config. A property has to be registered first // before write/read. If tokenFunc is not nullptr, it would be used to generate a unique // property token to act as the key the property store. Otherwise, {propertyID, areaID} would be // used as the key. void registerProperty( const ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig& config, TokenFunction tokenFunc = nullptr); // Stores provided value. Returns true if value was written returns false if config wasn't // registered. ::android::base::Result<void> writeValue( const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue); // 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( const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue); // Remove all the values for the property. void removeValuesForProperty(int32_t propId); // Read all the stored values. std::vector<::aidl::android::hardware::automotive::vehicle::VehiclePropValue> readAllValues() const; // Read all the values for the property. ::android::base::Result< std::vector<::aidl::android::hardware::automotive::vehicle::VehiclePropValue>> readValuesForProperty(int32_t propId) const; // Read the value for the requested property. ::android::base::Result< std::unique_ptr<::aidl::android::hardware::automotive::vehicle::VehiclePropValue>> readValue( const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& request) const; // Read the value for the requested property. ::android::base::Result< std::unique_ptr<::aidl::android::hardware::automotive::vehicle::VehiclePropValue>> readValue(int32_t prop, int32_t area = 0, int64_t token = 0) const; // Get all property configs. std::vector<::aidl::android::hardware::automotive::vehicle::VehiclePropConfig> getAllConfigs() const; // Get the property config for the requested property. ::android::base::Result< const ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig*> getConfig(int32_t propId) const; private: struct RecordId { int32_t area; int64_t token; bool operator==(const RecordId& other) const; bool operator<(const RecordId& other) const; std::string toString() const; }; struct Record { ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig propConfig; TokenFunction tokenFunction; std::map<RecordId, ::aidl::android::hardware::automotive::vehicle::VehiclePropValue> values; }; mutable std::mutex mLock; std::unordered_map<int32_t, Record> mRecordsByPropId GUARDED_BY(mLock); const Record* getRecordLocked(int32_t propId) const; Record* getRecordLocked(int32_t propId); RecordId getRecordIdLocked( const ::aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue, const Record& record) const; ::android::base::Result< std::unique_ptr<::aidl::android::hardware::automotive::vehicle::VehiclePropValue>> readValueLocked(const RecordId& recId, const Record& record) const; }; } // namespace vehicle } // namespace automotive } // namespace hardware } // namespace android #endif // android_hardware_automotive_vehicle_aidl_impl_utils_common_include_VehiclePropertyStore_H_
automotive/vehicle/aidl/impl/utils/common/src/VehiclePropertyStore.cpp 0 → 100644 +231 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "VehiclePropertyStore" #include <utils/Log.h> #include "VehiclePropertyStore.h" #include <VehicleUtils.h> #include <android-base/format.h> namespace android { namespace hardware { namespace automotive { namespace vehicle { using ::aidl::android::hardware::automotive::vehicle::VehicleAreaConfig; using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig; using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue; using ::android::base::Result; bool VehiclePropertyStore::RecordId::operator==(const VehiclePropertyStore::RecordId& other) const { return area == other.area && token == other.token; } bool VehiclePropertyStore::RecordId::operator<(const VehiclePropertyStore::RecordId& other) const { return area < other.area || (area == other.area && token < other.token); } std::string VehiclePropertyStore::RecordId::toString() const { return ::fmt::format("RecordID{{.areaId={:d}, .token={:d}}}", area, token); } const VehiclePropertyStore::Record* VehiclePropertyStore::getRecordLocked(int32_t propId) const REQUIRES(mLock) { auto RecordIt = mRecordsByPropId.find(propId); return RecordIt == mRecordsByPropId.end() ? nullptr : &RecordIt->second; } VehiclePropertyStore::Record* VehiclePropertyStore::getRecordLocked(int32_t propId) REQUIRES(mLock) { auto RecordIt = mRecordsByPropId.find(propId); return RecordIt == mRecordsByPropId.end() ? nullptr : &RecordIt->second; } VehiclePropertyStore::RecordId VehiclePropertyStore::getRecordIdLocked( const VehiclePropValue& propValue, const VehiclePropertyStore::Record& record) const REQUIRES(mLock) { VehiclePropertyStore::RecordId recId{ .area = isGlobalProp(propValue.prop) ? 0 : propValue.areaId, .token = 0}; if (record.tokenFunction != nullptr) { recId.token = record.tokenFunction(propValue); } return recId; } Result<std::unique_ptr<VehiclePropValue>> VehiclePropertyStore::readValueLocked( const RecordId& recId, const Record& record) const REQUIRES(mLock) { auto it = record.values.find(recId); if (it == record.values.end()) { return Errorf("Record ID: {} is not found", recId.toString()); } return std::make_unique<VehiclePropValue>(it->second); } void VehiclePropertyStore::registerProperty(const VehiclePropConfig& config, VehiclePropertyStore::TokenFunction tokenFunc) { std::lock_guard<std::mutex> g(mLock); mRecordsByPropId[config.prop] = Record{ .propConfig = config, .tokenFunction = tokenFunc, }; } Result<void> VehiclePropertyStore::writeValue(const VehiclePropValue& propValue) { std::lock_guard<std::mutex> g(mLock); VehiclePropertyStore::Record* record = getRecordLocked(propValue.prop); if (record == nullptr) { return Errorf("property: {:d} not registered", propValue.prop); } if (!isGlobalProp(propValue.prop) && getAreaConfig(propValue, record->propConfig) == nullptr) { return Errorf("no config for property: {:d} area: {:d}", propValue.prop, propValue.areaId); } VehiclePropertyStore::RecordId recId = getRecordIdLocked(propValue, *record); auto it = record->values.find(recId); if (it == record->values.end()) { record->values[recId] = propValue; return {}; } VehiclePropValue* valueToUpdate = &(it->second); // propValue is outdated and drops it. if (valueToUpdate->timestamp > propValue.timestamp) { return Errorf("outdated timestamp: {:d}", propValue.timestamp); } // Update the propertyValue. // The timestamp in propertyStore should only be updated by the server side. It indicates // the time when the event is generated by the server. valueToUpdate->timestamp = propValue.timestamp; valueToUpdate->value = propValue.value; valueToUpdate->status = propValue.status; return {}; } void VehiclePropertyStore::removeValue(const VehiclePropValue& propValue) { std::lock_guard<std::mutex> g(mLock); VehiclePropertyStore::Record* record = getRecordLocked(propValue.prop); if (record == nullptr) { return; } VehiclePropertyStore::RecordId recId = getRecordIdLocked(propValue, *record); if (auto it = record->values.find(recId); it != record->values.end()) { record->values.erase(it); } } void VehiclePropertyStore::removeValuesForProperty(int32_t propId) { std::lock_guard<std::mutex> g(mLock); VehiclePropertyStore::Record* record = getRecordLocked(propId); if (record == nullptr) { return; } record->values.clear(); } std::vector<VehiclePropValue> VehiclePropertyStore::readAllValues() const { std::lock_guard<std::mutex> g(mLock); std::vector<VehiclePropValue> allValues; for (auto const& [_, record] : mRecordsByPropId) { for (auto const& [_, value] : record.values) { allValues.push_back(value); } } return allValues; } Result<std::vector<VehiclePropValue>> VehiclePropertyStore::readValuesForProperty( int32_t propId) const { std::lock_guard<std::mutex> g(mLock); std::vector<VehiclePropValue> values; const VehiclePropertyStore::Record* record = getRecordLocked(propId); if (record == nullptr) { return Errorf("property: {:d} not registered", propId); } for (auto const& [_, value] : record->values) { values.push_back(value); } return values; } Result<std::unique_ptr<VehiclePropValue>> VehiclePropertyStore::readValue( const VehiclePropValue& propValue) const { std::lock_guard<std::mutex> g(mLock); const VehiclePropertyStore::Record* record = getRecordLocked(propValue.prop); if (record == nullptr) { return Errorf("property: {:d} not registered", propValue.prop); } VehiclePropertyStore::RecordId recId = getRecordIdLocked(propValue, *record); return readValueLocked(recId, *record); } Result<std::unique_ptr<VehiclePropValue>> VehiclePropertyStore::readValue(int32_t propId, int32_t areaId, int64_t token) const { std::lock_guard<std::mutex> g(mLock); const VehiclePropertyStore::Record* record = getRecordLocked(propId); if (record == nullptr) { return Errorf("property: {:d} not registered", propId); } VehiclePropertyStore::RecordId recId{.area = isGlobalProp(propId) ? 0 : areaId, .token = token}; return readValueLocked(recId, *record); } std::vector<VehiclePropConfig> VehiclePropertyStore::getAllConfigs() const { std::lock_guard<std::mutex> g(mLock); std::vector<VehiclePropConfig> configs; configs.reserve(mRecordsByPropId.size()); for (auto& [_, config] : mRecordsByPropId) { configs.push_back(config.propConfig); } return configs; } Result<const VehiclePropConfig*> VehiclePropertyStore::getConfig(int32_t propId) const { std::lock_guard<std::mutex> g(mLock); const VehiclePropertyStore::Record* record = getRecordLocked(propId); if (record == nullptr) { return Errorf("property: {:d} not registered", propId); } return &record->propConfig; } } // namespace vehicle } // namespace automotive } // namespace hardware } // namespace android
automotive/vehicle/aidl/impl/utils/common/test/Android.bp +5 −1 Original line number Diff line number Diff line Loading @@ -22,7 +22,11 @@ cc_test { name: "VehicleHalVehicleUtilsTest", srcs: ["*.cpp"], vendor: true, static_libs: ["VehicleHalUtils"], static_libs: [ "VehicleHalUtils", "libgtest", "libgmock", ], defaults: ["VehicleHalDefaults"], test_suites: ["general-tests"], }
automotive/vehicle/aidl/impl/utils/common/test/VehiclePropertyStoreTest.cpp 0 → 100644 +314 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <PropertyUtils.h> #include <VehiclePropertyStore.h> #include <VehicleUtils.h> #include <gmock/gmock.h> #include <gtest/gtest.h> namespace android { namespace hardware { namespace automotive { namespace vehicle { namespace { using ::aidl::android::hardware::automotive::vehicle::VehicleAreaConfig; using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig; using ::aidl::android::hardware::automotive::vehicle::VehicleProperty; using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyAccess; using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyChangeMode; using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue; using ::android::base::Result; using ::testing::ElementsAre; using ::testing::Eq; using ::testing::WhenSortedBy; constexpr int INVALID_PROP_ID = 0; struct PropValueCmp { bool operator()(const VehiclePropValue& a, const VehiclePropValue& b) const { return (a.prop < b.prop) || ((a.prop == b.prop) && (a.value < b.value)) || ((a.prop == b.prop) && (a.value == b.value) && (a.areaId < b.areaId)); } } propValueCmp; int64_t timestampToken(const VehiclePropValue& value) { return value.timestamp; } } // namespace class VehiclePropertyStoreTest : public ::testing::Test { protected: void SetUp() override { mConfigFuelCapacity = { .prop = toInt(VehicleProperty::INFO_FUEL_CAPACITY), .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::STATIC, }; VehiclePropConfig configTirePressure = { .prop = toInt(VehicleProperty::TIRE_PRESSURE), .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::CONTINUOUS, .areaConfigs = {VehicleAreaConfig{.areaId = WHEEL_FRONT_LEFT}, VehicleAreaConfig{.areaId = WHEEL_FRONT_RIGHT}, VehicleAreaConfig{.areaId = WHEEL_REAR_LEFT}, VehicleAreaConfig{.areaId = WHEEL_REAR_RIGHT}}, }; mStore.registerProperty(mConfigFuelCapacity); mStore.registerProperty(configTirePressure); } VehiclePropertyStore mStore; VehiclePropConfig mConfigFuelCapacity; }; TEST_F(VehiclePropertyStoreTest, testGetAllConfigs) { std::vector<VehiclePropConfig> configs = mStore.getAllConfigs(); ASSERT_EQ(configs.size(), static_cast<size_t>(2)); } TEST_F(VehiclePropertyStoreTest, testGetConfig) { Result<const VehiclePropConfig*> result = mStore.getConfig(toInt(VehicleProperty::INFO_FUEL_CAPACITY)); ASSERT_RESULT_OK(result); ASSERT_EQ(*(result.value()), mConfigFuelCapacity); } TEST_F(VehiclePropertyStoreTest, testGetConfigWithInvalidPropId) { Result<const VehiclePropConfig*> result = mStore.getConfig(INVALID_PROP_ID); ASSERT_FALSE(result.ok()) << "expect error when getting a config for an invalid property ID"; } std::vector<VehiclePropValue> getTestPropValues() { VehiclePropValue fuelCapacity = { .prop = toInt(VehicleProperty::INFO_FUEL_CAPACITY), .value = {.floatValues = {1.0}}, }; VehiclePropValue leftTirePressure = { .prop = toInt(VehicleProperty::TIRE_PRESSURE), .value = {.floatValues = {170.0}}, .areaId = WHEEL_FRONT_LEFT, }; VehiclePropValue rightTirePressure = { .prop = toInt(VehicleProperty::TIRE_PRESSURE), .value = {.floatValues = {180.0}}, .areaId = WHEEL_FRONT_RIGHT, }; return {fuelCapacity, leftTirePressure, rightTirePressure}; } TEST_F(VehiclePropertyStoreTest, testWriteValueOk) { auto values = getTestPropValues(); ASSERT_RESULT_OK(mStore.writeValue(values[0])); } TEST_F(VehiclePropertyStoreTest, testReadAllValues) { auto values = getTestPropValues(); for (const auto& value : values) { ASSERT_RESULT_OK(mStore.writeValue(value)); } auto gotValues = mStore.readAllValues(); ASSERT_THAT(gotValues, WhenSortedBy(propValueCmp, Eq(values))); } TEST_F(VehiclePropertyStoreTest, testReadValuesForPropertyOneValue) { auto values = getTestPropValues(); for (const auto& value : values) { ASSERT_RESULT_OK(mStore.writeValue(value)); } auto result = mStore.readValuesForProperty(toInt(VehicleProperty::INFO_FUEL_CAPACITY)); ASSERT_RESULT_OK(result); ASSERT_THAT(result.value(), ElementsAre(values[0])); } TEST_F(VehiclePropertyStoreTest, testReadValuesForPropertyMultipleValues) { auto values = getTestPropValues(); for (const auto& value : values) { ASSERT_RESULT_OK(mStore.writeValue(value)); } auto result = mStore.readValuesForProperty(toInt(VehicleProperty::TIRE_PRESSURE)); ASSERT_RESULT_OK(result); ASSERT_THAT(result.value(), WhenSortedBy(propValueCmp, ElementsAre(values[1], values[2]))); } TEST_F(VehiclePropertyStoreTest, testReadValuesForPropertyError) { auto result = mStore.readValuesForProperty(INVALID_PROP_ID); ASSERT_FALSE(result.ok()) << "expect error when reading values for an invalid property"; } TEST_F(VehiclePropertyStoreTest, testReadValueOk) { auto values = getTestPropValues(); for (const auto& value : values) { ASSERT_RESULT_OK(mStore.writeValue(value)); } VehiclePropValue requestValue = { .prop = toInt(VehicleProperty::TIRE_PRESSURE), .areaId = WHEEL_FRONT_LEFT, }; auto result = mStore.readValue(requestValue); ASSERT_RESULT_OK(result); ASSERT_EQ(*(result.value()), values[1]); } TEST_F(VehiclePropertyStoreTest, testReadValueByPropIdOk) { auto values = getTestPropValues(); for (const auto& value : values) { ASSERT_RESULT_OK(mStore.writeValue(value)); } auto result = mStore.readValue(toInt(VehicleProperty::TIRE_PRESSURE), WHEEL_FRONT_RIGHT); ASSERT_EQ(*(result.value()), values[2]); } TEST_F(VehiclePropertyStoreTest, testReadValueError) { auto values = getTestPropValues(); for (const auto& value : values) { ASSERT_RESULT_OK(mStore.writeValue(value)); } auto result = mStore.readValue(toInt(VehicleProperty::TIRE_PRESSURE), WHEEL_REAR_LEFT); ASSERT_FALSE(result.ok()) << "expect error when reading a value that has not been written"; } TEST_F(VehiclePropertyStoreTest, testWriteValueError) { ASSERT_FALSE(mStore.writeValue({ .prop = INVALID_PROP_ID, .value = {.floatValues = {1.0}}, }) .ok()) << "expect error when writing value for an invalid property ID"; } TEST_F(VehiclePropertyStoreTest, testWriteValueNoAreaConfig) { ASSERT_FALSE(mStore.writeValue({ .prop = toInt(VehicleProperty::TIRE_PRESSURE), .value = {.floatValues = {180.0}}, // There is no config for ALL_WHEELS. .areaId = ALL_WHEELS, }) .ok()) << "expect error when writing value for an area without config"; } TEST_F(VehiclePropertyStoreTest, testWriteOutdatedValue) { ASSERT_RESULT_OK(mStore.writeValue({ .timestamp = 1, .prop = toInt(VehicleProperty::TIRE_PRESSURE), .value = {.floatValues = {180.0}}, .areaId = WHEEL_FRONT_LEFT, })); // Write an older value. ASSERT_FALSE(mStore.writeValue({ .timestamp = 0, .prop = toInt(VehicleProperty::TIRE_PRESSURE), .value = {.floatValues = {180.0}}, .areaId = WHEEL_FRONT_LEFT, }) .ok()) << "expect error when writing an outdated value"; } TEST_F(VehiclePropertyStoreTest, testToken) { int propId = toInt(VehicleProperty::INFO_FUEL_CAPACITY); VehiclePropConfig config = { .prop = propId, }; // Replace existing config. mStore.registerProperty(config, timestampToken); VehiclePropValue fuelCapacityValueToken1 = { .timestamp = 1, .prop = propId, .value = {.floatValues = {1.0}}, }; VehiclePropValue fuelCapacityValueToken2 = { .timestamp = 2, .prop = propId, .value = {.floatValues = {2.0}}, }; ASSERT_RESULT_OK(mStore.writeValue(fuelCapacityValueToken1)); ASSERT_RESULT_OK(mStore.writeValue(fuelCapacityValueToken2)); auto result = mStore.readValuesForProperty(propId); ASSERT_RESULT_OK(result); ASSERT_EQ(result.value().size(), static_cast<size_t>(2)); auto tokenResult = mStore.readValue(propId, /*areaId=*/0, /*token=*/2); ASSERT_RESULT_OK(tokenResult); ASSERT_EQ(*(tokenResult.value()), fuelCapacityValueToken2); } TEST_F(VehiclePropertyStoreTest, testRemoveValue) { auto values = getTestPropValues(); for (const auto& value : values) { ASSERT_RESULT_OK(mStore.writeValue(value)); } mStore.removeValue(values[0]); ASSERT_FALSE(mStore.readValue(values[0]).ok()) << "expect error when reading a removed value"; auto leftTirePressureResult = mStore.readValue(values[1]); ASSERT_RESULT_OK(leftTirePressureResult); ASSERT_EQ(*(leftTirePressureResult.value()), values[1]); } TEST_F(VehiclePropertyStoreTest, testRemoveValuesForProperty) { auto values = getTestPropValues(); for (const auto& value : values) { ASSERT_RESULT_OK(mStore.writeValue(value)); } mStore.removeValuesForProperty(toInt(VehicleProperty::INFO_FUEL_CAPACITY)); mStore.removeValuesForProperty(toInt(VehicleProperty::TIRE_PRESSURE)); auto gotValues = mStore.readAllValues(); ASSERT_TRUE(gotValues.empty()); } } // namespace vehicle } // namespace automotive } // namespace hardware } // namespace android