Merge "Use batched property updates in IVehicleHardware." into main
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 26fdee6..8cd92b3 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
@@ -142,6 +142,16 @@
         void handleRequestsOnce();
     };
 
+    struct RefreshInfo {
+        VehiclePropertyStore::EventMode eventMode;
+        int64_t intervalInNanos;
+    };
+
+    struct ActionForInterval {
+        std::unordered_set<PropIdAreaId, PropIdAreaIdHash> propIdAreaIdsToRefresh;
+        std::shared_ptr<RecurrentTimer::Callback> recurrentAction;
+    };
+
     const std::unique_ptr<obd2frame::FakeObd2Frame> mFakeObd2Frame;
     const std::unique_ptr<FakeUserHal> mFakeUserHal;
     // RecurrentTimer is thread-safe.
@@ -154,8 +164,9 @@
     std::unique_ptr<const PropertySetErrorCallback> mOnPropertySetErrorCallback;
 
     std::mutex mLock;
-    std::unordered_map<PropIdAreaId, std::shared_ptr<RecurrentTimer::Callback>, PropIdAreaIdHash>
-            mRecurrentActions GUARDED_BY(mLock);
+    std::unordered_map<PropIdAreaId, RefreshInfo, PropIdAreaIdHash> mRefreshInfoByPropIdAreaId
+            GUARDED_BY(mLock);
+    std::unordered_map<int64_t, ActionForInterval> mActionByIntervalInNanos GUARDED_BY(mLock);
     std::unordered_map<PropIdAreaId, VehiclePropValuePool::RecyclableType, PropIdAreaIdHash>
             mSavedProps GUARDED_BY(mLock);
     std::unordered_set<PropIdAreaId, PropIdAreaIdHash> mSubOnChangePropIdAreaIds GUARDED_BY(mLock);
@@ -183,6 +194,10 @@
     void onValueChangeCallback(
             const aidl::android::hardware::automotive::vehicle::VehiclePropValue& value)
             EXCLUDES(mLock);
+    // The callback that would be called when multiple vehicle property value changes happen.
+    void onValuesChangeCallback(
+            std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue> values)
+            EXCLUDES(mLock);
     // Load the config files in format '*.json' from the directory and parse the config files
     // into a map from property ID to ConfigDeclarations.
     void loadPropConfigsFromDir(const std::string& dirPath,
@@ -276,6 +291,11 @@
             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);
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 754ae1e..7b62b2d 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
@@ -346,8 +346,9 @@
         mFakeObd2Frame->initObd2FreezeFrame(maybeObd2FreezeFrame.value());
     }
 
-    mServerSidePropStore->setOnValueChangeCallback(
-            [this](const VehiclePropValue& value) { return onValueChangeCallback(value); });
+    mServerSidePropStore->setOnValuesChangeCallback([this](std::vector<VehiclePropValue> values) {
+        return onValuesChangeCallback(std::move(values));
+    });
 }
 
 std::vector<VehiclePropConfig> FakeVehicleHardware::getAllPropertyConfigs() const {
@@ -2080,6 +2081,81 @@
     return false;
 }
 
+void FakeVehicleHardware::refreshTimeStampForInterval(int64_t intervalInNanos) {
+    std::unordered_map<PropIdAreaId, VehiclePropertyStore::EventMode, PropIdAreaIdHash>
+            eventModeByPropIdAreaId;
+
+    {
+        std::scoped_lock<std::mutex> lockGuard(mLock);
+
+        if (mActionByIntervalInNanos.find(intervalInNanos) == mActionByIntervalInNanos.end()) {
+            ALOGE("No actions scheduled for the interval: %" PRId64 ", ignore the refresh request",
+                  intervalInNanos);
+            return;
+        }
+
+        ActionForInterval actionForInterval = mActionByIntervalInNanos[intervalInNanos];
+
+        // Make a copy so that we don't hold the lock while trying to refresh the timestamp.
+        // Refreshing the timestamp will inovke onValueChangeCallback which also requires lock, so
+        // we must not hold lock.
+        for (const PropIdAreaId& propIdAreaId : actionForInterval.propIdAreaIdsToRefresh) {
+            const RefreshInfo& refreshInfo = mRefreshInfoByPropIdAreaId[propIdAreaId];
+            eventModeByPropIdAreaId[propIdAreaId] = refreshInfo.eventMode;
+        }
+    }
+
+    mServerSidePropStore->refreshTimestamps(eventModeByPropIdAreaId);
+}
+
+void FakeVehicleHardware::registerRefreshLocked(PropIdAreaId propIdAreaId,
+                                                VehiclePropertyStore::EventMode eventMode,
+                                                float sampleRateHz) {
+    if (mRefreshInfoByPropIdAreaId.find(propIdAreaId) != mRefreshInfoByPropIdAreaId.end()) {
+        unregisterRefreshLocked(propIdAreaId);
+    }
+
+    int64_t intervalInNanos = static_cast<int64_t>(1'000'000'000. / sampleRateHz);
+    RefreshInfo refreshInfo = {
+            .eventMode = eventMode,
+            .intervalInNanos = intervalInNanos,
+    };
+    mRefreshInfoByPropIdAreaId[propIdAreaId] = refreshInfo;
+
+    if (mActionByIntervalInNanos.find(intervalInNanos) != mActionByIntervalInNanos.end()) {
+        // If we have already registered for this interval, then add the action info to the
+        // actions list.
+        mActionByIntervalInNanos[intervalInNanos].propIdAreaIdsToRefresh.insert(propIdAreaId);
+        return;
+    }
+
+    // This is the first action for the interval, register a timer callback for that interval.
+    auto action = std::make_shared<RecurrentTimer::Callback>(
+            [this, intervalInNanos] { refreshTimeStampForInterval(intervalInNanos); });
+    mActionByIntervalInNanos[intervalInNanos] = ActionForInterval{
+            .propIdAreaIdsToRefresh = {propIdAreaId},
+            .recurrentAction = action,
+    };
+    mRecurrentTimer->registerTimerCallback(intervalInNanos, action);
+}
+
+void FakeVehicleHardware::unregisterRefreshLocked(PropIdAreaId propIdAreaId) {
+    if (mRefreshInfoByPropIdAreaId.find(propIdAreaId) == mRefreshInfoByPropIdAreaId.end()) {
+        ALOGW("PropId: %" PRId32 ", areaId: %" PRId32 " was not registered for refresh, ignore",
+              propIdAreaId.propId, propIdAreaId.areaId);
+        return;
+    }
+
+    int64_t intervalInNanos = mRefreshInfoByPropIdAreaId[propIdAreaId].intervalInNanos;
+    auto& actionForInterval = mActionByIntervalInNanos[intervalInNanos];
+    actionForInterval.propIdAreaIdsToRefresh.erase(propIdAreaId);
+    if (actionForInterval.propIdAreaIdsToRefresh.empty()) {
+        mRecurrentTimer->unregisterTimerCallback(actionForInterval.recurrentAction);
+        mActionByIntervalInNanos.erase(intervalInNanos);
+    }
+    mRefreshInfoByPropIdAreaId.erase(propIdAreaId);
+}
+
 StatusCode FakeVehicleHardware::subscribePropIdAreaIdLocked(
         int32_t propId, int32_t areaId, float sampleRateHz, bool enableVariableUpdateRate,
         const VehiclePropConfig& vehiclePropConfig) {
@@ -2099,11 +2175,6 @@
                 ALOGE("Must not use sample rate 0 for a continuous property");
                 return StatusCode::INTERNAL_ERROR;
             }
-            if (mRecurrentActions.find(propIdAreaId) != mRecurrentActions.end()) {
-                mRecurrentTimer->unregisterTimerCallback(mRecurrentActions[propIdAreaId]);
-            }
-            int64_t intervalInNanos = static_cast<int64_t>(1'000'000'000. / sampleRateHz);
-
             // For continuous properties, we must generate a new onPropertyChange event
             // periodically according to the sample rate.
             auto eventMode = VehiclePropertyStore::EventMode::ALWAYS;
@@ -2111,12 +2182,8 @@
                 enableVariableUpdateRate) {
                 eventMode = VehiclePropertyStore::EventMode::ON_VALUE_CHANGE;
             }
-            auto action =
-                    std::make_shared<RecurrentTimer::Callback>([this, propId, areaId, eventMode] {
-                        mServerSidePropStore->refreshTimestamp(propId, areaId, eventMode);
-                    });
-            mRecurrentTimer->registerTimerCallback(intervalInNanos, action);
-            mRecurrentActions[propIdAreaId] = action;
+
+            registerRefreshLocked(propIdAreaId, eventMode, sampleRateHz);
             return StatusCode::OK;
     }
 }
@@ -2127,39 +2194,47 @@
             .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) {
-    if (mOnPropertyChangeCallback == nullptr) {
-        return;
-    }
+    ATRACE_CALL();
+    onValuesChangeCallback({value});
+}
 
-    PropIdAreaId propIdAreaId{
-            .propId = value.prop,
-            .areaId = value.areaId,
-    };
+void FakeVehicleHardware::onValuesChangeCallback(std::vector<VehiclePropValue> values) {
+    ATRACE_CALL();
+    std::vector<VehiclePropValue> subscribedUpdatedValues;
 
     {
         std::scoped_lock<std::mutex> lockGuard(mLock);
-        if (mRecurrentActions.find(propIdAreaId) == mRecurrentActions.end() &&
-            mSubOnChangePropIdAreaIds.find(propIdAreaId) == mSubOnChangePropIdAreaIds.end()) {
-            if (FAKE_VEHICLEHARDWARE_DEBUG) {
-                ALOGD("The updated property value: %s is not subscribed, ignore",
-                      value.toString().c_str());
-            }
+        if (mOnPropertyChangeCallback == nullptr) {
             return;
         }
+
+        for (const auto& value : values) {
+            PropIdAreaId propIdAreaId{
+                    .propId = value.prop,
+                    .areaId = value.areaId,
+            };
+            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());
+                }
+                continue;
+            }
+
+            subscribedUpdatedValues.push_back(value);
+        }
     }
 
-    std::vector<VehiclePropValue> updatedValues;
-    updatedValues.push_back(value);
-    (*mOnPropertyChangeCallback)(std::move(updatedValues));
+    (*mOnPropertyChangeCallback)(std::move(subscribedUpdatedValues));
 }
 
 void FakeVehicleHardware::loadPropConfigsFromDir(
diff --git a/automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h b/automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h
index 7b328f2..d9599ed 100644
--- a/automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h
+++ b/automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h
@@ -48,21 +48,21 @@
 
     enum class EventMode : uint8_t {
         /**
-         * Only invoke OnValueChangeCallback if the new property value (ignoring timestamp) is
-         * different than the existing value.
+         * Only invoke OnValueChangeCallback or OnValuesChangeCallback if the new property value
+         * (ignoring timestamp) is different than the existing value.
          *
          * This should be used for regular cases.
          */
         ON_VALUE_CHANGE,
         /**
-         * Always invoke OnValueChangeCallback.
+         * Always invoke OnValueChangeCallback or OnValuesChangeCallback.
          *
          * This should be used for the special properties that are used for delivering event, e.g.
          * HW_KEY_INPUT.
          */
         ALWAYS,
         /**
-         * Never invoke OnValueChangeCallback.
+         * Never invoke OnValueChangeCallback or OnValuesChangeCalblack.
          *
          * This should be used for continuous property subscription when the sample rate for the
          * subscription is smaller than the refresh rate for the property. E.g., the vehicle speed
@@ -82,6 +82,10 @@
     using OnValueChangeCallback = std::function<void(
             const aidl::android::hardware::automotive::vehicle::VehiclePropValue&)>;
 
+    // Callback when one or more property values have been updated or new values added.
+    using OnValuesChangeCallback = std::function<void(
+            std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>)>;
+
     // Function that used to calculate unique token for given VehiclePropValue.
     using TokenFunction = std::function<int64_t(
             const aidl::android::hardware::automotive::vehicle::VehiclePropValue& value)>;
@@ -99,7 +103,8 @@
     // 'status' would be initialized to {@code VehiclePropertyStatus::AVAILABLE}, if this is to
     // override an existing value, the status for the existing value would be used for the
     // overridden value.
-    // 'EventMode' controls whether the 'OnValueChangeCallback' will be called for this operation.
+    // 'EventMode' controls whether the 'OnValueChangeCallback' or 'OnValuesChangeCallback' will be
+    // called for this operation.
     // If 'useCurrentTimestamp' is true, the property value will be set to the current timestamp.
     VhalResult<void> writeValue(VehiclePropValuePool::RecyclableType propValue,
                                 bool updateStatus = false,
@@ -111,6 +116,11 @@
     // without generating event. This operation is atomic with other writeValue operations.
     void refreshTimestamp(int32_t propId, int32_t areaId, EventMode eventMode) EXCLUDES(mLock);
 
+    // Refresh the timestamp for multiple [propId, areaId]s.
+    void refreshTimestamps(
+            std::unordered_map<PropIdAreaId, EventMode, PropIdAreaIdHash> eventModeByPropIdAreaId)
+            EXCLUDES(mLock);
+
     // Remove a given property value from the property store. The 'propValue' would be used to
     // generate the key for the value to remove.
     void removeValue(
@@ -157,6 +167,13 @@
     // Set a callback that would be called when a property value has been updated.
     void setOnValueChangeCallback(const OnValueChangeCallback& callback) EXCLUDES(mLock);
 
+    // Set a callback that would be called when one or more property values have been updated.
+    // For backward compatibility, this is optional. If this is not set, then multiple property
+    // updates will be delivered through multiple OnValueChangeCallback instead.
+    // It is recommended to set this and batch the property update events for better performance.
+    // If this is set, then OnValueChangeCallback will not be used.
+    void setOnValuesChangeCallback(const OnValuesChangeCallback& callback) EXCLUDES(mLock);
+
     inline std::shared_ptr<VehiclePropValuePool> getValuePool() { return mValuePool; }
 
   private:
@@ -184,6 +201,7 @@
     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;
 
diff --git a/automotive/vehicle/aidl/impl/utils/common/src/VehiclePropertyStore.cpp b/automotive/vehicle/aidl/impl/utils/common/src/VehiclePropertyStore.cpp
index 4171cf7..6a2a695 100644
--- a/automotive/vehicle/aidl/impl/utils/common/src/VehiclePropertyStore.cpp
+++ b/automotive/vehicle/aidl/impl/utils/common/src/VehiclePropertyStore.cpp
@@ -113,6 +113,9 @@
     bool valueUpdated = true;
     VehiclePropValue updatedValue;
     OnValueChangeCallback onValueChangeCallback = nullptr;
+    OnValuesChangeCallback onValuesChangeCallback = nullptr;
+    int32_t propId;
+    int32_t areaId;
     {
         std::scoped_lock<std::mutex> g(mLock);
 
@@ -122,7 +125,8 @@
             propValue->timestamp = elapsedRealtimeNano();
         }
 
-        int32_t propId = propValue->prop;
+        propId = propValue->prop;
+        areaId = propValue->areaId;
 
         VehiclePropertyStore::Record* record = getRecordLocked(propId);
         if (record == nullptr) {
@@ -163,52 +167,96 @@
             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) {
-        onValueChangeCallback(updatedValue);
+        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);
 
-        VehiclePropertyStore::Record* record = getRecordLocked(propId);
-        if (record == nullptr) {
-            return;
-        }
-
-        VehiclePropValue propValue = {
-                .areaId = areaId,
-                .prop = propId,
-                .value = {},
-        };
-
-        VehiclePropertyStore::RecordId recId = getRecordIdLocked(propValue, *record);
-        if (auto it = record->values.find(recId); it != record->values.end()) {
-            it->second->timestamp = elapsedRealtimeNano();
-            updatedValue = *(it->second);
-        } else {
-            return;
-        }
-        if (!mOnValueChangeCallback) {
-            return;
-        }
+        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) {
+                continue;
+            }
+
+            VehiclePropValue propValue = {
+                    .areaId = areaId,
+                    .prop = propId,
+                    .value = {},
+            };
+
+            VehiclePropertyStore::RecordId recId = getRecordIdLocked(propValue, *record);
+            if (auto it = record->values.find(recId); it != record->values.end()) {
+                it->second->timestamp = elapsedRealtimeNano();
+                if (eventMode == EventMode::ALWAYS) {
+                    updatedValues.push_back(*(it->second));
+                }
+            } else {
+                continue;
+            }
+        }
     }
 
     // Invoke the callback outside the lock to prevent dead-lock.
-    if (eventMode == EventMode::ALWAYS) {
-        onValueChangeCallback(updatedValue);
+    if (updatedValues.empty()) {
+        return;
+    }
+    if (!onValuesChangeCallback && !onValueChangeCallback) {
+        // If no callback is set, then we don't have to do anything.
+        for (const auto& updateValue : updatedValues) {
+            ALOGW("No callback registered, ignoring property update for propId: %" PRId32
+                  ", area ID: %" PRId32,
+                  updateValue.prop, updateValue.areaId);
+        }
+        return;
+    }
+    if (onValuesChangeCallback != nullptr) {
+        onValuesChangeCallback(updatedValues);
+    } else {
+        // Fallback to use multiple onValueChangeCallback
+        for (const auto& updateValue : updatedValues) {
+            onValueChangeCallback(updateValue);
+        }
     }
 }
 
@@ -336,6 +384,13 @@
     mOnValueChangeCallback = callback;
 }
 
+void VehiclePropertyStore::setOnValuesChangeCallback(
+        const VehiclePropertyStore::OnValuesChangeCallback& callback) {
+    std::scoped_lock<std::mutex> g(mLock);
+
+    mOnValuesChangeCallback = callback;
+}
+
 }  // namespace vehicle
 }  // namespace automotive
 }  // namespace hardware
diff --git a/automotive/vehicle/aidl/impl/utils/common/test/VehiclePropertyStoreTest.cpp b/automotive/vehicle/aidl/impl/utils/common/test/VehiclePropertyStoreTest.cpp
index 328d244..6646b7e 100644
--- a/automotive/vehicle/aidl/impl/utils/common/test/VehiclePropertyStoreTest.cpp
+++ b/automotive/vehicle/aidl/impl/utils/common/test/VehiclePropertyStoreTest.cpp
@@ -20,6 +20,7 @@
 #include <VehicleUtils.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <utils/SystemClock.h>
 
 namespace android {
 namespace hardware {
@@ -527,6 +528,133 @@
     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
diff --git a/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp b/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
index 847f3b8..76d2f31 100644
--- a/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
@@ -194,6 +194,7 @@
 void DefaultVehicleHal::onPropertyChangeEvent(
         const std::weak_ptr<SubscriptionManager>& subscriptionManager,
         std::vector<VehiclePropValue>&& updatedValues) {
+    ATRACE_CALL();
     auto manager = subscriptionManager.lock();
     if (manager == nullptr) {
         ALOGW("the SubscriptionManager is destroyed, DefaultVehicleHal is ending");