Support VUR in FakeVehicleHardware.

Support VUR in reference VHAL FakeVehicleHardware layer. Unless
specified in config, all continuous properties in reference VHAL
supports VUR.

Test: atest FakeVehicleHardwareTest
Bug: 306748801
Change-Id: I5265172996418a5d405392570673355e7860b50c
diff --git a/automotive/vehicle/aidl/impl/default_config/JsonConfigLoader/src/JsonConfigLoader.cpp b/automotive/vehicle/aidl/impl/default_config/JsonConfigLoader/src/JsonConfigLoader.cpp
index 39ce10e..82dc8a6 100644
--- a/automotive/vehicle/aidl/impl/default_config/JsonConfigLoader/src/JsonConfigLoader.cpp
+++ b/automotive/vehicle/aidl/impl/default_config/JsonConfigLoader/src/JsonConfigLoader.cpp
@@ -275,6 +275,16 @@
 }
 
 template <>
+Result<bool> JsonValueParser::convertValueToType<bool>(const std::string& fieldName,
+                                                       const Json::Value& value) {
+    if (!value.isBool()) {
+        return Error() << "The value: " << value << " for field: " << fieldName
+                       << " is not in correct type, expect bool";
+    }
+    return value.asBool();
+}
+
+template <>
 Result<int32_t> JsonValueParser::convertValueToType<int32_t>(const std::string& fieldName,
                                                              const Json::Value& value) {
     if (!value.isInt()) {
@@ -531,6 +541,12 @@
         tryParseJsonValueToVariable(jsonAreaConfig, "maxFloatValue", /*optional=*/true,
                                     &areaConfig.maxFloatValue, errors);
 
+        // By default we support variable update rate for all properties except it is explicitly
+        // disabled.
+        areaConfig.supportVariableUpdateRate = true;
+        tryParseJsonValueToVariable(jsonAreaConfig, "supportVariableUpdateRate", /*optional=*/true,
+                                    &areaConfig.supportVariableUpdateRate, errors);
+
         std::vector<int64_t> supportedEnumValues;
         tryParseJsonArrayToVariable(jsonAreaConfig, "supportedEnumValues", /*optional=*/true,
                                     &supportedEnumValues, errors);
@@ -585,6 +601,16 @@
     if (errors->size() != initialErrorCount) {
         return std::nullopt;
     }
+
+    // If there is no area config, by default we allow variable update rate, so we have to add
+    // a global area config.
+    if (configDecl.config.areaConfigs.size() == 0) {
+        VehicleAreaConfig areaConfig = {
+                .areaId = 0,
+                .supportVariableUpdateRate = true,
+        };
+        configDecl.config.areaConfigs.push_back(std::move(areaConfig));
+    }
     return configDecl;
 }
 
diff --git a/automotive/vehicle/aidl/impl/default_config/config/DefaultProperties.json b/automotive/vehicle/aidl/impl/default_config/config/DefaultProperties.json
index 6c8d59c..d3bb60c 100644
--- a/automotive/vehicle/aidl/impl/default_config/config/DefaultProperties.json
+++ b/automotive/vehicle/aidl/impl/default_config/config/DefaultProperties.json
@@ -3579,7 +3579,13 @@
             "property": "VehicleProperty::WATCHDOG_TERMINATED_PROCESS"
         },
         {
-            "property": "VehicleProperty::VHAL_HEARTBEAT"
+            "property": "VehicleProperty::VHAL_HEARTBEAT",
+            "areas": [
+                {
+                    "areaId": 0,
+                    "supportVariableUpdateRate": false
+                }
+            ]
         },
         {
             "property": "VehicleProperty::CLUSTER_SWITCH_UI",
@@ -3641,6 +3647,12 @@
                 0,
                 16
             ],
+            "areas": [
+                {
+                    "areaId": 0,
+                    "supportVariableUpdateRate": false
+                }
+            ],
             "comment": "configArray specifies it consists of int64[2] and byte[16]."
         },
         {
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h b/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
index 6115c49..718f68e 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
@@ -269,9 +269,9 @@
             std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropConfig>&) const;
 
     aidl::android::hardware::automotive::vehicle::StatusCode subscribePropIdAreaIdLocked(
-            int32_t propId, int32_t areaId, float sampleRateHz,
-            aidl::android::hardware::automotive::vehicle::VehiclePropertyChangeMode changeMode)
-            REQUIRES(mLock);
+            int32_t propId, int32_t areaId, float sampleRateHz, bool enableVariableUpdateRate,
+            const aidl::android::hardware::automotive::vehicle::VehiclePropConfig&
+                    vehiclePropConfig) REQUIRES(mLock);
 
     static aidl::android::hardware::automotive::vehicle::VehiclePropValue createHwInputKeyProp(
             aidl::android::hardware::automotive::vehicle::VehicleHwKeyInputAction action,
@@ -286,6 +286,10 @@
 
     static std::string genFakeDataHelp();
     static std::string parseErrMsg(std::string fieldName, std::string value, std::string type);
+    static bool isVariableUpdateRateSupported(
+            const aidl::android::hardware::automotive::vehicle::VehiclePropConfig&
+                    vehiclePropConfig,
+            int32_t areaId);
 };
 
 }  // namespace fake
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp b/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
index bc66f6d..cb8e51f 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
@@ -1941,7 +1941,8 @@
     std::scoped_lock<std::mutex> lockGuard(mLock);
     for (int areaId : options.areaIds) {
         if (StatusCode status = subscribePropIdAreaIdLocked(propId, areaId, options.sampleRate,
-                                                            configResult.value()->changeMode);
+                                                            options.enableVariableUpdateRate,
+                                                            *configResult.value());
             status != StatusCode::OK) {
             return status;
         }
@@ -1949,14 +1950,29 @@
     return StatusCode::OK;
 }
 
-StatusCode FakeVehicleHardware::subscribePropIdAreaIdLocked(int32_t propId, int32_t areaId,
-                                                            float sampleRateHz,
-                                                            VehiclePropertyChangeMode changeMode) {
+bool FakeVehicleHardware::isVariableUpdateRateSupported(const VehiclePropConfig& vehiclePropConfig,
+                                                        int32_t areaId) {
+    for (size_t i = 0; i < vehiclePropConfig.areaConfigs.size(); i++) {
+        const auto& areaConfig = vehiclePropConfig.areaConfigs[i];
+        if (areaConfig.areaId != areaId) {
+            continue;
+        }
+        if (areaConfig.supportVariableUpdateRate) {
+            return true;
+        }
+        break;
+    }
+    return false;
+}
+
+StatusCode FakeVehicleHardware::subscribePropIdAreaIdLocked(
+        int32_t propId, int32_t areaId, float sampleRateHz, bool enableVariableUpdateRate,
+        const VehiclePropConfig& vehiclePropConfig) {
     PropIdAreaId propIdAreaId{
             .propId = propId,
             .areaId = areaId,
     };
-    switch (changeMode) {
+    switch (vehiclePropConfig.changeMode) {
         case VehiclePropertyChangeMode::STATIC:
             ALOGW("subscribe to a static property, do nothing.");
             return StatusCode::OK;
@@ -1972,7 +1988,16 @@
                 mRecurrentTimer->unregisterTimerCallback(mRecurrentActions[propIdAreaId]);
             }
             int64_t intervalInNanos = static_cast<int64_t>(1'000'000'000. / sampleRateHz);
-            auto action = std::make_shared<RecurrentTimer::Callback>([this, propId, areaId] {
+
+            // For continuous properties, we must generate a new onPropertyChange event
+            // periodically according to the sample rate.
+            auto eventMode = VehiclePropertyStore::EventMode::ALWAYS;
+            if (isVariableUpdateRateSupported(vehiclePropConfig, areaId) &&
+                enableVariableUpdateRate) {
+                eventMode = VehiclePropertyStore::EventMode::ON_VALUE_CHANGE;
+            }
+            auto action = std::make_shared<RecurrentTimer::Callback>([this, propId, areaId,
+                                                                      eventMode] {
                 // Refresh the property value. In real implementation, this should poll the latest
                 // value from vehicle bus. Here, we are just refreshing the existing value with a
                 // new timestamp.
@@ -1986,10 +2011,9 @@
                     return;
                 }
                 result.value()->timestamp = elapsedRealtimeNano();
-                // For continuous properties, we must generate a new onPropertyChange event
-                // periodically according to the sample rate.
+
                 mServerSidePropStore->writeValue(std::move(result.value()), /*updateStatus=*/true,
-                                                 VehiclePropertyStore::EventMode::ALWAYS);
+                                                 eventMode);
             });
             mRecurrentTimer->registerTimerCallback(intervalInNanos, action);
             mRecurrentActions[propIdAreaId] = action;
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
index 85eefa4..0432500 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
@@ -95,6 +95,7 @@
 using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::IsSubsetOf;
+using ::testing::UnorderedElementsAre;
 using ::testing::WhenSortedBy;
 
 using std::chrono::milliseconds;
@@ -454,6 +455,29 @@
     ASSERT_EQ(configs.size(), helper.loadConfigDeclarations().size());
 }
 
+TEST_F(FakeVehicleHardwareTest, testGetAllPropertyConfigs_defaultSupportVUR) {
+    std::vector<VehiclePropConfig> configs = getHardware()->getAllPropertyConfigs();
+
+    for (const auto& config : configs) {
+        bool expectedSupportVUR = true;
+        if (config.prop == toInt(VehicleProperty::VHAL_HEARTBEAT) ||
+            config.prop == toInt(VehicleProperty::CLUSTER_HEARTBEAT)) {
+            expectedSupportVUR = false;
+        }
+        EXPECT_GE(config.areaConfigs.size(), 1u)
+                << "expect at least one area config, including global area config, propId: "
+                << config.prop;
+        if (config.areaConfigs.size() == 0) {
+            continue;
+        }
+        for (const auto& areaConfig : config.areaConfigs) {
+            EXPECT_EQ(areaConfig.supportVariableUpdateRate, expectedSupportVUR)
+                    << "unexpected supportVariableUpdateRate for propId: " << config.prop
+                    << ", areaId: " << areaConfig.areaId;
+        }
+    }
+}
+
 TEST_F(FakeVehicleHardwareTest, testGetDefaultValues) {
     std::vector<GetValueRequest> getValueRequests;
     std::vector<GetValueResult> expectedGetValueResults;
@@ -3117,6 +3141,47 @@
     }
 }
 
+TEST_F(FakeVehicleHardwareTest, testSubscribe_enableVUR) {
+    int32_t propSpeed = toInt(VehicleProperty::PERF_VEHICLE_SPEED);
+    int32_t areaId = 0;
+    SubscribeOptions options;
+    options.propId = propSpeed;
+    options.areaIds = {areaId};
+    options.enableVariableUpdateRate = true;
+    options.sampleRate = 5;
+    int64_t timestamp = elapsedRealtimeNano();
+
+    auto status = getHardware()->subscribe(options);
+    ASSERT_EQ(status, StatusCode::OK) << "failed to subscribe";
+
+    status = setValue({
+            .prop = propSpeed,
+            .areaId = 0,
+            .value.floatValues = {1.1f},
+    });
+    ASSERT_EQ(status, StatusCode::OK) << "failed to set speed";
+
+    status = setValue({
+            .prop = propSpeed,
+            .areaId = 0,
+            .value.floatValues = {1.2f},
+    });
+    ASSERT_EQ(status, StatusCode::OK) << "failed to set speed";
+
+    ASSERT_TRUE(waitForChangedProperties(propSpeed, areaId, /*count=*/2, milliseconds(100)))
+            << "not enough events generated for speed";
+    auto updatedValues = getChangedProperties();
+    std::unordered_set<float> gotValues;
+    for (auto& value : updatedValues) {
+        EXPECT_GE(value.timestamp, timestamp) << "timestamp must be updated";
+        EXPECT_EQ(value.prop, propSpeed) << "propId must be correct";
+        EXPECT_EQ(value.areaId, areaId) << "areaId must be correct";
+        gotValues.insert(value.value.floatValues[0]);
+    }
+    EXPECT_THAT(gotValues, UnorderedElementsAre(1.1f, 1.2f))
+            << "must only receive property event for changed value";
+}
+
 TEST_F(FakeVehicleHardwareTest, testSubscribeUnusubscribe_onChange) {
     int32_t propHvac = toInt(VehicleProperty::HVAC_TEMPERATURE_SET);
     int32_t areaId = SEAT_1_LEFT;