Merge "Fix return values in AIDL multi-HAL shim" into tm-dev
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 892b406..34b2b24 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
@@ -17,6 +17,7 @@
 #ifndef android_hardware_automotive_vehicle_aidl_impl_fake_impl_hardware_include_FakeVehicleHardware_H_
 #define android_hardware_automotive_vehicle_aidl_impl_fake_impl_hardware_include_FakeVehicleHardware_H_
 
+#include <ConcurrentQueue.h>
 #include <DefaultConfig.h>
 #include <FakeObd2Frame.h>
 #include <FakeUserHal.h>
@@ -48,6 +49,8 @@
 
     explicit FakeVehicleHardware(std::unique_ptr<VehiclePropValuePool> valuePool);
 
+    ~FakeVehicleHardware();
+
     // Get all the property configs.
     std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropConfig>
     getAllPropertyConfigs() const override;
@@ -102,6 +105,29 @@
     // Expose private methods to unit test.
     friend class FakeVehicleHardwareTestHelper;
 
+    template <class CallbackType, class RequestType>
+    struct RequestWithCallback {
+        RequestType request;
+        std::shared_ptr<const CallbackType> callback;
+    };
+
+    template <class CallbackType, class RequestType>
+    class PendingRequestHandler {
+      public:
+        PendingRequestHandler(FakeVehicleHardware* hardware);
+
+        void addRequest(RequestType request, std::shared_ptr<const CallbackType> callback);
+
+        void stop();
+
+      private:
+        FakeVehicleHardware* mHardware;
+        std::thread mThread;
+        ConcurrentQueue<RequestWithCallback<CallbackType, RequestType>> mRequests;
+
+        void handleRequestsOnce();
+    };
+
     const std::unique_ptr<obd2frame::FakeObd2Frame> mFakeObd2Frame;
     const std::unique_ptr<FakeUserHal> mFakeUserHal;
     // RecurrentTimer is thread-safe.
@@ -111,6 +137,13 @@
     std::unique_ptr<const PropertySetErrorCallback> mOnPropertySetErrorCallback GUARDED_BY(mLock);
     std::unordered_map<PropIdAreaId, std::shared_ptr<RecurrentTimer::Callback>, PropIdAreaIdHash>
             mRecurrentActions GUARDED_BY(mLock);
+    // PendingRequestHandler is thread-safe.
+    mutable PendingRequestHandler<GetValuesCallback,
+                                  aidl::android::hardware::automotive::vehicle::GetValueRequest>
+            mPendingGetValueRequests;
+    mutable PendingRequestHandler<SetValuesCallback,
+                                  aidl::android::hardware::automotive::vehicle::SetValueRequest>
+            mPendingSetValueRequests;
 
     void init();
     // Stores the initial value to property store.
@@ -170,6 +203,10 @@
 
     android::base::Result<void> checkArgumentsSize(const std::vector<std::string>& options,
                                                    size_t minSize);
+    aidl::android::hardware::automotive::vehicle::GetValueResult handleGetValueRequest(
+            const aidl::android::hardware::automotive::vehicle::GetValueRequest& request);
+    aidl::android::hardware::automotive::vehicle::SetValueResult handleSetValueRequest(
+            const aidl::android::hardware::automotive::vehicle::SetValueRequest& request);
 };
 
 }  // 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 8f8cc5c..7b3de58 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
@@ -139,10 +139,17 @@
       mServerSidePropStore(new VehiclePropertyStore(mValuePool)),
       mFakeObd2Frame(new obd2frame::FakeObd2Frame(mServerSidePropStore)),
       mFakeUserHal(new FakeUserHal(mValuePool)),
-      mRecurrentTimer(new RecurrentTimer()) {
+      mRecurrentTimer(new RecurrentTimer()),
+      mPendingGetValueRequests(this),
+      mPendingSetValueRequests(this) {
     init();
 }
 
+FakeVehicleHardware::~FakeVehicleHardware() {
+    mPendingGetValueRequests.stop();
+    mPendingSetValueRequests.stop();
+}
+
 void FakeVehicleHardware::init() {
     for (auto& it : defaultconfig::getDefaultConfigs()) {
         VehiclePropConfig cfg = it.config;
@@ -427,37 +434,25 @@
 
 StatusCode FakeVehicleHardware::setValues(std::shared_ptr<const SetValuesCallback> callback,
                                           const std::vector<SetValueRequest>& requests) {
-    std::vector<SetValueResult> results;
     for (auto& request : requests) {
-        const VehiclePropValue& value = request.value;
-        int propId = value.prop;
-
         if (FAKE_VEHICLEHARDWARE_DEBUG) {
-            ALOGD("Set value for property ID: %d", propId);
+            ALOGD("Set value for property ID: %d", request.value.prop);
         }
 
-        SetValueResult setValueResult;
-        setValueResult.requestId = request.requestId;
-
-        if (auto result = setValue(value); !result.ok()) {
-            ALOGE("failed to set value, error: %s, code: %d", getErrorMsg(result).c_str(),
-                  getIntErrorCode(result));
-            setValueResult.status = getErrorCode(result);
-        } else {
-            setValueResult.status = StatusCode::OK;
-        }
-
-        results.push_back(std::move(setValueResult));
+        // In a real VHAL implementation, you could either send the setValue request to vehicle bus
+        // here in the binder thread, or you could send the request in setValue which runs in
+        // the handler thread. If you decide to send the setValue request here, you should not
+        // wait for the response here and the handler thread should handle the setValue response.
+        mPendingSetValueRequests.addRequest(request, callback);
     }
 
-    // In the real vhal, the values will be sent to Car ECU. We just pretend it is done here and
-    // send back the updated property values to client.
-    (*callback)(std::move(results));
-
     return StatusCode::OK;
 }
 
 VhalResult<void> FakeVehicleHardware::setValue(const VehiclePropValue& value) {
+    // In a real VHAL implementation, this will send the request to vehicle bus if not already
+    // sent in setValues, and wait for the response from vehicle bus.
+    // Here we are just updating mValuePool.
     bool isSpecialValue = false;
     auto setSpecialValueResult = maybeSetSpecialValue(value, &isSpecialValue);
 
@@ -484,41 +479,59 @@
     return {};
 }
 
-StatusCode FakeVehicleHardware::getValues(std::shared_ptr<const GetValuesCallback> callback,
-                                          const std::vector<GetValueRequest>& requests) const {
-    std::vector<GetValueResult> results;
-    for (auto& request : requests) {
-        const VehiclePropValue& value = request.prop;
+SetValueResult FakeVehicleHardware::handleSetValueRequest(const SetValueRequest& request) {
+    SetValueResult setValueResult;
+    setValueResult.requestId = request.requestId;
 
-        if (FAKE_VEHICLEHARDWARE_DEBUG) {
-            ALOGD("getValues(%d)", value.prop);
-        }
-
-        GetValueResult getValueResult;
-        getValueResult.requestId = request.requestId;
-
-        auto result = getValue(value);
-        if (!result.ok()) {
-            ALOGE("failed to get value, error: %s, code: %d", getErrorMsg(result).c_str(),
-                  getIntErrorCode(result));
-            getValueResult.status = getErrorCode(result);
-        } else {
-            getValueResult.status = StatusCode::OK;
-            getValueResult.prop = *result.value();
-        }
-        results.push_back(std::move(getValueResult));
+    if (auto result = setValue(request.value); !result.ok()) {
+        ALOGE("failed to set value, error: %s, code: %d", getErrorMsg(result).c_str(),
+              getIntErrorCode(result));
+        setValueResult.status = getErrorCode(result);
+    } else {
+        setValueResult.status = StatusCode::OK;
     }
 
-    // In a real VHAL implementation, getValue would be async and we would call the callback after
-    // we actually received the values from vehicle bus. Here we are getting the result
-    // synchronously so we could call the callback here.
-    (*callback)(std::move(results));
+    return setValueResult;
+}
+
+StatusCode FakeVehicleHardware::getValues(std::shared_ptr<const GetValuesCallback> callback,
+                                          const std::vector<GetValueRequest>& requests) const {
+    for (auto& request : requests) {
+        if (FAKE_VEHICLEHARDWARE_DEBUG) {
+            ALOGD("getValues(%d)", request.prop.prop);
+        }
+
+        // In a real VHAL implementation, you could either send the getValue request to vehicle bus
+        // here in the binder thread, or you could send the request in getValue which runs in
+        // the handler thread. If you decide to send the getValue request here, you should not
+        // wait for the response here and the handler thread should handle the getValue response.
+        mPendingGetValueRequests.addRequest(request, callback);
+    }
 
     return StatusCode::OK;
 }
 
+GetValueResult FakeVehicleHardware::handleGetValueRequest(const GetValueRequest& request) {
+    GetValueResult getValueResult;
+    getValueResult.requestId = request.requestId;
+
+    auto result = getValue(request.prop);
+    if (!result.ok()) {
+        ALOGE("failed to get value, error: %s, code: %d", getErrorMsg(result).c_str(),
+              getIntErrorCode(result));
+        getValueResult.status = getErrorCode(result);
+    } else {
+        getValueResult.status = StatusCode::OK;
+        getValueResult.prop = *result.value();
+    }
+    return getValueResult;
+}
+
 FakeVehicleHardware::ValueResultType FakeVehicleHardware::getValue(
         const VehiclePropValue& value) const {
+    // In a real VHAL implementation, this will send the request to vehicle bus if not already
+    // sent in getValues, and wait for the response from vehicle bus.
+    // Here we are just reading value from mValuePool.
     bool isSpecialValue = false;
     auto result = maybeGetSpecialValue(value, &isSpecialValue);
     if (isSpecialValue) {
@@ -564,6 +577,12 @@
         result.buffer = dumpSpecificProperty(options);
     } else if (EqualsIgnoreCase(option, "--set")) {
         result.buffer = dumpSetProperties(options);
+    } else if (EqualsIgnoreCase(option, kUserHalDumpOption)) {
+        if (options.size() == 1) {
+            result.buffer = mFakeUserHal->showDumpHelp();
+        } else {
+            result.buffer = mFakeUserHal->dump(options[1]);
+        }
     } else {
         result.buffer = StringPrintf("Invalid option: %s\n", option.c_str());
     }
@@ -581,7 +600,9 @@
            "[-b BYTES_VALUE] [-a AREA_ID] : sets the value of property PROP. "
            "Notice that the string, bytes and area value can be set just once, while the other can"
            " have multiple values (so they're used in the respective array), "
-           "BYTES_VALUE is in the form of 0xXXXX, e.g. 0xdeadbeef.\n";
+           "BYTES_VALUE is in the form of 0xXXXX, e.g. 0xdeadbeef.\n\n"
+           "Fake user HAL usage: \n" +
+           mFakeUserHal->showDumpHelp();
 }
 
 std::string FakeVehicleHardware::dumpAllProperties() {
@@ -971,6 +992,60 @@
     return bytes;
 }
 
+template <class CallbackType, class RequestType>
+FakeVehicleHardware::PendingRequestHandler<CallbackType, RequestType>::PendingRequestHandler(
+        FakeVehicleHardware* hardware)
+    : mHardware(hardware), mThread([this] {
+          while (mRequests.waitForItems()) {
+              handleRequestsOnce();
+          }
+      }) {}
+
+template <class CallbackType, class RequestType>
+void FakeVehicleHardware::PendingRequestHandler<CallbackType, RequestType>::addRequest(
+        RequestType request, std::shared_ptr<const CallbackType> callback) {
+    mRequests.push({
+            request,
+            callback,
+    });
+}
+
+template <class CallbackType, class RequestType>
+void FakeVehicleHardware::PendingRequestHandler<CallbackType, RequestType>::stop() {
+    mRequests.deactivate();
+    if (mThread.joinable()) {
+        mThread.join();
+    }
+}
+
+template <>
+void FakeVehicleHardware::PendingRequestHandler<FakeVehicleHardware::GetValuesCallback,
+                                                GetValueRequest>::handleRequestsOnce() {
+    std::unordered_map<std::shared_ptr<const GetValuesCallback>, std::vector<GetValueResult>>
+            callbackToResults;
+    for (const auto& rwc : mRequests.flush()) {
+        auto result = mHardware->handleGetValueRequest(rwc.request);
+        callbackToResults[rwc.callback].push_back(std::move(result));
+    }
+    for (const auto& [callback, results] : callbackToResults) {
+        (*callback)(std::move(results));
+    }
+}
+
+template <>
+void FakeVehicleHardware::PendingRequestHandler<FakeVehicleHardware::SetValuesCallback,
+                                                SetValueRequest>::handleRequestsOnce() {
+    std::unordered_map<std::shared_ptr<const SetValuesCallback>, std::vector<SetValueResult>>
+            callbackToResults;
+    for (const auto& rwc : mRequests.flush()) {
+        auto result = mHardware->handleSetValueRequest(rwc.request);
+        callbackToResults[rwc.callback].push_back(std::move(result));
+    }
+    for (const auto& [callback, results] : callbackToResults) {
+        (*callback)(std::move(results));
+    }
+}
+
 }  // namespace fake
 }  // namespace vehicle
 }  // namespace automotive
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 8839c18..3e8f634 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
@@ -34,6 +34,8 @@
 #include <inttypes.h>
 #include <chrono>
 #include <condition_variable>
+#include <unordered_map>
+#include <unordered_set>
 #include <vector>
 
 namespace android {
@@ -99,11 +101,51 @@
     FakeVehicleHardware* getHardware() { return &mHardware; }
 
     StatusCode setValues(const std::vector<SetValueRequest>& requests) {
-        return getHardware()->setValues(mSetValuesCallback, requests);
+        {
+            std::scoped_lock<std::mutex> lockGuard(mLock);
+            for (const auto& request : requests) {
+                mPendingSetValueRequests.insert(request.requestId);
+            }
+        }
+        if (StatusCode status = getHardware()->setValues(mSetValuesCallback, requests);
+            status != StatusCode::OK) {
+            return status;
+        }
+        std::unique_lock<std::mutex> lk(mLock);
+        // Wait for the onSetValueResults.
+        bool result = mCv.wait_for(lk, milliseconds(1000), [this] {
+            ScopedLockAssertion lockAssertion(mLock);
+            return mPendingSetValueRequests.size() == 0;
+        });
+        if (!result) {
+            ALOGE("wait for callbacks for setValues timed-out");
+            return StatusCode::INTERNAL_ERROR;
+        }
+        return StatusCode::OK;
     }
 
     StatusCode getValues(const std::vector<GetValueRequest>& requests) {
-        return getHardware()->getValues(mGetValuesCallback, requests);
+        {
+            std::scoped_lock<std::mutex> lockGuard(mLock);
+            for (const auto& request : requests) {
+                mPendingGetValueRequests.insert(request.requestId);
+            }
+        }
+        if (StatusCode status = getHardware()->getValues(mGetValuesCallback, requests);
+            status != StatusCode::OK) {
+            return status;
+        }
+        std::unique_lock<std::mutex> lk(mLock);
+        // Wait for the onGetValueResults.
+        bool result = mCv.wait_for(lk, milliseconds(1000), [this] {
+            ScopedLockAssertion lockAssertion(mLock);
+            return mPendingGetValueRequests.size() == 0;
+        });
+        if (!result) {
+            ALOGE("wait for callbacks for getValues timed-out");
+            return StatusCode::INTERNAL_ERROR;
+        }
+        return StatusCode::OK;
     }
 
     StatusCode setValue(const VehiclePropValue& value) {
@@ -167,7 +209,9 @@
         std::scoped_lock<std::mutex> lockGuard(mLock);
         for (auto& result : results) {
             mSetValueResults.push_back(result);
+            mPendingSetValueRequests.erase(result.requestId);
         }
+        mCv.notify_all();
     }
 
     const std::vector<SetValueResult>& getSetValueResults() {
@@ -179,7 +223,9 @@
         std::scoped_lock<std::mutex> lockGuard(mLock);
         for (auto& result : results) {
             mGetValueResults.push_back(result);
+            mPendingGetValueRequests.erase(result.requestId);
         }
+        mCv.notify_all();
     }
 
     const std::vector<GetValueResult>& getGetValueResults() {
@@ -197,7 +243,7 @@
             };
             mEventCount[propIdAreaId]++;
         }
-        mCv.notify_one();
+        mCv.notify_all();
     }
 
     const std::vector<VehiclePropValue>& getChangedProperties() {
@@ -295,6 +341,8 @@
     std::vector<SetValueResult> mSetValueResults GUARDED_BY(mLock);
     std::vector<GetValueResult> mGetValueResults GUARDED_BY(mLock);
     std::vector<VehiclePropValue> mChangedProperties GUARDED_BY(mLock);
+    std::unordered_set<int64_t> mPendingSetValueRequests GUARDED_BY(mLock);
+    std::unordered_set<int64_t> mPendingGetValueRequests GUARDED_BY(mLock);
 };
 
 TEST_F(FakeVehicleHardwareTest, testGetAllPropertyConfigs) {
@@ -1357,6 +1405,28 @@
     ASSERT_THAT(result.buffer, ContainsRegex("Invalid option: --invalid"));
 }
 
+TEST_F(FakeVehicleHardwareTest, testDumpFakeUserHalHelp) {
+    std::vector<std::string> options;
+    options.push_back("--user-hal");
+
+    DumpResult result = getHardware()->dump(options);
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_NE(result.buffer, "");
+    ASSERT_THAT(result.buffer, ContainsRegex("dumps state used for user management"));
+}
+
+TEST_F(FakeVehicleHardwareTest, testDumpFakeUserHal) {
+    std::vector<std::string> options;
+    options.push_back("--user-hal");
+    // Indent: " ".
+    options.push_back(" ");
+
+    DumpResult result = getHardware()->dump(options);
+    ASSERT_FALSE(result.callerShouldDumpState);
+    ASSERT_NE(result.buffer, "");
+    ASSERT_THAT(result.buffer, ContainsRegex(" No InitialUserInfo response\n"));
+}
+
 struct SetPropTestCase {
     std::string test_name;
     std::vector<std::string> options;
diff --git a/automotive/vehicle/vts/src/VtsHalAutomotiveVehicle_TargetTest.cpp b/automotive/vehicle/vts/src/VtsHalAutomotiveVehicle_TargetTest.cpp
index c33f3e9..c431d85 100644
--- a/automotive/vehicle/vts/src/VtsHalAutomotiveVehicle_TargetTest.cpp
+++ b/automotive/vehicle/vts/src/VtsHalAutomotiveVehicle_TargetTest.cpp
@@ -30,6 +30,7 @@
 #include <hidl/ServiceManagement.h>
 #include <inttypes.h>
 #include <utils/Log.h>
+#include <utils/SystemClock.h>
 
 #include <chrono>
 #include <mutex>
@@ -67,6 +68,7 @@
   private:
     std::mutex mLock;
     std::unordered_map<int32_t, size_t> mEventsCount GUARDED_BY(mLock);
+    std::unordered_map<int32_t, std::vector<int64_t>> mEventTimestamps GUARDED_BY(mLock);
     std::condition_variable mEventCond;
 
   public:
@@ -74,7 +76,9 @@
         {
             std::lock_guard<std::mutex> lockGuard(mLock);
             for (auto& value : values) {
-                mEventsCount[value->getPropId()] += 1;
+                int32_t propId = value->getPropId();
+                mEventsCount[propId] += 1;
+                mEventTimestamps[propId].push_back(value->getTimestamp());
             }
         }
         mEventCond.notify_one();
@@ -94,6 +98,13 @@
         });
     }
 
+    std::vector<int64_t> getEventTimestamps(int32_t propId) {
+        {
+            std::lock_guard<std::mutex> lockGuard(mLock);
+            return mEventTimestamps[propId];
+        }
+    }
+
     void reset() {
         std::lock_guard<std::mutex> lockGuard(mLock);
         mEventsCount.clear();
@@ -285,19 +296,59 @@
 
     int32_t propId = toInt(VehicleProperty::PERF_VEHICLE_SPEED);
 
-    std::vector<SubscribeOptions> options = {
-            SubscribeOptions{.propId = propId, .sampleRate = 10.0}};
+    auto propConfigsResult = mVhalClient->getPropConfigs({propId});
+
+    ASSERT_TRUE(propConfigsResult.ok()) << "Failed to get property config for PERF_VEHICLE_SPEED: "
+                                        << "error: " << propConfigsResult.error().message();
+    ASSERT_EQ(propConfigsResult.value().size(), 1u)
+            << "Expect to return 1 config for PERF_VEHICLE_SPEED";
+    auto& propConfig = propConfigsResult.value()[0];
+    float minSampleRate = propConfig->getMinSampleRate();
+    float maxSampleRate = propConfig->getMaxSampleRate();
+
+    if (minSampleRate < 1) {
+        GTEST_SKIP() << "Sample rate for vehicle speed < 1 times/sec, skip test since it would "
+                        "take too long";
+    }
 
     auto client = mVhalClient->getSubscriptionClient(mCallback);
     ASSERT_NE(client, nullptr) << "Failed to get subscription client";
 
-    auto result = client->subscribe(options);
+    auto result = client->subscribe({{.propId = propId, .sampleRate = minSampleRate}});
 
     ASSERT_TRUE(result.ok()) << StringPrintf("Failed to subscribe to property: %" PRId32
                                              ", error: %s",
                                              propId, result.error().message().c_str());
-    ASSERT_TRUE(mCallback->waitForExpectedEvents(propId, 10, std::chrono::seconds(10)))
-            << "Didn't get enough events for subscription";
+
+    if (mVhalClient->isAidlVhal()) {
+        // Skip checking timestamp for HIDL because the behavior for sample rate and timestamp is
+        // only specified clearly for AIDL.
+
+        // Timeout is 2 seconds, which gives a 1 second buffer.
+        ASSERT_TRUE(mCallback->waitForExpectedEvents(propId, std::floor(minSampleRate),
+                                                     std::chrono::seconds(2)))
+                << "Didn't get enough events for subscribing to minSampleRate";
+    }
+
+    result = client->subscribe({{.propId = propId, .sampleRate = maxSampleRate}});
+
+    ASSERT_TRUE(result.ok()) << StringPrintf("Failed to subscribe to property: %" PRId32
+                                             ", error: %s",
+                                             propId, result.error().message().c_str());
+
+    if (mVhalClient->isAidlVhal()) {
+        ASSERT_TRUE(mCallback->waitForExpectedEvents(propId, std::floor(maxSampleRate),
+                                                     std::chrono::seconds(2)))
+                << "Didn't get enough events for subscribing to maxSampleRate";
+
+        std::unordered_set<int64_t> timestamps;
+        // Event event should have a different timestamp.
+        for (const int64_t& eventTimestamp : mCallback->getEventTimestamps(propId)) {
+            ASSERT_TRUE(timestamps.find(eventTimestamp) == timestamps.end())
+                    << "two events for the same property must not have the same timestamp";
+            timestamps.insert(eventTimestamp);
+        }
+    }
 
     result = client->unsubscribe({propId});
     ASSERT_TRUE(result.ok()) << StringPrintf("Failed to unsubscribe to property: %" PRId32
@@ -325,6 +376,49 @@
                                               kInvalidProp);
 }
 
+// Test the timestamp returned in GetValues results is the timestamp when the value is retrieved.
+TEST_P(VtsHalAutomotiveVehicleTargetTest, testGetValuesTimestampAIDL) {
+    if (!mVhalClient->isAidlVhal()) {
+        GTEST_SKIP() << "Skip checking timestamp for HIDL because the behavior is only specified "
+                        "for AIDL";
+    }
+
+    int32_t propId = toInt(VehicleProperty::PARKING_BRAKE_ON);
+    auto prop = mVhalClient->createHalPropValue(propId);
+
+    auto result = mVhalClient->getValueSync(*prop);
+
+    ASSERT_TRUE(result.ok()) << StringPrintf("Failed to get value for property: %" PRId32
+                                             ", error: %s",
+                                             propId, result.error().message().c_str());
+    ASSERT_NE(result.value(), nullptr) << "Result value must not be null";
+    ASSERT_EQ(result.value()->getInt32Values().size(), 1u) << "Result must contain 1 int value";
+
+    bool parkBrakeOnValue1 = (result.value()->getInt32Values()[0] == 1);
+    int64_t timestampValue1 = result.value()->getTimestamp();
+
+    result = mVhalClient->getValueSync(*prop);
+
+    ASSERT_TRUE(result.ok()) << StringPrintf("Failed to get value for property: %" PRId32
+                                             ", error: %s",
+                                             propId, result.error().message().c_str());
+    ASSERT_NE(result.value(), nullptr) << "Result value must not be null";
+    ASSERT_EQ(result.value()->getInt32Values().size(), 1u) << "Result must contain 1 int value";
+
+    bool parkBarkeOnValue2 = (result.value()->getInt32Values()[0] == 1);
+    int64_t timestampValue2 = result.value()->getTimestamp();
+
+    if (parkBarkeOnValue2 == parkBrakeOnValue1) {
+        ASSERT_EQ(timestampValue2, timestampValue1)
+                << "getValue result must contain a timestamp updated when the value was updated, if"
+                   "the value does not change, expect the same timestamp";
+    } else {
+        ASSERT_GT(timestampValue2, timestampValue1)
+                << "getValue result must contain a timestamp updated when the value was updated, if"
+                   "the value changes, expect the newer value has a larger timestamp";
+    }
+}
+
 std::vector<ServiceDescriptor> getDescriptors() {
     std::vector<ServiceDescriptor> descriptors;
     for (std::string name : getAidlHalInstanceNames(IVehicle::descriptor)) {