Support hvac temperature synchronization in VHAL

Bug: 306055692
Test: atest FakeVehicleHardwareTest
Change-Id: Ibfdfb7ae45854c969070757b0d809be0e9b6a9ed
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 718f68e..26fdee6 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
@@ -222,6 +222,9 @@
             const aidl::android::hardware::automotive::vehicle::VehiclePropValue& value) const;
     bool isHvacPropAndHvacNotAvailable(int32_t propId, int32_t areaId) const;
     VhalResult<void> isAdasPropertyAvailable(int32_t adasStatePropertyId) const;
+    VhalResult<void> synchronizeHvacTemp(int32_t hvacDualOnAreaId,
+                                         std::optional<float> newTempC) const;
+    std::optional<int32_t> getSyncedAreaIdIfHvacDualOn(int32_t hvacTemperatureSetAreaId) const;
 
     std::unordered_map<int32_t, ConfigDeclaration> loadConfigDeclarations();
 
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 cb8e51f..7ff03c6 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
@@ -601,6 +601,65 @@
     return {};
 }
 
+VhalResult<void> FakeVehicleHardware::synchronizeHvacTemp(int32_t hvacDualOnAreaId,
+                                                          std::optional<float> newTempC) const {
+    auto hvacTemperatureSetResults = mServerSidePropStore->readValuesForProperty(
+            toInt(VehicleProperty::HVAC_TEMPERATURE_SET));
+    if (!hvacTemperatureSetResults.ok()) {
+        return StatusError(StatusCode::NOT_AVAILABLE)
+               << "Failed to get HVAC_TEMPERATURE_SET, error: "
+               << getErrorMsg(hvacTemperatureSetResults);
+    }
+    auto& hvacTemperatureSetValues = hvacTemperatureSetResults.value();
+    std::optional<float> tempCToSynchronize = newTempC;
+    for (size_t i = 0; i < hvacTemperatureSetValues.size(); i++) {
+        int32_t areaId = hvacTemperatureSetValues[i]->areaId;
+        if ((hvacDualOnAreaId & areaId) != areaId) {
+            continue;
+        }
+        if (hvacTemperatureSetValues[i]->status != VehiclePropertyStatus::AVAILABLE) {
+            continue;
+        }
+        // When HVAC_DUAL_ON is initially enabled, synchronize all area IDs
+        // to the temperature of the first area ID, which is the driver's.
+        if (!tempCToSynchronize.has_value()) {
+            tempCToSynchronize = hvacTemperatureSetValues[i]->value.floatValues[0];
+            continue;
+        }
+        auto updatedValue = std::move(hvacTemperatureSetValues[i]);
+        updatedValue->value.floatValues[0] = tempCToSynchronize.value();
+        updatedValue->timestamp = elapsedRealtimeNano();
+        // This will trigger a property change event for the current hvac property value.
+        auto writeResult =
+                mServerSidePropStore->writeValue(std::move(updatedValue), /*updateStatus=*/true,
+                                                 VehiclePropertyStore::EventMode::ALWAYS);
+        if (!writeResult.ok()) {
+            return StatusError(getErrorCode(writeResult))
+                   << "Failed to write value into property store, error: "
+                   << getErrorMsg(writeResult);
+        }
+    }
+    return {};
+}
+
+std::optional<int32_t> FakeVehicleHardware::getSyncedAreaIdIfHvacDualOn(
+        int32_t hvacTemperatureSetAreaId) const {
+    auto hvacDualOnResults =
+            mServerSidePropStore->readValuesForProperty(toInt(VehicleProperty::HVAC_DUAL_ON));
+    if (!hvacDualOnResults.ok()) {
+        return std::nullopt;
+    }
+    auto& hvacDualOnValues = hvacDualOnResults.value();
+    for (size_t i = 0; i < hvacDualOnValues.size(); i++) {
+        if ((hvacDualOnValues[i]->areaId & hvacTemperatureSetAreaId) == hvacTemperatureSetAreaId &&
+            hvacDualOnValues[i]->value.int32Values.size() == 1 &&
+            hvacDualOnValues[i]->value.int32Values[0] == 1) {
+            return hvacDualOnValues[i]->areaId;
+        }
+    }
+    return std::nullopt;
+}
+
 FakeVehicleHardware::ValueResultType FakeVehicleHardware::getUserHalProp(
         const VehiclePropValue& value) const {
     auto propId = value.prop;
@@ -853,6 +912,28 @@
         case toInt(VehicleProperty::HVAC_TEMPERATURE_VALUE_SUGGESTION):
             *isSpecialValue = true;
             return setHvacTemperatureValueSuggestion(value);
+        case toInt(VehicleProperty::HVAC_TEMPERATURE_SET):
+            if (value.value.floatValues.size() != 1) {
+                *isSpecialValue = true;
+                return StatusError(StatusCode::INVALID_ARG)
+                       << "HVAC_DUAL_ON requires only one float value";
+            }
+            if (auto hvacDualOnAreaId = getSyncedAreaIdIfHvacDualOn(value.areaId);
+                hvacDualOnAreaId.has_value()) {
+                *isSpecialValue = true;
+                return synchronizeHvacTemp(hvacDualOnAreaId.value(), value.value.floatValues[0]);
+            }
+            return {};
+        case toInt(VehicleProperty::HVAC_DUAL_ON):
+            if (value.value.int32Values.size() != 1) {
+                *isSpecialValue = true;
+                return StatusError(StatusCode::INVALID_ARG)
+                       << "HVAC_DUAL_ON requires only one int32 value";
+            }
+            if (value.value.int32Values[0] == 1) {
+                synchronizeHvacTemp(value.areaId, std::nullopt);
+            }
+            return {};
         case toInt(VehicleProperty::LANE_CENTERING_ASSIST_COMMAND): {
             isAdasPropertyAvailableResult =
                     isAdasPropertyAvailable(toInt(VehicleProperty::LANE_CENTERING_ASSIST_STATE));
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 0432500..3b6f717 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
@@ -1889,6 +1889,101 @@
     }
 }
 
+TEST_F(FakeVehicleHardwareTest, testHvacDualOnSynchronizesTemp) {
+    auto hvacDualOnConfig = std::move(getVehiclePropConfig(toInt(VehicleProperty::HVAC_DUAL_ON)));
+    auto hvacTemperatureSetConfig =
+            std::move(getVehiclePropConfig(toInt(VehicleProperty::HVAC_TEMPERATURE_SET)));
+    EXPECT_NE(hvacDualOnConfig, nullptr);
+    EXPECT_NE(hvacTemperatureSetConfig, nullptr);
+    for (auto& hvacTemperatureSetConfig : hvacTemperatureSetConfig->areaConfigs) {
+        int32_t hvacTemperatureSetAreaId = hvacTemperatureSetConfig.areaId;
+        subscribe(toInt(VehicleProperty::HVAC_TEMPERATURE_SET), hvacTemperatureSetAreaId,
+                  /*sampleRateHz*/ 0);
+    }
+    for (auto& hvacDualOnConfig : hvacDualOnConfig->areaConfigs) {
+        int32_t hvacDualOnAreaId = hvacDualOnConfig.areaId;
+        subscribe(toInt(VehicleProperty::HVAC_DUAL_ON), hvacDualOnAreaId, /*sampleRateHz*/ 0);
+        StatusCode status = setValue(VehiclePropValue{.prop = toInt(VehicleProperty::HVAC_DUAL_ON),
+                                                      .areaId = hvacDualOnAreaId,
+                                                      .value.int32Values = {1}});
+        EXPECT_EQ(status, StatusCode::OK);
+
+        // Verify there's an event for all HVAC_TEMPERATURE_SET
+        // area IDs covered by the HVAC_DUAL_ON area ID
+        auto events = getChangedProperties();
+        std::unordered_set<float> temperatureValues;
+        for (const auto& event : events) {
+            // Ignore HVAC_DUAL_ON event
+            if (event.prop == toInt(VehicleProperty::HVAC_DUAL_ON)) {
+                continue;
+            }
+            EXPECT_EQ(event.prop, toInt(VehicleProperty::HVAC_TEMPERATURE_SET));
+            EXPECT_EQ((hvacDualOnAreaId & event.areaId), event.areaId);
+            EXPECT_EQ(1u, event.value.floatValues.size());
+            temperatureValues.insert(event.value.floatValues[0]);
+        }
+        // Verify that the temperature value is the same for all events
+        // Ie the temperature in all area IDs are synchronized
+        EXPECT_EQ(1u, temperatureValues.size());
+        clearChangedProperties();
+
+        // Verify when any HVAC_TEMPERATURE_SET area ID is changed all
+        // area IDs covered by the HVAC_DUAL_ON area ID are also changed
+        for (auto& hvacTemperatureSetConfig : hvacTemperatureSetConfig->areaConfigs) {
+            int32_t hvacTemperatureSetAreaId = hvacTemperatureSetConfig.areaId;
+            if ((hvacDualOnAreaId & hvacTemperatureSetAreaId) != hvacTemperatureSetAreaId) {
+                continue;
+            }
+            float expectedValue = 25;
+            status = setValue(VehiclePropValue{.prop = toInt(VehicleProperty::HVAC_TEMPERATURE_SET),
+                                               .areaId = hvacTemperatureSetAreaId,
+                                               .value.floatValues = {expectedValue}});
+            EXPECT_EQ(status, StatusCode::OK);
+            events = getChangedProperties();
+            for (const auto& event : events) {
+                EXPECT_EQ(event.prop, toInt(VehicleProperty::HVAC_TEMPERATURE_SET));
+                EXPECT_EQ(1u, event.value.floatValues.size());
+                EXPECT_EQ(expectedValue, event.value.floatValues[0]);
+            }
+            clearChangedProperties();
+        }
+
+        status = setValue(VehiclePropValue{.prop = toInt(VehicleProperty::HVAC_DUAL_ON),
+                                           .areaId = hvacDualOnAreaId,
+                                           .value.int32Values = {0}});
+        EXPECT_EQ(status, StatusCode::OK);
+
+        // When HVAC_DUAL_ON is disabled, there should be no events created
+        // for HVAC_TEMPERATURE_SET ie no temperature synchronization.
+        events = getChangedProperties();
+        EXPECT_EQ(1u, events.size());
+        EXPECT_EQ(events[0].prop, toInt(VehicleProperty::HVAC_DUAL_ON));
+        EXPECT_EQ(events[0].areaId, hvacDualOnAreaId);
+        clearChangedProperties();
+
+        // Verify when any HVAC_TEMPERATURE_SET area ID is
+        // changed other area IDs do not change.
+        for (auto& hvacTemperatureSetConfig : hvacTemperatureSetConfig->areaConfigs) {
+            int32_t hvacTemperatureSetAreaId = hvacTemperatureSetConfig.areaId;
+            if ((hvacDualOnAreaId & hvacTemperatureSetAreaId) != hvacTemperatureSetAreaId) {
+                continue;
+            }
+            float expectedValue = 24;
+            status = setValue(VehiclePropValue{.prop = toInt(VehicleProperty::HVAC_TEMPERATURE_SET),
+                                               .areaId = hvacTemperatureSetAreaId,
+                                               .value.floatValues = {expectedValue}});
+            EXPECT_EQ(status, StatusCode::OK);
+            events = getChangedProperties();
+            EXPECT_EQ(1u, events.size());
+            EXPECT_EQ(events[0].prop, toInt(VehicleProperty::HVAC_TEMPERATURE_SET));
+            EXPECT_EQ(events[0].areaId, hvacTemperatureSetAreaId);
+            EXPECT_EQ(1u, events[0].value.floatValues.size());
+            EXPECT_EQ(expectedValue, events[0].value.floatValues[0]);
+            clearChangedProperties();
+        }
+    }
+}
+
 TEST_F(FakeVehicleHardwareTest, testGetAdasPropNotAvailable) {
     std::unordered_map<int32_t, std::vector<int32_t>> adasEnabledPropToDependentProps = {
             {