Merge "Add IPTV default implementation" into main
diff --git a/audio/aidl/vts/Android.bp b/audio/aidl/vts/Android.bp
index 1d96a0e..ad816c7 100644
--- a/audio/aidl/vts/Android.bp
+++ b/audio/aidl/vts/Android.bp
@@ -40,7 +40,10 @@
         "general-tests",
         "vts",
     ],
-    srcs: [":effectCommonFile"],
+    srcs: [
+        ":effectCommonFile",
+        "TestUtils.cpp",
+    ],
 }
 
 cc_test {
diff --git a/audio/aidl/vts/TestUtils.cpp b/audio/aidl/vts/TestUtils.cpp
new file mode 100644
index 0000000..f018468
--- /dev/null
+++ b/audio/aidl/vts/TestUtils.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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 "TestUtils.h"
+
+#define LOG_TAG "VtsHalAudio_TestUtils"
+
+#include <android-base/logging.h>
+
+namespace android::hardware::audio::common::testing {
+
+namespace detail {
+void TestExecutionTracer::OnTestStart(const ::testing::TestInfo& test_info) {
+    TraceTestState("Started", test_info);
+}
+
+void TestExecutionTracer::OnTestEnd(const ::testing::TestInfo& test_info) {
+    TraceTestState("Completed", test_info);
+}
+
+void TestExecutionTracer::OnTestPartResult(const ::testing::TestPartResult& result) {
+    LOG(INFO) << result;
+}
+
+void TestExecutionTracer::TraceTestState(const std::string& state,
+                                         const ::testing::TestInfo& test_info) {
+    LOG(INFO) << state << " " << test_info.test_suite_name() << "::" << test_info.name();
+}
+}
+}
\ No newline at end of file
diff --git a/audio/aidl/vts/TestUtils.h b/audio/aidl/vts/TestUtils.h
index b559669..191e980 100644
--- a/audio/aidl/vts/TestUtils.h
+++ b/audio/aidl/vts/TestUtils.h
@@ -22,11 +22,21 @@
 
 #include <android/binder_auto_utils.h>
 #include <gtest/gtest.h>
+#include <system/audio_aidl_utils.h>
 
 namespace android::hardware::audio::common::testing {
 
 namespace detail {
 
+class TestExecutionTracer : public ::testing::EmptyTestEventListener {
+  public:
+    void OnTestStart(const ::testing::TestInfo& test_info) override;
+    void OnTestEnd(const ::testing::TestInfo& test_info) override;
+    void OnTestPartResult(const ::testing::TestPartResult& result) override;
+  private:
+    static void TraceTestState(const std::string& state, const ::testing::TestInfo& test_info);
+};
+
 inline ::testing::AssertionResult assertIsOk(const char* expr, const ::ndk::ScopedAStatus& status) {
     if (status.isOk()) {
         return ::testing::AssertionSuccess();
diff --git a/audio/aidl/vts/VtsHalAECTargetTest.cpp b/audio/aidl/vts/VtsHalAECTargetTest.cpp
index 0354e3c..39168b1 100644
--- a/audio/aidl/vts/VtsHalAECTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAECTargetTest.cpp
@@ -34,6 +34,7 @@
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::hardware::audio::effect::Parameter;
 using aidl::android::hardware::audio::effect::Range;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 
 enum ParamName { PARAM_INSTANCE_NAME, PARAM_ECHO_DELAY, PARAM_MOBILE_MODE };
 using AECParamTestParam = std::tuple<std::pair<std::shared_ptr<IFactory>, Descriptor>,
@@ -178,6 +179,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalAGC1TargetTest.cpp b/audio/aidl/vts/VtsHalAGC1TargetTest.cpp
index 65c6a8f..6066025 100644
--- a/audio/aidl/vts/VtsHalAGC1TargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAGC1TargetTest.cpp
@@ -28,6 +28,7 @@
 using aidl::android::hardware::audio::effect::IEffect;
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::hardware::audio::effect::Parameter;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 
 enum ParamName {
     PARAM_INSTANCE_NAME,
@@ -189,6 +190,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalAGC2TargetTest.cpp b/audio/aidl/vts/VtsHalAGC2TargetTest.cpp
index 46a569e..8793e4c 100644
--- a/audio/aidl/vts/VtsHalAGC2TargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAGC2TargetTest.cpp
@@ -29,6 +29,7 @@
 using aidl::android::hardware::audio::effect::IEffect;
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::hardware::audio::effect::Parameter;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 
 enum ParamName {
     PARAM_INSTANCE_NAME,
@@ -194,6 +195,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
index 9c52d19..53e51f4 100644
--- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
@@ -108,6 +108,7 @@
 using aidl::android::media::audio::common::Void;
 using android::hardware::audio::common::StreamLogic;
 using android::hardware::audio::common::StreamWorker;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 using ndk::enum_range;
 using ndk::ScopedAStatus;
 
@@ -4444,21 +4445,6 @@
                          ::testing::ValuesIn(getRemoteSubmixModuleInstance()));
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioModuleRemoteSubmix);
 
-class TestExecutionTracer : public ::testing::EmptyTestEventListener {
-  public:
-    void OnTestStart(const ::testing::TestInfo& test_info) override {
-        TraceTestState("Started", test_info);
-    }
-    void OnTestEnd(const ::testing::TestInfo& test_info) override {
-        TraceTestState("Completed", test_info);
-    }
-
-  private:
-    static void TraceTestState(const std::string& state, const ::testing::TestInfo& test_info) {
-        LOG(INFO) << state << " " << test_info.test_suite_name() << "::" << test_info.name();
-    }
-};
-
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
     ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
diff --git a/audio/aidl/vts/VtsHalAudioEffectFactoryTargetTest.cpp b/audio/aidl/vts/VtsHalAudioEffectFactoryTargetTest.cpp
index 225640e..523f20d 100644
--- a/audio/aidl/vts/VtsHalAudioEffectFactoryTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioEffectFactoryTargetTest.cpp
@@ -52,6 +52,7 @@
 using aidl::android::media::audio::common::AudioSource;
 using aidl::android::media::audio::common::AudioStreamType;
 using aidl::android::media::audio::common::AudioUuid;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 
 /// Effect factory testing.
 class EffectFactoryTest : public testing::TestWithParam<std::string> {
@@ -303,6 +304,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp b/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp
index 1876756..ca1cea9 100644
--- a/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp
@@ -51,6 +51,7 @@
 using aidl::android::media::audio::common::AudioDeviceType;
 using aidl::android::media::audio::common::AudioMode;
 using aidl::android::media::audio::common::AudioSource;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 
 enum ParamName { PARAM_INSTANCE_NAME };
 using EffectTestParam = std::tuple<std::pair<std::shared_ptr<IFactory>, Descriptor>>;
@@ -907,6 +908,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalBassBoostTargetTest.cpp b/audio/aidl/vts/VtsHalBassBoostTargetTest.cpp
index afddb84..2d9a233 100644
--- a/audio/aidl/vts/VtsHalBassBoostTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalBassBoostTargetTest.cpp
@@ -32,7 +32,7 @@
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::hardware::audio::effect::Parameter;
 using aidl::android::hardware::audio::effect::Range;
-
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 /**
  * Here we focus on specific parameter checking, general IEffect interfaces testing performed in
  * VtsAudioEffectTargetTest.
@@ -155,6 +155,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalDownmixTargetTest.cpp b/audio/aidl/vts/VtsHalDownmixTargetTest.cpp
index 7a2f31b..c01a9a2 100644
--- a/audio/aidl/vts/VtsHalDownmixTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalDownmixTargetTest.cpp
@@ -28,7 +28,7 @@
 using aidl::android::hardware::audio::effect::IEffect;
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::hardware::audio::effect::Parameter;
-
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 /**
  * Here we focus on specific parameter checking, general IEffect interfaces testing performed in
  * VtsAudioEffectTargetTest.
@@ -136,6 +136,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalDynamicsProcessingTest.cpp b/audio/aidl/vts/VtsHalDynamicsProcessingTest.cpp
index 5509c76..2650f49 100644
--- a/audio/aidl/vts/VtsHalDynamicsProcessingTest.cpp
+++ b/audio/aidl/vts/VtsHalDynamicsProcessingTest.cpp
@@ -36,6 +36,7 @@
 using aidl::android::hardware::audio::effect::IEffect;
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::hardware::audio::effect::Parameter;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 
 /**
  * Here we focus on specific parameter checking, general IEffect interfaces testing performed in
@@ -996,6 +997,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalEnvironmentalReverbTargetTest.cpp b/audio/aidl/vts/VtsHalEnvironmentalReverbTargetTest.cpp
index f2ef185..474b361 100644
--- a/audio/aidl/vts/VtsHalEnvironmentalReverbTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalEnvironmentalReverbTargetTest.cpp
@@ -28,6 +28,7 @@
 using aidl::android::hardware::audio::effect::IEffect;
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::hardware::audio::effect::Parameter;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 
 /**
  * Here we focus on specific parameter checking, general IEffect interfaces testing performed in
@@ -536,6 +537,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalEqualizerTargetTest.cpp b/audio/aidl/vts/VtsHalEqualizerTargetTest.cpp
index 37e7c0a..09396d1 100644
--- a/audio/aidl/vts/VtsHalEqualizerTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalEqualizerTargetTest.cpp
@@ -46,6 +46,7 @@
 using aidl::android::hardware::audio::effect::IEffect;
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::hardware::audio::effect::Parameter;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 
 /**
  * Here we focus on specific effect (equalizer) parameter checking, general IEffect interfaces
@@ -220,6 +221,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalHapticGeneratorTargetTest.cpp b/audio/aidl/vts/VtsHalHapticGeneratorTargetTest.cpp
index b33234b..5a32398 100644
--- a/audio/aidl/vts/VtsHalHapticGeneratorTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalHapticGeneratorTargetTest.cpp
@@ -33,6 +33,7 @@
 using aidl::android::hardware::audio::effect::IEffect;
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::hardware::audio::effect::Parameter;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 
 /**
  * Here we focus on specific parameter checking, general IEffect interfaces testing performed in
@@ -431,6 +432,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalLoudnessEnhancerTargetTest.cpp b/audio/aidl/vts/VtsHalLoudnessEnhancerTargetTest.cpp
index cbb80a9..925f9ec 100644
--- a/audio/aidl/vts/VtsHalLoudnessEnhancerTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalLoudnessEnhancerTargetTest.cpp
@@ -30,28 +30,21 @@
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::hardware::audio::effect::LoudnessEnhancer;
 using aidl::android::hardware::audio::effect::Parameter;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 
-/**
- * Here we focus on specific parameter checking, general IEffect interfaces testing performed in
- * VtsAudioEffectTargetTest.
- */
-enum ParamName { PARAM_INSTANCE_NAME, PARAM_GAIN_MB };
-using LoudnessEnhancerParamTestParam =
-        std::tuple<std::pair<std::shared_ptr<IFactory>, Descriptor>, int>;
+static constexpr float kMaxAudioSample = 1;
+static constexpr int kZeroGain = 0;
+static constexpr int kMaxGain = std::numeric_limits<int>::max();
+static constexpr int kMinGain = std::numeric_limits<int>::min();
+static constexpr float kAbsError = 0.0001;
 
 // Every int 32 bit value is a valid gain, so testing the corner cases and one regular value.
 // TODO : Update the test values once range/capability is updated by implementation.
-const std::vector<int> kGainMbValues = {std::numeric_limits<int>::min(), 100,
-                                        std::numeric_limits<int>::max()};
+static const std::vector<int> kGainMbValues = {kMinGain, -100, -50, kZeroGain, 50, 100, kMaxGain};
 
-class LoudnessEnhancerParamTest : public ::testing::TestWithParam<LoudnessEnhancerParamTestParam>,
-                                  public EffectHelper {
+class LoudnessEnhancerEffectHelper : public EffectHelper {
   public:
-    LoudnessEnhancerParamTest() : mParamGainMb(std::get<PARAM_GAIN_MB>(GetParam())) {
-        std::tie(mFactory, mDescriptor) = std::get<PARAM_INSTANCE_NAME>(GetParam());
-    }
-
-    void SetUp() override {
+    void SetUpLoudnessEnhancer() {
         ASSERT_NE(nullptr, mFactory);
         ASSERT_NO_FATAL_FAILURE(create(mFactory, mEffect, mDescriptor));
 
@@ -59,13 +52,14 @@
         Parameter::Common common = EffectHelper::createParamCommon(
                 0 /* session */, 1 /* ioHandle */, 44100 /* iSampleRate */, 44100 /* oSampleRate */,
                 kInputFrameCount /* iFrameCount */, kOutputFrameCount /* oFrameCount */);
-        IEffect::OpenEffectReturn ret;
-        ASSERT_NO_FATAL_FAILURE(open(mEffect, common, specific, &ret, EX_NONE));
+        ASSERT_NO_FATAL_FAILURE(open(mEffect, common, specific, &mOpenEffectReturn, EX_NONE));
         ASSERT_NE(nullptr, mEffect);
     }
-    void TearDown() override {
+
+    void TearDownLoudnessEnhancer() {
         ASSERT_NO_FATAL_FAILURE(close(mEffect));
         ASSERT_NO_FATAL_FAILURE(destroy(mFactory, mEffect));
+        mOpenEffectReturn = IEffect::OpenEffectReturn{};
     }
 
     Parameter::Specific getDefaultParamSpecific() {
@@ -75,52 +69,230 @@
         return specific;
     }
 
-    static const long kInputFrameCount = 0x100, kOutputFrameCount = 0x100;
-    std::shared_ptr<IFactory> mFactory;
-    std::shared_ptr<IEffect> mEffect;
-    Descriptor mDescriptor;
-    int mParamGainMb = 0;
+    Parameter createLoudnessParam(int gainMb) {
+        LoudnessEnhancer le;
+        le.set<LoudnessEnhancer::gainMb>(gainMb);
+        Parameter param;
+        Parameter::Specific specific;
+        specific.set<Parameter::Specific::loudnessEnhancer>(le);
+        param.set<Parameter::specific>(specific);
+        return param;
+    }
 
-    void SetAndGetParameters() {
-        for (auto& it : mTags) {
-            auto& tag = it.first;
-            auto& le = it.second;
-
-            // set parameter
-            Parameter expectParam;
-            Parameter::Specific specific;
-            specific.set<Parameter::Specific::loudnessEnhancer>(le);
-            expectParam.set<Parameter::specific>(specific);
-            // All values are valid, set parameter should succeed
-            EXPECT_STATUS(EX_NONE, mEffect->setParameter(expectParam)) << expectParam.toString();
-
-            // get parameter
-            Parameter getParam;
-            Parameter::Id id;
-            LoudnessEnhancer::Id leId;
-            leId.set<LoudnessEnhancer::Id::commonTag>(tag);
-            id.set<Parameter::Id::loudnessEnhancerTag>(leId);
-            EXPECT_STATUS(EX_NONE, mEffect->getParameter(id, &getParam));
-
-            EXPECT_EQ(expectParam, getParam) << "\nexpect:" << expectParam.toString()
-                                             << "\ngetParam:" << getParam.toString();
+    binder_exception_t isGainValid(int gainMb) {
+        LoudnessEnhancer le;
+        le.set<LoudnessEnhancer::gainMb>(gainMb);
+        if (isParameterValid<LoudnessEnhancer, Range::loudnessEnhancer>(le, mDescriptor)) {
+            return EX_NONE;
+        } else {
+            return EX_ILLEGAL_ARGUMENT;
         }
     }
 
-    void addGainMbParam(int gainMb) {
-        LoudnessEnhancer le;
-        le.set<LoudnessEnhancer::gainMb>(gainMb);
-        mTags.push_back({LoudnessEnhancer::gainMb, le});
+    void setParameters(int gain, binder_exception_t expected) {
+        // set parameter
+        auto param = createLoudnessParam(gain);
+        EXPECT_STATUS(expected, mEffect->setParameter(param)) << param.toString();
     }
 
-  private:
-    std::vector<std::pair<LoudnessEnhancer::Tag, LoudnessEnhancer>> mTags;
-    void CleanUp() { mTags.clear(); }
+    void validateParameters(int gain) {
+        // get parameter
+        LoudnessEnhancer::Id leId;
+        Parameter getParam;
+        Parameter::Id id;
+
+        LoudnessEnhancer::Tag tag(LoudnessEnhancer::gainMb);
+        leId.set<LoudnessEnhancer::Id::commonTag>(tag);
+        id.set<Parameter::Id::loudnessEnhancerTag>(leId);
+        EXPECT_STATUS(EX_NONE, mEffect->getParameter(id, &getParam));
+        auto expectedParam = createLoudnessParam(gain);
+        EXPECT_EQ(expectedParam, getParam) << "\nexpectedParam:" << expectedParam.toString()
+                                           << "\ngetParam:" << getParam.toString();
+    }
+
+    static const long kInputFrameCount = 0x100, kOutputFrameCount = 0x100;
+    IEffect::OpenEffectReturn mOpenEffectReturn;
+    std::shared_ptr<IFactory> mFactory;
+    std::shared_ptr<IEffect> mEffect;
+    Descriptor mDescriptor;
+};
+
+/**
+ * Here we focus on specific parameter checking, general IEffect interfaces testing performed in
+ * VtsAudioEffectTargetTest.
+ */
+enum ParamName { PARAM_INSTANCE_NAME, PARAM_GAIN_MB };
+using LoudnessEnhancerParamTestParam =
+        std::tuple<std::pair<std::shared_ptr<IFactory>, Descriptor>, int>;
+
+class LoudnessEnhancerParamTest : public ::testing::TestWithParam<LoudnessEnhancerParamTestParam>,
+                                  public LoudnessEnhancerEffectHelper {
+  public:
+    LoudnessEnhancerParamTest() : mParamGainMb(std::get<PARAM_GAIN_MB>(GetParam())) {
+        std::tie(mFactory, mDescriptor) = std::get<PARAM_INSTANCE_NAME>(GetParam());
+    }
+
+    void SetUp() override { SetUpLoudnessEnhancer(); }
+    void TearDown() override { TearDownLoudnessEnhancer(); }
+    int mParamGainMb = 0;
 };
 
 TEST_P(LoudnessEnhancerParamTest, SetAndGetGainMb) {
-    EXPECT_NO_FATAL_FAILURE(addGainMbParam(mParamGainMb));
-    SetAndGetParameters();
+    binder_exception_t expected = isGainValid(mParamGainMb);
+    setParameters(mParamGainMb, expected);
+    if (expected == EX_NONE) {
+        validateParameters(mParamGainMb);
+    }
+}
+
+using LoudnessEnhancerDataTestParam = std::pair<std::shared_ptr<IFactory>, Descriptor>;
+
+class LoudnessEnhancerDataTest : public ::testing::TestWithParam<LoudnessEnhancerDataTestParam>,
+                                 public LoudnessEnhancerEffectHelper {
+  public:
+    LoudnessEnhancerDataTest() {
+        std::tie(mFactory, mDescriptor) = GetParam();
+        generateInputBuffer();
+        mOutputBuffer.resize(kBufferSize);
+    }
+
+    void SetUp() override {
+        SetUpLoudnessEnhancer();
+
+        // Creating AidlMessageQueues
+        mStatusMQ = std::make_unique<EffectHelper::StatusMQ>(mOpenEffectReturn.statusMQ);
+        mInputMQ = std::make_unique<EffectHelper::DataMQ>(mOpenEffectReturn.inputDataMQ);
+        mOutputMQ = std::make_unique<EffectHelper::DataMQ>(mOpenEffectReturn.outputDataMQ);
+    }
+
+    void TearDown() override { TearDownLoudnessEnhancer(); }
+
+    // Fill inputBuffer with random values between -kMaxAudioSample to kMaxAudioSample
+    void generateInputBuffer() {
+        for (size_t i = 0; i < kBufferSize; i++) {
+            mInputBuffer.push_back(((static_cast<float>(std::rand()) / RAND_MAX) * 2 - 1) *
+                                   kMaxAudioSample);
+        }
+    }
+
+    // Add gains to the mInputBuffer and store processed output to mOutputBuffer
+    void processAndWriteToOutput() {
+        // Check AidlMessageQueues are not null
+        ASSERT_TRUE(mStatusMQ->isValid());
+        ASSERT_TRUE(mInputMQ->isValid());
+        ASSERT_TRUE(mOutputMQ->isValid());
+
+        // Enabling the process
+        ASSERT_NO_FATAL_FAILURE(command(mEffect, CommandId::START));
+        ASSERT_NO_FATAL_FAILURE(expectState(mEffect, State::PROCESSING));
+
+        // Write from buffer to message queues and calling process
+        EXPECT_NO_FATAL_FAILURE(EffectHelper::writeToFmq(mStatusMQ, mInputMQ, mInputBuffer));
+
+        // Read the updated message queues into buffer
+        EXPECT_NO_FATAL_FAILURE(EffectHelper::readFromFmq(mStatusMQ, 1, mOutputMQ,
+                                                          mOutputBuffer.size(), mOutputBuffer));
+
+        // Disable the process
+        ASSERT_NO_FATAL_FAILURE(command(mEffect, CommandId::STOP));
+    }
+
+    void assertGreaterGain(const std::vector<float>& first, const std::vector<float>& second) {
+        for (size_t i = 0; i < first.size(); i++) {
+            if (first[i] != 0) {
+                ASSERT_GT(abs(first[i]), abs(second[i]));
+
+            } else {
+                ASSERT_EQ(first[i], second[i]);
+            }
+        }
+    }
+
+    void assertSequentialGains(const std::vector<int>& gainValues, bool isIncreasing) {
+        std::vector<float> baseOutput(kBufferSize);
+
+        // Process a reference output buffer with 0 gain which gives compressed input values
+        binder_exception_t expected;
+        expected = isGainValid(kZeroGain);
+        ASSERT_EQ(expected, EX_NONE);
+        setParameters(kZeroGain, expected);
+        ASSERT_NO_FATAL_FAILURE(processAndWriteToOutput());
+        baseOutput = mOutputBuffer;
+
+        // Compare the outputs for increasing gain
+        for (int gain : gainValues) {
+            // Setting the parameters
+            binder_exception_t expected = isGainValid(gain);
+            if (expected != EX_NONE) {
+                GTEST_SKIP() << "Gains not supported.";
+            }
+            setParameters(gain, expected);
+            ASSERT_NO_FATAL_FAILURE(processAndWriteToOutput());
+
+            // Compare the mOutputBuffer values with baseOutput and update it
+            if (isIncreasing) {
+                ASSERT_NO_FATAL_FAILURE(assertGreaterGain(mOutputBuffer, baseOutput));
+            } else {
+                ASSERT_NO_FATAL_FAILURE(assertGreaterGain(baseOutput, mOutputBuffer));
+            }
+
+            baseOutput = mOutputBuffer;
+        }
+    }
+
+    std::unique_ptr<StatusMQ> mStatusMQ;
+    std::unique_ptr<DataMQ> mInputMQ;
+    std::unique_ptr<DataMQ> mOutputMQ;
+
+    std::vector<float> mInputBuffer;
+    std::vector<float> mOutputBuffer;
+    static constexpr float kBufferSize = 128;
+};
+
+TEST_P(LoudnessEnhancerDataTest, IncreasingGains) {
+    static const std::vector<int> kIncreasingGains = {50, 100};
+
+    assertSequentialGains(kIncreasingGains, true /*isIncreasing*/);
+}
+
+TEST_P(LoudnessEnhancerDataTest, DecreasingGains) {
+    static const std::vector<int> kDecreasingGains = {-50, -100};
+
+    assertSequentialGains(kDecreasingGains, false /*isIncreasing*/);
+}
+
+TEST_P(LoudnessEnhancerDataTest, MinimumGain) {
+    // Setting the parameters
+    binder_exception_t expected = isGainValid(kMinGain);
+    if (expected != EX_NONE) {
+        GTEST_SKIP() << "Minimum integer value not supported";
+    }
+    setParameters(kMinGain, expected);
+    ASSERT_NO_FATAL_FAILURE(processAndWriteToOutput());
+
+    // Validate that mOutputBuffer has 0 values for INT_MIN gain
+    for (size_t i = 0; i < mOutputBuffer.size(); i++) {
+        ASSERT_FLOAT_EQ(mOutputBuffer[i], 0);
+    }
+}
+
+TEST_P(LoudnessEnhancerDataTest, MaximumGain) {
+    // Setting the parameters
+    binder_exception_t expected = isGainValid(kMaxGain);
+    if (expected != EX_NONE) {
+        GTEST_SKIP() << "Maximum integer value not supported";
+    }
+    setParameters(kMaxGain, expected);
+    ASSERT_NO_FATAL_FAILURE(processAndWriteToOutput());
+
+    // Validate that mOutputBuffer reaches to kMaxAudioSample for INT_MAX gain
+    for (size_t i = 0; i < mOutputBuffer.size(); i++) {
+        if (mInputBuffer[i] != 0) {
+            EXPECT_NEAR(kMaxAudioSample, abs(mOutputBuffer[i]), kAbsError);
+        } else {
+            ASSERT_EQ(mOutputBuffer[i], mInputBuffer[i]);
+        }
+    }
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -139,8 +311,23 @@
 
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(LoudnessEnhancerParamTest);
 
+INSTANTIATE_TEST_SUITE_P(
+        LoudnessEnhancerTest, LoudnessEnhancerDataTest,
+        testing::ValuesIn(EffectFactoryHelper::getAllEffectDescriptors(
+                IFactory::descriptor, getEffectTypeUuidLoudnessEnhancer())),
+        [](const testing::TestParamInfo<LoudnessEnhancerDataTest::ParamType>& info) {
+            auto descriptor = info.param;
+            std::string name = getPrefix(descriptor.second);
+            std::replace_if(
+                    name.begin(), name.end(), [](const char c) { return !std::isalnum(c); }, '_');
+            return name;
+        });
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(LoudnessEnhancerDataTest);
+
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalNSTargetTest.cpp b/audio/aidl/vts/VtsHalNSTargetTest.cpp
index 624d5d2..12d56b0 100644
--- a/audio/aidl/vts/VtsHalNSTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalNSTargetTest.cpp
@@ -32,6 +32,7 @@
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::hardware::audio::effect::NoiseSuppression;
 using aidl::android::hardware::audio::effect::Parameter;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 
 enum ParamName { PARAM_INSTANCE_NAME, PARAM_LEVEL, PARAM_TYPE };
 using NSParamTestParam = std::tuple<std::pair<std::shared_ptr<IFactory>, Descriptor>,
@@ -171,6 +172,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalPresetReverbTargetTest.cpp b/audio/aidl/vts/VtsHalPresetReverbTargetTest.cpp
index 3056c6c..57eda09 100644
--- a/audio/aidl/vts/VtsHalPresetReverbTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalPresetReverbTargetTest.cpp
@@ -29,6 +29,7 @@
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::hardware::audio::effect::Parameter;
 using aidl::android::hardware::audio::effect::PresetReverb;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 
 /**
  * Here we focus on specific parameter checking, general IEffect interfaces testing performed in
@@ -147,6 +148,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalVirtualizerTargetTest.cpp b/audio/aidl/vts/VtsHalVirtualizerTargetTest.cpp
index 07a9fa4..3e39d3a 100644
--- a/audio/aidl/vts/VtsHalVirtualizerTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalVirtualizerTargetTest.cpp
@@ -28,6 +28,7 @@
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::hardware::audio::effect::Parameter;
 using aidl::android::hardware::audio::effect::Virtualizer;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 
 /**
  * Here we focus on specific parameter checking, general IEffect interfaces testing performed in
@@ -151,6 +152,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalVisualizerTargetTest.cpp b/audio/aidl/vts/VtsHalVisualizerTargetTest.cpp
index 903ba69..1b8352b 100644
--- a/audio/aidl/vts/VtsHalVisualizerTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalVisualizerTargetTest.cpp
@@ -31,6 +31,7 @@
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::hardware::audio::effect::Parameter;
 using aidl::android::hardware::audio::effect::Visualizer;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 
 /**
  * Here we focus on specific parameter checking, general IEffect interfaces testing performed in
@@ -207,6 +208,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalVolumeTargetTest.cpp b/audio/aidl/vts/VtsHalVolumeTargetTest.cpp
index 0b5b9fc..257100b 100644
--- a/audio/aidl/vts/VtsHalVolumeTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalVolumeTargetTest.cpp
@@ -28,6 +28,7 @@
 using aidl::android::hardware::audio::effect::IFactory;
 using aidl::android::hardware::audio::effect::Parameter;
 using aidl::android::hardware::audio::effect::Volume;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
 
 /**
  * Here we focus on specific parameter checking, general IEffect interfaces testing performed in
@@ -159,6 +160,7 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+    ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
     return RUN_ALL_TESTS();
diff --git a/audio/common/all-versions/test/utility/src/ValidateXml.cpp b/audio/common/all-versions/test/utility/src/ValidateXml.cpp
index 4d6f003..b7e685b 100644
--- a/audio/common/all-versions/test/utility/src/ValidateXml.cpp
+++ b/audio/common/all-versions/test/utility/src/ValidateXml.cpp
@@ -19,6 +19,7 @@
 
 #include <numeric>
 
+#include <libxml/parser.h>
 #define LIBXML_SCHEMAS_ENABLED
 #include <libxml/xmlschemastypes.h>
 #define LIBXML_XINCLUDE_ENABLED
diff --git a/audio/common/all-versions/test/utility/tests/utility_tests.cpp b/audio/common/all-versions/test/utility/tests/utility_tests.cpp
index c523066..c2bcfbc 100644
--- a/audio/common/all-versions/test/utility/tests/utility_tests.cpp
+++ b/audio/common/all-versions/test/utility/tests/utility_tests.cpp
@@ -70,6 +70,7 @@
 std::string substitute(const char* fmt, const char* param) {
     std::string buffer(static_cast<size_t>(strlen(fmt) + strlen(param)), '\0');
     snprintf(buffer.data(), buffer.size(), fmt, param);
+    buffer.resize(strlen(buffer.c_str()));
     return buffer;
 }
 
diff --git a/automotive/vehicle/TEST_MAPPING b/automotive/vehicle/TEST_MAPPING
index 02ad8bb..e1a90cb 100644
--- a/automotive/vehicle/TEST_MAPPING
+++ b/automotive/vehicle/TEST_MAPPING
@@ -51,6 +51,9 @@
           "include-filter": "com.android.car.hal.fakevhal.FakeVehicleStubUnitTest"
         }
       ]
+    },
+    {
+      "name": "VehicleHalProtoMessageConverterTest"
     }
   ]
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/SubscribeOptions.aidl b/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/SubscribeOptions.aidl
index 91e7c14..1b1696b 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/SubscribeOptions.aidl
+++ b/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/SubscribeOptions.aidl
@@ -37,4 +37,6 @@
   int propId;
   int[] areaIds;
   float sampleRate;
+  float resolution = 0.0f;
+  boolean enableVariableUpdateRate;
 }
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/VehicleAreaConfig.aidl b/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/VehicleAreaConfig.aidl
index 3322769..08d4ee4 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/VehicleAreaConfig.aidl
+++ b/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/VehicleAreaConfig.aidl
@@ -43,4 +43,5 @@
   float maxFloatValue;
   @nullable long[] supportedEnumValues;
   android.hardware.automotive.vehicle.VehiclePropertyAccess access = android.hardware.automotive.vehicle.VehiclePropertyAccess.NONE;
+  boolean supportVariableUpdateRate;
 }
diff --git a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/SubscribeOptions.aidl b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/SubscribeOptions.aidl
index e68f7e3..69f6190 100644
--- a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/SubscribeOptions.aidl
+++ b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/SubscribeOptions.aidl
@@ -39,4 +39,29 @@
      * This value indicates how many updates per second client wants to receive.
      */
     float sampleRate;
+
+    /**
+     * Requested resolution of property updates.
+     *
+     * This value indicates the resolution at which continuous property updates should be sent to
+     * the platform. For example, if resolution is 0.01, the subscribed property value should be
+     * rounded to two decimal places. If the incoming resolution value is not an integer multiple of
+     * 10, VHAL should return a StatusCode::INVALID_ARG.
+     */
+    float resolution = 0.0f;
+
+    /**
+     * Whether to enable variable update rate.
+     *
+     * This only applies for continuous property. If variable update rate is
+     * enabled, for each given areaId, if VHAL supports variable update rate for
+     * the [propId, areaId], VHAL must ignore duplicate property value events
+     * and only sends changed value events (a.k.a treat continuous as an
+     * on-change property).
+     *
+     * If VHAL does not support variable update rate for the [propId, areaId],
+     * indicated by 'supportVariableUpdateRate' in 'VehicleAreaConfig', or if
+     * this property is not a continuous property, this option must be ignored.
+     */
+    boolean enableVariableUpdateRate;
 }
diff --git a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/VehicleAreaConfig.aidl b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/VehicleAreaConfig.aidl
index 9fd9bda..08863b2 100644
--- a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/VehicleAreaConfig.aidl
+++ b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/VehicleAreaConfig.aidl
@@ -70,4 +70,31 @@
      * well.
      */
     VehiclePropertyAccess access = VehiclePropertyAccess.NONE;
+
+    /**
+     * Whether variable update rate is supported.
+     *
+     * This applies for continuous property only.
+     *
+     * It is HIGHLY RECOMMENDED to support variable update rate for all non-heartbeat continuous
+     * properties for better performance.
+     *
+     * If variable update rate is supported and 'enableVariableUpdateRate' is true in subscribe
+     * options, VHAL must only sends property update event when the property's value changes
+     * (a.k.a treat continuous as an on-change property).
+     *
+     * E.g. if the client is subscribing at 5hz at time 0. If the property's value is 0 initially
+     * and becomes 1 after 1 second.
+
+     * If variable update rate is not enabled, VHAL clients will receive 5 property change events
+     * with value 0 and 5 events with value 1 after 2 seconds.
+     *
+     * If variable update rate is enabled, VHAL clients will receive 1 property change event
+     * with value 1 at time 1s. VHAL may/may not send a property event for the initial value (e.g.
+     * a property change event with value 0 at time 0s). VHAL client must not rely on the first
+     * property event, and must use getValues to fetch the initial value. In fact, car service is
+     * using getValues to fetch the initial value, convert it to a property event and deliver to
+     * car service clients.
+     */
+    boolean supportVariableUpdateRate;
 }
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 844bea5..26fdee6 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
@@ -91,9 +91,13 @@
     void registerOnPropertySetErrorEvent(
             std::unique_ptr<const PropertySetErrorCallback> callback) override;
 
-    // Update the sample rate for the [propId, areaId] pair.
-    aidl::android::hardware::automotive::vehicle::StatusCode updateSampleRate(
-            int32_t propId, int32_t areaId, float sampleRate) override;
+    // Subscribe to a new [propId, areaId] or change the update rate.
+    aidl::android::hardware::automotive::vehicle::StatusCode subscribe(
+            aidl::android::hardware::automotive::vehicle::SubscribeOptions options) override;
+
+    // Unsubscribe to a [propId, areaId].
+    aidl::android::hardware::automotive::vehicle::StatusCode unsubscribe(int32_t propId,
+                                                                         int32_t areaId) override;
 
   protected:
     // mValuePool is also used in mServerSidePropStore.
@@ -154,6 +158,7 @@
             mRecurrentActions GUARDED_BY(mLock);
     std::unordered_map<PropIdAreaId, VehiclePropValuePool::RecyclableType, PropIdAreaIdHash>
             mSavedProps GUARDED_BY(mLock);
+    std::unordered_set<PropIdAreaId, PropIdAreaIdHash> mSubOnChangePropIdAreaIds GUARDED_BY(mLock);
     // PendingRequestHandler is thread-safe.
     mutable PendingRequestHandler<GetValuesCallback,
                                   aidl::android::hardware::automotive::vehicle::GetValueRequest>
@@ -176,7 +181,8 @@
     void storePropInitialValue(const ConfigDeclaration& config);
     // The callback that would be called when a vehicle property value change happens.
     void onValueChangeCallback(
-            const aidl::android::hardware::automotive::vehicle::VehiclePropValue& value);
+            const aidl::android::hardware::automotive::vehicle::VehiclePropValue& value)
+            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,
@@ -216,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();
 
@@ -262,6 +271,11 @@
     void generateVendorConfigs(
             std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropConfig>&) const;
 
+    aidl::android::hardware::automotive::vehicle::StatusCode subscribePropIdAreaIdLocked(
+            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,
             int32_t keyCode, int32_t targetDisplay);
@@ -275,6 +289,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 ee24fbd..7ff03c6 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
@@ -60,6 +60,8 @@
 using ::aidl::android::hardware::automotive::vehicle::SetValueRequest;
 using ::aidl::android::hardware::automotive::vehicle::SetValueResult;
 using ::aidl::android::hardware::automotive::vehicle::StatusCode;
+using ::aidl::android::hardware::automotive::vehicle::SubscribeOptions;
+using ::aidl::android::hardware::automotive::vehicle::toString;
 using ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateReport;
 using ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateReq;
 using ::aidl::android::hardware::automotive::vehicle::VehicleArea;
@@ -67,6 +69,7 @@
 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::VehiclePropertyGroup;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyStatus;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType;
@@ -598,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;
@@ -850,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));
@@ -1926,43 +2010,109 @@
     mOnPropertySetErrorCallback = std::move(callback);
 }
 
-StatusCode FakeVehicleHardware::updateSampleRate(int32_t propId, int32_t areaId, float sampleRate) {
-    // DefaultVehicleHal makes sure that sampleRate must be within minSampleRate and maxSampleRate.
-    // For fake implementation, we would write the same value with a new timestamp into propStore
-    // at sample rate.
-    std::scoped_lock<std::mutex> lockGuard(mLock);
+StatusCode FakeVehicleHardware::subscribe(SubscribeOptions options) {
+    int32_t propId = options.propId;
 
+    auto configResult = mServerSidePropStore->getConfig(propId);
+    if (!configResult.ok()) {
+        ALOGE("subscribe: property: %" PRId32 " is not supported", propId);
+        return StatusCode::INVALID_ARG;
+    }
+
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+    for (int areaId : options.areaIds) {
+        if (StatusCode status = subscribePropIdAreaIdLocked(propId, areaId, options.sampleRate,
+                                                            options.enableVariableUpdateRate,
+                                                            *configResult.value());
+            status != StatusCode::OK) {
+            return status;
+        }
+    }
+    return StatusCode::OK;
+}
+
+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 (vehiclePropConfig.changeMode) {
+        case VehiclePropertyChangeMode::STATIC:
+            ALOGW("subscribe to a static property, do nothing.");
+            return StatusCode::OK;
+        case VehiclePropertyChangeMode::ON_CHANGE:
+            mSubOnChangePropIdAreaIds.insert(std::move(propIdAreaId));
+            return StatusCode::OK;
+        case VehiclePropertyChangeMode::CONTINUOUS:
+            if (sampleRateHz == 0.f) {
+                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;
+            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.
+                auto result = getValue(VehiclePropValue{
+                        .areaId = areaId,
+                        .prop = propId,
+                        .value = {},
+                });
+                if (!result.ok()) {
+                    // Failed to read current value, skip refreshing.
+                    return;
+                }
+                result.value()->timestamp = elapsedRealtimeNano();
+
+                mServerSidePropStore->writeValue(std::move(result.value()), /*updateStatus=*/true,
+                                                 eventMode);
+            });
+            mRecurrentTimer->registerTimerCallback(intervalInNanos, action);
+            mRecurrentActions[propIdAreaId] = action;
+            return StatusCode::OK;
+    }
+}
+
+StatusCode FakeVehicleHardware::unsubscribe(int32_t propId, int32_t areaId) {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
     PropIdAreaId propIdAreaId{
             .propId = propId,
             .areaId = areaId,
     };
     if (mRecurrentActions.find(propIdAreaId) != mRecurrentActions.end()) {
         mRecurrentTimer->unregisterTimerCallback(mRecurrentActions[propIdAreaId]);
+        mRecurrentActions.erase(propIdAreaId);
     }
-    if (sampleRate == 0) {
-        return StatusCode::OK;
-    }
-    int64_t interval = static_cast<int64_t>(1'000'000'000. / sampleRate);
-    auto action = std::make_shared<RecurrentTimer::Callback>([this, propId, areaId] {
-        // 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.
-        auto result = getValue(VehiclePropValue{
-                .areaId = areaId,
-                .prop = propId,
-                .value = {},
-        });
-        if (!result.ok()) {
-            // Failed to read current value, skip refreshing.
-            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);
-    });
-    mRecurrentTimer->registerTimerCallback(interval, action);
-    mRecurrentActions[propIdAreaId] = action;
+    mSubOnChangePropIdAreaIds.erase(propIdAreaId);
     return StatusCode::OK;
 }
 
@@ -1971,6 +2121,23 @@
         return;
     }
 
+    PropIdAreaId propIdAreaId{
+            .propId = value.prop,
+            .areaId = value.areaId,
+    };
+
+    {
+        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());
+            }
+            return;
+        }
+    }
+
     std::vector<VehiclePropValue> updatedValues;
     updatedValues.push_back(value);
     (*mOnPropertyChangeCallback)(std::move(updatedValues));
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 cf9beee..3b6f717 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
@@ -72,6 +72,7 @@
 using ::aidl::android::hardware::automotive::vehicle::SetValueRequest;
 using ::aidl::android::hardware::automotive::vehicle::SetValueResult;
 using ::aidl::android::hardware::automotive::vehicle::StatusCode;
+using ::aidl::android::hardware::automotive::vehicle::SubscribeOptions;
 using ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateReport;
 using ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateReq;
 using ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateShutdownParam;
@@ -94,6 +95,7 @@
 using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::IsSubsetOf;
+using ::testing::UnorderedElementsAre;
 using ::testing::WhenSortedBy;
 
 using std::chrono::milliseconds;
@@ -149,6 +151,15 @@
         mHardware = std::move(hardware);
     }
 
+    static SubscribeOptions newSubscribeOptions(int32_t propId, int32_t areaId,
+                                                float sampleRateHz) {
+        SubscribeOptions options;
+        options.areaIds = {areaId};
+        options.propId = propId;
+        options.sampleRate = sampleRateHz;
+        return options;
+    }
+
     StatusCode setValues(const std::vector<SetValueRequest>& requests) {
         {
             std::scoped_lock<std::mutex> lockGuard(mLock);
@@ -336,6 +347,13 @@
         return mEventCount[propIdAreaId];
     }
 
+    void subscribe(int32_t propId, int32_t areaId, float sampleRateHz) {
+        ASSERT_EQ(StatusCode::OK,
+                  getHardware()->subscribe(newSubscribeOptions(propId, areaId, sampleRateHz)))
+                << "failed to subscribe to propId: " << propId << "areaId: " << areaId
+                << ", sampleRateHz: " << sampleRateHz;
+    }
+
     static void addSetValueRequest(std::vector<SetValueRequest>& requests,
                                    std::vector<SetValueResult>& expectedResults, int64_t requestId,
                                    const VehiclePropValue& value, StatusCode expectedStatus) {
@@ -370,24 +388,24 @@
     }
 
     std::vector<VehiclePropValue> getTestPropValues() {
-        VehiclePropValue fuelCapacity = {
-                .prop = toInt(VehicleProperty::INFO_FUEL_CAPACITY),
-                .value = {.floatValues = {1.0}},
+        VehiclePropValue oilLevel = {
+                .prop = toInt(VehicleProperty::ENGINE_OIL_LEVEL),
+                .value = {.int32Values = {1}},
         };
 
-        VehiclePropValue leftTirePressure = {
-                .prop = toInt(VehicleProperty::TIRE_PRESSURE),
+        VehiclePropValue leftHvacTemp = {
+                .prop = toInt(VehicleProperty::HVAC_TEMPERATURE_CURRENT),
                 .value = {.floatValues = {170.0}},
-                .areaId = WHEEL_FRONT_LEFT,
+                .areaId = SEAT_1_LEFT,
         };
 
-        VehiclePropValue rightTirePressure = {
-                .prop = toInt(VehicleProperty::TIRE_PRESSURE),
+        VehiclePropValue rightHvacTemp = {
+                .prop = toInt(VehicleProperty::HVAC_TEMPERATURE_CURRENT),
                 .value = {.floatValues = {180.0}},
-                .areaId = WHEEL_FRONT_RIGHT,
+                .areaId = SEAT_1_RIGHT,
         };
 
-        return {fuelCapacity, leftTirePressure, rightTirePressure};
+        return {oilLevel, leftHvacTemp, rightHvacTemp};
     }
 
     struct PropValueCmp {
@@ -437,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;
@@ -559,17 +600,13 @@
     ASSERT_THAT(getSetValueResults(), ContainerEq(expectedResults));
 }
 
-TEST_F(FakeVehicleHardwareTest, testRegisterOnPropertyChangeEvent) {
-    // We have already registered this callback in Setup, here we are registering again.
-    auto callback = std::make_unique<IVehicleHardware::PropertyChangeCallback>(
-            [this](const std::vector<VehiclePropValue>& values) { onPropertyChangeEvent(values); });
-    getHardware()->registerOnPropertyChangeEvent(std::move(callback));
-
+TEST_F(FakeVehicleHardwareTest, testSetValues_getUpdateEvents) {
     auto testValues = getTestPropValues();
     std::vector<SetValueRequest> requests;
     std::vector<SetValueResult> expectedResults;
     int64_t requestId = 1;
     for (auto& value : testValues) {
+        subscribe(value.prop, value.areaId, /*sampleRateHz=*/0);
         addSetValueRequest(requests, expectedResults, requestId++, value, StatusCode::OK);
     }
     int64_t timestamp = elapsedRealtimeNano();
@@ -1624,27 +1661,30 @@
             return info.param.name;
         });
 
-TEST_F(FakeVehicleHardwareTest, testSetWaitForVhalAfterCarServiceCrash) {
-    int32_t propId = toInt(VehicleProperty::AP_POWER_STATE_REPORT);
+TEST_F(FakeVehicleHardwareTest, testSetWaitForVhal_alwaysTriggerEvents) {
+    int32_t powerReq = toInt(VehicleProperty::AP_POWER_STATE_REQ);
+    subscribe(powerReq, /*areaId*/ 0, /*sampleRateHz*/ 0);
+
+    int32_t powerReport = toInt(VehicleProperty::AP_POWER_STATE_REPORT);
     VehiclePropValue request = VehiclePropValue{
-            .prop = propId,
+            .prop = powerReport,
             .value.int32Values = {toInt(VehicleApPowerStateReport::WAIT_FOR_VHAL)},
     };
-    ASSERT_EQ(setValue(request), StatusCode::OK) << "failed to set property " << propId;
+    ASSERT_EQ(setValue(request), StatusCode::OK) << "failed to set property " << powerReport;
 
     // Clear existing events.
     clearChangedProperties();
 
     // Simulate a Car Service crash, Car Service would restart and send the message again.
-    ASSERT_EQ(setValue(request), StatusCode::OK) << "failed to set property " << propId;
+    ASSERT_EQ(setValue(request), StatusCode::OK) << "failed to set property " << powerReport;
 
     std::vector<VehiclePropValue> events = getChangedProperties();
     // Even though the state is already ON, we should receive another ON event.
-    ASSERT_EQ(events.size(), 1u);
+    ASSERT_EQ(events.size(), 1u) << "failed to receive on-change events AP_POWER_STATE_REQ ON";
     // Erase the timestamp for comparison.
     events[0].timestamp = 0;
     auto expectedValue = VehiclePropValue{
-            .prop = toInt(VehicleProperty::AP_POWER_STATE_REQ),
+            .prop = powerReq,
             .status = VehiclePropertyStatus::AVAILABLE,
             .value.int32Values = {toInt(VehicleApPowerStateReq::ON), 0},
     };
@@ -1849,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 = {
             {
@@ -2015,6 +2150,22 @@
                     },
             },
     };
+
+    // First subscribe to all the properties that we will change.
+    for (auto& enabledToErrorStateProps : adasEnabledPropToAdasPropWithErrorState) {
+        std::unordered_set<int32_t> expectedChangedPropIds(enabledToErrorStateProps.second.begin(),
+                                                           enabledToErrorStateProps.second.end());
+        expectedChangedPropIds.insert(enabledToErrorStateProps.first);
+
+        for (int32_t propId : expectedChangedPropIds) {
+            int32_t areaId = 0;
+            if (propId == toInt(VehicleProperty::BLIND_SPOT_WARNING_STATE)) {
+                areaId = toInt(VehicleAreaMirror::DRIVER_LEFT);
+            }
+            subscribe(propId, areaId, /*sampleRateHz*/ 0);
+        }
+    }
+
     for (auto& enabledToErrorStateProps : adasEnabledPropToAdasPropWithErrorState) {
         int32_t adasEnabledPropertyId = enabledToErrorStateProps.first;
         StatusCode status =
@@ -2095,9 +2246,16 @@
 }
 
 TEST_F(FakeVehicleHardwareTest, testSwitchUser) {
+    SubscribeOptions options;
+    int32_t propSwitchUser = toInt(VehicleProperty::SWITCH_USER);
+    options.propId = propSwitchUser;
+    options.areaIds = {0, 1};
+    ASSERT_EQ(StatusCode::OK, getHardware()->subscribe(options))
+            << "failed to subscribe to propId: " << propSwitchUser;
+
     // This is the same example as used in User HAL Emulation doc.
     VehiclePropValue valueToSet = {
-            .prop = toInt(VehicleProperty::SWITCH_USER),
+            .prop = propSwitchUser,
             .areaId = 1,
             .value.int32Values = {666, 3, 2},
     };
@@ -2108,7 +2266,7 @@
 
     // Simulate a request from Android side.
     VehiclePropValue switchUserRequest = {
-            .prop = toInt(VehicleProperty::SWITCH_USER),
+            .prop = propSwitchUser,
             .areaId = 0,
             .value.int32Values = {666, 3},
     };
@@ -2138,7 +2296,7 @@
     events[0].timestamp = 0;
     auto expectedValue = VehiclePropValue{
             .areaId = 0,
-            .prop = toInt(VehicleProperty::SWITCH_USER),
+            .prop = propSwitchUser,
             .value.int32Values =
                     {
                             // Request ID
@@ -2153,6 +2311,13 @@
 }
 
 TEST_F(FakeVehicleHardwareTest, testCreateUser) {
+    SubscribeOptions options;
+    int32_t propCreateUser = toInt(VehicleProperty::CREATE_USER);
+    options.propId = propCreateUser;
+    options.areaIds = {0, 1};
+    ASSERT_EQ(StatusCode::OK, getHardware()->subscribe(options))
+            << "failed to subscribe to propId: " << propCreateUser;
+
     // This is the same example as used in User HAL Emulation doc.
     VehiclePropValue valueToSet = {
             .prop = toInt(VehicleProperty::CREATE_USER),
@@ -2166,7 +2331,7 @@
 
     // Simulate a request from Android side.
     VehiclePropValue createUserRequest = {
-            .prop = toInt(VehicleProperty::CREATE_USER),
+            .prop = propCreateUser,
             .areaId = 0,
             .value.int32Values = {666},
     };
@@ -2195,7 +2360,7 @@
     events[0].timestamp = 0;
     auto expectedValue = VehiclePropValue{
             .areaId = 0,
-            .prop = toInt(VehicleProperty::CREATE_USER),
+            .prop = propCreateUser,
             .value.int32Values =
                     {
                             // Request ID
@@ -2208,9 +2373,16 @@
 }
 
 TEST_F(FakeVehicleHardwareTest, testInitialUserInfo) {
+    SubscribeOptions options;
+    int32_t propInitialUserInfo = toInt(VehicleProperty::INITIAL_USER_INFO);
+    options.propId = propInitialUserInfo;
+    options.areaIds = {0, 1};
+    ASSERT_EQ(StatusCode::OK, getHardware()->subscribe(options))
+            << "failed to subscribe to propId: " << propInitialUserInfo;
+
     // This is the same example as used in User HAL Emulation doc.
     VehiclePropValue valueToSet = {
-            .prop = toInt(VehicleProperty::INITIAL_USER_INFO),
+            .prop = propInitialUserInfo,
             .areaId = 1,
             .value.int32Values = {666, 1, 11},
     };
@@ -2221,7 +2393,7 @@
 
     // Simulate a request from Android side.
     VehiclePropValue initialUserInfoRequest = {
-            .prop = toInt(VehicleProperty::INITIAL_USER_INFO),
+            .prop = propInitialUserInfo,
             .areaId = 0,
             .value.int32Values = {3},
     };
@@ -2238,7 +2410,7 @@
     events[0].timestamp = 0;
     auto expectedValue = VehiclePropValue{
             .areaId = 0,
-            .prop = toInt(VehicleProperty::INITIAL_USER_INFO),
+            .prop = propInitialUserInfo,
             .value.int32Values = {3, 1, 11},
     };
     EXPECT_EQ(events[0], expectedValue);
@@ -2253,7 +2425,7 @@
     events[0].timestamp = 0;
     expectedValue = VehiclePropValue{
             .areaId = 0,
-            .prop = toInt(VehicleProperty::INITIAL_USER_INFO),
+            .prop = propInitialUserInfo,
             .value.int32Values =
                     {
                             // Request ID
@@ -2395,13 +2567,14 @@
 }
 
 TEST_F(FakeVehicleHardwareTest, testDumpInjectEvent) {
-    int32_t prop = toInt(VehicleProperty::PERF_VEHICLE_SPEED);
+    int32_t prop = toInt(VehicleProperty::ENGINE_OIL_LEVEL);
     std::string propIdStr = std::to_string(prop);
 
+    subscribe(prop, /*areaId*/ 0, /*sampleRateHz*/ 0);
+
     int64_t timestamp = elapsedRealtimeNano();
-    // Inject an event with float value 123.4 and timestamp.
     DumpResult result = getHardware()->dump(
-            {"--inject-event", propIdStr, "-f", "123.4", "-t", std::to_string(timestamp)});
+            {"--inject-event", propIdStr, "-i", "1234", "-t", std::to_string(timestamp)});
 
     ASSERT_FALSE(result.callerShouldDumpState);
     ASSERT_THAT(result.buffer,
@@ -2412,7 +2585,7 @@
     ASSERT_EQ(events.size(), 1u);
     auto event = events[0];
     ASSERT_EQ(event.timestamp, timestamp);
-    ASSERT_EQ(event.value.floatValues, std::vector<float>({123.4}));
+    ASSERT_EQ(event.value.int32Values, std::vector<int32_t>({1234}));
 }
 
 TEST_F(FakeVehicleHardwareTest, testDumpInvalidOptions) {
@@ -2755,9 +2928,13 @@
         });
 
 TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataLinear) {
-    // Start a fake linear data generator for vehicle speed at 0.1s interval.
+    // Start a fake linear data generator for engine oil level at 0.1s interval.
     // range: 0 - 100, current value: 30, step: 20.
-    std::string propIdString = StringPrintf("%d", toInt(VehicleProperty::PERF_VEHICLE_SPEED));
+    int32_t prop = toInt(VehicleProperty::ENGINE_OIL_LEVEL);
+
+    subscribe(prop, /*areaId*/ 0, /*sampleRateHz*/ 0);
+
+    std::string propIdString = StringPrintf("%d", prop);
     std::vector<std::string> options = {"--genfakedata",         "--startlinear", propIdString,
                                         /*middleValue=*/"50",
                                         /*currentValue=*/"30",
@@ -2770,15 +2947,14 @@
     ASSERT_FALSE(result.callerShouldDumpState);
     ASSERT_THAT(result.buffer, HasSubstr("successfully"));
 
-    ASSERT_TRUE(waitForChangedProperties(toInt(VehicleProperty::PERF_VEHICLE_SPEED), 0, /*count=*/5,
-                                         milliseconds(1000)))
+    ASSERT_TRUE(waitForChangedProperties(prop, 0, /*count=*/5, milliseconds(1000)))
             << "not enough events generated for linear data generator";
 
     int32_t value = 30;
     auto events = getChangedProperties();
     for (size_t i = 0; i < 5; i++) {
-        ASSERT_EQ(1u, events[i].value.floatValues.size());
-        EXPECT_EQ(static_cast<float>(value), events[i].value.floatValues[0]);
+        ASSERT_EQ(1u, events[i].value.int32Values.size());
+        EXPECT_EQ(value, events[i].value.int32Values[0]);
         value = (value + 20) % 100;
     }
 
@@ -2794,7 +2970,7 @@
     std::this_thread::sleep_for(std::chrono::milliseconds(200));
 
     // There should be no new events generated.
-    EXPECT_EQ(0u, getEventCount(toInt(VehicleProperty::PERF_VEHICLE_SPEED), 0));
+    EXPECT_EQ(0u, getEventCount(prop, 0));
 }
 
 std::string getTestFilePath(const char* filename) {
@@ -2803,6 +2979,8 @@
 }
 
 TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataJson) {
+    subscribe(toInt(VehicleProperty::GEAR_SELECTION), /*areaId*/ 0, /*sampleRateHz*/ 0);
+
     std::vector<std::string> options = {"--genfakedata", "--startjson", "--path",
                                         getTestFilePath("prop.json"), "2"};
 
@@ -2829,6 +3007,8 @@
 }
 
 TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataJsonByContent) {
+    subscribe(toInt(VehicleProperty::GEAR_SELECTION), /*areaId*/ 0, /*sampleRateHz*/ 0);
+
     std::vector<std::string> options = {
             "--genfakedata", "--startjson", "--content",
             "[{\"timestamp\":1000000,\"areaId\":0,\"value\":8,\"prop\":289408000}]", "1"};
@@ -2903,8 +3083,11 @@
 }
 
 TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataKeyPress) {
+    int32_t propHwKeyInput = toInt(VehicleProperty::HW_KEY_INPUT);
     std::vector<std::string> options = {"--genfakedata", "--keypress", "1", "2"};
 
+    subscribe(propHwKeyInput, /*areaId*/ 0, /*sampleRateHz*/ 0);
+
     DumpResult result = getHardware()->dump(options);
 
     ASSERT_FALSE(result.callerShouldDumpState);
@@ -2912,8 +3095,8 @@
 
     auto events = getChangedProperties();
     ASSERT_EQ(2u, events.size());
-    EXPECT_EQ(toInt(VehicleProperty::HW_KEY_INPUT), events[0].prop);
-    EXPECT_EQ(toInt(VehicleProperty::HW_KEY_INPUT), events[1].prop);
+    EXPECT_EQ(propHwKeyInput, events[0].prop);
+    EXPECT_EQ(propHwKeyInput, events[1].prop);
     ASSERT_EQ(3u, events[0].value.int32Values.size());
     ASSERT_EQ(3u, events[1].value.int32Values.size());
     EXPECT_EQ(toInt(VehicleHwKeyInputAction::ACTION_DOWN), events[0].value.int32Values[0]);
@@ -2925,8 +3108,11 @@
 }
 
 TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataKeyInputV2) {
+    int32_t propHwKeyInputV2 = toInt(VehicleProperty::HW_KEY_INPUT_V2);
     std::vector<std::string> options = {"--genfakedata", "--keyinputv2", "1", "2", "3", "4", "5"};
 
+    subscribe(propHwKeyInputV2, /*areaId*/ 1, /*sampleRateHz*/ 0);
+
     DumpResult result = getHardware()->dump(options);
 
     ASSERT_FALSE(result.callerShouldDumpState);
@@ -2944,6 +3130,7 @@
 }
 
 TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataMotionInput) {
+    int32_t propHwMotionInput = toInt(VehicleProperty::HW_MOTION_INPUT);
     std::vector<std::string> options = {"--genfakedata",
                                         "--motioninput",
                                         "1",
@@ -2966,6 +3153,8 @@
                                         "65.5",
                                         "76.6"};
 
+    subscribe(propHwMotionInput, /*areaId*/ 1, /*sampleRateHz*/ 0);
+
     DumpResult result = getHardware()->dump(options);
 
     ASSERT_FALSE(result.callerShouldDumpState);
@@ -2973,7 +3162,7 @@
 
     auto events = getChangedProperties();
     ASSERT_EQ(1u, events.size());
-    EXPECT_EQ(toInt(VehicleProperty::HW_MOTION_INPUT), events[0].prop);
+    EXPECT_EQ(propHwMotionInput, events[0].prop);
     ASSERT_EQ(9u, events[0].value.int32Values.size());
     EXPECT_EQ(2, events[0].value.int32Values[0]);
     EXPECT_EQ(3, events[0].value.int32Values[1]);
@@ -3014,23 +3203,27 @@
     ASSERT_EQ(result.value().value.byteValues, std::vector<uint8_t>({0x04, 0x03, 0x02, 0x01}));
 }
 
-TEST_F(FakeVehicleHardwareTest, testUpdateSampleRate) {
+TEST_F(FakeVehicleHardwareTest, testSubscribeUnsubscribe_continuous) {
     int32_t propSpeed = toInt(VehicleProperty::PERF_VEHICLE_SPEED);
     int32_t propSteering = toInt(VehicleProperty::PERF_STEERING_ANGLE);
     int32_t areaId = 0;
-    getHardware()->updateSampleRate(propSpeed, areaId, 5);
+
+    auto status = getHardware()->subscribe(newSubscribeOptions(propSpeed, areaId, 5));
+    ASSERT_EQ(status, StatusCode::OK) << "failed to subscribe";
 
     ASSERT_TRUE(waitForChangedProperties(propSpeed, areaId, /*count=*/5, milliseconds(1500)))
             << "not enough events generated for speed";
 
-    getHardware()->updateSampleRate(propSteering, areaId, 10);
+    status = getHardware()->subscribe(newSubscribeOptions(propSteering, areaId, 10));
+    ASSERT_EQ(status, StatusCode::OK) << "failed to subscribe";
 
     ASSERT_TRUE(waitForChangedProperties(propSteering, areaId, /*count=*/10, milliseconds(1500)))
             << "not enough events generated for steering";
 
     int64_t timestamp = elapsedRealtimeNano();
     // Disable refreshing for propSpeed.
-    getHardware()->updateSampleRate(propSpeed, areaId, 0);
+    status = getHardware()->unsubscribe(propSpeed, areaId);
+    ASSERT_EQ(status, StatusCode::OK) << "failed to unsubscribe";
     clearChangedProperties();
 
     ASSERT_TRUE(waitForChangedProperties(propSteering, areaId, /*count=*/5, milliseconds(1500)))
@@ -3043,12 +3236,99 @@
     }
 }
 
+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;
+
+    auto status = getHardware()->subscribe(newSubscribeOptions(propHvac, areaId, 0));
+    ASSERT_EQ(status, StatusCode::OK) << "failed to subscribe";
+
+    status = setValue({
+            .prop = propHvac,
+            .areaId = areaId,
+            .value.floatValues = {20.0f},
+    });
+    ASSERT_EQ(status, StatusCode::OK) << "failed to set hvac value";
+
+    ASSERT_TRUE(waitForChangedProperties(propHvac, areaId, /*count=*/1, milliseconds(100)))
+            << "not enough on change events generated for hvac";
+    clearChangedProperties();
+
+    status = setValue({
+            .prop = propHvac,
+            .areaId = areaId,
+            .value.floatValues = {21.0f},
+    });
+    ASSERT_EQ(status, StatusCode::OK) << "failed to set hvac value";
+
+    ASSERT_TRUE(waitForChangedProperties(propHvac, areaId, /*count=*/1, milliseconds(100)))
+            << "not enough on change events generated for hvac";
+    clearChangedProperties();
+
+    status = getHardware()->unsubscribe(propHvac, areaId);
+    ASSERT_EQ(status, StatusCode::OK);
+
+    status = setValue({
+            .prop = propHvac,
+            .areaId = areaId,
+            .value.floatValues = {22.0f},
+    });
+    ASSERT_EQ(status, StatusCode::OK) << "failed to set hvac value";
+
+    ASSERT_FALSE(waitForChangedProperties(propHvac, areaId, /*count=*/1, milliseconds(100)))
+            << "must not receive on change events if the propId, areaId is unsubscribed";
+}
+
 TEST_F(FakeVehicleHardwareTest, testSetHvacTemperatureValueSuggestion) {
     float CELSIUS = static_cast<float>(toInt(VehicleUnit::CELSIUS));
     float FAHRENHEIT = static_cast<float>(toInt(VehicleUnit::FAHRENHEIT));
+    int32_t propHvacTempValueSuggest = toInt(VehicleProperty::HVAC_TEMPERATURE_VALUE_SUGGESTION);
+
+    subscribe(propHvacTempValueSuggest, HVAC_ALL, /*sampleRateHz*/ 0);
 
     VehiclePropValue floatArraySizeFour = {
-            .prop = toInt(VehicleProperty::HVAC_TEMPERATURE_VALUE_SUGGESTION),
+            .prop = propHvacTempValueSuggest,
             .areaId = HVAC_ALL,
             .value.floatValues = {0, CELSIUS, 0, 0},
     };
@@ -3056,14 +3336,14 @@
     EXPECT_EQ(status, StatusCode::OK);
 
     VehiclePropValue floatArraySizeZero = {
-            .prop = toInt(VehicleProperty::HVAC_TEMPERATURE_VALUE_SUGGESTION),
+            .prop = propHvacTempValueSuggest,
             .areaId = HVAC_ALL,
     };
     status = setValue(floatArraySizeZero);
     EXPECT_EQ(status, StatusCode::INVALID_ARG);
 
     VehiclePropValue floatArraySizeFive = {
-            .prop = toInt(VehicleProperty::HVAC_TEMPERATURE_VALUE_SUGGESTION),
+            .prop = propHvacTempValueSuggest,
             .areaId = HVAC_ALL,
             .value.floatValues = {0, CELSIUS, 0, 0, 0},
     };
@@ -3071,7 +3351,7 @@
     EXPECT_EQ(status, StatusCode::INVALID_ARG);
 
     VehiclePropValue invalidUnit = {
-            .prop = toInt(VehicleProperty::HVAC_TEMPERATURE_VALUE_SUGGESTION),
+            .prop = propHvacTempValueSuggest,
             .areaId = HVAC_ALL,
             .value.floatValues = {0, 0, 0, 0},
     };
@@ -3102,9 +3382,7 @@
                     .valuesToSet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {minTempInCelsius, CELSIUS, 0, 0},
                                     },
@@ -3112,9 +3390,7 @@
                     .expectedValuesToGet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {minTempInCelsius, CELSIUS,
                                                                   minTempInCelsius,
@@ -3127,9 +3403,7 @@
                     .valuesToSet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {minTempInFahrenheit, FAHRENHEIT,
                                                                   0, 0},
@@ -3138,9 +3412,7 @@
                     .expectedValuesToGet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {minTempInFahrenheit, FAHRENHEIT,
                                                                   minTempInCelsius,
@@ -3153,9 +3425,7 @@
                     .valuesToSet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {maxTempInCelsius, CELSIUS, 0, 0},
                                     },
@@ -3163,9 +3433,7 @@
                     .expectedValuesToGet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {maxTempInCelsius, CELSIUS,
                                                                   maxTempInCelsius,
@@ -3178,9 +3446,7 @@
                     .valuesToSet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {maxTempInFahrenheit, FAHRENHEIT,
                                                                   0, 0},
@@ -3189,9 +3455,7 @@
                     .expectedValuesToGet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {maxTempInFahrenheit, FAHRENHEIT,
                                                                   maxTempInCelsius,
@@ -3204,9 +3468,7 @@
                     .valuesToSet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {minTempInCelsius - 1, CELSIUS, 0,
                                                                   0},
@@ -3215,9 +3477,7 @@
                     .expectedValuesToGet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {minTempInCelsius - 1, CELSIUS,
                                                                   minTempInCelsius,
@@ -3230,9 +3490,7 @@
                     .valuesToSet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {minTempInFahrenheit - 1,
                                                                   FAHRENHEIT, 0, 0},
@@ -3241,9 +3499,7 @@
                     .expectedValuesToGet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {minTempInFahrenheit - 1,
                                                                   FAHRENHEIT, minTempInCelsius,
@@ -3256,9 +3512,7 @@
                     .valuesToSet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {maxTempInCelsius + 1, CELSIUS, 0,
                                                                   0},
@@ -3267,9 +3521,7 @@
                     .expectedValuesToGet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {maxTempInCelsius + 1, CELSIUS,
                                                                   maxTempInCelsius,
@@ -3282,9 +3534,7 @@
                     .valuesToSet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {maxTempInFahrenheit + 1,
                                                                   FAHRENHEIT, 0, 0},
@@ -3293,9 +3543,7 @@
                     .expectedValuesToGet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {maxTempInFahrenheit + 1,
                                                                   FAHRENHEIT, maxTempInCelsius,
@@ -3308,9 +3556,7 @@
                     .valuesToSet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {minTempInCelsius +
                                                                           incrementInCelsius * 2.5f,
@@ -3320,9 +3566,7 @@
                     .expectedValuesToGet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues =
                                                     {minTempInCelsius + incrementInCelsius * 2.5f,
@@ -3338,9 +3582,7 @@
                     .valuesToSet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues = {minTempInFahrenheit +
                                                                           incrementInFahrenheit *
@@ -3351,9 +3593,7 @@
                     .expectedValuesToGet =
                             {
                                     VehiclePropValue{
-                                            .prop = toInt(
-                                                    VehicleProperty::
-                                                            HVAC_TEMPERATURE_VALUE_SUGGESTION),
+                                            .prop = propHvacTempValueSuggest,
                                             .areaId = HVAC_ALL,
                                             .value.floatValues =
                                                     {minTempInFahrenheit +
diff --git a/automotive/vehicle/aidl/impl/grpc/utils/proto_message_converter/src/ProtoMessageConverter.cpp b/automotive/vehicle/aidl/impl/grpc/utils/proto_message_converter/src/ProtoMessageConverter.cpp
index 6b789bb..491aa10 100644
--- a/automotive/vehicle/aidl/impl/grpc/utils/proto_message_converter/src/ProtoMessageConverter.cpp
+++ b/automotive/vehicle/aidl/impl/grpc/utils/proto_message_converter/src/ProtoMessageConverter.cpp
@@ -78,6 +78,7 @@
                 protoACfg->add_supported_enum_values(supportedEnumValue);
             }
         }
+        protoACfg->set_support_variable_update_rate(areaConfig.supportVariableUpdateRate);
     }
 }
 
@@ -100,9 +101,14 @@
                 .maxInt64Value = protoAcfg.max_int64_value(),
                 .minFloatValue = protoAcfg.min_float_value(),
                 .maxFloatValue = protoAcfg.max_float_value(),
+                .supportVariableUpdateRate = protoAcfg.support_variable_update_rate(),
         };
-        COPY_PROTOBUF_VEC_TO_VHAL_TYPE(protoAcfg, supported_enum_values, (&vehicleAreaConfig),
-                                       supportedEnumValues.value());
+        if (protoAcfg.supported_enum_values().size() != 0) {
+            vehicleAreaConfig.supportedEnumValues = std::vector<int64_t>();
+            COPY_PROTOBUF_VEC_TO_VHAL_TYPE(protoAcfg, supported_enum_values, (&vehicleAreaConfig),
+                                           supportedEnumValues.value());
+        }
+
         return vehicleAreaConfig;
     };
     CAST_COPY_PROTOBUF_VEC_TO_VHAL_TYPE(in, area_configs, out, areaConfigs, cast_to_acfg);
diff --git a/automotive/vehicle/aidl/impl/hardware/include/IVehicleHardware.h b/automotive/vehicle/aidl/impl/hardware/include/IVehicleHardware.h
index 3fecbd9..f49d91b 100644
--- a/automotive/vehicle/aidl/impl/hardware/include/IVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/hardware/include/IVehicleHardware.h
@@ -82,35 +82,6 @@
             const std::vector<aidl::android::hardware::automotive::vehicle::GetValueRequest>&
                     requests) const = 0;
 
-    // Update the sampling rate for the specified property and the specified areaId (0 for global
-    // property) if server supports it. The property must be a continuous property.
-    // {@code sampleRate} means that for this specific property, the server must generate at least
-    // this many OnPropertyChange events per seconds.
-    // A sampleRate of 0 means the property is no longer subscribed and server does not need to
-    // generate any onPropertyEvent for this property.
-    // This would be called if sample rate is updated for a subscriber, a new subscriber is added
-    // or an existing subscriber is removed. For example:
-    // 1. We have no subscriber for speed.
-    // 2. A new subscriber is subscribing speed for 10 times/s, updsateSampleRate would be called
-    //    with sampleRate as 10. The impl is now polling vehicle speed from bus 10 times/s.
-    // 3. A new subscriber is subscribing speed for 5 times/s, because it is less than 10
-    //    times/sec, updateSampleRate would not be called.
-    // 4. The initial subscriber is removed, updateSampleRate would be called with sampleRate as
-    //    5, because now it only needs to report event 5times/sec. The impl can now poll vehicle
-    //    speed 5 times/s. If the impl is still polling at 10 times/s, that is okay as long as
-    //    the polling rate is larger than 5times/s. DefaultVehicleHal would ignore the additional
-    //    events.
-    // 5. The second subscriber is removed, updateSampleRate would be called with sampleRate as 0.
-    //    The impl can optionally disable the polling for vehicle speed.
-    //
-    // If the impl is always polling at {@code maxSampleRate} as specified in config, then this
-    // function can be a no-op.
-    virtual aidl::android::hardware::automotive::vehicle::StatusCode updateSampleRate(
-            [[maybe_unused]] int32_t propId, [[maybe_unused]] int32_t areaId,
-            [[maybe_unused]] float sampleRate) {
-        return aidl::android::hardware::automotive::vehicle::StatusCode::OK;
-    }
-
     // Dump debug information in the server.
     virtual DumpResult dump(const std::vector<std::string>& options) = 0;
 
@@ -145,6 +116,111 @@
         // By default batching is disabled.
         return std::chrono::nanoseconds(0);
     }
+
+    // A [propId, areaId] is newly subscribed or the subscribe options are changed.
+    //
+    // The subscribe options contain sample rate in Hz or enable/disable variable update rate.
+    //
+    // For continuous properties:
+    //
+    // The sample rate is never 0 and indicates the desired polling rate for this property. The
+    // sample rate is guaranteed to be within supported {@code minSampleRate} and
+    // {@code maxSampleRate} as specified in {@code VehiclePropConfig}.
+    //
+    // If the specified sample rate is not supported, e.g. vehicle bus only supports 5hz and 10hz
+    // polling rate but the sample rate is 8hz, impl must choose the higher polling rate (10hz).
+    //
+    // Whether variable update rate is enabled is specified by {@code enableVariableUpdateRate} in
+    // {@code SubscribeOptions}. If variable update rate is not supported for the
+    // [propId, areaId], impl must ignore this option and always treat it as disabled.
+    //
+    // If variable update rate is disabled/not supported, impl must report all the property events
+    // for this [propId, areaId] through {@code propertyChangeCallback} according to the sample
+    // rate. E.g. a sample rate of 10hz must generate at least 10 property change events per second.
+    //
+    // If variable update rate is enabled AND supported, impl must only report property events
+    // when the [propId, areaId]'s value or status changes (a.k.a same as on-change property).
+    // The sample rate still guides the polling rate, but duplicate property events must be dropped
+    // and not reported via {@code propertyChangeCallback}.
+    //
+    // Async property set error events are not affected by variable update rate and must always
+    // be reported.
+    //
+    // If the impl is always polling at {@code maxSampleRate} for all continuous [propId, areaId]s,
+    // and do not support variable update rate for any [propId, areaId], then this function can be a
+    // no-op.
+    //
+    // For on-change properties:
+    //
+    // The sample rate is always 0 and must be ignored. If the impl is always subscribing to all
+    // on-change properties, then this function can be no-op.
+    //
+    // For all properties:
+    //
+    // It is recommended to only deliver the subscribed property events to DefaultVehicleHal to
+    // improve performance. However, even if unsubscribed property events are delivered, they
+    // will be filtered out by DefaultVehicleHal.
+    //
+    // A subscription from VHAL client might not necessarily trigger this function.
+    // DefaultVehicleHal will aggregate all the subscriptions from all the clients and notify
+    // IVehicleHardware if new subscriptions are required or subscribe options are updated.
+    //
+    // For example:
+    // 1. VHAL initially have no subscriber for speed.
+    // 2. A new subscriber is subscribing speed for 10 times/s, 'subscribe' is called
+    //    with sampleRate as 10. The impl is now polling vehicle speed from bus 10 times/s.
+    // 3. A new subscriber is subscribing speed for 5 times/s, because it is less than 10
+    //    times/sec, 'subscribe' is not called.
+    // 4. The initial subscriber is removed, 'subscribe' is called with sampleRate as
+    //    5, because now it only needs to report event 5times/sec. The impl can now poll vehicle
+    //    speed 5 times/s. If the impl is still polling at 10 times/s, that is okay as long as
+    //    the polling rate is larger than 5times/s. DefaultVehicleHal would ignore the additional
+    //    events.
+    // 5. The second subscriber is removed, 'unsubscribe' is called.
+    //    The impl can optionally disable the polling for vehicle speed.
+    //
+    virtual aidl::android::hardware::automotive::vehicle::StatusCode subscribe(
+            [[maybe_unused]] aidl::android::hardware::automotive::vehicle::SubscribeOptions
+                    options) {
+        return aidl::android::hardware::automotive::vehicle::StatusCode::OK;
+    }
+
+    // A [propId, areaId] is unsubscribed. This applies for both continuous or on-change property.
+    virtual aidl::android::hardware::automotive::vehicle::StatusCode unsubscribe(
+            [[maybe_unused]] int32_t propId, [[maybe_unused]] int32_t areaId) {
+        return aidl::android::hardware::automotive::vehicle::StatusCode::OK;
+    }
+
+    // This function is deprecated, subscribe/unsubscribe should be used instead.
+    //
+    // Update the sampling rate for the specified property and the specified areaId (0 for global
+    // property) if server supports it. The property must be a continuous property.
+    // {@code sampleRate} means that for this specific property, the server must generate at least
+    // this many OnPropertyChange events per seconds.
+    // A sampleRate of 0 means the property is no longer subscribed and server does not need to
+    // generate any onPropertyEvent for this property.
+    // This would be called if sample rate is updated for a subscriber, a new subscriber is added
+    // or an existing subscriber is removed. For example:
+    // 1. We have no subscriber for speed.
+    // 2. A new subscriber is subscribing speed for 10 times/s, updateSampleRate would be called
+    //    with sampleRate as 10. The impl is now polling vehicle speed from bus 10 times/s.
+    // 3. A new subscriber is subscribing speed for 5 times/s, because it is less than 10
+    //    times/sec, updateSampleRate would not be called.
+    // 4. The initial subscriber is removed, updateSampleRate would be called with sampleRate as
+    //    5, because now it only needs to report event 5times/sec. The impl can now poll vehicle
+    //    speed 5 times/s. If the impl is still polling at 10 times/s, that is okay as long as
+    //    the polling rate is larger than 5times/s. DefaultVehicleHal would ignore the additional
+    //    events.
+    // 5. The second subscriber is removed, updateSampleRate would be called with sampleRate as 0.
+    //    The impl can optionally disable the polling for vehicle speed.
+    //
+    // If the impl is always polling at {@code maxSampleRate} as specified in config, then this
+    // function can be a no-op.
+    virtual aidl::android::hardware::automotive::vehicle::StatusCode updateSampleRate(
+            [[maybe_unused]] int32_t propId, [[maybe_unused]] int32_t areaId,
+            [[maybe_unused]] float sampleRate) {
+        return aidl::android::hardware::automotive::vehicle::StatusCode::OK;
+    }
 };
 
 }  // namespace vehicle
diff --git a/automotive/vehicle/aidl/impl/proto/android/hardware/automotive/vehicle/VehicleAreaConfig.proto b/automotive/vehicle/aidl/impl/proto/android/hardware/automotive/vehicle/VehicleAreaConfig.proto
index 04b7dd4..8093658 100644
--- a/automotive/vehicle/aidl/impl/proto/android/hardware/automotive/vehicle/VehicleAreaConfig.proto
+++ b/automotive/vehicle/aidl/impl/proto/android/hardware/automotive/vehicle/VehicleAreaConfig.proto
@@ -44,4 +44,5 @@
      */
     repeated int64 supported_enum_values = 8;
     int32 access = 9;
+    bool support_variable_update_rate = 10;
 };
diff --git a/automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h b/automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h
index 3d25cd3..b74dff5 100644
--- a/automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h
+++ b/automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h
@@ -92,7 +92,7 @@
     // used as the key.
     void registerProperty(
             const aidl::android::hardware::automotive::vehicle::VehiclePropConfig& config,
-            TokenFunction tokenFunc = nullptr);
+            TokenFunction tokenFunc = nullptr) EXCLUDES(mLock);
 
     // Stores provided value. Returns error if config wasn't registered. If 'updateStatus' is
     // true, the 'status' in 'propValue' would be stored. Otherwise, if this is a new value,
@@ -102,44 +102,47 @@
     // 'EventMode' controls whether the 'OnValueChangeCallback' will be called for this operation.
     VhalResult<void> writeValue(VehiclePropValuePool::RecyclableType propValue,
                                 bool updateStatus = false,
-                                EventMode mode = EventMode::ON_VALUE_CHANGE);
+                                EventMode mode = EventMode::ON_VALUE_CHANGE) 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(
-            const aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue);
+            const aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue)
+            EXCLUDES(mLock);
 
     // Remove all the values for the property.
-    void removeValuesForProperty(int32_t propId);
+    void removeValuesForProperty(int32_t propId) EXCLUDES(mLock);
 
     // Read all the stored values.
-    std::vector<VehiclePropValuePool::RecyclableType> readAllValues() const;
+    std::vector<VehiclePropValuePool::RecyclableType> readAllValues() const EXCLUDES(mLock);
 
     // Read all the values for the property.
-    ValuesResultType readValuesForProperty(int32_t propId) const;
+    ValuesResultType readValuesForProperty(int32_t propId) const EXCLUDES(mLock);
 
     // Read the value for the requested property. Returns {@code StatusCode::NOT_AVAILABLE} if the
     // value has not been set yet. Returns {@code StatusCode::INVALID_ARG} if the property is
     // not configured.
     ValueResultType readValue(
-            const aidl::android::hardware::automotive::vehicle::VehiclePropValue& request) const;
+            const aidl::android::hardware::automotive::vehicle::VehiclePropValue& request) const
+            EXCLUDES(mLock);
 
     // Read the value for the requested property. Returns {@code StatusCode::NOT_AVAILABLE} if the
     // value has not been set yet. Returns {@code StatusCode::INVALID_ARG} if the property is
     // not configured.
-    ValueResultType readValue(int32_t prop, int32_t area = 0, int64_t token = 0) const;
+    ValueResultType readValue(int32_t prop, int32_t area = 0, int64_t token = 0) const
+            EXCLUDES(mLock);
 
     // Get all property configs.
     std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropConfig> getAllConfigs()
-            const;
+            const EXCLUDES(mLock);
 
     // Get the property config for the requested property.
     android::base::Result<const aidl::android::hardware::automotive::vehicle::VehiclePropConfig*,
                           VhalError>
-    getConfig(int32_t propId) const;
+    getConfig(int32_t propId) const EXCLUDES(mLock);
 
     // Set a callback that would be called when a property value has been updated.
-    void setOnValueChangeCallback(const OnValueChangeCallback& callback);
+    void setOnValueChangeCallback(const OnValueChangeCallback& callback) EXCLUDES(mLock);
 
     inline std::shared_ptr<VehiclePropValuePool> getValuePool() { return mValuePool; }
 
diff --git a/automotive/vehicle/aidl/impl/utils/common/include/VehicleUtils.h b/automotive/vehicle/aidl/impl/utils/common/include/VehicleUtils.h
index c94bad6..546421e 100644
--- a/automotive/vehicle/aidl/impl/utils/common/include/VehicleUtils.h
+++ b/automotive/vehicle/aidl/impl/utils/common/include/VehicleUtils.h
@@ -329,6 +329,11 @@
     }
 };
 
+inline std::string propIdToString(int32_t propId) {
+    return toString(
+            static_cast<aidl::android::hardware::automotive::vehicle::VehicleProperty>(propId));
+}
+
 }  // namespace vehicle
 }  // namespace automotive
 }  // namespace hardware
diff --git a/automotive/vehicle/aidl/impl/utils/common/src/VehiclePropertyStore.cpp b/automotive/vehicle/aidl/impl/utils/common/src/VehiclePropertyStore.cpp
index 646dc0e..3fd2aa8 100644
--- a/automotive/vehicle/aidl/impl/utils/common/src/VehiclePropertyStore.cpp
+++ b/automotive/vehicle/aidl/impl/utils/common/src/VehiclePropertyStore.cpp
@@ -108,51 +108,62 @@
 VhalResult<void> VehiclePropertyStore::writeValue(VehiclePropValuePool::RecyclableType propValue,
                                                   bool updateStatus,
                                                   VehiclePropertyStore::EventMode eventMode) {
-    std::scoped_lock<std::mutex> g(mLock);
-
-    int32_t propId = propValue->prop;
-
-    VehiclePropertyStore::Record* record = getRecordLocked(propId);
-    if (record == nullptr) {
-        return StatusError(StatusCode::INVALID_ARG) << "property: " << propId << " not registered";
-    }
-
-    if (!isGlobalProp(propId) && getAreaConfig(*propValue, record->propConfig) == nullptr) {
-        return StatusError(StatusCode::INVALID_ARG)
-               << "no config for property: " << propId << " area: " << propValue->areaId;
-    }
-
-    VehiclePropertyStore::RecordId recId = getRecordIdLocked(*propValue, *record);
     bool valueUpdated = true;
-    if (auto it = record->values.find(recId); it != record->values.end()) {
-        const VehiclePropValue* valueToUpdate = it->second.get();
-        int64_t oldTimestamp = valueToUpdate->timestamp;
-        VehiclePropertyStatus oldStatus = valueToUpdate->status;
-        // propValue is outdated and drops it.
-        if (oldTimestamp > propValue->timestamp) {
+    VehiclePropValue updatedValue;
+    OnValueChangeCallback onValueChangeCallback = nullptr;
+    {
+        std::scoped_lock<std::mutex> g(mLock);
+
+        int32_t propId = propValue->prop;
+
+        VehiclePropertyStore::Record* record = getRecordLocked(propId);
+        if (record == nullptr) {
             return StatusError(StatusCode::INVALID_ARG)
-                   << "outdated timestamp: " << propValue->timestamp;
-        }
-        if (!updateStatus) {
-            propValue->status = oldStatus;
+                   << "property: " << propId << " not registered";
         }
 
-        valueUpdated = (valueToUpdate->value != propValue->value ||
-                        valueToUpdate->status != propValue->status ||
-                        valueToUpdate->prop != propValue->prop ||
-                        valueToUpdate->areaId != propValue->areaId);
-    } else if (!updateStatus) {
-        propValue->status = VehiclePropertyStatus::AVAILABLE;
+        if (!isGlobalProp(propId) && getAreaConfig(*propValue, record->propConfig) == nullptr) {
+            return StatusError(StatusCode::INVALID_ARG)
+                   << "no config for property: " << propId << " area ID: " << propValue->areaId;
+        }
+
+        VehiclePropertyStore::RecordId recId = getRecordIdLocked(*propValue, *record);
+        if (auto it = record->values.find(recId); it != record->values.end()) {
+            const VehiclePropValue* valueToUpdate = it->second.get();
+            int64_t oldTimestampNanos = valueToUpdate->timestamp;
+            VehiclePropertyStatus oldStatus = valueToUpdate->status;
+            // propValue is outdated and drops it.
+            if (oldTimestampNanos > propValue->timestamp) {
+                return StatusError(StatusCode::INVALID_ARG)
+                       << "outdated timestampNanos: " << propValue->timestamp;
+            }
+            if (!updateStatus) {
+                propValue->status = oldStatus;
+            }
+
+            valueUpdated = (valueToUpdate->value != propValue->value ||
+                            valueToUpdate->status != propValue->status ||
+                            valueToUpdate->prop != propValue->prop ||
+                            valueToUpdate->areaId != propValue->areaId);
+        } else if (!updateStatus) {
+            propValue->status = VehiclePropertyStatus::AVAILABLE;
+        }
+
+        record->values[recId] = std::move(propValue);
+
+        if (eventMode == EventMode::NEVER) {
+            return {};
+        }
+        updatedValue = *(record->values[recId]);
+        if (mOnValueChangeCallback == nullptr) {
+            return {};
+        }
+        onValueChangeCallback = mOnValueChangeCallback;
     }
 
-    record->values[recId] = std::move(propValue);
-
-    if (eventMode == EventMode::NEVER) {
-        return {};
-    }
-
-    if ((eventMode == EventMode::ALWAYS || valueUpdated) && mOnValueChangeCallback != nullptr) {
-        mOnValueChangeCallback(*(record->values[recId]));
+    // Invoke the callback outside the lock to prevent dead-lock.
+    if (eventMode == EventMode::ALWAYS || valueUpdated) {
+        onValueChangeCallback(updatedValue);
     }
     return {};
 }
diff --git a/automotive/vehicle/aidl/impl/utils/common/test/VehiclePropertyStoreTest.cpp b/automotive/vehicle/aidl/impl/utils/common/test/VehiclePropertyStoreTest.cpp
index fea5034..625652e 100644
--- a/automotive/vehicle/aidl/impl/utils/common/test/VehiclePropertyStoreTest.cpp
+++ b/automotive/vehicle/aidl/impl/utils/common/test/VehiclePropertyStoreTest.cpp
@@ -509,6 +509,24 @@
     ASSERT_EQ(updatedValue.prop, INVALID_PROP_ID);
 }
 
+TEST_F(VehiclePropertyStoreTest, testPropertyChangeCallbackUseVehiclePropertyStore_noDeadLock) {
+    VehiclePropValue fuelCapacity = {
+            .prop = toInt(VehicleProperty::INFO_FUEL_CAPACITY),
+            .value = {.floatValues = {1.0}},
+    };
+
+    std::vector<VehiclePropConfig> configs;
+
+    mStore->setOnValueChangeCallback(
+            [this, &configs]([[maybe_unused]] const VehiclePropValue& value) {
+                configs = mStore->getAllConfigs();
+            });
+
+    ASSERT_RESULT_OK(mStore->writeValue(mValuePool->obtain(fuelCapacity), /*updateStatus=*/true,
+                                        VehiclePropertyStore::EventMode::ALWAYS));
+    ASSERT_EQ(configs.size(), static_cast<size_t>(2));
+}
+
 }  // namespace vehicle
 }  // namespace automotive
 }  // namespace hardware
diff --git a/automotive/vehicle/aidl/impl/vhal/include/SubscriptionManager.h b/automotive/vehicle/aidl/impl/vhal/include/SubscriptionManager.h
index b91895e..5053c96 100644
--- a/automotive/vehicle/aidl/impl/vhal/include/SubscriptionManager.h
+++ b/automotive/vehicle/aidl/impl/vhal/include/SubscriptionManager.h
@@ -36,20 +36,29 @@
 namespace automotive {
 namespace vehicle {
 
+// A structure to represent subscription config for one subscription client.
+struct SubConfig {
+    float sampleRateHz;
+    bool enableVur;
+};
+
 // A class to represent all the subscription configs for a continuous [propId, areaId].
 class ContSubConfigs final {
   public:
     using ClientIdType = const AIBinder*;
 
-    void addClient(const ClientIdType& clientId, float sampleRateHz);
+    void addClient(const ClientIdType& clientId, float sampleRateHz, bool enableVur);
     void removeClient(const ClientIdType& clientId);
     float getMaxSampleRateHz() const;
+    bool isVurEnabled() const;
+    bool isVurEnabledForClient(const ClientIdType& clientId);
 
   private:
     float mMaxSampleRateHz = 0.;
-    std::unordered_map<ClientIdType, float> mSampleRateHzByClient;
+    bool mEnableVur;
+    std::unordered_map<ClientIdType, SubConfig> mConfigByClient;
 
-    void refreshMaxSampleRateHz();
+    void refreshCombinedConfig();
 };
 
 // A thread-safe subscription manager that manages all VHAL subscriptions.
@@ -58,6 +67,7 @@
     using ClientIdType = const AIBinder*;
     using CallbackType =
             std::shared_ptr<aidl::android::hardware::automotive::vehicle::IVehicleCallback>;
+    using VehiclePropValue = aidl::android::hardware::automotive::vehicle::VehiclePropValue;
 
     explicit SubscriptionManager(IVehicleHardware* vehicleHardware);
     ~SubscriptionManager();
@@ -92,11 +102,8 @@
     // For a list of updated properties, returns a map that maps clients subscribing to
     // the updated properties to a list of updated values. This would only return on-change property
     // clients that should be informed for the given updated values.
-    std::unordered_map<CallbackType,
-                       std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>>
-    getSubscribedClients(
-            std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>&&
-                    updatedValues);
+    std::unordered_map<CallbackType, std::vector<VehiclePropValue>> getSubscribedClients(
+            std::vector<VehiclePropValue>&& updatedValues);
 
     // For a list of set property error events, returns a map that maps clients subscribing to the
     // properties to a list of errors for each client.
@@ -116,28 +123,60 @@
 
     IVehicleHardware* mVehicleHardware;
 
+    struct VehiclePropValueHashPropIdAreaId {
+        inline size_t operator()(const VehiclePropValue& vehiclePropValue) const {
+            size_t res = 0;
+            hashCombine(res, vehiclePropValue.prop);
+            hashCombine(res, vehiclePropValue.areaId);
+            return res;
+        }
+    };
+
+    struct VehiclePropValueEqualPropIdAreaId {
+        inline bool operator()(const VehiclePropValue& left, const VehiclePropValue& right) const {
+            return left.prop == right.prop && left.areaId == right.areaId;
+        }
+    };
+
     mutable std::mutex mLock;
     std::unordered_map<PropIdAreaId, std::unordered_map<ClientIdType, CallbackType>,
                        PropIdAreaIdHash>
-            mClientsByPropIdArea GUARDED_BY(mLock);
+            mClientsByPropIdAreaId GUARDED_BY(mLock);
     std::unordered_map<ClientIdType, std::unordered_set<PropIdAreaId, PropIdAreaIdHash>>
             mSubscribedPropsByClient GUARDED_BY(mLock);
     std::unordered_map<PropIdAreaId, ContSubConfigs, PropIdAreaIdHash> mContSubConfigsByPropIdArea
             GUARDED_BY(mLock);
+    std::unordered_map<CallbackType,
+                       std::unordered_set<VehiclePropValue, VehiclePropValueHashPropIdAreaId,
+                                          VehiclePropValueEqualPropIdAreaId>>
+            mContSubValuesByCallback GUARDED_BY(mLock);
 
     VhalResult<void> addContinuousSubscriberLocked(const ClientIdType& clientId,
                                                    const PropIdAreaId& propIdAreaId,
-                                                   float sampleRateHz) REQUIRES(mLock);
+                                                   float sampleRateHz, bool enableVur)
+            REQUIRES(mLock);
+    VhalResult<void> addOnChangeSubscriberLocked(const PropIdAreaId& propIdAreaId) REQUIRES(mLock);
+    // Removes the subscription client for the continuous [propId, areaId].
     VhalResult<void> removeContinuousSubscriberLocked(const ClientIdType& clientId,
                                                       const PropIdAreaId& propIdAreaId)
             REQUIRES(mLock);
+    // Removes one subscription client for the on-change [propId, areaId].
+    VhalResult<void> removeOnChangeSubscriberLocked(const PropIdAreaId& propIdAreaId)
+            REQUIRES(mLock);
 
-    VhalResult<void> updateContSubConfigs(const PropIdAreaId& PropIdAreaId,
-                                          const ContSubConfigs& newConfig) REQUIRES(mLock);
+    VhalResult<void> updateContSubConfigsLocked(const PropIdAreaId& PropIdAreaId,
+                                                const ContSubConfigs& newConfig) REQUIRES(mLock);
+
+    VhalResult<void> unsubscribePropIdAreaIdLocked(SubscriptionManager::ClientIdType clientId,
+                                                   const PropIdAreaId& propIdAreaId)
+            REQUIRES(mLock);
 
     // Checks whether the manager is empty. For testing purpose.
     bool isEmpty();
 
+    bool isValueUpdatedLocked(const CallbackType& callback, const VehiclePropValue& value)
+            REQUIRES(mLock);
+
     // Get the interval in nanoseconds accroding to sample rate.
     static android::base::Result<int64_t> getIntervalNanos(float sampleRateHz);
 };
diff --git a/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp b/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
index 111a6ec..d85cc09 100644
--- a/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
@@ -695,7 +695,39 @@
         if (config.changeMode == VehiclePropertyChangeMode::CONTINUOUS) {
             optionCopy.sampleRate = getDefaultSampleRateHz(
                     optionCopy.sampleRate, config.minSampleRate, config.maxSampleRate);
-            continuousSubscriptions.push_back(std::move(optionCopy));
+            if (!optionCopy.enableVariableUpdateRate) {
+                continuousSubscriptions.push_back(std::move(optionCopy));
+            } else {
+                // If clients enables to VUR, we need to check whether VUR is supported for the
+                // specific [propId, areaId] and overwrite the option to disable if not supported.
+                std::vector<int32_t> areasVurEnabled;
+                std::vector<int32_t> areasVurDisabled;
+                for (int32_t areaId : optionCopy.areaIds) {
+                    const VehicleAreaConfig* areaConfig = getAreaConfig(propId, areaId, config);
+                    if (areaConfig == nullptr) {
+                        areasVurDisabled.push_back(areaId);
+                        continue;
+                    }
+                    if (!areaConfig->supportVariableUpdateRate) {
+                        areasVurDisabled.push_back(areaId);
+                        continue;
+                    }
+                    areasVurEnabled.push_back(areaId);
+                }
+                if (!areasVurEnabled.empty()) {
+                    SubscribeOptions optionVurEnabled = optionCopy;
+                    optionVurEnabled.areaIds = areasVurEnabled;
+                    optionVurEnabled.enableVariableUpdateRate = true;
+                    continuousSubscriptions.push_back(std::move(optionVurEnabled));
+                }
+
+                if (!areasVurDisabled.empty()) {
+                    // We use optionCopy for areas with VUR disabled.
+                    optionCopy.areaIds = areasVurDisabled;
+                    optionCopy.enableVariableUpdateRate = false;
+                    continuousSubscriptions.push_back(std::move(optionCopy));
+                }
+            }
         } else {
             onChangeSubscriptions.push_back(std::move(optionCopy));
         }
diff --git a/automotive/vehicle/aidl/impl/vhal/src/SubscriptionManager.cpp b/automotive/vehicle/aidl/impl/vhal/src/SubscriptionManager.cpp
index b3c2693..29d81a7 100644
--- a/automotive/vehicle/aidl/impl/vhal/src/SubscriptionManager.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/src/SubscriptionManager.cpp
@@ -16,6 +16,7 @@
 
 #include "SubscriptionManager.h"
 
+#include <VehicleUtils.h>
 #include <android-base/stringprintf.h>
 #include <utils/Log.h>
 #include <utils/SystemClock.h>
@@ -29,10 +30,6 @@
 
 namespace {
 
-constexpr float ONE_SECOND_IN_NANO = 1'000'000'000.;
-
-}  // namespace
-
 using ::aidl::android::hardware::automotive::vehicle::IVehicleCallback;
 using ::aidl::android::hardware::automotive::vehicle::StatusCode;
 using ::aidl::android::hardware::automotive::vehicle::SubscribeOptions;
@@ -43,13 +40,28 @@
 using ::android::base::StringPrintf;
 using ::ndk::ScopedAStatus;
 
+constexpr float ONE_SECOND_IN_NANOS = 1'000'000'000.;
+
+SubscribeOptions newSubscribeOptions(int32_t propId, int32_t areaId, float sampleRateHz,
+                                     bool enableVur) {
+    SubscribeOptions subscribedOptions;
+    subscribedOptions.propId = propId;
+    subscribedOptions.areaIds = {areaId};
+    subscribedOptions.sampleRate = sampleRateHz;
+    subscribedOptions.enableVariableUpdateRate = enableVur;
+
+    return subscribedOptions;
+}
+
+}  // namespace
+
 SubscriptionManager::SubscriptionManager(IVehicleHardware* vehicleHardware)
     : mVehicleHardware(vehicleHardware) {}
 
 SubscriptionManager::~SubscriptionManager() {
     std::scoped_lock<std::mutex> lockGuard(mLock);
 
-    mClientsByPropIdArea.clear();
+    mClientsByPropIdAreaId.clear();
     mSubscribedPropsByClient.clear();
 }
 
@@ -62,45 +74,83 @@
     if (sampleRateHz <= 0) {
         return Error() << "invalid sample rate, must be a positive number";
     }
-    if (sampleRateHz <= (ONE_SECOND_IN_NANO / static_cast<float>(INT64_MAX))) {
+    if (sampleRateHz <= (ONE_SECOND_IN_NANOS / static_cast<float>(INT64_MAX))) {
         return Error() << "invalid sample rate: " << sampleRateHz << ", too small";
     }
-    intervalNanos = static_cast<int64_t>(ONE_SECOND_IN_NANO / sampleRateHz);
+    intervalNanos = static_cast<int64_t>(ONE_SECOND_IN_NANOS / sampleRateHz);
     return intervalNanos;
 }
 
-void ContSubConfigs::refreshMaxSampleRateHz() {
+void ContSubConfigs::refreshCombinedConfig() {
     float maxSampleRateHz = 0.;
+    bool enableVur = true;
     // This is not called frequently so a brute-focre is okay. More efficient way exists but this
     // is simpler.
-    for (const auto& [_, sampleRateHz] : mSampleRateHzByClient) {
-        if (sampleRateHz > maxSampleRateHz) {
-            maxSampleRateHz = sampleRateHz;
+    for (const auto& [_, subConfig] : mConfigByClient) {
+        if (subConfig.sampleRateHz > maxSampleRateHz) {
+            maxSampleRateHz = subConfig.sampleRateHz;
+        }
+        if (!subConfig.enableVur) {
+            // If one client does not enable variable update rate, we cannot enable variable update
+            // rate in IVehicleHardware.
+            enableVur = false;
         }
     }
     mMaxSampleRateHz = maxSampleRateHz;
+    mEnableVur = enableVur;
 }
 
-void ContSubConfigs::addClient(const ClientIdType& clientId, float sampleRateHz) {
-    mSampleRateHzByClient[clientId] = sampleRateHz;
-    refreshMaxSampleRateHz();
+void ContSubConfigs::addClient(const ClientIdType& clientId, float sampleRateHz, bool enableVur) {
+    mConfigByClient[clientId] = {
+            .sampleRateHz = sampleRateHz,
+            .enableVur = enableVur,
+    };
+    refreshCombinedConfig();
 }
 
 void ContSubConfigs::removeClient(const ClientIdType& clientId) {
-    mSampleRateHzByClient.erase(clientId);
-    refreshMaxSampleRateHz();
+    mConfigByClient.erase(clientId);
+    refreshCombinedConfig();
 }
 
 float ContSubConfigs::getMaxSampleRateHz() const {
     return mMaxSampleRateHz;
 }
 
+bool ContSubConfigs::isVurEnabled() const {
+    return mEnableVur;
+}
+
+bool ContSubConfigs::isVurEnabledForClient(const ClientIdType& clientId) {
+    return mConfigByClient[clientId].enableVur;
+}
+
+VhalResult<void> SubscriptionManager::addOnChangeSubscriberLocked(
+        const PropIdAreaId& propIdAreaId) {
+    if (mClientsByPropIdAreaId.find(propIdAreaId) != mClientsByPropIdAreaId.end()) {
+        // This propId, areaId is already subscribed, ignore the request.
+        return {};
+    }
+
+    int32_t propId = propIdAreaId.propId;
+    int32_t areaId = propIdAreaId.areaId;
+    if (auto status = mVehicleHardware->subscribe(
+                newSubscribeOptions(propId, areaId, /*updateRateHz=*/0, /*enableVur*/ false));
+        status != StatusCode::OK) {
+        return StatusError(status)
+               << StringPrintf("failed subscribe for prop: %s, areaId: %" PRId32,
+                               propIdToString(propId).c_str(), areaId);
+    }
+    return {};
+}
+
 VhalResult<void> SubscriptionManager::addContinuousSubscriberLocked(
-        const ClientIdType& clientId, const PropIdAreaId& propIdAreaId, float sampleRateHz) {
+        const ClientIdType& clientId, const PropIdAreaId& propIdAreaId, float sampleRateHz,
+        bool enableVur) {
     // Make a copy so that we don't modify 'mContSubConfigsByPropIdArea' on failure cases.
     ContSubConfigs newConfig = mContSubConfigsByPropIdArea[propIdAreaId];
-    newConfig.addClient(clientId, sampleRateHz);
-    return updateContSubConfigs(propIdAreaId, newConfig);
+    newConfig.addClient(clientId, sampleRateHz, enableVur);
+    return updateContSubConfigsLocked(propIdAreaId, newConfig);
 }
 
 VhalResult<void> SubscriptionManager::removeContinuousSubscriberLocked(
@@ -108,25 +158,62 @@
     // Make a copy so that we don't modify 'mContSubConfigsByPropIdArea' on failure cases.
     ContSubConfigs newConfig = mContSubConfigsByPropIdArea[propIdAreaId];
     newConfig.removeClient(clientId);
-    return updateContSubConfigs(propIdAreaId, newConfig);
+    return updateContSubConfigsLocked(propIdAreaId, newConfig);
 }
 
-VhalResult<void> SubscriptionManager::updateContSubConfigs(const PropIdAreaId& propIdAreaId,
-                                                           const ContSubConfigs& newConfig) {
-    if (newConfig.getMaxSampleRateHz() ==
-        mContSubConfigsByPropIdArea[propIdAreaId].getMaxSampleRateHz()) {
+VhalResult<void> SubscriptionManager::removeOnChangeSubscriberLocked(
+        const PropIdAreaId& propIdAreaId) {
+    if (mClientsByPropIdAreaId[propIdAreaId].size() > 1) {
+        // After unsubscribing this client, there is still client subscribed, so do nothing.
+        return {};
+    }
+
+    int32_t propId = propIdAreaId.propId;
+    int32_t areaId = propIdAreaId.areaId;
+    if (auto status = mVehicleHardware->unsubscribe(propId, areaId); status != StatusCode::OK) {
+        return StatusError(status)
+               << StringPrintf("failed unsubscribe for prop: %s, areaId: %" PRId32,
+                               propIdToString(propId).c_str(), areaId);
+    }
+    return {};
+}
+
+VhalResult<void> SubscriptionManager::updateContSubConfigsLocked(const PropIdAreaId& propIdAreaId,
+                                                                 const ContSubConfigs& newConfig) {
+    const auto& oldConfig = mContSubConfigsByPropIdArea[propIdAreaId];
+    float newRateHz = newConfig.getMaxSampleRateHz();
+    float oldRateHz = oldConfig.getMaxSampleRateHz();
+    if (newRateHz == oldRateHz && newConfig.isVurEnabled() == oldConfig.isVurEnabled()) {
         mContSubConfigsByPropIdArea[propIdAreaId] = newConfig;
         return {};
     }
-    float newRateHz = newConfig.getMaxSampleRateHz();
     int32_t propId = propIdAreaId.propId;
     int32_t areaId = propIdAreaId.areaId;
-    if (auto status = mVehicleHardware->updateSampleRate(propId, areaId, newRateHz);
-        status != StatusCode::OK) {
-        return StatusError(status) << StringPrintf("failed to update sample rate for prop: %" PRId32
-                                                   ", area"
-                                                   ": %" PRId32 ", sample rate: %f HZ",
-                                                   propId, areaId, newRateHz);
+    if (newRateHz != oldRateHz) {
+        if (auto status = mVehicleHardware->updateSampleRate(propId, areaId, newRateHz);
+            status != StatusCode::OK) {
+            return StatusError(status)
+                   << StringPrintf("failed to update sample rate for prop: %s, areaId: %" PRId32
+                                   ", sample rate: %f HZ",
+                                   propIdToString(propId).c_str(), areaId, newRateHz);
+        }
+    }
+    if (newRateHz != 0) {
+        if (auto status = mVehicleHardware->subscribe(
+                    newSubscribeOptions(propId, areaId, newRateHz, newConfig.isVurEnabled()));
+            status != StatusCode::OK) {
+            return StatusError(status) << StringPrintf(
+                           "failed subscribe for prop: %s, areaId"
+                           ": %" PRId32 ", sample rate: %f HZ",
+                           propIdToString(propId).c_str(), areaId, newRateHz);
+        }
+    } else {
+        if (auto status = mVehicleHardware->unsubscribe(propId, areaId); status != StatusCode::OK) {
+            return StatusError(status) << StringPrintf(
+                           "failed unsubscribe for prop: %s, areaId"
+                           ": %" PRId32,
+                           propIdToString(propId).c_str(), areaId);
+        }
     }
     mContSubConfigsByPropIdArea[propIdAreaId] = newConfig;
     return {};
@@ -163,21 +250,54 @@
                     .propId = propId,
                     .areaId = areaId,
             };
+            VhalResult<void> result;
             if (isContinuousProperty) {
-                if (auto result = addContinuousSubscriberLocked(clientId, propIdAreaId,
-                                                                option.sampleRate);
-                    !result.ok()) {
-                    return result;
-                }
+                result = addContinuousSubscriberLocked(clientId, propIdAreaId, option.sampleRate,
+                                                       option.enableVariableUpdateRate);
+            } else {
+                result = addOnChangeSubscriberLocked(propIdAreaId);
+            }
+
+            if (!result.ok()) {
+                return result;
             }
 
             mSubscribedPropsByClient[clientId].insert(propIdAreaId);
-            mClientsByPropIdArea[propIdAreaId][clientId] = callback;
+            mClientsByPropIdAreaId[propIdAreaId][clientId] = callback;
         }
     }
     return {};
 }
 
+VhalResult<void> SubscriptionManager::unsubscribePropIdAreaIdLocked(
+        SubscriptionManager::ClientIdType clientId, const PropIdAreaId& propIdAreaId) {
+    if (mContSubConfigsByPropIdArea.find(propIdAreaId) != mContSubConfigsByPropIdArea.end()) {
+        // This is a subscribed continuous property.
+        if (auto result = removeContinuousSubscriberLocked(clientId, propIdAreaId); !result.ok()) {
+            return result;
+        }
+    } else {
+        if (mClientsByPropIdAreaId.find(propIdAreaId) == mClientsByPropIdAreaId.end()) {
+            ALOGW("Unsubscribe: The property: %s, areaId: %" PRId32
+                  " was not previously subscribed, do nothing",
+                  propIdToString(propIdAreaId.propId).c_str(), propIdAreaId.areaId);
+            return {};
+        }
+        // This is an on-change property.
+        if (auto result = removeOnChangeSubscriberLocked(propIdAreaId); !result.ok()) {
+            return result;
+        }
+    }
+
+    auto& clients = mClientsByPropIdAreaId[propIdAreaId];
+    clients.erase(clientId);
+    if (clients.empty()) {
+        mClientsByPropIdAreaId.erase(propIdAreaId);
+        mContSubConfigsByPropIdArea.erase(propIdAreaId);
+    }
+    return {};
+}
+
 VhalResult<void> SubscriptionManager::unsubscribe(SubscriptionManager::ClientIdType clientId,
                                                   const std::vector<int32_t>& propIds) {
     std::scoped_lock<std::mutex> lockGuard(mLock);
@@ -186,39 +306,27 @@
         return StatusError(StatusCode::INVALID_ARG)
                << "No property was subscribed for the callback";
     }
-    std::unordered_set<int32_t> subscribedPropIds;
-    for (auto const& propIdAreaId : mSubscribedPropsByClient[clientId]) {
-        subscribedPropIds.insert(propIdAreaId.propId);
-    }
 
+    std::vector<PropIdAreaId> propIdAreaIdsToUnsubscribe;
+    std::unordered_set<int32_t> propIdSet;
     for (int32_t propId : propIds) {
-        if (subscribedPropIds.find(propId) == subscribedPropIds.end()) {
-            return StatusError(StatusCode::INVALID_ARG)
-                   << "property ID: " << propId << " is not subscribed";
+        propIdSet.insert(propId);
+    }
+    auto& subscribedPropIdsAreaIds = mSubscribedPropsByClient[clientId];
+    for (const auto& propIdAreaId : subscribedPropIdsAreaIds) {
+        if (propIdSet.find(propIdAreaId.propId) != propIdSet.end()) {
+            propIdAreaIdsToUnsubscribe.push_back(propIdAreaId);
         }
     }
 
-    auto& propIdAreaIds = mSubscribedPropsByClient[clientId];
-    auto it = propIdAreaIds.begin();
-    while (it != propIdAreaIds.end()) {
-        int32_t propId = it->propId;
-        if (std::find(propIds.begin(), propIds.end(), propId) != propIds.end()) {
-            if (auto result = removeContinuousSubscriberLocked(clientId, *it); !result.ok()) {
-                return result;
-            }
-
-            auto& clients = mClientsByPropIdArea[*it];
-            clients.erase(clientId);
-            if (clients.empty()) {
-                mClientsByPropIdArea.erase(*it);
-                mContSubConfigsByPropIdArea.erase(*it);
-            }
-            it = propIdAreaIds.erase(it);
-        } else {
-            it++;
+    for (const auto& propIdAreaId : propIdAreaIdsToUnsubscribe) {
+        if (auto result = unsubscribePropIdAreaIdLocked(clientId, propIdAreaId); !result.ok()) {
+            return result;
         }
+        subscribedPropIdsAreaIds.erase(propIdAreaId);
     }
-    if (propIdAreaIds.empty()) {
+
+    if (subscribedPropIdsAreaIds.empty()) {
         mSubscribedPropsByClient.erase(clientId);
     }
     return {};
@@ -233,21 +341,42 @@
 
     auto& subscriptions = mSubscribedPropsByClient[clientId];
     for (auto const& propIdAreaId : subscriptions) {
-        if (auto result = removeContinuousSubscriberLocked(clientId, propIdAreaId); !result.ok()) {
+        if (auto result = unsubscribePropIdAreaIdLocked(clientId, propIdAreaId); !result.ok()) {
             return result;
         }
-
-        auto& clients = mClientsByPropIdArea[propIdAreaId];
-        clients.erase(clientId);
-        if (clients.empty()) {
-            mClientsByPropIdArea.erase(propIdAreaId);
-            mContSubConfigsByPropIdArea.erase(propIdAreaId);
-        }
     }
     mSubscribedPropsByClient.erase(clientId);
     return {};
 }
 
+bool SubscriptionManager::isValueUpdatedLocked(const std::shared_ptr<IVehicleCallback>& callback,
+                                               const VehiclePropValue& value) {
+    const auto& it = mContSubValuesByCallback[callback].find(value);
+    if (it == mContSubValuesByCallback[callback].end()) {
+        mContSubValuesByCallback[callback].insert(value);
+        return true;
+    }
+
+    if (it->timestamp > value.timestamp) {
+        ALOGE("The updated property value: %s is outdated, ignored", value.toString().c_str());
+        return false;
+    }
+
+    if (it->value == value.value && it->status == value.status) {
+        // Even though the property value is the same, we need to store the new property event to
+        // update the timestamp.
+        mContSubValuesByCallback[callback].insert(value);
+        ALOGD("The updated property value for propId: %" PRId32 ", areaId: %" PRId32
+              " has the "
+              "same value and status, ignored if VUR is enabled",
+              it->prop, it->areaId);
+        return false;
+    }
+
+    mContSubValuesByCallback[callback].insert(value);
+    return true;
+}
+
 std::unordered_map<std::shared_ptr<IVehicleCallback>, std::vector<VehiclePropValue>>
 SubscriptionManager::getSubscribedClients(std::vector<VehiclePropValue>&& updatedValues) {
     std::scoped_lock<std::mutex> lockGuard(mLock);
@@ -258,12 +387,22 @@
                 .propId = value.prop,
                 .areaId = value.areaId,
         };
-        if (mClientsByPropIdArea.find(propIdAreaId) == mClientsByPropIdArea.end()) {
+        if (mClientsByPropIdAreaId.find(propIdAreaId) == mClientsByPropIdAreaId.end()) {
             continue;
         }
 
-        for (const auto& [_, client] : mClientsByPropIdArea[propIdAreaId]) {
-            clients[client].push_back(value);
+        for (const auto& [client, callback] : mClientsByPropIdAreaId[propIdAreaId]) {
+            auto& subConfigs = mContSubConfigsByPropIdArea[propIdAreaId];
+            // If client wants VUR (and VUR is supported as checked in DefaultVehicleHal), it is
+            // possible that VUR is not enabled in IVehicleHardware because another client does not
+            // enable VUR. We will implement VUR filtering here for the client that enables it.
+            if (subConfigs.isVurEnabledForClient(client) && !subConfigs.isVurEnabled()) {
+                if (isValueUpdatedLocked(callback, value)) {
+                    clients[callback].push_back(value);
+                }
+            } else {
+                clients[callback].push_back(value);
+            }
         }
     }
     return clients;
@@ -280,11 +419,11 @@
                 .propId = errorEvent.propId,
                 .areaId = errorEvent.areaId,
         };
-        if (mClientsByPropIdArea.find(propIdAreaId) == mClientsByPropIdArea.end()) {
+        if (mClientsByPropIdAreaId.find(propIdAreaId) == mClientsByPropIdAreaId.end()) {
             continue;
         }
 
-        for (const auto& [_, client] : mClientsByPropIdArea[propIdAreaId]) {
+        for (const auto& [_, client] : mClientsByPropIdAreaId[propIdAreaId]) {
             clients[client].push_back({
                     .propId = errorEvent.propId,
                     .areaId = errorEvent.areaId,
@@ -297,7 +436,7 @@
 
 bool SubscriptionManager::isEmpty() {
     std::scoped_lock<std::mutex> lockGuard(mLock);
-    return mSubscribedPropsByClient.empty() && mClientsByPropIdArea.empty();
+    return mSubscribedPropsByClient.empty() && mClientsByPropIdAreaId.empty();
 }
 
 size_t SubscriptionManager::countClients() {
diff --git a/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp b/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp
index e775612..7195d97 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp
@@ -98,12 +98,22 @@
 constexpr int32_t READ_ONLY_PROP = 10006 + 0x10000000 + 0x01000000 + 0x00400000;
 // VehiclePropertyGroup:SYSTEM,VehicleArea:GLOBAL,VehiclePropertyType:INT32
 constexpr int32_t WRITE_ONLY_PROP = 10007 + 0x10000000 + 0x01000000 + 0x00400000;
+// VehiclePropertyGroup:SYSTEM,VehicleArea:GLOBAL,VehiclePropertyType:INT32
+constexpr int32_t GLOBAL_CONTINUOUS_PROP_NO_VUR = 10008 + 0x10000000 + 0x01000000 + 0x00400000;
 
 int32_t testInt32VecProp(size_t i) {
     // VehiclePropertyGroup:SYSTEM,VehicleArea:GLOBAL,VehiclePropertyType:INT32_VEC
     return static_cast<int32_t>(i) + 0x10000000 + 0x01000000 + 0x00410000;
 }
 
+std::string toString(const std::vector<SubscribeOptions>& options) {
+    std::string optionsStr;
+    for (const auto& option : options) {
+        optionsStr += option.toString() + "\n";
+    }
+    return optionsStr;
+}
+
 struct PropConfigCmp {
     bool operator()(const VehiclePropConfig& a, const VehiclePropConfig& b) const {
         return (a.prop < b.prop);
@@ -245,8 +255,18 @@
                 .changeMode = VehiclePropertyChangeMode::ON_CHANGE,
         });
         // A global continuous property.
+        testConfigs.push_back(VehiclePropConfig{.prop = GLOBAL_CONTINUOUS_PROP,
+                                                .access = VehiclePropertyAccess::READ_WRITE,
+                                                .changeMode = VehiclePropertyChangeMode::CONTINUOUS,
+                                                .minSampleRate = 0.0,
+                                                .maxSampleRate = 100.0,
+                                                .areaConfigs = {{
+                                                        .areaId = 0,
+                                                        .supportVariableUpdateRate = true,
+                                                }}});
+        // A global continuous property that does not support VUR.
         testConfigs.push_back(VehiclePropConfig{
-                .prop = GLOBAL_CONTINUOUS_PROP,
+                .prop = GLOBAL_CONTINUOUS_PROP_NO_VUR,
                 .access = VehiclePropertyAccess::READ_WRITE,
                 .changeMode = VehiclePropertyChangeMode::CONTINUOUS,
                 .minSampleRate = 0.0,
@@ -286,11 +306,13 @@
                                         .areaId = toInt(VehicleAreaWindow::ROW_1_LEFT),
                                         .minInt32Value = 0,
                                         .maxInt32Value = 100,
+                                        .supportVariableUpdateRate = true,
                                 },
                                 {
                                         .areaId = toInt(VehicleAreaWindow::ROW_1_RIGHT),
                                         .minInt32Value = 0,
                                         .maxInt32Value = 100,
+                                        .supportVariableUpdateRate = false,
                                 },
                         },
         });
@@ -1352,6 +1374,62 @@
     EXPECT_EQ(countClients(), static_cast<size_t>(1));
 }
 
+TEST_F(DefaultVehicleHalTest, testSubscribeContinuous_propNotSupportVur) {
+    std::vector<SubscribeOptions> options = {
+            {
+                    .propId = GLOBAL_CONTINUOUS_PROP,
+                    .sampleRate = 20.0,
+                    .enableVariableUpdateRate = true,
+            },
+            {
+                    .propId = GLOBAL_CONTINUOUS_PROP_NO_VUR,
+                    .sampleRate = 30.0,
+                    .enableVariableUpdateRate = true,
+            },
+    };
+
+    auto status = getClient()->subscribe(getCallbackClient(), options, 0);
+
+    ASSERT_TRUE(status.isOk()) << "subscribe failed: " << status.getMessage();
+    auto receivedSubscribeOptions = getHardware()->getSubscribeOptions();
+    ASSERT_THAT(receivedSubscribeOptions, UnorderedElementsAre(
+                                                  SubscribeOptions{
+                                                          .propId = GLOBAL_CONTINUOUS_PROP,
+                                                          .areaIds = {0},
+                                                          .enableVariableUpdateRate = true,
+                                                          .sampleRate = 20.0,
+                                                  },
+                                                  SubscribeOptions{
+                                                          .propId = GLOBAL_CONTINUOUS_PROP_NO_VUR,
+                                                          .areaIds = {0},
+                                                          .enableVariableUpdateRate = false,
+                                                          .sampleRate = 30.0,
+                                                  }))
+            << "received unexpected subscribe options: " << toString(receivedSubscribeOptions);
+}
+
+TEST_F(DefaultVehicleHalTest, testSubscribeContinuous_propSupportVurNotEnabled) {
+    std::vector<SubscribeOptions> options = {
+            {
+                    .propId = GLOBAL_CONTINUOUS_PROP,
+                    .sampleRate = 20.0,
+                    .enableVariableUpdateRate = false,
+            },
+    };
+
+    auto status = getClient()->subscribe(getCallbackClient(), options, 0);
+
+    ASSERT_TRUE(status.isOk()) << "subscribe failed: " << status.getMessage();
+    auto receivedSubscribeOptions = getHardware()->getSubscribeOptions();
+    ASSERT_THAT(receivedSubscribeOptions, UnorderedElementsAre(SubscribeOptions{
+                                                  .propId = GLOBAL_CONTINUOUS_PROP,
+                                                  .areaIds = {0},
+                                                  .enableVariableUpdateRate = false,
+                                                  .sampleRate = 20.0,
+                                          }))
+            << "received unexpected subscribe options: " << toString(receivedSubscribeOptions);
+}
+
 TEST_F(DefaultVehicleHalTest, testSubscribeAreaContinuous) {
     std::vector<SubscribeOptions> options = {
             {
@@ -1404,6 +1482,44 @@
     ASSERT_GE(rightCount, static_cast<size_t>(5));
 }
 
+TEST_F(DefaultVehicleHalTest, testAreaContinuous_areaNotSupportVur) {
+    std::vector<SubscribeOptions> options = {
+            {
+                    .propId = AREA_CONTINUOUS_PROP,
+                    .sampleRate = 20.0,
+                    .areaIds = {toInt(VehicleAreaWindow::ROW_1_LEFT)},
+                    .enableVariableUpdateRate = true,
+            },
+            {
+                    .propId = AREA_CONTINUOUS_PROP,
+                    .sampleRate = 10.0,
+                    .areaIds = {toInt(VehicleAreaWindow::ROW_1_RIGHT)},
+                    .enableVariableUpdateRate = true,
+            },
+    };
+
+    auto status = getClient()->subscribe(getCallbackClient(), options, 0);
+
+    ASSERT_TRUE(status.isOk()) << "subscribe failed: " << status.getMessage();
+    auto receivedSubscribeOptions = getHardware()->getSubscribeOptions();
+    ASSERT_THAT(receivedSubscribeOptions,
+                UnorderedElementsAre(
+                        SubscribeOptions{
+                                .propId = AREA_CONTINUOUS_PROP,
+                                .sampleRate = 20.0,
+                                .areaIds = {toInt(VehicleAreaWindow::ROW_1_LEFT)},
+                                .enableVariableUpdateRate = true,
+                        },
+                        SubscribeOptions{
+                                .propId = AREA_CONTINUOUS_PROP,
+                                .sampleRate = 10.0,
+                                .areaIds = {toInt(VehicleAreaWindow::ROW_1_RIGHT)},
+                                // Area2 actually does not support VUR.
+                                .enableVariableUpdateRate = false,
+                        }))
+            << "received unexpected subscribe options: " << toString(receivedSubscribeOptions);
+}
+
 TEST_F(DefaultVehicleHalTest, testUnsubscribeOnChange) {
     std::vector<SubscribeOptions> options = {
             {
diff --git a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp
index 3fae596..db15c89 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp
@@ -29,6 +29,7 @@
 using ::aidl::android::hardware::automotive::vehicle::SetValueRequest;
 using ::aidl::android::hardware::automotive::vehicle::SetValueResult;
 using ::aidl::android::hardware::automotive::vehicle::StatusCode;
+using ::aidl::android::hardware::automotive::vehicle::SubscribeOptions;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
 
@@ -88,7 +89,40 @@
     return StatusCode::OK;
 }
 
-StatusCode MockVehicleHardware::updateSampleRate(int32_t propId, int32_t areaId, float sampleRate) {
+StatusCode MockVehicleHardware::subscribe(SubscribeOptions options) {
+    {
+        std::scoped_lock<std::mutex> lockGuard(mLock);
+        mSubscribeOptions.push_back(options);
+    }
+    for (int32_t areaId : options.areaIds) {
+        if (auto status = subscribePropIdAreaId(options.propId, areaId, options.sampleRate);
+            status != StatusCode::OK) {
+            return status;
+        }
+    }
+    return StatusCode::OK;
+}
+
+std::vector<SubscribeOptions> MockVehicleHardware::getSubscribeOptions() {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+    return mSubscribeOptions;
+}
+
+void MockVehicleHardware::clearSubscribeOptions() {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+    mSubscribeOptions.clear();
+}
+
+StatusCode MockVehicleHardware::subscribePropIdAreaId(int32_t propId, int32_t areaId,
+                                                      float sampleRateHz) {
+    if (sampleRateHz == 0) {
+        // on-change property.
+        std::scoped_lock<std::mutex> lockGuard(mLock);
+        mSubOnChangePropIdAreaIds.insert(std::pair<int32_t, int32_t>(propId, areaId));
+        return StatusCode::OK;
+    }
+
+    // continuous property.
     std::shared_ptr<std::function<void()>> action;
 
     {
@@ -97,9 +131,6 @@
             // Remove the previous action register for this [propId, areaId].
             mRecurrentTimer->unregisterTimerCallback(mRecurrentActions[propId][areaId]);
         }
-        if (sampleRate == 0) {
-            return StatusCode::OK;
-        }
 
         // We are sure 'propertyChangeCallback' would be alive because we would unregister timer
         // before destroying 'this' which owns mPropertyChangeCallback.
@@ -107,8 +138,8 @@
         action = std::make_shared<std::function<void()>>([propertyChangeCallback, propId, areaId] {
             std::vector<VehiclePropValue> values = {
                     {
-                            .prop = propId,
                             .areaId = areaId,
+                            .prop = propId,
                     },
             };
             (*propertyChangeCallback)(values);
@@ -119,11 +150,45 @@
 
     // In mock implementation, we generate a new property change event for this property at sample
     // rate.
-    int64_t interval = static_cast<int64_t>(1'000'000'000. / sampleRate);
+    int64_t interval = static_cast<int64_t>(1'000'000'000. / sampleRateHz);
     mRecurrentTimer->registerTimerCallback(interval, action);
     return StatusCode::OK;
 }
 
+StatusCode MockVehicleHardware::unsubscribe(int32_t propId, int32_t areaId) {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+    // For on-change property.
+    mSubOnChangePropIdAreaIds.erase(std::make_pair(propId, areaId));
+    // for continuous property.
+    if (mRecurrentActions[propId][areaId] != nullptr) {
+        // Remove the previous action register for this [propId, areaId].
+        mRecurrentTimer->unregisterTimerCallback(mRecurrentActions[propId][areaId]);
+        mRecurrentActions[propId].erase(areaId);
+        if (mRecurrentActions[propId].empty()) {
+            mRecurrentActions.erase(propId);
+        }
+    }
+    return StatusCode::OK;
+}
+
+std::set<std::pair<int32_t, int32_t>> MockVehicleHardware::getSubscribedOnChangePropIdAreaIds() {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+    std::set<std::pair<int32_t, int32_t>> propIdAreaIds;
+    propIdAreaIds = mSubOnChangePropIdAreaIds;
+    return propIdAreaIds;
+}
+
+std::set<std::pair<int32_t, int32_t>> MockVehicleHardware::getSubscribedContinuousPropIdAreaIds() {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+    std::set<std::pair<int32_t, int32_t>> propIdAreaIds;
+    for (const auto& [propId, actionByAreaId] : mRecurrentActions) {
+        for (const auto& [areaId, _] : actionByAreaId) {
+            propIdAreaIds.insert(std::make_pair(propId, areaId));
+        }
+    }
+    return propIdAreaIds;
+}
+
 void MockVehicleHardware::registerOnPropertyChangeEvent(
         std::unique_ptr<const PropertyChangeCallback> callback) {
     std::scoped_lock<std::mutex> lockGuard(mLock);
diff --git a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h
index 3ce18c5..eeca582 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h
@@ -29,6 +29,7 @@
 #include <list>
 #include <memory>
 #include <mutex>
+#include <set>
 #include <thread>
 #include <unordered_map>
 #include <vector>
@@ -59,8 +60,10 @@
     void registerOnPropertyChangeEvent(
             std::unique_ptr<const PropertyChangeCallback> callback) override;
     void registerOnPropertySetErrorEvent(std::unique_ptr<const PropertySetErrorCallback>) override;
-    aidl::android::hardware::automotive::vehicle::StatusCode updateSampleRate(
-            int32_t propId, int32_t areaId, float sampleRate) override;
+    aidl::android::hardware::automotive::vehicle::StatusCode subscribe(
+            aidl::android::hardware::automotive::vehicle::SubscribeOptions options) override;
+    aidl::android::hardware::automotive::vehicle::StatusCode unsubscribe(int32_t propId,
+                                                                         int32_t areaId) override;
     std::chrono::nanoseconds getPropertyOnChangeEventBatchingWindow() override;
 
     // Test functions.
@@ -90,6 +93,12 @@
     void sendOnPropertySetErrorEvent(const std::vector<SetValueErrorEvent>& errorEvents);
     void setPropertyOnChangeEventBatchingWindow(std::chrono::nanoseconds window);
 
+    std::set<std::pair<int32_t, int32_t>> getSubscribedOnChangePropIdAreaIds();
+    std::set<std::pair<int32_t, int32_t>> getSubscribedContinuousPropIdAreaIds();
+    std::vector<aidl::android::hardware::automotive::vehicle::SubscribeOptions>
+    getSubscribeOptions();
+    void clearSubscribeOptions();
+
   private:
     mutable std::mutex mLock;
     mutable std::condition_variable mCv;
@@ -114,6 +123,9 @@
             const std::vector<aidl::android::hardware::automotive::vehicle::GetValueRequest>&)>
             mGetValueResponder GUARDED_BY(mLock);
     std::chrono::nanoseconds mEventBatchingWindow GUARDED_BY(mLock) = std::chrono::nanoseconds(0);
+    std::set<std::pair<int32_t, int32_t>> mSubOnChangePropIdAreaIds GUARDED_BY(mLock);
+    std::vector<aidl::android::hardware::automotive::vehicle::SubscribeOptions> mSubscribeOptions
+            GUARDED_BY(mLock);
 
     template <class ResultType>
     aidl::android::hardware::automotive::vehicle::StatusCode returnResponse(
@@ -126,6 +138,8 @@
             const std::vector<RequestType>& requests,
             std::list<std::vector<RequestType>>* storedRequests,
             std::list<std::vector<ResultType>>* storedResponses) const REQUIRES(mLock);
+    aidl::android::hardware::automotive::vehicle::StatusCode subscribePropIdAreaId(
+            int32_t propId, int32_t areaId, float sampleRateHz);
 
     DumpResult mDumpResult;
 
diff --git a/automotive/vehicle/aidl/impl/vhal/test/SubscriptionManagerTest.cpp b/automotive/vehicle/aidl/impl/vhal/test/SubscriptionManagerTest.cpp
index 5464304..aa5f003 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/SubscriptionManagerTest.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/test/SubscriptionManagerTest.cpp
@@ -43,10 +43,12 @@
 using ::aidl::android::hardware::automotive::vehicle::SetValueResults;
 using ::aidl::android::hardware::automotive::vehicle::SubscribeOptions;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropErrors;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyStatus;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
 using ::aidl::android::hardware::automotive::vehicle::VehiclePropValues;
 using ::ndk::ScopedAStatus;
 using ::ndk::SpAIBinder;
+using ::testing::Contains;
 using ::testing::ElementsAre;
 using ::testing::UnorderedElementsAre;
 
@@ -114,6 +116,8 @@
 
     void clearEvents() { return getCallback()->clearEvents(); }
 
+    std::shared_ptr<MockVehicleHardware> getHardware() { return mHardware; }
+
   private:
     std::unique_ptr<SubscriptionManager> mManager;
     std::shared_ptr<PropertyCallback> mCallback;
@@ -132,6 +136,9 @@
     auto result = getManager()->subscribe(getCallbackClient(), options, true);
     ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
 
+    ASSERT_THAT(getHardware()->getSubscribedContinuousPropIdAreaIds(),
+                UnorderedElementsAre(std::pair<int32_t, int32_t>(0, 0)));
+
     std::this_thread::sleep_for(std::chrono::seconds(1));
 
     // Theoretically trigger 10 times, but check for at least 9 times to be stable.
@@ -240,6 +247,8 @@
     result = getManager()->unsubscribe(getCallbackClient()->asBinder().get());
     ASSERT_TRUE(result.ok()) << "failed to unsubscribe: " << result.error().message();
 
+    ASSERT_EQ(getHardware()->getSubscribedContinuousPropIdAreaIds().size(), 0u);
+
     // Wait for the last events to come.
     std::this_thread::sleep_for(std::chrono::milliseconds(100));
 
@@ -316,7 +325,7 @@
     EXPECT_TRUE(getEvents().empty());
 }
 
-TEST_F(SubscriptionManagerTest, testUnsubscribeFailure) {
+TEST_F(SubscriptionManagerTest, testUnsubscribeUnsubscribedPropId) {
     std::vector<SubscribeOptions> options = {
             {
                     .propId = 0,
@@ -334,14 +343,21 @@
     // Property ID: 2 was not subscribed.
     result = getManager()->unsubscribe(getCallbackClient()->asBinder().get(),
                                        std::vector<int32_t>({0, 1, 2}));
-    ASSERT_FALSE(result.ok()) << "unsubscribe an unsubscribed property must fail";
+    ASSERT_TRUE(result.ok()) << "unsubscribe an unsubscribed property must do nothing";
 
-    // Since property 0 and property 1 was not unsubscribed successfully, we should be able to
-    // unsubscribe them again.
-    result = getManager()->unsubscribe(getCallbackClient()->asBinder().get(),
-                                       std::vector<int32_t>({0, 1}));
-    ASSERT_TRUE(result.ok()) << "a failed unsubscription must not unsubscribe any properties"
-                             << result.error().message();
+    std::vector<VehiclePropValue> updatedValues = {
+            {
+                    .prop = 0,
+                    .areaId = 0,
+            },
+            {
+                    .prop = 1,
+                    .areaId = 0,
+            },
+    };
+    auto clients = getManager()->getSubscribedClients(std::vector<VehiclePropValue>(updatedValues));
+
+    ASSERT_EQ(clients.size(), 0u) << "all subscribed properties must be unsubscribed";
 }
 
 TEST_F(SubscriptionManagerTest, testSubscribeOnchange) {
@@ -370,6 +386,11 @@
     ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
     result = getManager()->subscribe(client2, options2, false);
     ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+    ASSERT_THAT(getHardware()->getSubscribedOnChangePropIdAreaIds(),
+                UnorderedElementsAre(std::pair<int32_t, int32_t>(0, 0),
+                                     std::pair<int32_t, int32_t>(0, 1),
+                                     std::pair<int32_t, int32_t>(1, 0)));
+    ASSERT_EQ(getHardware()->getSubscribedContinuousPropIdAreaIds().size(), 0u);
 
     std::vector<VehiclePropValue> updatedValues = {
             {
@@ -483,6 +504,8 @@
     auto clients = getManager()->getSubscribedClients(std::vector<VehiclePropValue>(updatedValues));
 
     ASSERT_THAT(clients[getCallbackClient()], ElementsAre(updatedValues[1]));
+    ASSERT_THAT(getHardware()->getSubscribedOnChangePropIdAreaIds(),
+                UnorderedElementsAre(std::pair<int32_t, int32_t>(1, 0)));
 }
 
 TEST_F(SubscriptionManagerTest, testCheckSampleRateHzValid) {
@@ -497,6 +520,257 @@
     ASSERT_FALSE(SubscriptionManager::checkSampleRateHz(0));
 }
 
+TEST_F(SubscriptionManagerTest, testSubscribe_enableVur) {
+    std::vector<SubscribeOptions> options = {{
+            .propId = 0,
+            .areaIds = {0},
+            .sampleRate = 10.0,
+            .enableVariableUpdateRate = true,
+    }};
+
+    auto result = getManager()->subscribe(getCallbackClient(), options, true);
+    ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+    ASSERT_THAT(getHardware()->getSubscribeOptions(), ElementsAre(options[0]));
+}
+
+TEST_F(SubscriptionManagerTest, testSubscribe_VurStateChange) {
+    std::vector<SubscribeOptions> options = {{
+            .propId = 0,
+            .areaIds = {0},
+            .sampleRate = 10.0,
+            .enableVariableUpdateRate = true,
+    }};
+
+    auto result = getManager()->subscribe(getCallbackClient(), options, true);
+    ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+    ASSERT_THAT(getHardware()->getSubscribeOptions(), ElementsAre(options[0]));
+
+    getHardware()->clearSubscribeOptions();
+    result = getManager()->subscribe(getCallbackClient(), options, true);
+    ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+    ASSERT_TRUE(getHardware()->getSubscribeOptions().empty());
+
+    std::vector<SubscribeOptions> newOptions = {{
+            .propId = 0,
+            .areaIds = {0},
+            .sampleRate = 10.0,
+            .enableVariableUpdateRate = false,
+    }};
+    result = getManager()->subscribe(getCallbackClient(), newOptions, true);
+    ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+    ASSERT_THAT(getHardware()->getSubscribeOptions(), ElementsAre(newOptions[0]));
+}
+
+TEST_F(SubscriptionManagerTest, testSubscribe_enableVur_filterUnchangedEvents) {
+    SpAIBinder binder1 = ndk::SharedRefBase::make<PropertyCallback>()->asBinder();
+    std::shared_ptr<IVehicleCallback> client1 = IVehicleCallback::fromBinder(binder1);
+    SpAIBinder binder2 = ndk::SharedRefBase::make<PropertyCallback>()->asBinder();
+    std::shared_ptr<IVehicleCallback> client2 = IVehicleCallback::fromBinder(binder2);
+    SubscribeOptions client1Option = {
+            .propId = 0,
+            .areaIds = {0},
+            .sampleRate = 10.0,
+            .enableVariableUpdateRate = false,
+    };
+    auto result = getManager()->subscribe(client1, {client1Option}, true);
+    ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+    ASSERT_THAT(getHardware()->getSubscribeOptions(), UnorderedElementsAre(client1Option));
+
+    getHardware()->clearSubscribeOptions();
+    SubscribeOptions client2Option = {
+            .propId = 0,
+            .areaIds = {0, 1},
+            .sampleRate = 20.0,
+            .enableVariableUpdateRate = true,
+    };
+
+    result = getManager()->subscribe(client2, {client2Option}, true);
+    ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+    ASSERT_THAT(getHardware()->getSubscribeOptions(),
+                UnorderedElementsAre(
+                        SubscribeOptions{
+                                .propId = 0,
+                                .areaIds = {0},
+                                .sampleRate = 20.0,
+                                // This is enabled for client2, but disabled for client1.
+                                .enableVariableUpdateRate = false,
+                        },
+                        SubscribeOptions{
+                                .propId = 0,
+                                .areaIds = {1},
+                                .sampleRate = 20.0,
+                                .enableVariableUpdateRate = true,
+                        }));
+
+    std::vector<VehiclePropValue> propertyEvents = {{
+                                                            .prop = 0,
+                                                            .areaId = 0,
+                                                            .value = {.int32Values = {0}},
+                                                            .timestamp = 1,
+                                                    },
+                                                    {
+                                                            .prop = 0,
+                                                            .areaId = 1,
+                                                            .value = {.int32Values = {1}},
+                                                            .timestamp = 1,
+                                                    }};
+    auto clients =
+            getManager()->getSubscribedClients(std::vector<VehiclePropValue>(propertyEvents));
+
+    ASSERT_THAT(clients[client1], UnorderedElementsAre(propertyEvents[0]));
+    ASSERT_THAT(clients[client2], UnorderedElementsAre(propertyEvents[0], propertyEvents[1]));
+
+    // If the same property events happen again with a new timestamp.
+    // VUR is disabled for client1, enabled for client2.
+    clients = getManager()->getSubscribedClients({{
+            .prop = 0,
+            .areaId = 0,
+            .value = {.int32Values = {0}},
+            .timestamp = 2,
+    }});
+
+    ASSERT_FALSE(clients.find(client1) == clients.end())
+            << "Must not filter out property events if VUR is not enabled";
+    ASSERT_TRUE(clients.find(client2) == clients.end())
+            << "Must filter out property events if VUR is enabled";
+}
+
+TEST_F(SubscriptionManagerTest, testSubscribe_enableVur_mustNotFilterStatusChange) {
+    SpAIBinder binder1 = ndk::SharedRefBase::make<PropertyCallback>()->asBinder();
+    std::shared_ptr<IVehicleCallback> client1 = IVehicleCallback::fromBinder(binder1);
+    SpAIBinder binder2 = ndk::SharedRefBase::make<PropertyCallback>()->asBinder();
+    std::shared_ptr<IVehicleCallback> client2 = IVehicleCallback::fromBinder(binder2);
+    SubscribeOptions client1Option = {
+            .propId = 0,
+            .areaIds = {0},
+            .sampleRate = 10.0,
+            .enableVariableUpdateRate = false,
+    };
+    auto result = getManager()->subscribe(client1, {client1Option}, true);
+    ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+    ASSERT_THAT(getHardware()->getSubscribeOptions(), UnorderedElementsAre(client1Option));
+
+    getHardware()->clearSubscribeOptions();
+    SubscribeOptions client2Option = {
+            .propId = 0,
+            .areaIds = {0, 1},
+            .sampleRate = 20.0,
+            .enableVariableUpdateRate = true,
+    };
+
+    result = getManager()->subscribe(client2, {client2Option}, true);
+    ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+    ASSERT_THAT(getHardware()->getSubscribeOptions(),
+                UnorderedElementsAre(
+                        SubscribeOptions{
+                                .propId = 0,
+                                .areaIds = {0},
+                                .sampleRate = 20.0,
+                                // This is enabled for client2, but disabled for client1.
+                                .enableVariableUpdateRate = false,
+                        },
+                        SubscribeOptions{
+                                .propId = 0,
+                                .areaIds = {1},
+                                .sampleRate = 20.0,
+                                .enableVariableUpdateRate = true,
+                        }));
+
+    VehiclePropValue propValue1 = {
+            .prop = 0,
+            .areaId = 0,
+            .value = {.int32Values = {0}},
+            .timestamp = 1,
+    };
+    auto clients = getManager()->getSubscribedClients(std::vector<VehiclePropValue>({propValue1}));
+
+    ASSERT_THAT(clients[client1], UnorderedElementsAre(propValue1));
+
+    // A new event with the same value, but different status must not be filtered out.
+    VehiclePropValue propValue2 = {
+            .prop = 0,
+            .areaId = 0,
+            .value = {.int32Values = {0}},
+            .status = VehiclePropertyStatus::UNAVAILABLE,
+            .timestamp = 2,
+    };
+    clients = getManager()->getSubscribedClients({propValue2});
+
+    ASSERT_THAT(clients[client1], UnorderedElementsAre(propValue2))
+            << "Must not filter out property events that has status change";
+}
+
+TEST_F(SubscriptionManagerTest, testSubscribe_enableVur_timestampUpdated_filterOutdatedEvent) {
+    SpAIBinder binder1 = ndk::SharedRefBase::make<PropertyCallback>()->asBinder();
+    std::shared_ptr<IVehicleCallback> client1 = IVehicleCallback::fromBinder(binder1);
+    SpAIBinder binder2 = ndk::SharedRefBase::make<PropertyCallback>()->asBinder();
+    std::shared_ptr<IVehicleCallback> client2 = IVehicleCallback::fromBinder(binder2);
+    std::vector<SubscribeOptions> options = {{
+            .propId = 0,
+            .areaIds = {0},
+            .sampleRate = 10.0,
+            .enableVariableUpdateRate = true,
+    }};
+
+    // client1 subscribe with VUR enabled.
+    auto result = getManager()->subscribe(client1, options, true);
+    ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+    // Let client2 subscribe with VUR disabled so that we enabled VUR in DefaultVehicleHal layer.
+    result = getManager()->subscribe(client2,
+                                     {{
+                                             .propId = 0,
+                                             .areaIds = {0},
+                                             .sampleRate = 10.0,
+                                             .enableVariableUpdateRate = false,
+                                     }},
+                                     true);
+    ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+    VehiclePropValue value0 = {
+            .prop = 0,
+            .areaId = 0,
+            .value = {.int32Values = {0}},
+            .timestamp = 1,
+    };
+    auto clients = getManager()->getSubscribedClients({value0});
+
+    ASSERT_THAT(clients[client1], UnorderedElementsAre(value0));
+
+    // A new event with the same value arrived. This must update timestamp to 3.
+    VehiclePropValue value1 = {
+            .prop = 0,
+            .areaId = 0,
+            .value = {.int32Values = {0}},
+            .timestamp = 3,
+    };
+    clients = getManager()->getSubscribedClients({value1});
+
+    ASSERT_TRUE(clients.find(client1) == clients.end())
+            << "Must filter out duplicate property events if VUR is enabled";
+
+    // The latest timestamp is 3, so even though the value is not the same, this is outdated and
+    // must be ignored.
+    VehiclePropValue value2 = {
+            .prop = 0,
+            .areaId = 0,
+            .value = {.int32Values = {1}},
+            .timestamp = 2,
+    };
+    clients = getManager()->getSubscribedClients({value1});
+
+    ASSERT_TRUE(clients.find(client1) == clients.end())
+            << "Must filter out outdated property events if VUR is enabled";
+}
+
 }  // namespace vehicle
 }  // namespace automotive
 }  // namespace hardware
diff --git a/automotive/vehicle/aidl_property/aidl_api/android.hardware.automotive.vehicle.property/current/android/hardware/automotive/vehicle/VehiclePropertyGroup.aidl b/automotive/vehicle/aidl_property/aidl_api/android.hardware.automotive.vehicle.property/current/android/hardware/automotive/vehicle/VehiclePropertyGroup.aidl
index 714d514..b4f6850 100644
--- a/automotive/vehicle/aidl_property/aidl_api/android.hardware.automotive.vehicle.property/current/android/hardware/automotive/vehicle/VehiclePropertyGroup.aidl
+++ b/automotive/vehicle/aidl_property/aidl_api/android.hardware.automotive.vehicle.property/current/android/hardware/automotive/vehicle/VehiclePropertyGroup.aidl
@@ -36,5 +36,6 @@
 enum VehiclePropertyGroup {
   SYSTEM = 0x10000000,
   VENDOR = 0x20000000,
+  BACKPORTED = 0x30000000,
   MASK = 0xf0000000,
 }
diff --git a/automotive/vehicle/aidl_property/android/hardware/automotive/vehicle/VehiclePropertyGroup.aidl b/automotive/vehicle/aidl_property/android/hardware/automotive/vehicle/VehiclePropertyGroup.aidl
index a2cbdec..a417388 100644
--- a/automotive/vehicle/aidl_property/android/hardware/automotive/vehicle/VehiclePropertyGroup.aidl
+++ b/automotive/vehicle/aidl_property/android/hardware/automotive/vehicle/VehiclePropertyGroup.aidl
@@ -29,5 +29,34 @@
      */
     VENDOR = 0x20000000,
 
+    /**
+     * Group reserved for backporting system properties introduced in a newer Android
+     * release to an older Android release.
+     *
+     * It is recommended to map the system property ID to a backported property ID by replacing the
+     * VehiclePropertyGroup, e.g. backported PERF_VEHICLE_SPEED(0x11600207) would be 0x31600207.
+     *
+     * When updated to a newer Android release where the property is defined as system properties,
+     * the backported properties must be migrated to system properties.
+     *
+     * In Android system, the backported property is treated the same as a vendor defined property
+     * with the same vendor permission model, a.k.a. Default required permission is
+     * `android.car.Car.PERMISSION_VENDOR_EXTENSION`, or customized by
+     * `SUPPORT_CUSTOMIZE_VENDOR_PERMISSION` VHAL property.
+     *
+     * Only applications with vendor permissions may access these backported properties.
+     *
+     * Vendors must also make sure this property's behavior is consistent with what is expected for
+     * the backported system property, e.g. the access mode, the change mode and the config array
+     * must be correct.
+     *
+     * When vendors define custom properties, they must use {@code VENDOR} flag, instead of
+     * {@code BACKPORTED}
+     */
+    BACKPORTED = 0x30000000,
+
+    /**
+     * The bit mask for {@code VehiclePropertyGroup}. This is not a group by itself.
+     */
     MASK = 0xf0000000,
 }
diff --git a/biometrics/fingerprint/aidl/default/Android.bp b/biometrics/fingerprint/aidl/default/Android.bp
index 3bb3f3a..a173a00 100644
--- a/biometrics/fingerprint/aidl/default/Android.bp
+++ b/biometrics/fingerprint/aidl/default/Android.bp
@@ -11,8 +11,6 @@
     name: "android.hardware.biometrics.fingerprint-service.example",
     vendor: true,
     relative_install_path: "hw",
-    init_rc: [":fingerprint-example.rc"],
-    vintf_fragments: [":fingerprint-example.xml"],
     local_include_dirs: ["include"],
     srcs: [
         "FakeLockoutTracker.cpp",
@@ -24,15 +22,21 @@
         "Session.cpp",
         "main.cpp",
     ],
+    stl: "c++_static",
     shared_libs: [
-        "libbase",
         "libbinder_ndk",
+        "liblog",
+    ],
+    static_libs: [
+        "libandroid.hardware.biometrics.fingerprint.VirtualProps",
+        "libbase",
         "android.hardware.biometrics.fingerprint-V3-ndk",
         "android.hardware.biometrics.common-V3-ndk",
         "android.hardware.biometrics.common.thread",
         "android.hardware.biometrics.common.util",
+        "android.hardware.keymaster-V4-ndk",
     ],
-    static_libs: ["libandroid.hardware.biometrics.fingerprint.VirtualProps"],
+    installable: false, // install APEX instead
 }
 
 cc_test {
@@ -143,12 +147,35 @@
     vendor: true,
 }
 
-filegroup {
+prebuilt_etc {
     name: "fingerprint-example.rc",
-    srcs: ["fingerprint-example.rc"],
+    src: "fingerprint-example.rc",
+    installable: false,
 }
 
-filegroup {
+prebuilt_etc {
     name: "fingerprint-example.xml",
-    srcs: ["fingerprint-example.xml"],
+    src: "fingerprint-example.xml",
+    sub_dir: "vintf",
+    installable: false,
+}
+
+apex {
+    name: "com.android.hardware.biometrics.fingerprint.virtual",
+    manifest: "apex_manifest.json",
+    file_contexts: "apex_file_contexts",
+    key: "com.android.hardware.key",
+    certificate: ":com.android.hardware.certificate",
+    updatable: false,
+    vendor: true,
+
+    binaries: [
+        "android.hardware.biometrics.fingerprint-service.example",
+    ],
+    prebuilts: [
+        // init_rc
+        "fingerprint-example.rc",
+        // vintf_fragment
+        "fingerprint-example.xml",
+    ],
 }
diff --git a/biometrics/fingerprint/aidl/default/README.md b/biometrics/fingerprint/aidl/default/README.md
index 823cd18..4b4533a 100644
--- a/biometrics/fingerprint/aidl/default/README.md
+++ b/biometrics/fingerprint/aidl/default/README.md
@@ -11,12 +11,6 @@
 following to your device's `.mk` file to include it:
 
 ```
-PRODUCT_PACKAGES_DEBUG += android.hardware.biometrics.fingerprint-service.example
-```
-
-or add the following to include it as an apex:
-
-```
 PRODUCT_PACKAGES_DEBUG += com.android.hardware.biometrics.fingerprint.virtual
 ```
 
diff --git a/biometrics/fingerprint/aidl/default/apex/Android.bp b/biometrics/fingerprint/aidl/default/apex/Android.bp
deleted file mode 100644
index 75d84a9..0000000
--- a/biometrics/fingerprint/aidl/default/apex/Android.bp
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (C) 2023 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.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-apex {
-    name: "com.android.hardware.biometrics.fingerprint.virtual",
-    manifest: "manifest.json",
-    file_contexts: "file_contexts",
-    key: "com.android.hardware.key",
-    certificate: ":com.android.hardware.certificate",
-    updatable: false,
-    vendor: true,
-
-    binaries: [
-        "android.hardware.biometrics.fingerprint-service.example",
-    ],
-    prebuilts: [
-        // init_rc
-        "fingerprint-example-apex.rc",
-        // vintf_fragment
-        "fingerprint-example-apex.xml",
-    ],
-
-    overrides: [
-        "android.hardware.biometrics.fingerprint-service.example",
-    ],
-}
-
-genrule {
-    name: "gen-fingerprint-example-apex.rc",
-    srcs: [":fingerprint-example.rc"],
-    out: ["fingerprint-example-apex.rc"],
-    cmd: "sed -e 's@/vendor/bin/@/apex/com.android.hardware.biometrics.fingerprint.virtual/bin/@' $(in) > $(out)",
-}
-
-prebuilt_etc {
-    name: "fingerprint-example-apex.rc",
-    src: ":gen-fingerprint-example-apex.rc",
-    installable: false,
-}
-
-prebuilt_etc {
-    name: "fingerprint-example-apex.xml",
-    src: ":fingerprint-example.xml",
-    sub_dir: "vintf",
-    installable: false,
-}
diff --git a/biometrics/fingerprint/aidl/default/apex/file_contexts b/biometrics/fingerprint/aidl/default/apex_file_contexts
similarity index 100%
rename from biometrics/fingerprint/aidl/default/apex/file_contexts
rename to biometrics/fingerprint/aidl/default/apex_file_contexts
diff --git a/biometrics/fingerprint/aidl/default/apex/manifest.json b/biometrics/fingerprint/aidl/default/apex_manifest.json
similarity index 100%
rename from biometrics/fingerprint/aidl/default/apex/manifest.json
rename to biometrics/fingerprint/aidl/default/apex_manifest.json
diff --git a/biometrics/fingerprint/aidl/default/fingerprint-example.rc b/biometrics/fingerprint/aidl/default/fingerprint-example.rc
index ee4713c..da4ea45 100644
--- a/biometrics/fingerprint/aidl/default/fingerprint-example.rc
+++ b/biometrics/fingerprint/aidl/default/fingerprint-example.rc
@@ -1,4 +1,4 @@
-service vendor.fingerprint-example /vendor/bin/hw/android.hardware.biometrics.fingerprint-service.example
+service vendor.fingerprint-example /apex/com.android.hardware.biometrics.fingerprint.virtual/bin/hw/android.hardware.biometrics.fingerprint-service.example
     class hal
     user nobody
     group nobody
diff --git a/broadcastradio/aidl/Android.bp b/broadcastradio/aidl/Android.bp
index 3f89029..e8bc5eb 100644
--- a/broadcastradio/aidl/Android.bp
+++ b/broadcastradio/aidl/Android.bp
@@ -43,6 +43,6 @@
             imports: [],
         },
     ],
-    frozen: true,
+    frozen: false,
 
 }
diff --git a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AmFmRegionConfig.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AmFmRegionConfig.aidl
index fe8489c..b96def3 100644
--- a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AmFmRegionConfig.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AmFmRegionConfig.aidl
@@ -37,8 +37,8 @@
   android.hardware.broadcastradio.AmFmBandRange[] ranges;
   int fmDeemphasis;
   int fmRds;
-  const int DEEMPHASIS_D50 = (1 << 0);
-  const int DEEMPHASIS_D75 = (1 << 1);
-  const int RDS = (1 << 0);
-  const int RBDS = (1 << 1);
+  const int DEEMPHASIS_D50 = (1 << 0) /* 1 */;
+  const int DEEMPHASIS_D75 = (1 << 1) /* 2 */;
+  const int RDS = (1 << 0) /* 1 */;
+  const int RBDS = (1 << 1) /* 2 */;
 }
diff --git a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ConfigFlag.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ConfigFlag.aidl
index 98af437..d6d33bc 100644
--- a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ConfigFlag.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ConfigFlag.aidl
@@ -35,6 +35,9 @@
 @Backing(type="int") @JavaDerive(equals=true, toString=true) @VintfStability
 enum ConfigFlag {
   FORCE_MONO = 1,
+  /**
+   * @deprecated Use {link #FORCE_ANALOG_FM} instead
+   */
   FORCE_ANALOG,
   FORCE_DIGITAL,
   RDS_AF,
@@ -43,4 +46,6 @@
   DAB_FM_LINKING,
   DAB_DAB_SOFT_LINKING,
   DAB_FM_SOFT_LINKING,
+  FORCE_ANALOG_FM,
+  FORCE_ANALOG_AM,
 }
diff --git a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/HdSubChannel.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/HdSubChannel.aidl
new file mode 100644
index 0000000..dd06134
--- /dev/null
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/HdSubChannel.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.broadcastradio;
+@Backing(type="int") @JavaDerive(equals=true, toString=true) @VintfStability
+enum HdSubChannel {
+  HD1 = 0,
+  HD2 = 1,
+  HD3 = 2,
+  HD4 = 3,
+  HD5 = 4,
+  HD6 = 5,
+  HD7 = 6,
+  HD8 = 7,
+}
diff --git a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IdentifierType.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IdentifierType.aidl
index 4df272c..ed41af0 100644
--- a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IdentifierType.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IdentifierType.aidl
@@ -47,6 +47,13 @@
   DAB_FREQUENCY_KHZ,
   DRMO_SERVICE_ID,
   DRMO_FREQUENCY_KHZ,
-  SXM_SERVICE_ID = (DRMO_FREQUENCY_KHZ + 2),
+  /**
+   * @deprecated SiriusXM Satellite Radio is not supported.
+   */
+  SXM_SERVICE_ID = (DRMO_FREQUENCY_KHZ + 2) /* 12 */,
+  /**
+   * @deprecated SiriusXM Satellite Radio is not supported.
+   */
   SXM_CHANNEL,
+  HD_STATION_LOCATION,
 }
diff --git a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Metadata.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Metadata.aidl
index e02b6b1..b4a1efa 100644
--- a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Metadata.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Metadata.aidl
@@ -50,4 +50,12 @@
   String dabServiceNameShort;
   String dabComponentName;
   String dabComponentNameShort;
+  String genre;
+  String commentShortDescription;
+  String commentActualText;
+  String commercial;
+  String[] ufids;
+  String hdStationNameShort;
+  String hdStationNameLong;
+  int hdSubChannelsAvailable;
 }
diff --git a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramInfo.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramInfo.aidl
index b14023a..997cdd7 100644
--- a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramInfo.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramInfo.aidl
@@ -42,10 +42,13 @@
   int signalQuality;
   android.hardware.broadcastradio.Metadata[] metadata;
   android.hardware.broadcastradio.VendorKeyValue[] vendorInfo;
-  const int FLAG_LIVE = (1 << 0);
-  const int FLAG_MUTED = (1 << 1);
-  const int FLAG_TRAFFIC_PROGRAM = (1 << 2);
-  const int FLAG_TRAFFIC_ANNOUNCEMENT = (1 << 3);
-  const int FLAG_TUNABLE = (1 << 4);
-  const int FLAG_STEREO = (1 << 5);
+  const int FLAG_LIVE = (1 << 0) /* 1 */;
+  const int FLAG_MUTED = (1 << 1) /* 2 */;
+  const int FLAG_TRAFFIC_PROGRAM = (1 << 2) /* 4 */;
+  const int FLAG_TRAFFIC_ANNOUNCEMENT = (1 << 3) /* 8 */;
+  const int FLAG_TUNABLE = (1 << 4) /* 16 */;
+  const int FLAG_STEREO = (1 << 5) /* 32 */;
+  const int FLAG_SIGNAL_ACQUISITION = (1 << 6) /* 64 */;
+  const int FLAG_HD_SIS_ACQUISITION = (1 << 7) /* 128 */;
+  const int FLAG_HD_AUDIO_ACQUISITION = (1 << 8) /* 256 */;
 }
diff --git a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Result.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Result.aidl
index 8af74c7..b0fc018 100644
--- a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Result.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Result.aidl
@@ -34,7 +34,7 @@
 package android.hardware.broadcastradio;
 @Backing(type="int") @JavaDerive(equals=true, toString=true) @VintfStability
 enum Result {
-  OK,
+  OK = 0,
   INTERNAL_ERROR,
   INVALID_ARGUMENTS,
   INVALID_STATE,
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/ConfigFlag.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/ConfigFlag.aidl
index 11da39c..ddf60e0 100644
--- a/broadcastradio/aidl/android/hardware/broadcastradio/ConfigFlag.aidl
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/ConfigFlag.aidl
@@ -36,10 +36,12 @@
      * Forces the analog playback for the supporting radio technology.
      *
      * User may disable digital playback for FM HD Radio or hybrid FM/DAB with
-     * this option. This is purely user choice, ie. does not reflect digital-
+     * this option. This is purely user choice, i.e. does not reflect digital-
      * analog handover state managed from the HAL implementation side.
      *
-     * Some radio technologies may not support this, ie. DAB.
+     * Some radio technologies may not support this, i.e. DAB.
+     *
+     * @deprecated Use {link #FORCE_ANALOG_FM} instead
      */
     FORCE_ANALOG,
 
@@ -89,4 +91,26 @@
      * Enables DAB-FM soft-linking (related content).
      */
     DAB_FM_SOFT_LINKING,
+
+    /**
+     * Forces the FM analog playback for the supporting radio technology.
+     *
+     * User may disable FM digital playback for FM HD Radio or hybrid FM/DAB
+     * with this option. This is purely user choice, i.e. does not reflect
+     * digital-analog handover state managed from the HAL implementation side.
+     *
+     * Some radio technologies may not support this, i.e. DAB.
+     */
+    FORCE_ANALOG_FM,
+
+    /**
+     * Forces the AM analog playback for the supporting radio technology.
+     *
+     * User may disable AM digital playback for AM HD Radio or hybrid AM/DAB
+     * with this option. This is purely user choice, i.e. does not reflect
+     * digital-analog handover state managed from the HAL implementation side.
+     *
+     * Some radio technologies may not support this, i.e. DAB.
+     */
+    FORCE_ANALOG_AM,
 }
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/HdSubChannel.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/HdSubChannel.aidl
new file mode 100644
index 0000000..46a3e0c
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/HdSubChannel.aidl
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package android.hardware.broadcastradio;
+
+/**
+ * Index of HD radio subchannel.
+ */
+@VintfStability
+@Backing(type="int")
+@JavaDerive(equals=true, toString=true)
+enum HdSubChannel {
+    /**
+     * Index of HD radio subchannel 1.
+     *
+     * <p>There are at most 8 HD radio subchannels of 1-based om HD radio standard. It is
+     * converted to 0-based index. 0 is the index of main program service (MPS). 1 to 7
+     * are indexes of additional supplemental program services (SPS).
+     */
+    HD1 = 0,
+    /**
+     * {@see HD1}
+     */
+    HD2 = 1,
+    /**
+     * {@see HD1}
+     */
+    HD3 = 2,
+    /**
+     * {@see HD1}
+     */
+    HD4 = 3,
+    /**
+     * {@see HD1}
+     */
+    HD5 = 4,
+    /**
+     * {@see HD1}
+     */
+    HD6 = 5,
+    /**
+     * {@see HD1}
+     */
+    HD7 = 6,
+    /**
+     * {@see HD1}
+     */
+    HD8 = 7,
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl
index 646c502..4a95a41 100644
--- a/broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl
@@ -154,11 +154,42 @@
 
     /**
      * 32bit primary identifier for SiriusXM Satellite Radio.
+     *
+     * @deprecated SiriusXM Satellite Radio is not supported.
      */
     SXM_SERVICE_ID = DRMO_FREQUENCY_KHZ + 2,
 
     /**
      * 0-999 range
+     *
+     * @deprecated SiriusXM Satellite Radio is not supported.
      */
     SXM_CHANNEL,
+
+    /**
+     * 64bit additional identifier for HD Radio representing station location.
+     *
+     * Consists of (from the LSB):
+     * - 4 bit: Bits 0:3 of altitude
+     * - 13 bit: Fractional bits of longitude
+     * - 8 bit: Integer bits of longitude
+     * - 1 bit: 0 for east and 1 for west for longitude
+     * - 1 bit: 0, representing latitude
+     * - 5 bit: pad of zeros separating longitude and latitude
+     * - 4 bit: Bits 4:7 of altitude
+     * - 13 bit: Fractional bits of latitude
+     * - 8 bit: Integer bits of latitude
+     * - 1 bit: 0 for north and 1 for south for latitude
+     * - 1 bit: 1, representing latitude
+     * - 5 bit: pad of zeros
+     *
+     * This format is defined in NRSC-5-C document: SY_IDD_1020s.
+     *
+     * Due to Station ID abuse, some HD_STATION_ID_EXT identifiers may be not
+     * globally unique. To provide a best-effort solution, the station’s
+     * broadcast antenna containing the latitude and longitude may be carried
+     * as additional identifier and may be used by the tuner hardware to
+     * double-check tuning.
+     */
+    HD_STATION_LOCATION,
 }
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/Metadata.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/Metadata.aidl
index 7769b8c..0ce967f 100644
--- a/broadcastradio/aidl/android/hardware/broadcastradio/Metadata.aidl
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/Metadata.aidl
@@ -116,4 +116,70 @@
      * <p>Note: The string must be up to 8 characters long.
      */
     String dabComponentNameShort;
+
+    /**
+     * Genre of the current audio piece (string)
+     *
+     * <p>(see NRSC-G200-A and id3v2.3.0 for more info)
+     */
+    String genre;
+
+    /**
+     * Short context description of comment (string)
+     *
+     * <p>Comment could relate to the current audio program content, or it might
+     * be unrelated information that the station chooses to send. It is
+     * composed of short content description and actual text (see NRSC-G200-A
+     * and id3v2.3.0 for more info).
+     */
+    String commentShortDescription;
+
+    /**
+     * Actual text of comment (string)
+     *
+     * @see #commentShortDescription
+     */
+    String commentActualText;
+
+    /**
+     * Commercial (string)
+     *
+     * <p>Commercial is application specific and generally used to facilitate the
+     * sale of products and services (see NRSC-G200-A and id3v2.3.0 for more info).
+     */
+    String commercial;
+
+    /**
+     * HD Unique File Identifiers (Array of strings)
+     *
+     * <p>Unique File Identifier (UFID) can be used to transmit an alphanumeric
+     * identifier of the current content, or of an advertised product or service
+     * (see NRSC-G200-A and id3v2.3.0 for more info).
+     */
+    String[] ufids;
+
+    /**
+     * HD short station name or HD universal short station name
+     *
+     * <p>It can be up to 12 characters (see SY_IDD_1020s for more info).
+     */
+    String hdStationNameShort;
+
+    /**
+     * HD long station name, HD station slogan or HD station message
+     *
+     * <p>(see SY_IDD_1020s for more info)
+     */
+    String hdStationNameLong;
+
+    /**
+     * Bit mask of all HD Radio subchannels available (uint8_t)
+     *
+     * <p>Bit {@link HdSubChannel#HD1} from LSB represents the availability
+     * of HD-1 subchannel (main program service, MPS). Bits
+     * {@link HdSubChannel#HD2} to {@link HdSubChannel#HD8} from LSB represent
+     * HD-2 to HD-8 subchannel (supplemental program services, SPS)
+     * respectively.
+     */
+    int hdSubChannelsAvailable;
 }
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/ProgramInfo.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/ProgramInfo.aidl
index 7632c81..d4ccd01 100644
--- a/broadcastradio/aidl/android/hardware/broadcastradio/ProgramInfo.aidl
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/ProgramInfo.aidl
@@ -71,6 +71,23 @@
     const int FLAG_STEREO = 1 << 5;
 
     /**
+     * A signal has been acquired if this bit is set.
+     */
+
+    const int FLAG_SIGNAL_ACQUISITION = 1 << 6;
+    /**
+     * An HD Station Information Service (SIS) information is available if this
+     * bit is set.
+     */
+
+    const int FLAG_HD_SIS_ACQUISITION = 1 << 7;
+
+    /**
+     * An HD digital audio is available if this bit is set.
+     */
+    const int FLAG_HD_AUDIO_ACQUISITION = 1 << 8;
+
+    /**
      * An identifier used to point at the program (primarily to tune to it).
      *
      * This field is required - its type field must not be set to
@@ -153,7 +170,8 @@
      *
      * It can be a combination of {@link #FLAG_LIVE}, {@link #FLAG_MUTED},
      * {@link #FLAG_TRAFFIC_PROGRAM}, {@link #FLAG_TRAFFIC_ANNOUNCEMENT},
-     * {@link #FLAG_TUNABLE}, and {@link #FLAG_STEREO}.
+     * {@link #FLAG_TUNABLE}, {@link #FLAG_STEREO}, {@link #FLAG_SIGNAL_ACQUISITION},
+     * {@link #FLAG_HD_SIS_ACQUISITION}, and {@link #FLAG_HD_AUDIO_ACQUISITION}.
      */
     int infoFlags;
 
diff --git a/broadcastradio/aidl/default/Android.bp b/broadcastradio/aidl/default/Android.bp
index e95208c..743365a 100644
--- a/broadcastradio/aidl/default/Android.bp
+++ b/broadcastradio/aidl/default/Android.bp
@@ -26,11 +26,11 @@
 cc_defaults {
     name: "BroadcastRadioHalDefaults",
     static_libs: [
-        "android.hardware.broadcastradio@common-utils-aidl-lib",
+        "android.hardware.broadcastradio@common-utils-aidl-lib-V2",
         "android.hardware.broadcastradio@common-utils-lib",
     ],
     shared_libs: [
-        "android.hardware.broadcastradio-V1-ndk",
+        "android.hardware.broadcastradio-V2-ndk",
         "libbase",
         "libbinder_ndk",
         "liblog",
@@ -84,7 +84,7 @@
     ],
     static_libs: [
         "DefaultBroadcastRadioHal",
-        "android.hardware.broadcastradio-V1-ndk",
+        "android.hardware.broadcastradio-V2-ndk",
     ],
     srcs: [
         "fuzzer.cpp",
diff --git a/broadcastradio/aidl/default/BroadcastRadio.cpp b/broadcastradio/aidl/default/BroadcastRadio.cpp
index 8584921..54186b0 100644
--- a/broadcastradio/aidl/default/BroadcastRadio.cpp
+++ b/broadcastradio/aidl/default/BroadcastRadio.cpp
@@ -16,6 +16,7 @@
 
 #include "BroadcastRadio.h"
 #include <broadcastradio-utils-aidl/Utils.h>
+#include <broadcastradio-utils-aidl/UtilsV2.h>
 #include "resources.h"
 
 #include <aidl/android/hardware/broadcastradio/IdentifierType.h>
@@ -221,7 +222,7 @@
                 resultToInt(Result::NOT_SUPPORTED), "selector is not supported");
     }
 
-    if (!utils::isValid(program)) {
+    if (!utils::isValidV2(program)) {
         LOG(ERROR) << __func__ << ": selector is not valid: " << program.toString();
         return ScopedAStatus::fromServiceSpecificErrorWithMessage(
                 resultToInt(Result::INVALID_ARGUMENTS), "selector is not valid");
diff --git a/broadcastradio/aidl/default/broadcastradio-default.xml b/broadcastradio/aidl/default/broadcastradio-default.xml
index 1555822..a57b724 100644
--- a/broadcastradio/aidl/default/broadcastradio-default.xml
+++ b/broadcastradio/aidl/default/broadcastradio-default.xml
@@ -1,6 +1,7 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>android.hardware.broadcastradio</name>
+        <version>2</version>
         <fqname>IBroadcastRadio/amfm</fqname>
         <fqname>IBroadcastRadio/dab</fqname>
     </hal>
diff --git a/broadcastradio/aidl/vts/Android.bp b/broadcastradio/aidl/vts/Android.bp
index b60387e..87e48a9 100644
--- a/broadcastradio/aidl/vts/Android.bp
+++ b/broadcastradio/aidl/vts/Android.bp
@@ -35,8 +35,8 @@
         "libxml2",
     ],
     static_libs: [
-        "android.hardware.broadcastradio-V1-ndk",
-        "android.hardware.broadcastradio@common-utils-aidl-lib",
+        "android.hardware.broadcastradio-V2-ndk",
+        "android.hardware.broadcastradio@common-utils-aidl-lib-V2",
         "android.hardware.broadcastradio@vts-utils-lib",
         "libgmock",
     ],
diff --git a/broadcastradio/aidl/vts/src/VtsHalBroadcastradioAidlTargetTest.cpp b/broadcastradio/aidl/vts/src/VtsHalBroadcastradioAidlTargetTest.cpp
index 790d60b..72869cc 100644
--- a/broadcastradio/aidl/vts/src/VtsHalBroadcastradioAidlTargetTest.cpp
+++ b/broadcastradio/aidl/vts/src/VtsHalBroadcastradioAidlTargetTest.cpp
@@ -32,6 +32,7 @@
 #include <aidl/Gtest.h>
 #include <aidl/Vintf.h>
 #include <broadcastradio-utils-aidl/Utils.h>
+#include <broadcastradio-utils-aidl/UtilsV2.h>
 #include <cutils/bitops.h>
 #include <gmock/gmock.h>
 
@@ -50,7 +51,6 @@
 using ::aidl::android::hardware::broadcastradio::utils::resultToInt;
 using ::ndk::ScopedAStatus;
 using ::ndk::SharedRefBase;
-using ::std::string;
 using ::std::vector;
 using ::testing::_;
 using ::testing::AnyNumber;
@@ -73,20 +73,29 @@
         ConfigFlag::DAB_FM_SOFT_LINKING,
 };
 
-void printSkipped(const string& msg) {
+constexpr int32_t kAidlVersion1 = 1;
+constexpr int32_t kAidlVersion2 = 2;
+
+void printSkipped(const std::string& msg) {
     const auto testInfo = testing::UnitTest::GetInstance()->current_test_info();
     LOG(INFO) << "[  SKIPPED ] " << testInfo->test_case_name() << "." << testInfo->name()
               << " with message: " << msg;
 }
 
-bool isValidAmFmFreq(int64_t freq) {
+bool isValidAmFmFreq(int64_t freq, int aidlVersion) {
     ProgramIdentifier id = bcutils::makeIdentifier(IdentifierType::AMFM_FREQUENCY_KHZ, freq);
-    return bcutils::isValid(id);
+    if (aidlVersion == kAidlVersion1) {
+        return bcutils::isValid(id);
+    } else if (aidlVersion == kAidlVersion2) {
+        return bcutils::isValidV2(id);
+    }
+    LOG(ERROR) << "Unknown AIDL version " << aidlVersion;
+    return false;
 }
 
-void validateRange(const AmFmBandRange& range) {
-    EXPECT_TRUE(isValidAmFmFreq(range.lowerBound));
-    EXPECT_TRUE(isValidAmFmFreq(range.upperBound));
+void validateRange(const AmFmBandRange& range, int aidlVersion) {
+    EXPECT_TRUE(isValidAmFmFreq(range.lowerBound, aidlVersion));
+    EXPECT_TRUE(isValidAmFmFreq(range.upperBound, aidlVersion));
     EXPECT_LT(range.lowerBound, range.upperBound);
     EXPECT_GT(range.spacing, 0u);
     EXPECT_EQ((range.upperBound - range.lowerBound) % range.spacing, 0u);
@@ -142,7 +151,7 @@
 
 class TunerCallbackImpl final : public BnTunerCallback {
   public:
-    TunerCallbackImpl();
+    explicit TunerCallbackImpl(int32_t aidlVersion);
     ScopedAStatus onTuneFailed(Result result, const ProgramSelector& selector) override;
     ScopedAStatus onCurrentProgramInfoChanged(const ProgramInfo& info) override;
     ScopedAStatus onProgramListUpdated(const ProgramListChunk& chunk) override;
@@ -160,6 +169,7 @@
 
   private:
     std::mutex mLock;
+    int32_t mCallbackAidlVersion;
     bool mAntennaConnectionState GUARDED_BY(mLock);
     ProgramInfo mCurrentProgramInfo GUARDED_BY(mLock);
     bcutils::ProgramInfoSet mProgramList GUARDED_BY(mLock);
@@ -171,7 +181,7 @@
     MOCK_METHOD1(onListUpdated, ScopedAStatus(const vector<Announcement>&));
 };
 
-class BroadcastRadioHalTest : public testing::TestWithParam<string> {
+class BroadcastRadioHalTest : public testing::TestWithParam<std::string> {
   protected:
     void SetUp() override;
     void TearDown() override;
@@ -183,14 +193,17 @@
     std::shared_ptr<IBroadcastRadio> mModule;
     Properties mProperties;
     std::shared_ptr<TunerCallbackImpl> mCallback;
+    int32_t mAidlVersion;
 };
 
-MATCHER_P(InfoHasId, id, string(negation ? "does not contain" : "contains") + " " + id.toString()) {
+MATCHER_P(InfoHasId, id,
+          std::string(negation ? "does not contain" : "contains") + " " + id.toString()) {
     vector<int> ids = bcutils::getAllIds(arg.selector, id.type);
     return ids.end() != find(ids.begin(), ids.end(), id.value);
 }
 
-TunerCallbackImpl::TunerCallbackImpl() {
+TunerCallbackImpl::TunerCallbackImpl(int32_t aidlVersion) {
+    mCallbackAidlVersion = aidlVersion;
     mAntennaConnectionState = true;
 }
 
@@ -230,7 +243,12 @@
                 physically > IdentifierType::SXM_CHANNEL);
 
     if (logically == IdentifierType::AMFM_FREQUENCY_KHZ) {
-        std::optional<string> ps = bcutils::getMetadataString(info, Metadata::rdsPs);
+        std::optional<std::string> ps;
+        if (mCallbackAidlVersion == kAidlVersion1) {
+            ps = bcutils::getMetadataString(info, Metadata::rdsPs);
+        } else {
+            ps = bcutils::getMetadataStringV2(info, Metadata::rdsPs);
+        }
         if (ps.has_value()) {
             EXPECT_NE(::android::base::Trim(*ps), "")
                     << "Don't use empty RDS_PS as an indicator of missing RSD PS data.";
@@ -323,9 +341,13 @@
     EXPECT_FALSE(mProperties.product.empty());
     EXPECT_GT(mProperties.supportedIdentifierTypes.size(), 0u);
 
-    mCallback = SharedRefBase::make<TunerCallbackImpl>();
+    // get AIDL HAL version
+    ASSERT_TRUE(mModule->getInterfaceVersion(&mAidlVersion).isOk());
+    EXPECT_GE(mAidlVersion, kAidlVersion1);
+    EXPECT_LE(mAidlVersion, kAidlVersion2);
 
     // set callback
+    mCallback = SharedRefBase::make<TunerCallbackImpl>(mAidlVersion);
     EXPECT_TRUE(mModule->setTunerCallback(mCallback).isOk());
 }
 
@@ -443,7 +465,7 @@
 
     EXPECT_GT(config.ranges.size(), 0u);
     for (const auto& range : config.ranges) {
-        validateRange(range);
+        validateRange(range, mAidlVersion);
         EXPECT_EQ(range.seekSpacing % range.spacing, 0u);
         EXPECT_GE(range.seekSpacing, range.spacing);
     }
@@ -494,7 +516,7 @@
     EXPECT_GT(config.ranges.size(), 0u);
 
     for (const auto& range : config.ranges) {
-        validateRange(range);
+        validateRange(range, mAidlVersion);
         EXPECT_EQ(range.seekSpacing, 0u);
     }
 }
@@ -522,11 +544,17 @@
     std::regex re("^[A-Z0-9][A-Z0-9 ]{0,5}[A-Z0-9]$");
 
     for (const auto& entry : config) {
-        EXPECT_TRUE(std::regex_match(string(entry.label), re));
+        EXPECT_TRUE(std::regex_match(std::string(entry.label), re));
 
         ProgramIdentifier id =
                 bcutils::makeIdentifier(IdentifierType::DAB_FREQUENCY_KHZ, entry.frequencyKhz);
-        EXPECT_TRUE(bcutils::isValid(id));
+        if (mAidlVersion == kAidlVersion1) {
+            EXPECT_TRUE(bcutils::isValid(id));
+        } else if (mAidlVersion == kAidlVersion2) {
+            EXPECT_TRUE(bcutils::isValidV2(id));
+        } else {
+            LOG(ERROR) << "Unknown callback AIDL version " << mAidlVersion;
+        }
     }
 }
 
@@ -1175,10 +1203,21 @@
             continue;
         }
 
-        std::optional<string> name = bcutils::getMetadataString(program, Metadata::programName);
-        if (!name) {
-            name = bcutils::getMetadataString(program, Metadata::rdsPs);
+        std::optional<std::string> name;
+        if (mAidlVersion == kAidlVersion1) {
+            name = bcutils::getMetadataString(program, Metadata::programName);
+            if (!name) {
+                name = bcutils::getMetadataString(program, Metadata::rdsPs);
+            }
+        } else if (mAidlVersion == kAidlVersion2) {
+            name = bcutils::getMetadataStringV2(program, Metadata::programName);
+            if (!name) {
+                name = bcutils::getMetadataStringV2(program, Metadata::rdsPs);
+            }
+        } else {
+            LOG(ERROR) << "Unknown HAL AIDL version " << mAidlVersion;
         }
+
         ASSERT_TRUE(name.has_value());
 
         ProgramIdentifier expectedId = bcutils::makeHdRadioStationName(*name);
diff --git a/broadcastradio/common/utilsaidl/Android.bp b/broadcastradio/common/utilsaidl/Android.bp
index fa6de19..4ec635b 100644
--- a/broadcastradio/common/utilsaidl/Android.bp
+++ b/broadcastradio/common/utilsaidl/Android.bp
@@ -25,6 +25,29 @@
 
 cc_library_static {
     name: "android.hardware.broadcastradio@common-utils-aidl-lib",
+    defaults: [
+        "VtsBroadcastRadioDefaults",
+    ],
+    shared_libs: [
+        "android.hardware.broadcastradio-V1-ndk",
+    ],
+}
+
+cc_library_static {
+    name: "android.hardware.broadcastradio@common-utils-aidl-lib-V2",
+    defaults: [
+        "VtsBroadcastRadioDefaults",
+    ],
+    srcs: [
+        "src/UtilsV2.cpp",
+    ],
+    shared_libs: [
+        "android.hardware.broadcastradio-V2-ndk",
+    ],
+}
+
+cc_defaults {
+    name: "VtsBroadcastRadioDefaults",
     vendor_available: true,
     relative_install_path: "hw",
     cflags: [
@@ -37,11 +60,10 @@
         "-std=c++1z",
     ],
     srcs: [
-        "Utils.cpp",
+        "src/Utils.cpp",
     ],
     export_include_dirs: ["include"],
     shared_libs: [
-        "android.hardware.broadcastradio-V1-ndk",
         "libbase",
     ],
     static_libs: [
diff --git a/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/Utils.h b/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/Utils.h
index ee85a17..b6fb33f 100644
--- a/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/Utils.h
+++ b/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/Utils.h
@@ -17,7 +17,6 @@
 #pragma once
 
 #include <aidl/android/hardware/broadcastradio/IdentifierType.h>
-#include <aidl/android/hardware/broadcastradio/Metadata.h>
 #include <aidl/android/hardware/broadcastradio/ProgramFilter.h>
 #include <aidl/android/hardware/broadcastradio/ProgramIdentifier.h>
 #include <aidl/android/hardware/broadcastradio/ProgramInfo.h>
diff --git a/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/UtilsV2.h b/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/UtilsV2.h
new file mode 100644
index 0000000..e411aa4
--- /dev/null
+++ b/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/UtilsV2.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/broadcastradio/IdentifierType.h>
+#include <aidl/android/hardware/broadcastradio/Metadata.h>
+#include <aidl/android/hardware/broadcastradio/ProgramIdentifier.h>
+#include <aidl/android/hardware/broadcastradio/ProgramInfo.h>
+#include <aidl/android/hardware/broadcastradio/ProgramSelector.h>
+
+namespace aidl::android::hardware::broadcastradio {
+
+namespace utils {
+
+bool isValidV2(const ProgramIdentifier& id);
+bool isValidV2(const ProgramSelector& sel);
+std::optional<std::string> getMetadataStringV2(const ProgramInfo& info, const Metadata::Tag& tag);
+
+}  // namespace utils
+
+}  // namespace aidl::android::hardware::broadcastradio
diff --git a/broadcastradio/common/utilsaidl/Utils.cpp b/broadcastradio/common/utilsaidl/src/Utils.cpp
similarity index 95%
rename from broadcastradio/common/utilsaidl/Utils.cpp
rename to broadcastradio/common/utilsaidl/src/Utils.cpp
index de4f529..2875318 100644
--- a/broadcastradio/common/utilsaidl/Utils.cpp
+++ b/broadcastradio/common/utilsaidl/src/Utils.cpp
@@ -31,7 +31,6 @@
 namespace {
 
 using ::android::base::EqualsIgnoreCase;
-using ::std::string;
 using ::std::vector;
 
 const int64_t kValueForNotFoundIdentifier = 0;
@@ -207,7 +206,7 @@
     uint64_t val = static_cast<uint64_t>(id.value);
     bool valid = true;
 
-    auto expect = [&valid](bool condition, const string& message) {
+    auto expect = [&valid](bool condition, const std::string& message) {
         if (!condition) {
             valid = false;
             LOG(ERROR) << "identifier not valid, expected " << message;
@@ -278,9 +277,9 @@
         case IdentifierType::SXM_CHANNEL:
             expect(val < 1000u, "SXM channel < 1000");
             break;
-        case IdentifierType::VENDOR_START:
-        case IdentifierType::VENDOR_END:
-            // skip
+        default:
+            expect(id.type >= IdentifierType::VENDOR_START && id.type <= IdentifierType::VENDOR_END,
+                   "Undefined identifier type");
             break;
     }
 
@@ -452,10 +451,10 @@
     return metadataString;
 }
 
-ProgramIdentifier makeHdRadioStationName(const string& name) {
+ProgramIdentifier makeHdRadioStationName(const std::string& name) {
     constexpr size_t maxlen = 8;
 
-    string shortName;
+    std::string shortName;
     shortName.reserve(maxlen);
 
     const auto& loc = std::locale::classic();
@@ -484,7 +483,7 @@
     return static_cast<IdentifierType>(typeAsInt);
 }
 
-bool parseArgInt(const string& s, int* out) {
+bool parseArgInt(const std::string& s, int* out) {
     return ::android::base::ParseInt(s, out);
 }
 
@@ -492,7 +491,7 @@
     return ::android::base::ParseInt(s, out);
 }
 
-bool parseArgBool(const string& s, bool* out) {
+bool parseArgBool(const std::string& s, bool* out) {
     if (EqualsIgnoreCase(s, "true")) {
         *out = true;
     } else if (EqualsIgnoreCase(s, "false")) {
@@ -503,7 +502,7 @@
     return true;
 }
 
-bool parseArgDirection(const string& s, bool* out) {
+bool parseArgDirection(const std::string& s, bool* out) {
     if (EqualsIgnoreCase(s, "up")) {
         *out = true;
     } else if (EqualsIgnoreCase(s, "down")) {
@@ -514,8 +513,8 @@
     return true;
 }
 
-bool parseArgIdentifierTypeArray(const string& s, vector<IdentifierType>* out) {
-    for (const string& val : ::android::base::Split(s, ",")) {
+bool parseArgIdentifierTypeArray(const std::string& s, vector<IdentifierType>* out) {
+    for (const std::string& val : ::android::base::Split(s, ",")) {
         int outInt;
         if (!parseArgInt(val, &outInt)) {
             return false;
@@ -526,8 +525,8 @@
 }
 
 bool parseProgramIdentifierList(const std::string& s, vector<ProgramIdentifier>* out) {
-    for (const string& idStr : ::android::base::Split(s, ",")) {
-        const vector<string> idStrPair = ::android::base::Split(idStr, ":");
+    for (const std::string& idStr : ::android::base::Split(s, ",")) {
+        const vector<std::string> idStrPair = ::android::base::Split(idStr, ":");
         if (idStrPair.size() != 2) {
             return false;
         }
diff --git a/broadcastradio/common/utilsaidl/src/UtilsV2.cpp b/broadcastradio/common/utilsaidl/src/UtilsV2.cpp
new file mode 100644
index 0000000..ef739df
--- /dev/null
+++ b/broadcastradio/common/utilsaidl/src/UtilsV2.cpp
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2023 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 "BcRadioAidlDef.utilsV2"
+
+#include "broadcastradio-utils-aidl/UtilsV2.h"
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+namespace aidl::android::hardware::broadcastradio {
+
+namespace utils {
+
+bool isValidV2(const ProgramIdentifier& id) {
+    uint64_t val = static_cast<uint64_t>(id.value);
+    bool valid = true;
+
+    auto expect = [&valid](bool condition, const std::string& message) {
+        if (!condition) {
+            valid = false;
+            LOG(ERROR) << "identifier not valid, expected " << message;
+        }
+    };
+
+    switch (id.type) {
+        case IdentifierType::INVALID:
+            expect(false, "IdentifierType::INVALID");
+            break;
+        case IdentifierType::DAB_FREQUENCY_KHZ:
+            expect(val > 100000u, "f > 100MHz");
+            [[fallthrough]];
+        case IdentifierType::AMFM_FREQUENCY_KHZ:
+        case IdentifierType::DRMO_FREQUENCY_KHZ:
+            expect(val > 100u, "f > 100kHz");
+            expect(val < 10000000u, "f < 10GHz");
+            break;
+        case IdentifierType::RDS_PI:
+            expect(val != 0u, "RDS PI != 0");
+            expect(val <= 0xFFFFu, "16bit id");
+            break;
+        case IdentifierType::HD_STATION_ID_EXT: {
+            uint64_t stationId = val & 0xFFFFFFFF;  // 32bit
+            val >>= 32;
+            uint64_t subchannel = val & 0xF;  // 4bit
+            val >>= 4;
+            uint64_t freq = val & 0x3FFFF;  // 18bit
+            expect(stationId != 0u, "HD station id != 0");
+            expect(subchannel < 8u, "HD subch < 8");
+            expect(freq > 100u, "f > 100kHz");
+            expect(freq < 10000000u, "f < 10GHz");
+            break;
+        }
+        case IdentifierType::HD_STATION_NAME: {
+            while (val > 0) {
+                char ch = static_cast<char>(val & 0xFF);
+                val >>= 8;
+                expect((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z'),
+                       "HD_STATION_NAME does not match [A-Z0-9]+");
+            }
+            break;
+        }
+        case IdentifierType::DAB_SID_EXT: {
+            uint64_t sid = val & 0xFFFFFFFF;  // 32bit
+            val >>= 32;
+            uint64_t ecc = val & 0xFF;  // 8bit
+            expect(sid != 0u, "DAB SId != 0");
+            expect(ecc >= 0xA0u && ecc <= 0xF6u, "Invalid ECC, see ETSI TS 101 756 V2.1.1");
+            break;
+        }
+        case IdentifierType::DAB_ENSEMBLE:
+            expect(val != 0u, "DAB ensemble != 0");
+            expect(val <= 0xFFFFu, "16bit id");
+            break;
+        case IdentifierType::DAB_SCID:
+            expect(val > 0xFu, "12bit SCId (not 4bit SCIdS)");
+            expect(val <= 0xFFFu, "12bit id");
+            break;
+        case IdentifierType::DRMO_SERVICE_ID:
+            expect(val != 0u, "DRM SId != 0");
+            expect(val <= 0xFFFFFFu, "24bit id");
+            break;
+        case IdentifierType::SXM_SERVICE_ID:
+            expect(val != 0u, "SXM SId != 0");
+            expect(val <= 0xFFFFFFFFu, "32bit id");
+            break;
+        case IdentifierType::SXM_CHANNEL:
+            expect(val < 1000u, "SXM channel < 1000");
+            break;
+        case IdentifierType::HD_STATION_LOCATION: {
+            uint64_t latitudeBit = val & 0x1;
+            expect(latitudeBit == 1u, "Latitude comes first");
+            val >>= 27;
+            uint64_t latitudePad = val & 0x1Fu;
+            expect(latitudePad == 0u, "Latitude padding");
+            val >>= 5;
+            uint64_t longitudeBit = val & 0x1;
+            expect(longitudeBit == 1u, "Longitude comes next");
+            val >>= 27;
+            uint64_t longitudePad = val & 0x1Fu;
+            expect(longitudePad == 0u, "Latitude padding");
+            break;
+        }
+        default:
+            expect(id.type >= IdentifierType::VENDOR_START && id.type <= IdentifierType::VENDOR_END,
+                   "Undefined identifier type");
+            break;
+    }
+
+    return valid;
+}
+
+bool isValidV2(const ProgramSelector& sel) {
+    if (sel.primaryId.type != IdentifierType::AMFM_FREQUENCY_KHZ &&
+        sel.primaryId.type != IdentifierType::RDS_PI &&
+        sel.primaryId.type != IdentifierType::HD_STATION_ID_EXT &&
+        sel.primaryId.type != IdentifierType::DAB_SID_EXT &&
+        sel.primaryId.type != IdentifierType::DRMO_SERVICE_ID &&
+        sel.primaryId.type != IdentifierType::SXM_SERVICE_ID &&
+        (sel.primaryId.type < IdentifierType::VENDOR_START ||
+         sel.primaryId.type > IdentifierType::VENDOR_END)) {
+        return false;
+    }
+    return isValidV2(sel.primaryId);
+}
+
+std::optional<std::string> getMetadataStringV2(const ProgramInfo& info, const Metadata::Tag& tag) {
+    auto isRdsPs = [tag](const Metadata& item) { return item.getTag() == tag; };
+
+    auto it = std::find_if(info.metadata.begin(), info.metadata.end(), isRdsPs);
+    if (it == info.metadata.end()) {
+        return std::nullopt;
+    }
+
+    std::string metadataString;
+    switch (it->getTag()) {
+        case Metadata::rdsPs:
+            metadataString = it->get<Metadata::rdsPs>();
+            break;
+        case Metadata::rdsPty:
+            metadataString = std::to_string(it->get<Metadata::rdsPty>());
+            break;
+        case Metadata::rbdsPty:
+            metadataString = std::to_string(it->get<Metadata::rbdsPty>());
+            break;
+        case Metadata::rdsRt:
+            metadataString = it->get<Metadata::rdsRt>();
+            break;
+        case Metadata::songTitle:
+            metadataString = it->get<Metadata::songTitle>();
+            break;
+        case Metadata::songArtist:
+            metadataString = it->get<Metadata::songArtist>();
+            break;
+        case Metadata::songAlbum:
+            metadataString = it->get<Metadata::songAlbum>();
+            break;
+        case Metadata::stationIcon:
+            metadataString = std::to_string(it->get<Metadata::stationIcon>());
+            break;
+        case Metadata::albumArt:
+            metadataString = std::to_string(it->get<Metadata::albumArt>());
+            break;
+        case Metadata::programName:
+            metadataString = it->get<Metadata::programName>();
+            break;
+        case Metadata::dabEnsembleName:
+            metadataString = it->get<Metadata::dabEnsembleName>();
+            break;
+        case Metadata::dabEnsembleNameShort:
+            metadataString = it->get<Metadata::dabEnsembleNameShort>();
+            break;
+        case Metadata::dabServiceName:
+            metadataString = it->get<Metadata::dabServiceName>();
+            break;
+        case Metadata::dabServiceNameShort:
+            metadataString = it->get<Metadata::dabServiceNameShort>();
+            break;
+        case Metadata::dabComponentName:
+            metadataString = it->get<Metadata::dabComponentName>();
+            break;
+        case Metadata::dabComponentNameShort:
+            metadataString = it->get<Metadata::dabComponentNameShort>();
+            break;
+        case Metadata::genre:
+            metadataString = it->get<Metadata::genre>();
+            break;
+        case Metadata::commentShortDescription:
+            metadataString = it->get<Metadata::commentShortDescription>();
+            break;
+        case Metadata::commentActualText:
+            metadataString = it->get<Metadata::commentActualText>();
+            break;
+        case Metadata::commercial:
+            metadataString = it->get<Metadata::commercial>();
+            break;
+        case Metadata::ufids: {
+            auto& ufids = it->get<Metadata::ufids>();
+            metadataString = "[";
+            for (const auto& ufid : ufids) {
+                metadataString += std::string(ufid) + ",";
+            }
+            if (ufids.empty()) {
+                metadataString += "]";
+            } else {
+                metadataString[metadataString.size() - 1] = ']';
+            }
+        } break;
+        case Metadata::hdStationNameShort:
+            metadataString = it->get<Metadata::hdStationNameShort>();
+            break;
+        case Metadata::hdStationNameLong:
+            metadataString = it->get<Metadata::hdStationNameLong>();
+            break;
+        case Metadata::hdSubChannelsAvailable:
+            metadataString = std::to_string(it->get<Metadata::hdSubChannelsAvailable>());
+            break;
+        default:
+            LOG(ERROR) << "Metadata " << it->toString() << " is not converted.";
+            return std::nullopt;
+    }
+    return metadataString;
+}
+
+}  // namespace utils
+
+}  // namespace aidl::android::hardware::broadcastradio
diff --git a/cas/aidl/default/Android.bp b/cas/aidl/default/Android.bp
index 34ecbf2..06e167c 100644
--- a/cas/aidl/default/Android.bp
+++ b/cas/aidl/default/Android.bp
@@ -80,7 +80,8 @@
 
 cc_fuzz {
     name: "android.hardware.cas-service_fuzzer",
-    vendor: true,
+    // TODO(b/307611931): avoid fuzzing on vendor until hermiticity issue is fixed
+    // vendor: true,
 
     defaults: ["service_fuzzer_defaults"],
     srcs: ["fuzzer.cpp"],
diff --git a/compatibility_matrices/compatibility_matrix.9.xml b/compatibility_matrices/compatibility_matrix.9.xml
index 75915e3..6bda4a1 100644
--- a/compatibility_matrices/compatibility_matrix.9.xml
+++ b/compatibility_matrices/compatibility_matrix.9.xml
@@ -157,6 +157,7 @@
     </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.broadcastradio</name>
+        <version>1-2</version>
         <interface>
             <name>IBroadcastRadio</name>
             <regex-instance>.*</regex-instance>
@@ -240,15 +241,6 @@
             <instance>default</instance>
         </interface>
     </hal>
-    <!-- Either the native or the HIDL mapper HAL must exist on the device -->
-    <hal format="hidl" optional="true">
-        <name>android.hardware.graphics.mapper</name>
-        <version>4.0</version>
-        <interface>
-            <name>IMapper</name>
-            <instance>default</instance>
-        </interface>
-    </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.health</name>
         <version>1-2</version>
@@ -511,6 +503,14 @@
         </interface>
     </hal>
     <hal format="aidl" optional="true" updatable-via-apex="true">
+        <name>android.hardware.security.authgraph</name>
+        <version>1</version>
+        <interface>
+            <name>IAuthGraphKeyExchange</name>
+            <instance>nonsecure</instance>
+        </interface>
+    </hal>
+    <hal format="aidl" optional="true" updatable-via-apex="true">
         <name>android.hardware.security.secureclock</name>
         <version>1</version>
         <interface>
@@ -678,7 +678,7 @@
             <instance>default</instance>
         </interface>
     </hal>
-    <!-- Either the native or the HIDL mapper HAL must exist on the device -->
+    <!-- The native mapper HAL must exist on the device -->
     <hal format="native" optional="true">
         <name>mapper</name>
         <version>5.0</version>
diff --git a/gatekeeper/aidl/Android.bp b/gatekeeper/aidl/Android.bp
index b050f6a..169a7d5 100644
--- a/gatekeeper/aidl/Android.bp
+++ b/gatekeeper/aidl/Android.bp
@@ -25,6 +25,9 @@
         cpp: {
             enabled: false,
         },
+        rust: {
+            enabled: true,
+        },
     },
     versions_with_info: [
         {
diff --git a/gnss/aidl/android/hardware/gnss/GnssSignalType.aidl b/gnss/aidl/android/hardware/gnss/GnssSignalType.aidl
index c66d9e8..69de750 100644
--- a/gnss/aidl/android/hardware/gnss/GnssSignalType.aidl
+++ b/gnss/aidl/android/hardware/gnss/GnssSignalType.aidl
@@ -46,27 +46,28 @@
     double carrierFrequencyHz;
 
     /**
-     * GNSS signal code type "A" representing GALILEO E1A, GALILEO E6A, IRNSS L5A SPS, IRNSS SA SPS,
+     * GNSS signal code type "A" representing GALILEO E1A, GALILEO E6A, NavIC L5A SPS, NavIC SA SPS,
      * GLONASS G1a L1OCd, GLONASS G2a L2CSI.
      */
     const @utf8InCpp String CODE_TYPE_A = "A";
 
     /**
-     * GNSS signal code type "B" representing GALILEO E1B, GALILEO E6B, IRNSS L5B RS (D),
-     * IRNSS SB RS (D), GLONASS G1a L1OCp, GLONASS G2a L2OCp, QZSS L1Sb.
+     * GNSS signal code type "B" representing GALILEO E1B, GALILEO E6B, NavIC L5B RS (D),
+     * NavIC SB RS (D), GLONASS G1a L1OCp, GLONASS G2a L2OCp, QZSS L1Sb.
      */
     const @utf8InCpp String CODE_TYPE_B = "B";
 
     /**
      * GNSS signal code type "C" representing GPS L1 C/A,  GPS L2 C/A, GLONASS G1 C/A,
-     * GLONASS G2 C/A, GALILEO E1C, GALILEO E6C, SBAS L1 C/A, QZSS L1 C/A, IRNSS L5C RS (P),
-     * IRNSS SC RS (P).
+     * GLONASS G2 C/A, GALILEO E1C, GALILEO E6C, SBAS L1 C/A, QZSS L1 C/A, NavIC L5C RS (P),
+     * NavIC SC RS (P).
      */
     const @utf8InCpp String CODE_TYPE_C = "C";
 
     /**
      * GNSS signal code type "D" representing GPS L2 (L1(C/A) + (P2-P1) (semi-codeless)),
-     * QZSS L5S(I), BDS B1C Data, BDS B2a Data, BDS B2b Data, BDS B2 (B2a+B2b) Data, BDS B3a Data.
+     * QZSS L5S(I), BDS B1C Data, BDS B2a Data, BDS B2b Data, BDS B2 (B2a+B2b) Data, BDS B3a Data,
+     * NavIC L1 Data.
      */
     const @utf8InCpp String CODE_TYPE_D = "D";
 
@@ -100,7 +101,7 @@
     /**
      * GNSS signal code type "P" representing GPS L1P, GPS L2P, GLONASS G1P, GLONASS G2P,
      * BDS B1C Pilot, BDS B2a Pilot, BDS B2b Pilot, BDS B2 (B2a+B2b) Pilot, BDS B3a Pilot,
-     * QZSS L5S(Q).
+     * QZSS L5S(Q), NavIC L1 Pilot.
      */
     const @utf8InCpp String CODE_TYPE_P = "P";
 
@@ -127,7 +128,7 @@
      * GALILEO E5a (I+Q), GALILEO E5b (I+Q), GALILEO E5a+b (I+Q), GALILEO E6 (B+C), SBAS L5 (I+Q),
      * QZSS L1C (D+P), QZSS L2C (M+L), QZSS L5 (I+Q), QZSS L6 (D+P), BDS B1 (I+Q),
      * BDS B1C Data+Pilot, BDS B2a Data+Pilot, BDS B2 (I+Q), BDS B2 (B2a+B2b) Data+Pilot,
-     * BDS B3 (I+Q), IRNSS L5 (B+C), IRNSS S (B+C).
+     * BDS B3 (I+Q), NavIC L5 (B+C), NavIC S (B+C), NavIC L1 Data+Pilot.
      */
     const @utf8InCpp String CODE_TYPE_X = "X";
 
diff --git a/gnss/aidl/default/Gnss.cpp b/gnss/aidl/default/Gnss.cpp
index f1b9cbf..c31f991 100644
--- a/gnss/aidl/default/Gnss.cpp
+++ b/gnss/aidl/default/Gnss.cpp
@@ -115,7 +115,9 @@
     mGnssMeasurementInterface->setLocationEnabled(true);
     this->reportGnssStatusValue(IGnssCallback::GnssStatusValue::SESSION_BEGIN);
     mThread = std::thread([this]() {
-        this->reportSvStatus();
+        if (!mGnssMeasurementEnabled || mMinIntervalMs <= mGnssMeasurementIntervalMs) {
+            this->reportSvStatus();
+        }
         if (!mFirstFixReceived) {
             std::this_thread::sleep_for(std::chrono::milliseconds(TTFF_MILLIS));
             mFirstFixReceived = true;
@@ -124,7 +126,9 @@
             if (!mIsActive) {
                 break;
             }
-            this->reportSvStatus();
+            if (!mGnssMeasurementEnabled || mMinIntervalMs <= mGnssMeasurementIntervalMs) {
+                this->reportSvStatus();
+            }
             this->reportNmea();
 
             auto currentLocation = getLocationFromHW();
@@ -386,4 +390,12 @@
     return ndk::ScopedAStatus::ok();
 }
 
+void Gnss::setGnssMeasurementEnabled(const bool enabled) {
+    mGnssMeasurementEnabled = enabled;
+}
+
+void Gnss::setGnssMeasurementInterval(const long intervalMs) {
+    mGnssMeasurementIntervalMs = intervalMs;
+}
+
 }  // namespace aidl::android::hardware::gnss
diff --git a/gnss/aidl/default/Gnss.h b/gnss/aidl/default/Gnss.h
index 00540cd..245d607 100644
--- a/gnss/aidl/default/Gnss.h
+++ b/gnss/aidl/default/Gnss.h
@@ -85,6 +85,8 @@
             override;
 
     void reportSvStatus() const;
+    void setGnssMeasurementEnabled(const bool enabled);
+    void setGnssMeasurementInterval(const long intervalMs);
     std::shared_ptr<GnssConfiguration> mGnssConfiguration;
     std::shared_ptr<GnssPowerIndication> mGnssPowerIndication;
     std::shared_ptr<GnssMeasurementInterface> mGnssMeasurementInterface;
@@ -101,10 +103,12 @@
     static std::shared_ptr<IGnssCallback> sGnssCallback;
 
     std::atomic<long> mMinIntervalMs;
+    std::atomic<long> mGnssMeasurementIntervalMs;
     std::atomic<bool> mIsActive;
     std::atomic<bool> mIsSvStatusActive;
     std::atomic<bool> mIsNmeaActive;
     std::atomic<bool> mFirstFixReceived;
+    std::atomic<bool> mGnssMeasurementEnabled;
     std::thread mThread;
     ::android::hardware::gnss::common::ThreadBlocker mThreadBlocker;
 
diff --git a/gnss/aidl/default/GnssMeasurementInterface.cpp b/gnss/aidl/default/GnssMeasurementInterface.cpp
index f7e4d4a..f324213 100644
--- a/gnss/aidl/default/GnssMeasurementInterface.cpp
+++ b/gnss/aidl/default/GnssMeasurementInterface.cpp
@@ -76,6 +76,7 @@
         stop();
     }
     mIntervalMs = std::max(options.intervalMs, 1000);
+    mGnss->setGnssMeasurementInterval(mIntervalMs);
     start(options.enableCorrVecOutputs, options.enableFullTracking);
 
     return ndk::ScopedAStatus::ok();
@@ -104,6 +105,7 @@
     }
 
     mIsActive = true;
+    mGnss->setGnssMeasurementEnabled(true);
     mThreads.emplace_back(std::thread([this, enableCorrVecOutputs, enableFullTracking]() {
         waitForStoppingThreads();
         mThreadBlocker.reset();
@@ -129,7 +131,7 @@
                 auto measurement =
                         Utils::getMockMeasurement(enableCorrVecOutputs, enableFullTracking);
                 this->reportMeasurement(measurement);
-                if (!mLocationEnabled) {
+                if (!mLocationEnabled || mLocationIntervalMs > mIntervalMs) {
                     mGnss->reportSvStatus();
                 }
             }
@@ -142,6 +144,7 @@
 void GnssMeasurementInterface::stop() {
     ALOGD("stop");
     mIsActive = false;
+    mGnss->setGnssMeasurementEnabled(false);
     mThreadBlocker.notify();
     for (auto iter = mThreads.begin(); iter != mThreads.end(); ++iter) {
         if (iter->joinable()) {
diff --git a/gnss/aidl/vts/gnss_hal_test_cases.cpp b/gnss/aidl/vts/gnss_hal_test_cases.cpp
index aa8bdfd..9381a0a 100644
--- a/gnss/aidl/vts/gnss_hal_test_cases.cpp
+++ b/gnss/aidl/vts/gnss_hal_test_cases.cpp
@@ -1430,13 +1430,13 @@
         startMeasurementWithInterval(intervals[i], iGnssMeasurement, callback);
 
         std::vector<int> measurementDeltas;
-        std::vector<int> svInfoListTimestampsDeltas;
+        std::vector<int> svInfoListDeltas;
 
         collectMeasurementIntervals(callback, numEvents[i], /* timeoutSeconds= */ 10,
                                     measurementDeltas);
         if (aidl_gnss_hal_->getInterfaceVersion() >= 3) {
-            collectSvInfoListTimestamps(numEvents[i], /* timeoutSeconds= */ 10,
-                                        svInfoListTimestampsDeltas);
+            collectSvInfoListTimestamps(numEvents[i], /* timeoutSeconds= */ 10, svInfoListDeltas);
+            EXPECT_TRUE(aidl_gnss_cb_->sv_info_list_cbq_.size() > 0);
         }
         status = iGnssMeasurement->close();
         ASSERT_TRUE(status.isOk());
@@ -1444,8 +1444,7 @@
         assertMeanAndStdev(intervals[i], measurementDeltas);
 
         if (aidl_gnss_hal_->getInterfaceVersion() >= 3) {
-            assertMeanAndStdev(intervals[i], svInfoListTimestampsDeltas);
-            EXPECT_TRUE(aidl_gnss_cb_->sv_info_list_cbq_.size() > 0);
+            assertMeanAndStdev(intervals[i], svInfoListDeltas);
         }
     }
 }
@@ -1477,13 +1476,25 @@
         auto callback = sp<GnssMeasurementCallbackAidl>::make();
         startMeasurementWithInterval(intervalMs, iGnssMeasurement, callback);
 
-        std::vector<int> deltas;
-        collectMeasurementIntervals(callback, /*numEvents=*/10, /*timeoutSeconds=*/10, deltas);
+        std::vector<int> measurementDeltas;
+        std::vector<int> svInfoListDeltas;
+
+        collectMeasurementIntervals(callback, /*numEvents=*/10, /*timeoutSeconds=*/10,
+                                    measurementDeltas);
+        if (aidl_gnss_hal_->getInterfaceVersion() >= 3) {
+            collectSvInfoListTimestamps(/*numEvents=*/10, /* timeoutSeconds= */ 10,
+                                        svInfoListDeltas);
+            EXPECT_TRUE(aidl_gnss_cb_->sv_info_list_cbq_.size() > 0);
+        }
 
         status = iGnssMeasurement->close();
         ASSERT_TRUE(status.isOk());
 
-        assertMeanAndStdev(locationIntervalMs, deltas);
+        assertMeanAndStdev(locationIntervalMs, measurementDeltas);
+        if (aidl_gnss_hal_->getInterfaceVersion() >= 3) {
+            // Verify the SvStatus interval is 1s (not 2s)
+            assertMeanAndStdev(locationIntervalMs, svInfoListDeltas);
+        }
     }
     StopAndClearLocations();
 }
@@ -1516,16 +1527,37 @@
 
         // Start location and verify the measurements are received at 1Hz
         StartAndCheckFirstLocation(locationIntervalMs, /* lowPowerMode= */ false);
-        std::vector<int> deltas;
-        collectMeasurementIntervals(callback, /*numEvents=*/10, kFirstMeasTimeoutSec, deltas);
-        assertMeanAndStdev(locationIntervalMs, deltas);
+        std::vector<int> measurementDeltas;
+        std::vector<int> svInfoListDeltas;
+        collectMeasurementIntervals(callback, /*numEvents=*/10, kFirstMeasTimeoutSec,
+                                    measurementDeltas);
+        assertMeanAndStdev(locationIntervalMs, measurementDeltas);
+        if (aidl_gnss_hal_->getInterfaceVersion() >= 3) {
+            collectSvInfoListTimestamps(/*numEvents=*/10, /* timeoutSeconds= */ 10,
+                                        svInfoListDeltas);
+            EXPECT_TRUE(aidl_gnss_cb_->sv_info_list_cbq_.size() > 0);
+            // Verify the SvStatus intervals are at 1s interval
+            assertMeanAndStdev(locationIntervalMs, svInfoListDeltas);
+        }
 
         // Stop location request and verify the measurements are received at 2s intervals
         StopAndClearLocations();
-        callback->gnss_data_cbq_.reset();
-        deltas.clear();
-        collectMeasurementIntervals(callback, /*numEvents=*/5, kFirstMeasTimeoutSec, deltas);
-        assertMeanAndStdev(intervalMs, deltas);
+        measurementDeltas.clear();
+        collectMeasurementIntervals(callback, /*numEvents=*/5, kFirstMeasTimeoutSec,
+                                    measurementDeltas);
+        assertMeanAndStdev(intervalMs, measurementDeltas);
+
+        if (aidl_gnss_hal_->getInterfaceVersion() >= 3) {
+            svInfoListDeltas.clear();
+            collectSvInfoListTimestamps(/*numEvents=*/5, /* timeoutSeconds= */ 10,
+                                        svInfoListDeltas);
+            EXPECT_TRUE(aidl_gnss_cb_->sv_info_list_cbq_.size() > 0);
+            // Verify the SvStatus intervals are at 2s interval
+            for (const int& delta : svInfoListDeltas) {
+                ALOGD("svInfoListDelta: %d", delta);
+            }
+            assertMeanAndStdev(intervalMs, svInfoListDeltas);
+        }
 
         status = iGnssMeasurement->close();
         ASSERT_TRUE(status.isOk());
@@ -1587,8 +1619,7 @@
  * TestGnssMeasurementIsFullTracking
  * 1. Start measurement with enableFullTracking=true. Verify the received measurements have
  *    isFullTracking=true.
- * 2. Start measurement with enableFullTracking = false. Verify the received measurements have
- *    isFullTracking=false.
+ * 2. Start measurement with enableFullTracking = false.
  * 3. Do step 1 again.
  */
 TEST_P(GnssHalTest, TestGnssMeasurementIsFullTracking) {
@@ -1675,4 +1706,59 @@
     ASSERT_TRUE(accumulatedDeltaRangeFound);
     status = iGnssMeasurement->close();
     ASSERT_TRUE(status.isOk());
-}
\ No newline at end of file
+}
+
+/*
+ * TestSvStatusIntervals:
+ * 1. start measurement and location with various intervals
+ * 2. verify the SvStatus are received at expected interval
+ */
+TEST_P(GnssHalTest, TestSvStatusIntervals) {
+    if (aidl_gnss_hal_->getInterfaceVersion() <= 2) {
+        return;
+    }
+    ALOGD("TestSvStatusIntervals");
+    sp<IGnssMeasurementInterface> iGnssMeasurement;
+    auto status = aidl_gnss_hal_->getExtensionGnssMeasurement(&iGnssMeasurement);
+    ASSERT_TRUE(status.isOk());
+    ASSERT_TRUE(iGnssMeasurement != nullptr);
+
+    std::vector<int> locationIntervals{1000, 2000, INT_MAX};
+    std::vector<int> measurementIntervals{1000, 2000, INT_MAX};
+
+    for (auto& locationIntervalMs : locationIntervals) {
+        for (auto& measurementIntervalMs : measurementIntervals) {
+            if (locationIntervalMs == INT_MAX && measurementIntervalMs == INT_MAX) {
+                continue;
+            }
+            auto measurementCallback = sp<GnssMeasurementCallbackAidl>::make();
+            // Start measurement
+            if (measurementIntervalMs < INT_MAX) {
+                startMeasurementWithInterval(measurementIntervalMs, iGnssMeasurement,
+                                             measurementCallback);
+            }
+            // Start location
+            if (locationIntervalMs < INT_MAX) {
+                StartAndCheckFirstLocation(locationIntervalMs, /* lowPowerMode= */ false);
+            }
+            ALOGD("location@%d(ms), measurement@%d(ms)", locationIntervalMs, measurementIntervalMs);
+            std::vector<int> svInfoListDeltas;
+            collectSvInfoListTimestamps(/*numEvents=*/5, /* timeoutSeconds= */ 10,
+                                        svInfoListDeltas);
+            EXPECT_TRUE(aidl_gnss_cb_->sv_info_list_cbq_.size() > 0);
+
+            int svStatusInterval = std::min(locationIntervalMs, measurementIntervalMs);
+            assertMeanAndStdev(svStatusInterval, svInfoListDeltas);
+
+            if (locationIntervalMs < INT_MAX) {
+                // Stop location request
+                StopAndClearLocations();
+            }
+            if (measurementIntervalMs < INT_MAX) {
+                // Stop measurement request
+                status = iGnssMeasurement->close();
+                ASSERT_TRUE(status.isOk());
+            }
+        }
+    }
+}
diff --git a/keymaster/4.0/support/fuzzer/keymaster4_utils_fuzzer.cpp b/keymaster/4.0/support/fuzzer/keymaster4_utils_fuzzer.cpp
index bf074e8..55c2630 100644
--- a/keymaster/4.0/support/fuzzer/keymaster4_utils_fuzzer.cpp
+++ b/keymaster/4.0/support/fuzzer/keymaster4_utils_fuzzer.cpp
@@ -46,33 +46,42 @@
     support::getOsVersion();
     support::getOsPatchlevel();
 
-    VerificationToken token;
-    token.challenge = mFdp->ConsumeIntegral<uint64_t>();
-    token.timestamp = mFdp->ConsumeIntegral<uint64_t>();
-    token.securityLevel = mFdp->PickValueInArray(kSecurityLevel);
-    size_t vectorSize = mFdp->ConsumeIntegralInRange<size_t>(0, kMaxVectorSize);
-    token.mac.resize(vectorSize);
-    for (size_t n = 0; n < vectorSize; ++n) {
-        token.mac[n] = n;
+    while (mFdp->remaining_bytes() > 0) {
+        auto keymaster_function = mFdp->PickValueInArray<const std::function<void()>>({
+                [&]() {
+                    VerificationToken token;
+                    token.challenge = mFdp->ConsumeIntegral<uint64_t>();
+                    token.timestamp = mFdp->ConsumeIntegral<uint64_t>();
+                    token.securityLevel = mFdp->PickValueInArray(kSecurityLevel);
+                    size_t vectorSize = mFdp->ConsumeIntegralInRange<size_t>(0, kMaxVectorSize);
+                    token.mac.resize(vectorSize);
+                    for (size_t n = 0; n < vectorSize; ++n) {
+                        token.mac[n] = mFdp->ConsumeIntegral<uint8_t>();
+                    }
+                    std::optional<std::vector<uint8_t>> serialized =
+                            serializeVerificationToken(token);
+                    if (serialized.has_value()) {
+                        std::optional<VerificationToken> deserialized =
+                                deserializeVerificationToken(serialized.value());
+                    }
+                },
+                [&]() {
+                    std::vector<uint8_t> dataVector;
+                    size_t size = mFdp->ConsumeIntegralInRange<size_t>(0, sizeof(hw_auth_token_t));
+                    dataVector = mFdp->ConsumeBytes<uint8_t>(size);
+                    support::blob2hidlVec(dataVector.data(), dataVector.size());
+                    support::blob2hidlVec(dataVector);
+                    HardwareAuthToken authToken = support::hidlVec2AuthToken(dataVector);
+                    hidl_vec<uint8_t> volatile hidlVector = support::authToken2HidlVec(authToken);
+                },
+                [&]() {
+                    std::string str = mFdp->ConsumeRandomLengthString(kMaxCharacters);
+                    support::blob2hidlVec(str);
+                },
+        });
+        keymaster_function();
     }
-    std::optional<std::vector<uint8_t>> serialized = serializeVerificationToken(token);
-    if (serialized.has_value()) {
-        std::optional<VerificationToken> deserialized =
-                deserializeVerificationToken(serialized.value());
-    }
-
-    std::vector<uint8_t> dataVector;
-    size_t size = mFdp->ConsumeIntegralInRange<size_t>(0, sizeof(hw_auth_token_t));
-    dataVector = mFdp->ConsumeBytes<uint8_t>(size);
-    support::blob2hidlVec(dataVector.data(), dataVector.size());
-
-    support::blob2hidlVec(dataVector);
-
-    std::string str = mFdp->ConsumeRandomLengthString(kMaxCharacters);
-    support::blob2hidlVec(str);
-
-    HardwareAuthToken authToken = support::hidlVec2AuthToken(dataVector);
-    hidl_vec<uint8_t> volatile hidlVector = support::authToken2HidlVec(authToken);
+    return;
 }
 
 void KeyMaster4UtilsFuzzer::process(const uint8_t* data, size_t size) {
diff --git a/media/c2/aidl/aidl_api/android.hardware.media.c2/current/android/hardware/media/c2/IComponent.aidl b/media/c2/aidl/aidl_api/android.hardware.media.c2/current/android/hardware/media/c2/IComponent.aidl
index c7d8a97..7d58340 100644
--- a/media/c2/aidl/aidl_api/android.hardware.media.c2/current/android/hardware/media/c2/IComponent.aidl
+++ b/media/c2/aidl/aidl_api/android.hardware.media.c2/current/android/hardware/media/c2/IComponent.aidl
@@ -49,8 +49,12 @@
     long blockPoolId;
     android.hardware.media.c2.IConfigurable configurable;
   }
+  parcelable C2AidlGbAllocator {
+    android.hardware.media.c2.IGraphicBufferAllocator igba;
+    ParcelFileDescriptor waitableFd;
+  }
   union BlockPoolAllocator {
     int allocatorId;
-    android.hardware.media.c2.IGraphicBufferAllocator igba;
+    android.hardware.media.c2.IComponent.C2AidlGbAllocator allocator;
   }
 }
diff --git a/media/c2/aidl/android/hardware/media/c2/IComponent.aidl b/media/c2/aidl/android/hardware/media/c2/IComponent.aidl
index a7d94b1..fc923ab 100644
--- a/media/c2/aidl/android/hardware/media/c2/IComponent.aidl
+++ b/media/c2/aidl/android/hardware/media/c2/IComponent.aidl
@@ -22,6 +22,8 @@
 import android.hardware.media.c2.IConfigurable;
 import android.hardware.media.c2.IGraphicBufferAllocator;
 import android.hardware.media.c2.WorkBundle;
+import android.os.ParcelFileDescriptor;
+
 
 /**
  * Interface for an AIDL Codec2 component.
@@ -46,6 +48,18 @@
     }
 
     /**
+     * C2AIDL allocator interface along with a waitable fd.
+     *
+     * The interface is used from a specific type of C2BlockPool to allocate
+     * graphic blocks. the waitable fd is used to create a specific type of
+     * C2Fence which can be used for waiting until to allocate is not blocked.
+     */
+    parcelable C2AidlGbAllocator {
+        IGraphicBufferAllocator igba;
+        ParcelFileDescriptor waitableFd;
+    }
+
+    /**
      * Allocator for C2BlockPool.
      *
      * C2BlockPool will use a C2Allocator which is specified by an id.
@@ -53,7 +67,7 @@
      */
     union BlockPoolAllocator {
         int allocatorId;
-        IGraphicBufferAllocator igba;
+        C2AidlGbAllocator allocator;
     }
 
     /**
diff --git a/rebootescrow/aidl/default/Android.bp b/rebootescrow/aidl/default/Android.bp
index 4409314..7f9b6d6 100644
--- a/rebootescrow/aidl/default/Android.bp
+++ b/rebootescrow/aidl/default/Android.bp
@@ -42,10 +42,10 @@
 
 cc_binary {
     name: "android.hardware.rebootescrow-service.default",
-    init_rc: ["rebootescrow-default.rc"],
     relative_install_path: "hw",
-    vintf_fragments: ["rebootescrow-default.xml"],
     vendor: true,
+    installable: false, // installed in APEX
+
     srcs: [
         "service.cpp",
     ],
@@ -53,12 +53,14 @@
         "-Wall",
         "-Werror",
     ],
+    stl: "c++_static",
     shared_libs: [
-        "libbase",
         "libbinder_ndk",
-        "android.hardware.rebootescrow-V1-ndk",
+        "liblog",
     ],
     static_libs: [
+        "android.hardware.rebootescrow-V1-ndk",
+        "libbase",
         "libhadamardutils",
         "librebootescrowdefaultimpl",
     ],
@@ -97,3 +99,35 @@
     ],
     test_suites: ["device-tests"],
 }
+
+prebuilt_etc {
+    name: "rebootescrow-default.rc",
+    src: "rebootescrow-default.rc",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "rebootescrow-default.xml",
+    src: "rebootescrow-default.xml",
+    sub_dir: "vintf",
+    installable: false,
+}
+
+apex {
+    name: "com.android.hardware.rebootescrow",
+    manifest: "apex_manifest.json",
+    file_contexts: "apex_file_contexts",
+    key: "com.android.hardware.key",
+    certificate: ":com.android.hardware.certificate",
+    vendor: true,
+    updatable: false,
+
+    binaries: [
+        "android.hardware.rebootescrow-service.default",
+    ],
+    prebuilts: [
+        "rebootescrow-default.rc",
+        "rebootescrow-default.xml",
+        "android.hardware.reboot_escrow.prebuilt.xml", // <feature>
+    ],
+}
diff --git a/rebootescrow/aidl/default/apex_file_contexts b/rebootescrow/aidl/default/apex_file_contexts
new file mode 100644
index 0000000..aa84984
--- /dev/null
+++ b/rebootescrow/aidl/default/apex_file_contexts
@@ -0,0 +1,3 @@
+(/.*)?                                                      u:object_r:vendor_file:s0
+/etc(/.*)?                                                  u:object_r:vendor_configs_file:s0
+/bin/hw/android\.hardware\.rebootescrow-service\.default    u:object_r:hal_rebootescrow_default_exec:s0
diff --git a/rebootescrow/aidl/default/apex_manifest.json b/rebootescrow/aidl/default/apex_manifest.json
new file mode 100644
index 0000000..8be495b
--- /dev/null
+++ b/rebootescrow/aidl/default/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+    "name": "com.android.hardware.rebootescrow",
+    "version": 1
+}
\ No newline at end of file
diff --git a/rebootescrow/aidl/default/rebootescrow-default.rc b/rebootescrow/aidl/default/rebootescrow-default.rc
index ad90465..024dd6d 100644
--- a/rebootescrow/aidl/default/rebootescrow-default.rc
+++ b/rebootescrow/aidl/default/rebootescrow-default.rc
@@ -1,4 +1,4 @@
-service vendor.rebootescrow-default /vendor/bin/hw/android.hardware.rebootescrow-service.default
+service vendor.rebootescrow-default /apex/com.android.hardware.rebootescrow/bin/hw/android.hardware.rebootescrow-service.default
     interface aidl android.hardware.rebootescrow.IRebootEscrow/default
     class hal
     user system
diff --git a/security/authgraph/aidl/Android.bp b/security/authgraph/aidl/Android.bp
new file mode 100644
index 0000000..d94f640
--- /dev/null
+++ b/security/authgraph/aidl/Android.bp
@@ -0,0 +1,88 @@
+// Copyright (C) 2023 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.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+aidl_interface {
+    name: "android.hardware.security.authgraph",
+    vendor_available: true,
+    srcs: [
+        "android/hardware/security/authgraph/*.aidl",
+    ],
+    stability: "vintf",
+    frozen: false,
+    backend: {
+        java: {
+            platform_apis: true,
+        },
+        ndk: {
+            apps_enabled: false,
+        },
+        rust: {
+            enabled: true,
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.virt",
+            ],
+        },
+    },
+}
+
+// cc_defaults that includes the latest Authgraph AIDL library.
+// Modules that depend on Authgraph directly can include this cc_defaults to avoid
+// managing dependency versions explicitly.
+cc_defaults {
+    name: "authgraph_use_latest_hal_aidl_ndk_static",
+    static_libs: [
+        "android.hardware.security.authgraph-V1-ndk",
+    ],
+}
+
+cc_defaults {
+    name: "authgraph_use_latest_hal_aidl_ndk_shared",
+    shared_libs: [
+        "android.hardware.security.authgraph-V1-ndk",
+    ],
+}
+
+cc_defaults {
+    name: "authgraph_use_latest_hal_aidl_cpp_static",
+    static_libs: [
+        "android.hardware.security.authgraph-V1-cpp",
+    ],
+}
+
+cc_defaults {
+    name: "authgraph_use_latest_hal_aidl_cpp_shared",
+    shared_libs: [
+        "android.hardware.security.authgraph-V1-cpp",
+    ],
+}
+
+// A rust_defaults that includes the latest Authgraph AIDL library.
+// Modules that depend on Authgraph directly can include this rust_defaults to avoid
+// managing dependency versions explicitly.
+rust_defaults {
+    name: "authgraph_use_latest_hal_aidl_rust",
+    rustlibs: [
+        "android.hardware.security.authgraph-V1-rust",
+    ],
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Arc.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Arc.aidl
new file mode 100644
index 0000000..dc86fbd
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Arc.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.authgraph;
+/* @hide */
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable Arc {
+  byte[] arc;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Error.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Error.aidl
new file mode 100644
index 0000000..1a78b54
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Error.aidl
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.authgraph;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum Error {
+  OK = 0,
+  INVALID_PEER_NONCE = (-1) /* -1 */,
+  INVALID_PEER_KE_KEY = (-2) /* -2 */,
+  INVALID_IDENTITY = (-3) /* -3 */,
+  INVALID_CERT_CHAIN = (-4) /* -4 */,
+  INVALID_SIGNATURE = (-5) /* -5 */,
+  INVALID_KE_KEY = (-6) /* -6 */,
+  INVALID_PUB_KEY_IN_KEY = (-7) /* -7 */,
+  INVALID_PRIV_KEY_ARC_IN_KEY = (-8) /* -8 */,
+  INVALID_SHARED_KEY_ARCS = (-9) /* -9 */,
+  MEMORY_ALLOCATION_FAILED = (-10) /* -10 */,
+  INCOMPATIBLE_PROTOCOL_VERSION = (-11) /* -11 */,
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/IAuthGraphKeyExchange.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/IAuthGraphKeyExchange.aidl
new file mode 100644
index 0000000..2c56f33
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/IAuthGraphKeyExchange.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.authgraph;
+/* @hide */
+@VintfStability
+interface IAuthGraphKeyExchange {
+  android.hardware.security.authgraph.SessionInitiationInfo create();
+  android.hardware.security.authgraph.KeInitResult init(in android.hardware.security.authgraph.PubKey peerPubKey, in android.hardware.security.authgraph.Identity peerId, in byte[] peerNonce, in int peerVersion);
+  android.hardware.security.authgraph.SessionInfo finish(in android.hardware.security.authgraph.PubKey peerPubKey, in android.hardware.security.authgraph.Identity peerId, in android.hardware.security.authgraph.SessionIdSignature peerSignature, in byte[] peerNonce, in int peerVersion, in android.hardware.security.authgraph.Key ownKey);
+  android.hardware.security.authgraph.Arc[2] authenticationComplete(in android.hardware.security.authgraph.SessionIdSignature peerSignature, in android.hardware.security.authgraph.Arc[2] sharedKeys);
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Identity.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Identity.aidl
new file mode 100644
index 0000000..bd5453e
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Identity.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable Identity {
+  byte[] identity;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/KeInitResult.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/KeInitResult.aidl
new file mode 100644
index 0000000..8c91523
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/KeInitResult.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable KeInitResult {
+  android.hardware.security.authgraph.SessionInitiationInfo sessionInitiationInfo;
+  android.hardware.security.authgraph.SessionInfo sessionInfo;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Key.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Key.aidl
new file mode 100644
index 0000000..5b4ebbf
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Key.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable Key {
+  @nullable android.hardware.security.authgraph.PubKey pubKey;
+  @nullable android.hardware.security.authgraph.Arc arcFromPBK;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/PlainPubKey.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/PlainPubKey.aidl
new file mode 100644
index 0000000..f070bfa
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/PlainPubKey.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable PlainPubKey {
+  byte[] plainPubKey;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/PubKey.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/PubKey.aidl
new file mode 100644
index 0000000..4c3376e
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/PubKey.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+union PubKey {
+  android.hardware.security.authgraph.PlainPubKey plainKey;
+  android.hardware.security.authgraph.SignedPubKey signedKey;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionIdSignature.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionIdSignature.aidl
new file mode 100644
index 0000000..6dabc0a
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionIdSignature.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable SessionIdSignature {
+  byte[] signature;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionInfo.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionInfo.aidl
new file mode 100644
index 0000000..427962b
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionInfo.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable SessionInfo {
+  android.hardware.security.authgraph.Arc[2] sharedKeys;
+  byte[] sessionId;
+  android.hardware.security.authgraph.SessionIdSignature signature;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionInitiationInfo.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionInitiationInfo.aidl
new file mode 100644
index 0000000..bf55e74
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionInitiationInfo.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable SessionInitiationInfo {
+  android.hardware.security.authgraph.Key key;
+  android.hardware.security.authgraph.Identity identity;
+  byte[] nonce;
+  int version;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SignedPubKey.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SignedPubKey.aidl
new file mode 100644
index 0000000..3dbaed8
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SignedPubKey.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable SignedPubKey {
+  byte[] signedPubKey;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/Arc.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/Arc.aidl
new file mode 100644
index 0000000..855ce5c
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/Arc.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package android.hardware.security.authgraph;
+
+/**
+ * This is the definition of the data format of an Arc.
+ * @hide
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable Arc {
+    /**
+     * The messages exchanged between the domains in the AuthGraph protocol are called Arcs.
+     * An arc is simply AES-GCM. Encryption of a payload P with a key K and additional
+     * authentication data (AAD) D: (i.e. Arc = Enc(K, P, D)).
+     *
+     * Data is CBOR-encoded according to the `Arc` CDDL definition in Arc.cddl.
+     */
+    byte[] arc;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/Arc.cddl b/security/authgraph/aidl/android/hardware/security/authgraph/Arc.cddl
new file mode 100644
index 0000000..4c1b965
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/Arc.cddl
@@ -0,0 +1,115 @@
+;
+; Copyright (C) 2023 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.
+;
+Arc = [                            ; COSE_Encrypt0 [RFC9052 s5.2]
+    protected : bstr .cbor ArcProtectedHeaders,
+    unprotected : {
+        5 : bstr .size 12          ; IV
+    },
+    ciphertext : bstr    ; Enc(K, bstr .cbor Payload, encoded ArcEncStruct)
+]
+
+ArcProtectedHeaders = {
+           1 : 3,                  ; Algorithm: AES-GCM mode w/ 256-bit key, 128-bit tag
+    ? -70001 : { + Permission }, ; One or more Permissions
+    ? -70002 : { + Limitation }, ; One or more Limitations
+    ? -70003 : int,   ; Timestamp in milliseconds since some starting point (generally
+                      ; the most recent device boot) which all of the applications within
+                      ; the secure domain must agree upon
+    ? -70004 : bstr .size 16,      ; Nonce used in key exchange methods
+    ? -70005 : PayloadType,        ; Payload type, if needed to disambiguate, when processing an arc
+    ? -70006 : int,                ; Version of the payload structure (if applicable)
+    ? -70007 : int,                ; Sequence number (if needed to prevent replay attacks)
+    ? -70008 : Direction           ; Direction of the encryption key (i.e. whether it is used to
+                                   ; encrypt incoming messages or outgoing messages)
+    ? -70009 : bool,               ; "authentication_completed" - this is used during authenticated
+                                   ; key exchange indicate whether signature verification is done
+    ? -70010 : bstr .size 32       ; "session_id" computed during key exchange protocol
+}
+
+; Permissions indicate what an arc can be used with. Permissions are added to an arc during the
+; `create()` primitive operation and are propagated during `mint` and `snap` primitive operations.
+Permission = &(
+    -4770552 : IdentityEncoded,  ; "source_id" - in the operations performed by a source, the
+                                 ; source adds its own identity to the permissions of an arc.
+    -4770553 : IdentityEncoded,  ; "sink_id" - in the operations performed by a sink, the sink
+                                 ; adds its own identity to the permissions of an arc.
+    -4770555 : [ +IdentityEncoded ]  ; "minting_allowed" - defines the set of TA identities
+                                     ; to whom the payload key is allowed to be minted.
+    -4770556 : bool                  ; "deleted_on_biometric_change" - A Boolean value that
+                                     ; indicates whether an auth key issued from a biometric TA is
+                                     ; invalidated on new biometric enrollment or removal of all
+                                     ; biometrics.
+)
+
+; Limitations indicate what restrictions are applied on the usage of an arc. Permissions are added
+; to an arc during the `create` primitive operation and are propagated during `snap` primitive
+; operation.
+Limitation = &(
+    -4770554 : bstr,      ; "challenge" - is added to an arc that transfers an auth key to a channel
+                          ; key, in order to ensure the freshness of the authentication.
+                          ; A challenge is issued by a sink (e.g. Keymint TA, Biometric TAs).
+)
+
+; INCLUDE Identity.cddl for: Identity
+IdentityEncoded = bstr .cbor Identity
+
+Direction = &(
+    In:  1,
+    Out: 2,
+)
+
+PayloadType = &(
+    SecretKey: 1,
+    Arc: 2,
+    ; Any other payload types should also be defined here
+)
+
+Payload = &(
+    SecretKey,
+    Arc,
+    ; Any other payload formats should also be defined here
+)
+
+SecretKey = &(                     ; One of the payload types of an Arc is a secret key
+    SymmetricKey,
+    ECPrivateKey,    ; Private key of a key pair generated for key exchange
+)
+
+ECPrivateKey = {              ; COSE_Key [RFC9052 s7]
+    1 : 2,                    ; Key type : EC2
+    3 : -25,                  ; Algorithm: ECDH ES w/ HKDF 256 - generate key directly
+ ?  4 : [7],                  ; Key_ops: [derive key]
+   -1 : 1,                    ; Curve: P-256
+ ? -2 : bstr,                 ; x coordinate
+ ? -3 : bstr,                 ; y coordinate
+   -4 : bstr,                 ; private key (d)
+}
+
+SymmetricKey = {              ; COSE_Key [RFC9052 s7] - For symmetric key encryption
+    1 : 4,                    ; Key type : Symmetric
+    3 : 3,                    ; Algorithm : AES-GCM mode w/ 256-bit key, 128-bit tag
+    4 : [ 4 ],                ; Key_ops: [decrypt]
+   -1 : bstr .size 32,        ; Key value (k)
+}
+
+ArcEncStruct = [              ; COSE_Enc_structure [RFC9052 s5.3]
+    context   : "Encrypt0",
+    protected : bstr .cbor ArcProtectedHeaders,
+    external_aad : bstr .size 0,
+]
+
+; INCLUDE generateCertificateRequestV2.cddl for: PubKeyEd25519, PubKeyECDSA256, PubKeyECDSA384
+; from hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/DicePolicy.cddl b/security/authgraph/aidl/android/hardware/security/authgraph/DicePolicy.cddl
new file mode 100644
index 0000000..a7dcbc6
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/DicePolicy.cddl
@@ -0,0 +1,33 @@
+;
+; Copyright (C) 2023 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.
+;
+DicePolicy = [
+    1, ; dice policy version
+    + nodeConstraintList ; for each entry in dice chain
+]
+
+nodeConstraintList = [
+    * nodeConstraint
+]
+
+; We may add a hashConstraint item later
+nodeConstraint = exactMatchConstraint / geConstraint
+
+exactMatchConstraint = [1, keySpec, value]
+geConstraint = [2, keySpec, int]
+
+keySpec = [value+]
+
+value = bool / int / tstr / bstr
\ No newline at end of file
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/Error.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/Error.aidl
new file mode 100644
index 0000000..1ad6054
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/Error.aidl
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package android.hardware.security.authgraph;
+
+/**
+ * AuthGraph error codes. Aidl will return these error codes as service specific errors in
+ * EX_SERVICE_SPECIFIC.
+ * @hide
+ */
+@VintfStability
+@Backing(type="int")
+enum Error {
+    /* Success */
+    OK = 0,
+    /* Invalid peer nonce for key exchange */
+    INVALID_PEER_NONCE = -1,
+    /* Invalid key exchange public key by the peer */
+    INVALID_PEER_KE_KEY = -2,
+    /* Invalid identity of the peer */
+    INVALID_IDENTITY = -3,
+    /* Invalid certificate chain in the identity of the peer */
+    INVALID_CERT_CHAIN = -4,
+    /* Invalid signature by the peer */
+    INVALID_SIGNATURE = -5,
+    /* Invalid key exchange key created by a particular party themselves to be used as a handle */
+    INVALID_KE_KEY = -6,
+    /* Invalid public key in the `Key` struct */
+    INVALID_PUB_KEY_IN_KEY = -7,
+    /* Invalid private key arc in the `Key` struct */
+    INVALID_PRIV_KEY_ARC_IN_KEY = -8,
+    /* Invalid shared key arcs */
+    INVALID_SHARED_KEY_ARCS = -9,
+    /* Memory allocation failed */
+    MEMORY_ALLOCATION_FAILED = -10,
+    /* The protocol version negotiated with the sink is incompatible */
+    INCOMPATIBLE_PROTOCOL_VERSION = -11,
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/ExplicitKeyDiceCertChain.cddl b/security/authgraph/aidl/android/hardware/security/authgraph/ExplicitKeyDiceCertChain.cddl
new file mode 100644
index 0000000..3de5617
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/ExplicitKeyDiceCertChain.cddl
@@ -0,0 +1,30 @@
+;
+; Copyright (C) 2023 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.
+;
+ExplicitKeyDiceCertChain = [
+    1, ; version, hopefully will never change
+    DiceCertChainInitialPayload,
+    * DiceChainEntry
+]
+
+DiceCertChainInitialPayload = {
+    -4670552 : bstr .cbor PubKeyEd25519 /
+               bstr .cbor PubKeyECDSA256 /
+               bstr .cbor PubKeyECDSA384 ; subjectPublicKey
+}
+
+; INCLUDE generateCertificateRequestV2.cddl for: PubKeyEd25519, PubKeyECDSA256, PubKeyECDSA384,
+;                                                DiceChainEntry
+; from hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/IAuthGraphKeyExchange.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/IAuthGraphKeyExchange.aidl
new file mode 100644
index 0000000..6ceb09c
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/IAuthGraphKeyExchange.aidl
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package android.hardware.security.authgraph;
+
+import android.hardware.security.authgraph.Arc;
+import android.hardware.security.authgraph.Identity;
+import android.hardware.security.authgraph.KeInitResult;
+import android.hardware.security.authgraph.Key;
+import android.hardware.security.authgraph.PubKey;
+import android.hardware.security.authgraph.SessionIdSignature;
+import android.hardware.security.authgraph.SessionInfo;
+import android.hardware.security.authgraph.SessionInitiationInfo;
+
+/**
+ * AuthGraph interface definition for authenticated key exchange between two parties: P1 (source)
+ * and P2 (sink).
+ * Pre-requisites: each participant should have a:
+ *     1. Persistent identity - e.g. a signing key pair with a self signed certificate or a DICE
+ *        certificate chain.
+ *     2. A symmetric encryption key kept in memory with per-boot life time of the participant
+ *        (a.k.a per-boot key)
+ *
+ * ErrorCodes are defined in android.hardware.security.authgraph.ErrorCode.aidl.
+ * @hide
+ */
+@VintfStability
+interface IAuthGraphKeyExchange {
+    /**
+     * This method is invoked on P1 (source).
+     * Create an ephermeral EC key pair on NIST curve P-256 and a nonce (of 16 bytes) for
+     * key exchange.
+     *
+     * @return SessionInitiationInfo including the `Key` containing the public key of the created
+     * key pair and an arc from the per-boot key to the private key, the nonce, the persistent
+     * identity and the latest protocol version (i.e. AIDL version) supported.
+     *
+     * Note: The arc from the per-boot key to the private key in `Key` of the return type:
+     * `SessionInitiationInfo` serves two purposes:
+     * i. A mapping to correlate `create` and `finish` calls to P1 in a particular instance of the
+     *    key exchange protocol.
+     * ii.A way to minimize the in-memory storage (P1 can include the nonce in the protected headers
+     *    of the arc).
+     * However, P1 should maintain some form of in-memory record to be able to verify that the input
+     * `Key` sent to `finish` is from an unfinished instance of a key exchange protocol, to prevent
+     * any replay attacks in `finish`.
+     */
+    SessionInitiationInfo create();
+
+    /**
+     * This method is invoked on P2 (sink).
+     * Perform the following steps for key exchange:
+     *     0. If either `peerPubKey`, `peerId`, `peerNonce` is not in the expected format, return
+     *        errors: INVALID_PEER_KE_KEY, INVALID_IDENTITY, INVALID_PEER_NONCE respectively.
+     *     1. Create an ephemeral EC key pair on NIST curve P-256.
+     *     2. Create a nonce (of 16 bytes).
+     *     3. Compute the diffie-hellman shared secret: Z.
+     *     4. Compute a salt = bstr .cbor [
+     *            source_version:    int,                    ; from input `peerVersion`
+     *            sink_pub_key:      bstr .cbor PlainPubKey, ; from step #1
+     *            source_pub_key:    bstr .cbor PlainPubKey, ; from input `peerPubKey`
+     *            sink_nonce:        bstr .size 16,          ; from step #2
+     *            source_nonce:      bstr .size 16,          ; from input `peerNonce`
+     *            sink_cert_chain:   bstr .cbor ExplicitKeyDiceCertChain, ; from own identity
+     *            source_cert_chain: bstr .cbor ExplicitKeyDiceCertChain, ; from input `peerId`
+     *        ]
+     *     5. Extract a cryptographic secret S from Z, using the salt from #4 above.
+     *     6. Derive two symmetric encryption keys of 256 bits with:
+     *        i. b"KE_ENCRYPTION_KEY_SOURCE_TO_SINK" as context for the key used to encrypt incoming
+     *           messages
+     *       ii. b"KE_ENCRYPTION_KEY_SINK_TO_SOURCE" as context for the key used to encrypt outgoing
+     *           messages
+     *     7. Create arcs from the per-boot key to each of the two shared keys from step #6 and
+     *        mark authentication_complete = false in arcs' protected headers.
+     *     8. Derive a MAC key with b"KE_HMAC_KEY" as the context.
+     *     9. Compute session_id_input = bstr .cbor [
+     *            sink_nonce:     bstr .size 16,
+     *            source_nonce:   bstr .size 16,
+     *        ],
+     *     10.Compute a session_id as a 256 bits HMAC over the session_id_input from step#9 with
+     *        the key from step #8.
+     *     11.Create a signature over the session_id from step #10, using the signing key which is
+     *        part of the party's identity.
+     *
+     * @param peerPubKey - the public key of the key pair created by the peer (P1) for key exchange
+     *
+     * @param peerId - the persistent identity of the peer
+     *
+     * @param peerNonce - nonce created by the peer
+     *
+     * @param peerVersion - an integer representing the latest protocol version (i.e. AIDL version)
+     *                      supported by the peer
+     *
+     * @return KeInitResult including the `Key` containing the public key of the created key pair,
+     * the nonce, the persistent identity, two shared key arcs from step #7, session id, signature
+     * over the session id and the negotiated protocol version. The negotiated protocol version
+     * should be less than or equal to the peer's version.
+     *
+     * Note: The two shared key arcs in the return type: `KeInitResult` serves two purposes:
+     * i. A mapping to correlate `init` and `authenticationComplete` calls to P2 in a particular
+     *    instance of the key exchange protocol.
+     * ii.A way to minimize the in-memory storage of P2 allocated for key exchange.
+     * However, P2 should maintain some in-memory record to be able to verify that the input
+     * `sharedkeys` sent to `authenticationComplete` and to any subsequent AuthGraph protocol
+     * methods are valid shared keys agreed with the party identified by `peerId`, to prevent
+     * any replay attacks in `authenticationComplete` and in any subsequent AuthGraph protocol
+     * methods which use the shared keys to encrypt the secret messages.
+     */
+    KeInitResult init(
+            in PubKey peerPubKey, in Identity peerId, in byte[] peerNonce, in int peerVersion);
+
+    /**
+     * This method is invoked on P1 (source).
+     * Perform the following steps:
+     *     0. If either `peerPubKey`, `peerId`, `peerNonce` is not in the expected format, return
+     *        errors: INVALID_PEER_KE_KEY, INVALID_IDENTITY, INVALID_PEER_NONCE respectively. If
+     *        `peerVersion` is greater than the version advertised in `create`, return error:
+     *        INCOMPATIBLE_PROTOCOL_VERSION.
+     *        If `ownKey` is not in the in-memory records for unfinished instances of a key
+     *        exchange protocol, return error: INVALID_KE_KEY. Similarly, if the public key or the
+     *        arc containing the private key in `ownKey` is invalid, return INVALID_PUB_KEY_IN_KEY
+     *        and INVALID_PRIV_KEY_ARC_IN_KEY respectively.
+     *     1. Compute the diffie-hellman shared secret: Z.
+     *     2. Compute a salt = bstr .cbor [
+     *            source_version:    int,                    ; the protocol version used in `create`
+     *            sink_pub_key:      bstr .cbor PlainPubKey, ; from input `peerPubKey`
+     *            source_pub_key:    bstr .cbor PlainPubKey, ; from the output of `create`
+     *            sink_nonce:        bstr .size 16,          ; from input `peerNonce`
+     *            source_nonce:      bstr .size 16,          ; from the output of `create`
+     *            sink_cert_chain:   bstr .cbor ExplicitKeyDiceCertChain, ; from input `peerId`
+     *            source_cert_chain: bstr .cbor ExplicitKeyDiceCertChain, ; from own identity
+     *        ]
+     *     3. Extract a cryptographic secret S from Z, using the salt from #2 above.
+     *     4. Derive two symmetric encryption keys of 256 bits with:
+     *        i. b"KE_ENCRYPTION_KEY_SOURCE_TO_SINK" as context for the key used to encrypt outgoing
+     *           messages
+     *       ii. b"KE_ENCRYPTION_KEY_SINK_TO_SOURCE" as context for the key used to encrypt incoming
+     *           messages
+     *     5. Derive a MAC key with b"KE_HMAC_KEY" as the context.
+     *     6. Compute session_id_input = bstr .cbor [
+     *            sink_nonce:     bstr .size 16,
+     *            source_nonce:   bstr .size 16,
+     *        ],
+     *     7. Compute a session_id as a 256 bits HMAC over the session_id_input from step #6 with
+     *        the key from step #5.
+     *     8. Verify the peer's signature over the session_id from step #7. If successful, proceed,
+     *        otherwise, return error: INVALID_SIGNATURE.
+     *     9. Create arcs from the per-boot key to each of the two shared keys from step #4 and
+     *        mark authentication_complete = true in arcs' protected headers.
+     *     10.Create a signature over the session_id from step #7, using the signing key which is
+     *        part of the party's identity.
+     *
+     * @param peerPubKey - the public key of the key pair created by the peer (P2) for key exchange
+     *
+     * @param peerId - the persistent identity of the peer
+     *
+     * @param peerSignature - the signature created by the peer over the session id computed by the
+     *                        peer
+     *
+     * @param peerNonce - nonce created by the peer
+     *
+     * @param peerVersion - an integer representing the protocol version (i.e. AIDL version)
+     *                      negotiated with the peer
+     *
+     * @param ownKey - the key created by P1 (source) in `create()` for key exchange
+     *
+     * @return SessionInfo including the two shared key arcs from step #9, session id and the
+     * signature over the session id.
+     *
+     * Note: The two shared key arcs in the return type: `SessionInfo` serves two purposes:
+     * i. A mapping to correlate the key exchange protocol taken place with a particular peer and
+     *    subsequent AuthGraph protocols execued with the same peer.
+     * ii.A way to minimize the in-memory storage for shared keys.
+     * However, P1 should maintain some in-memory record to be able to verify that the shared key
+     * arcs sent to any subsequent AuthGraph protocol methods are valid shared keys agreed with the
+     * party identified by `peerId`, to prevent any replay attacks.
+     */
+    SessionInfo finish(in PubKey peerPubKey, in Identity peerId,
+            in SessionIdSignature peerSignature, in byte[] peerNonce, in int peerVersion,
+            in Key ownKey);
+
+    /**
+     * This method is invoked on P2 (sink).
+     * Perform the following steps:
+     *   0. If input `sharedKeys` is invalid (i.e. they cannot be decrypted with P2's per-boot key
+     *      or they are not in P2's in-memory records as valid shared keys agreed with the party
+     *      identified by `peerId`), return error: INVALID_SHARED_KEY_ARCS.
+     *   1. Verify that both shared key arcs have the same session id and peer identity.
+     *   2. Verify the peer's signature over the session id attached to the shared key arcs'
+     *      headers. If successful, proceed, otherwise, return error: INVALID_SIGNATURE.
+     *   3. Mark authentication_complete = true in the shared key arcs' headers
+     *
+     * @param peerSignature - the signature created by the peer over the session id computed by the
+     *                        peer
+     *
+     * @param sharedKeys - two shared key arcs created by P2 in `init`. P2 obtains from the arcs'
+     *                     protected headers, the session id and the peer's identity to verify the
+     *                     peer's signature over the session id.
+     *
+     * @return Arc[] - an array of two updated shared key arcs
+     */
+    Arc[2] authenticationComplete(in SessionIdSignature peerSignature, in Arc[2] sharedKeys);
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/Identity.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/Identity.aidl
new file mode 100644
index 0000000..9e350e8
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/Identity.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package android.hardware.security.authgraph;
+
+/**
+ * Persistent (versioned) identity of a participant of Authgraph key exchange.
+ * Identity consists of two main parts:
+ *     1. a certificate chain (e.g. a DICE certificate chain)
+ *     2. (optional) a policy specifying how to verify the certificate chain - if a policy is not
+ *        provided, a simple byte-to-byte comparison of the certificate chain is assumed.
+ *
+ * During identity verification, the certificate chain of the identity attached to the access
+ * request is compared against the policy of the identity attached to the persistent resources.
+ *
+ * The usage of policy based identity verification in Authgraph is three-fold:
+ *        1. Retain access to persistent resources for the newer versions of the party who
+ *           created them, even when parts of the certificate chain are updated in the new version.
+ *        2. Deny access to the new persistent resources for the older versions of the party
+ *           who created the new persistent resources.
+ *        3. Trigger rotation of critical keys encrypted in persistent arcs created by the previous
+ *           version of the party, by including an updated policy in the identity attached to the
+ *           access request.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable Identity {
+    /* Data is CBOR-encoded according to the `Identity` CDDL definition in Identity.cddl */
+    byte[] identity;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/Identity.cddl b/security/authgraph/aidl/android/hardware/security/authgraph/Identity.cddl
new file mode 100644
index 0000000..0419421
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/Identity.cddl
@@ -0,0 +1,23 @@
+;
+; Copyright (C) 2023 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.
+;
+Identity = [
+    1, ; Version
+    cert_chain: bstr .cbor ExplicitKeyDiceCertChain,
+    policy: bstr .cbor DicePolicy / nil,
+]
+
+; INCLUDE ExplicitKeyDiceCertChain.cddl for: ExplicitKeyDiceCertChain
+; INCLUDE DicePolicy.cddl for: DicePolicy
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/KeInitResult.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/KeInitResult.aidl
new file mode 100644
index 0000000..b4ae451
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/KeInitResult.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package android.hardware.security.authgraph;
+
+import android.hardware.security.authgraph.SessionInfo;
+import android.hardware.security.authgraph.SessionInitiationInfo;
+
+/**
+ * The return type for the init() step of authenticated key exchange.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable KeInitResult {
+    /**
+     * Session initiation information.
+     */
+    SessionInitiationInfo sessionInitiationInfo;
+
+    /**
+     * Session information.
+     */
+    SessionInfo sessionInfo;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/Key.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/Key.aidl
new file mode 100644
index 0000000..11fe174
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/Key.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package android.hardware.security.authgraph;
+
+import android.hardware.security.authgraph.Arc;
+import android.hardware.security.authgraph.PubKey;
+
+/**
+ * The type that encapsulates a key. Key can be either a symmetric key or an asymmetric key.
+ * If it is an asymmetric key, it is used for key exchange.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable Key {
+    /**
+     * If the Key is an asymmetric key, public key should be present.
+     */
+    @nullable PubKey pubKey;
+
+    /**
+     * Arc from the per-boot key to the payload key. The payload key is either the symmetric key
+     * or the private key of an asymmetric key, based on the type of the key being created.
+     * This is marked as optional because there are instances where only the public key is returned,
+     * e.g. `init` method in the key exchange protocol.
+     */
+    @nullable Arc arcFromPBK;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/PlainPubKey.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/PlainPubKey.aidl
new file mode 100644
index 0000000..5483ec5
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/PlainPubKey.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package android.hardware.security.authgraph;
+
+/**
+ * One of the two enum variants of the enum type: `PubKey`. This represents the plain public key
+ * material encoded as a COSE_Key.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable PlainPubKey {
+    /* Data is CBOR-encoded according to the `PlainPubKey` CDDL definition in PlainPubKey.cddl */
+    byte[] plainPubKey;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/PlainPubKey.cddl b/security/authgraph/aidl/android/hardware/security/authgraph/PlainPubKey.cddl
new file mode 100644
index 0000000..34b316b
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/PlainPubKey.cddl
@@ -0,0 +1,24 @@
+;
+; Copyright (C) 2023 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.
+;
+
+; P-256 public key for key exchange.
+PlainPubKey = [ ; COSE_Key [RFC9052 s7]
+    1 : 2,                       ; Key type : EC2
+    3 : -27,                     ; Algorithm : ECDH-SS + HKDF-256
+    -1 : 1,                      ; Curve: P256
+    -2 : bstr,                   ; X coordinate, big-endian
+    -3 : bstr                    ; Y coordinate, big-endian
+]
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/PubKey.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/PubKey.aidl
new file mode 100644
index 0000000..8640871
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/PubKey.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package android.hardware.security.authgraph;
+
+import android.hardware.security.authgraph.PlainPubKey;
+import android.hardware.security.authgraph.SignedPubKey;
+
+/**
+ * The enum type representing the public key of an asymmetric key pair.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+union PubKey {
+    /**
+     * Plain public key material encoded as a COSE_Key.
+     */
+    PlainPubKey plainKey;
+
+    /**
+     * Public key signed with the long term signing key of the party.
+     */
+    SignedPubKey signedKey;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/SessionIdSignature.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/SessionIdSignature.aidl
new file mode 100644
index 0000000..2fa8b4c
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/SessionIdSignature.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package android.hardware.security.authgraph;
+
+/**
+ * Signature computed by a party over the session id during authenticated key exchange.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable SessionIdSignature {
+    /* Data is CBOR-encoded according to the `SessionIdSignature` CDDL definition in
+     * SessionIdSignature.cddl */
+    byte[] signature;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/SessionIdSignature.cddl b/security/authgraph/aidl/android/hardware/security/authgraph/SessionIdSignature.cddl
new file mode 100644
index 0000000..038a0f0
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/SessionIdSignature.cddl
@@ -0,0 +1,33 @@
+;
+; Copyright (C) 2023 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.
+;
+SessionIdSignature = [ ; COSE_Sign1 (untagged) [RFC9052 s4.2]
+    protected: bstr .cbor SessionIdSignatureProtected,
+    unprotected: {},
+    payload: nil,       ; session ID payload to be transported separately
+    signature: bstr     ; PureEd25519(privateKey, SessionIdSignatureSigStruct) /
+                        ; ECDSA(privateKey, SessionIdSignatureSigStruct)
+]
+
+SessionIdSignatureProtected = {
+    1 : AlgorithmEdDSA / AlgorithmES256,
+}
+
+SessionIdSignatureSigStruct = [ ; Sig_structure for SessionIdSignature [ RFC9052 s4.4]
+    context: "Signature1",
+    protected: bstr SessionIdSignatureProtected,
+    external_aad: bstr .size 0,
+    payload: bstr,       ; session ID payload provided separately
+]
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/SessionInfo.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/SessionInfo.aidl
new file mode 100644
index 0000000..ef49a1a
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/SessionInfo.aidl
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package android.hardware.security.authgraph;
+
+import android.hardware.security.authgraph.Arc;
+import android.hardware.security.authgraph.SessionIdSignature;
+
+/**
+ * Session information returned as part of authenticated key exchange.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable SessionInfo {
+    /**
+     * The arcs that encrypt the two derived symmetric encryption keys (for two-way communication)
+     * from the party's per-boot key.
+     */
+    Arc[2] sharedKeys;
+
+    /**
+     * The value of the session id computed by the two parties during the authenticate key
+     * exchange. Apart from the usage of the session id by the two peers, session id is also useful
+     * to verify (by a third party) that the key exchange was successful.
+     */
+    byte[] sessionId;
+
+    /**
+     * The signature over the session id, created by the party who computed the session id.
+     *
+     * If there is one or more `DiceChainEntry` in the `ExplicitKeyDiceCertChain` of the party's
+     * identity, the signature is verified with the public key in the leaf of the chain of
+     * DiceChainEntries (i.e the public key in the last of the array of DiceChainEntries).
+     * Otherwise, the signature is verified with the `DiceCertChainInitialPayload`.
+     */
+    SessionIdSignature signature;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/SessionInitiationInfo.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/SessionInitiationInfo.aidl
new file mode 100644
index 0000000..c630d91
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/SessionInitiationInfo.aidl
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package android.hardware.security.authgraph;
+
+import android.hardware.security.authgraph.Arc;
+import android.hardware.security.authgraph.Identity;
+import android.hardware.security.authgraph.Key;
+
+/**
+ * Session initiation information returned as part of authenticated key exchange.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable SessionInitiationInfo {
+    /**
+     * An ephemeral EC key created for the ECDH process.
+     */
+    Key key;
+
+    /**
+     * The identity of the party who created the Diffie-Hellman key exchange key.
+     */
+    Identity identity;
+
+    /**
+     * Nonce value specific to this session. The nonce serves three purposes:
+     * 1. freshness of key exchange
+     * 2. creating a session id (a publicly known value related to the exchanged keys)
+     * 3. usage as salt into the HKDF-EXTRACT function during key derivation from the shared DH key
+     */
+    byte[] nonce;
+
+    /**
+     * The protocol version (i.e. AIDL version) - This is used to prevent version downgrade attacks
+     * as follows:
+     * 1. In `create`, the source advertises the latest protocol version supported by the source,
+     *    which is given as input to the `init` call on the sink in the input parameter:
+     *    `peerVersion`.
+     * 2. In `init`, the sink includes the `peerVersion` in the inputs to the derivation of the
+     *    shared keys. Then the sink returns the latest protocol version supported by the sink,
+     *    which is given as input to the `finish` call on the source in the input parameter:
+     *    `peerVersion`.
+     * 3. In `finish`, the source first checks whether the sink's version is equal or less than the
+     *    source's version and includes in the source's version in the inputs to the derivation of
+     *    the shared keys.
+     * Analysis: if an attacker-in-the-middle wanted the two parties to use an older (vulnerable)
+     * version of the protocol, they can invoke `init` with a version that is lower than the version
+     * advertised by the source in `create`. However, since both parties include the source's
+     * version in the inputs to the derivation of the shared keys, the two parties won't end up with
+     * the same shared keys in the presence of such an attack. This is detected when checking the
+     * signature on the session id in `finish`, at which point the protocol aborts. Therefore,
+     * an attacker cannot successfully launch a version downgrade attack on this protocol.
+     */
+    int version;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/SignedPubKey.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/SignedPubKey.aidl
new file mode 100644
index 0000000..72ee219
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/SignedPubKey.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package android.hardware.security.authgraph;
+
+/**
+ * One of the two enum variants of the enum type: `PubKey`. This represents the public key signed
+ * with the long term signing key of the party.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable SignedPubKey {
+    /* Data is CBOR-encoded according to the `SignedPubKey` CDDL definition in SignedPubKey.cddl */
+    byte[] signedPubKey;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/SignedPubKey.cddl b/security/authgraph/aidl/android/hardware/security/authgraph/SignedPubKey.cddl
new file mode 100644
index 0000000..f23a492
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/SignedPubKey.cddl
@@ -0,0 +1,41 @@
+;
+; Copyright (C) 2023 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.
+;
+SignedPubKey = [ ; COSE_Sign1 (untagged) [RFC9052 s4.2]
+    protected: bstr .cbor SignedPubKeyProtected,
+    unprotected: {},
+    payload: bstr .cbor PlainPubKey,
+    signature: bstr     ; PureEd25519(privateKey, SignedPubKeySigStruct) /
+                        ; ECDSA(privateKey, SignedPubKeySigStruct)
+]
+
+SignedPubKeyProtected = {
+           1 : AlgorithmEdDSA / AlgorithmES256,
+    ? -70011 : Identity,        ; the party who performs the signing operation adds its own
+                                ; identity to the protected headers.
+}
+
+SignedPubKeySigStruct = [ ; Sig_structure for SignedPubKey [ RFC9052 s4.4]
+    context: "Signature1",
+    protected: bstr SignedPubKeyProtected,
+    external_aad: bstr .size 0,
+    payload: bstr .cbor PlainPubKey,
+]
+
+AlgorithmES256 = -7              ; [RFC9053 s2.1]
+AlgorithmEdDSA = -8              ; [RFC9053 s2.2]
+
+; INCLUDE PlainPubKey.cddl for: PlainPubKey
+; INCLUDE Identity.cddl for: Identity
\ No newline at end of file
diff --git a/security/authgraph/aidl/vts/functional/Android.bp b/security/authgraph/aidl/vts/functional/Android.bp
new file mode 100644
index 0000000..fc13759
--- /dev/null
+++ b/security/authgraph/aidl/vts/functional/Android.bp
@@ -0,0 +1,48 @@
+//
+// Copyright (C) 2023 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.
+//
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_test {
+    name: "VtsAidlAuthGraphSessionTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "authgraph_use_latest_hal_aidl_ndk_static",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    cflags: [
+        "-Wall",
+        "-Wextra",
+    ],
+    srcs: [
+        "AuthGraphSessionTest.cpp",
+    ],
+    shared_libs: [
+        "libbinder_ndk",
+        "libcrypto",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/security/authgraph/aidl/vts/functional/AuthGraphSessionTest.cpp b/security/authgraph/aidl/vts/functional/AuthGraphSessionTest.cpp
new file mode 100644
index 0000000..d9dea77
--- /dev/null
+++ b/security/authgraph/aidl/vts/functional/AuthGraphSessionTest.cpp
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2023 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 "authgraph_session_test"
+#include <android-base/logging.h>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/security/authgraph/Error.h>
+#include <aidl/android/hardware/security/authgraph/IAuthGraphKeyExchange.h>
+#include <android/binder_manager.h>
+#include <binder/ProcessState.h>
+#include <gtest/gtest.h>
+#include <vector>
+
+namespace aidl::android::hardware::security::authgraph::test {
+using ::aidl::android::hardware::security::authgraph::Error;
+
+namespace {
+
+// Check that the signature in the encoded COSE_Sign1 data is correct, and that the payload matches.
+// TODO: maybe drop separate payload, and extract it from cose_sign1.payload (and return it).
+void CheckSignature(std::vector<uint8_t>& /*pub_cose_key*/, std::vector<uint8_t>& /*payload*/,
+                    std::vector<uint8_t>& /*cose_sign1*/) {
+    // TODO: implement me
+}
+
+void CheckSignature(std::vector<uint8_t>& pub_cose_key, std::vector<uint8_t>& payload,
+                    SessionIdSignature& signature) {
+    return CheckSignature(pub_cose_key, payload, signature.signature);
+}
+
+std::vector<uint8_t> SigningKeyFromIdentity(const Identity& identity) {
+    // TODO: This is a CBOR-encoded `Identity` which currently happens to be a COSE_Key with the
+    // pubkey This will change in future.
+    return identity.identity;
+}
+
+}  // namespace
+
+class AuthGraphSessionTest : public ::testing::TestWithParam<std::string> {
+  public:
+    enum ErrorType { AIDL_ERROR, BINDER_ERROR };
+
+    union ErrorValue {
+        Error aidl_error;
+        int32_t binder_error;
+    };
+
+    struct ReturnedError {
+        ErrorType err_type;
+        ErrorValue err_val;
+
+        friend bool operator==(const ReturnedError& lhs, const ReturnedError& rhs) {
+            return lhs.err_type == rhs.err_type;
+            switch (lhs.err_type) {
+                case ErrorType::AIDL_ERROR:
+                    return lhs.err_val.aidl_error == rhs.err_val.aidl_error;
+                case ErrorType::BINDER_ERROR:
+                    return lhs.err_val.binder_error == rhs.err_val.binder_error;
+            }
+        }
+    };
+
+    const ReturnedError OK = {.err_type = ErrorType::AIDL_ERROR, .err_val.aidl_error = Error::OK};
+
+    ReturnedError GetReturnError(const ::ndk::ScopedAStatus& result) {
+        if (result.isOk()) {
+            return OK;
+        }
+        int32_t exception_code = result.getExceptionCode();
+        int32_t error_code = result.getServiceSpecificError();
+        if (exception_code == EX_SERVICE_SPECIFIC && error_code != 0) {
+            ReturnedError re = {.err_type = ErrorType::AIDL_ERROR,
+                                .err_val.aidl_error = static_cast<Error>(error_code)};
+            return re;
+        }
+        ReturnedError re = {.err_type = ErrorType::BINDER_ERROR,
+                            .err_val.binder_error = exception_code};
+        return re;
+    }
+
+    // Build the parameters for the VTS test by enumerating the available HAL instances
+    static std::vector<std::string> build_params() {
+        auto params = ::android::getAidlHalInstanceNames(IAuthGraphKeyExchange::descriptor);
+        return params;
+    }
+
+    void SetUp() override {
+        ASSERT_TRUE(AServiceManager_isDeclared(GetParam().c_str()))
+                << "No instance declared for " << GetParam();
+        ::ndk::SpAIBinder binder(AServiceManager_waitForService(GetParam().c_str()));
+        authNode_ = IAuthGraphKeyExchange::fromBinder(binder);
+        ASSERT_NE(authNode_, nullptr) << "Failed to get Binder reference for " << GetParam();
+    }
+
+    void TearDown() override {}
+
+  protected:
+    std::shared_ptr<IAuthGraphKeyExchange> authNode_;
+};
+
+TEST_P(AuthGraphSessionTest, Mainline) {
+    std::shared_ptr<IAuthGraphKeyExchange> source = authNode_;
+    std::shared_ptr<IAuthGraphKeyExchange> sink = authNode_;
+
+    // Step 1: create an ephemeral ECDH key at the source.
+    SessionInitiationInfo source_init_info;
+    ASSERT_EQ(OK, GetReturnError(source->create(&source_init_info)));
+    ASSERT_TRUE(source_init_info.key.pubKey.has_value());
+    ASSERT_TRUE(source_init_info.key.arcFromPBK.has_value());
+
+    // Step 2: pass the source's ECDH public key and other session info to the sink.
+    KeInitResult init_result;
+    ASSERT_EQ(OK, GetReturnError(sink->init(source_init_info.key.pubKey.value(),
+                                            source_init_info.identity, source_init_info.nonce,
+                                            source_init_info.version, &init_result)));
+    SessionInitiationInfo sink_init_info = init_result.sessionInitiationInfo;
+    ASSERT_TRUE(sink_init_info.key.pubKey.has_value());
+    // The sink_init_info.arcFromPBK need not be populated, as the ephemeral key agreement
+    // key is no longer needed.
+
+    SessionInfo sink_info = init_result.sessionInfo;
+    ASSERT_EQ((int)sink_info.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
+    ASSERT_GT((int)sink_info.sessionId.size(), 0) << "Expect non-empty session ID from sink";
+    std::vector<uint8_t> sink_signing_key = SigningKeyFromIdentity(sink_init_info.identity);
+    CheckSignature(sink_signing_key, sink_info.sessionId, sink_info.signature);
+
+    // Step 3: pass the sink's ECDH public key and other session info to the source, so it can
+    // calculate the same pair of symmetric keys.
+    SessionInfo source_info;
+    ASSERT_EQ(OK, GetReturnError(source->finish(sink_init_info.key.pubKey.value(),
+                                                sink_init_info.identity, sink_info.signature,
+                                                sink_init_info.nonce, sink_init_info.version,
+                                                source_init_info.key, &source_info)));
+    ASSERT_EQ((int)source_info.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
+    ASSERT_GT((int)source_info.sessionId.size(), 0) << "Expect non-empty session ID from source";
+    std::vector<uint8_t> source_signing_key = SigningKeyFromIdentity(source_init_info.identity);
+    CheckSignature(source_signing_key, source_info.sessionId, source_info.signature);
+
+    // Both ends should agree on the session ID.
+    ASSERT_EQ(source_info.sessionId, sink_info.sessionId);
+
+    // Step 4: pass the source's session ID info back to the sink, so it can check it and
+    // update the symmetric keys so they're marked as authentication complete.
+    std::array<Arc, 2> auth_complete_result;
+    ASSERT_EQ(OK, GetReturnError(sink->authenticationComplete(
+                          source_info.signature, sink_info.sharedKeys, &auth_complete_result)));
+    ASSERT_EQ((int)auth_complete_result.size(), 2)
+            << "Expect two symmetric keys from authComplete()";
+    sink_info.sharedKeys = auth_complete_result;
+
+    // At this point the sink and source have agreed on the same pair of symmetric keys,
+    // encoded as `sink_info.sharedKeys` and `source_info.sharedKeys`.
+}
+
+TEST_P(AuthGraphSessionTest, ParallelSink) {
+    std::shared_ptr<IAuthGraphKeyExchange> source = authNode_;
+    std::shared_ptr<IAuthGraphKeyExchange> sink1 = authNode_;
+    std::shared_ptr<IAuthGraphKeyExchange> sink2 = authNode_;
+
+    // Step 1: create ephemeral ECDH keys at the source.
+    SessionInitiationInfo source_init1_info;
+    ASSERT_EQ(OK, GetReturnError(source->create(&source_init1_info)));
+    ASSERT_TRUE(source_init1_info.key.pubKey.has_value());
+    ASSERT_TRUE(source_init1_info.key.arcFromPBK.has_value());
+    SessionInitiationInfo source_init2_info;
+    ASSERT_EQ(OK, GetReturnError(source->create(&source_init2_info)));
+    ASSERT_TRUE(source_init2_info.key.pubKey.has_value());
+    ASSERT_TRUE(source_init2_info.key.arcFromPBK.has_value());
+
+    // Step 2: pass the source's ECDH public keys and other session info to the sinks.
+    KeInitResult init1_result;
+    ASSERT_EQ(OK, GetReturnError(sink1->init(source_init1_info.key.pubKey.value(),
+                                             source_init1_info.identity, source_init1_info.nonce,
+                                             source_init1_info.version, &init1_result)));
+    SessionInitiationInfo sink1_init_info = init1_result.sessionInitiationInfo;
+    ASSERT_TRUE(sink1_init_info.key.pubKey.has_value());
+
+    SessionInfo sink1_info = init1_result.sessionInfo;
+    ASSERT_EQ((int)sink1_info.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
+    ASSERT_GT((int)sink1_info.sessionId.size(), 0) << "Expect non-empty session ID from sink";
+    std::vector<uint8_t> sink1_signing_key = SigningKeyFromIdentity(sink1_init_info.identity);
+    CheckSignature(sink1_signing_key, sink1_info.sessionId, sink1_info.signature);
+    KeInitResult init2_result;
+    ASSERT_EQ(OK, GetReturnError(sink2->init(source_init2_info.key.pubKey.value(),
+                                             source_init2_info.identity, source_init2_info.nonce,
+                                             source_init2_info.version, &init2_result)));
+    SessionInitiationInfo sink2_init_info = init2_result.sessionInitiationInfo;
+    ASSERT_TRUE(sink2_init_info.key.pubKey.has_value());
+
+    SessionInfo sink2_info = init2_result.sessionInfo;
+    ASSERT_EQ((int)sink2_info.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
+    ASSERT_GT((int)sink2_info.sessionId.size(), 0) << "Expect non-empty session ID from sink";
+    std::vector<uint8_t> sink2_signing_key = SigningKeyFromIdentity(sink2_init_info.identity);
+    CheckSignature(sink2_signing_key, sink2_info.sessionId, sink2_info.signature);
+
+    // Step 3: pass each sink's ECDH public key and other session info to the source, so it can
+    // calculate the same pair of symmetric keys.
+    SessionInfo source_info1;
+    ASSERT_EQ(OK, GetReturnError(source->finish(sink1_init_info.key.pubKey.value(),
+                                                sink1_init_info.identity, sink1_info.signature,
+                                                sink1_init_info.nonce, sink1_init_info.version,
+                                                source_init1_info.key, &source_info1)));
+    ASSERT_EQ((int)source_info1.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
+    ASSERT_GT((int)source_info1.sessionId.size(), 0) << "Expect non-empty session ID from source";
+    std::vector<uint8_t> source_signing_key1 = SigningKeyFromIdentity(source_init1_info.identity);
+    CheckSignature(source_signing_key1, source_info1.sessionId, source_info1.signature);
+    SessionInfo source_info2;
+    ASSERT_EQ(OK, GetReturnError(source->finish(sink2_init_info.key.pubKey.value(),
+                                                sink2_init_info.identity, sink2_info.signature,
+                                                sink2_init_info.nonce, sink2_init_info.version,
+                                                source_init2_info.key, &source_info2)));
+    ASSERT_EQ((int)source_info2.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
+    ASSERT_GT((int)source_info2.sessionId.size(), 0) << "Expect non-empty session ID from source";
+    std::vector<uint8_t> source_signing_key2 = SigningKeyFromIdentity(source_init2_info.identity);
+    CheckSignature(source_signing_key2, source_info2.sessionId, source_info2.signature);
+
+    // Both ends should agree on the session ID.
+    ASSERT_EQ(source_info1.sessionId, sink1_info.sessionId);
+    ASSERT_EQ(source_info2.sessionId, sink2_info.sessionId);
+
+    // Step 4: pass the source's session ID info back to the sink, so it can check it and
+    // update the symmetric keys so they're marked as authentication complete.
+    std::array<Arc, 2> auth_complete_result1;
+    ASSERT_EQ(OK, GetReturnError(sink1->authenticationComplete(
+                          source_info1.signature, sink1_info.sharedKeys, &auth_complete_result1)));
+    ASSERT_EQ((int)auth_complete_result1.size(), 2)
+            << "Expect two symmetric keys from authComplete()";
+    sink1_info.sharedKeys = auth_complete_result1;
+    std::array<Arc, 2> auth_complete_result2;
+    ASSERT_EQ(OK, GetReturnError(sink2->authenticationComplete(
+                          source_info2.signature, sink2_info.sharedKeys, &auth_complete_result2)));
+    ASSERT_EQ((int)auth_complete_result2.size(), 2)
+            << "Expect two symmetric keys from authComplete()";
+    sink2_info.sharedKeys = auth_complete_result2;
+}
+
+TEST_P(AuthGraphSessionTest, ParallelSource) {
+    std::shared_ptr<IAuthGraphKeyExchange> source1 = authNode_;
+    std::shared_ptr<IAuthGraphKeyExchange> source2 = authNode_;
+    std::shared_ptr<IAuthGraphKeyExchange> sink = authNode_;
+
+    // Step 1: create an ephemeral ECDH key at each of the sources.
+    SessionInitiationInfo source1_init_info;
+    ASSERT_EQ(OK, GetReturnError(source1->create(&source1_init_info)));
+    ASSERT_TRUE(source1_init_info.key.pubKey.has_value());
+    ASSERT_TRUE(source1_init_info.key.arcFromPBK.has_value());
+    SessionInitiationInfo source2_init_info;
+    ASSERT_EQ(OK, GetReturnError(source1->create(&source2_init_info)));
+    ASSERT_TRUE(source2_init_info.key.pubKey.has_value());
+    ASSERT_TRUE(source2_init_info.key.arcFromPBK.has_value());
+
+    // Step 2: pass each source's ECDH public key and other session info to the sink.
+    KeInitResult init1_result;
+    ASSERT_EQ(OK, GetReturnError(sink->init(source1_init_info.key.pubKey.value(),
+                                            source1_init_info.identity, source1_init_info.nonce,
+                                            source1_init_info.version, &init1_result)));
+    SessionInitiationInfo sink_init1_info = init1_result.sessionInitiationInfo;
+    ASSERT_TRUE(sink_init1_info.key.pubKey.has_value());
+
+    SessionInfo sink_info1 = init1_result.sessionInfo;
+    ASSERT_EQ((int)sink_info1.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
+    ASSERT_GT((int)sink_info1.sessionId.size(), 0) << "Expect non-empty session ID from sink";
+    std::vector<uint8_t> sink_signing_key1 = SigningKeyFromIdentity(sink_init1_info.identity);
+    CheckSignature(sink_signing_key1, sink_info1.sessionId, sink_info1.signature);
+
+    KeInitResult init2_result;
+    ASSERT_EQ(OK, GetReturnError(sink->init(source2_init_info.key.pubKey.value(),
+                                            source2_init_info.identity, source2_init_info.nonce,
+                                            source2_init_info.version, &init2_result)));
+    SessionInitiationInfo sink_init2_info = init2_result.sessionInitiationInfo;
+    ASSERT_TRUE(sink_init2_info.key.pubKey.has_value());
+
+    SessionInfo sink_info2 = init2_result.sessionInfo;
+    ASSERT_EQ((int)sink_info2.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
+    ASSERT_GT((int)sink_info2.sessionId.size(), 0) << "Expect non-empty session ID from sink";
+    std::vector<uint8_t> sink_signing_key2 = SigningKeyFromIdentity(sink_init2_info.identity);
+    CheckSignature(sink_signing_key2, sink_info2.sessionId, sink_info2.signature);
+
+    // Step 3: pass the sink's ECDH public keys and other session info to the each of the sources.
+    SessionInfo source1_info;
+    ASSERT_EQ(OK, GetReturnError(source1->finish(sink_init1_info.key.pubKey.value(),
+                                                 sink_init1_info.identity, sink_info1.signature,
+                                                 sink_init1_info.nonce, sink_init1_info.version,
+                                                 source1_init_info.key, &source1_info)));
+    ASSERT_EQ((int)source1_info.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
+    ASSERT_GT((int)source1_info.sessionId.size(), 0) << "Expect non-empty session ID from source";
+    std::vector<uint8_t> source1_signing_key = SigningKeyFromIdentity(source1_init_info.identity);
+    CheckSignature(source1_signing_key, source1_info.sessionId, source1_info.signature);
+
+    SessionInfo source2_info;
+    ASSERT_EQ(OK, GetReturnError(source2->finish(sink_init2_info.key.pubKey.value(),
+                                                 sink_init2_info.identity, sink_info2.signature,
+                                                 sink_init2_info.nonce, sink_init2_info.version,
+                                                 source2_init_info.key, &source2_info)));
+    ASSERT_EQ((int)source2_info.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
+    ASSERT_GT((int)source2_info.sessionId.size(), 0) << "Expect non-empty session ID from source";
+    std::vector<uint8_t> source2_signing_key = SigningKeyFromIdentity(source2_init_info.identity);
+    CheckSignature(source2_signing_key, source2_info.sessionId, source2_info.signature);
+
+    // Both ends should agree on the session ID.
+    ASSERT_EQ(source1_info.sessionId, sink_info1.sessionId);
+    ASSERT_EQ(source2_info.sessionId, sink_info2.sessionId);
+
+    // Step 4: pass the each source's session ID info back to the sink, so it can check it and
+    // update the symmetric keys so they're marked as authentication complete.
+    std::array<Arc, 2> auth_complete_result1;
+    ASSERT_EQ(OK, GetReturnError(sink->authenticationComplete(
+                          source1_info.signature, sink_info1.sharedKeys, &auth_complete_result1)));
+    ASSERT_EQ((int)auth_complete_result1.size(), 2)
+            << "Expect two symmetric keys from authComplete()";
+    sink_info1.sharedKeys = auth_complete_result1;
+    std::array<Arc, 2> auth_complete_result2;
+    ASSERT_EQ(OK, GetReturnError(sink->authenticationComplete(
+                          source2_info.signature, sink_info2.sharedKeys, &auth_complete_result2)));
+    ASSERT_EQ((int)auth_complete_result2.size(), 2)
+            << "Expect two symmetric keys from authComplete()";
+    sink_info2.sharedKeys = auth_complete_result2;
+}
+
+TEST_P(AuthGraphSessionTest, FreshNonces) {
+    std::shared_ptr<IAuthGraphKeyExchange> source = authNode_;
+    std::shared_ptr<IAuthGraphKeyExchange> sink = authNode_;
+
+    SessionInitiationInfo source_init_info1;
+    ASSERT_EQ(OK, GetReturnError(source->create(&source_init_info1)));
+    SessionInitiationInfo source_init_info2;
+    ASSERT_EQ(OK, GetReturnError(source->create(&source_init_info2)));
+
+    // Two calls to create() should result in the same identity but different nonce values.
+    ASSERT_EQ(source_init_info1.identity, source_init_info2.identity);
+    ASSERT_NE(source_init_info1.nonce, source_init_info2.nonce);
+    ASSERT_NE(source_init_info1.key.pubKey, source_init_info2.key.pubKey);
+    ASSERT_NE(source_init_info1.key.arcFromPBK, source_init_info2.key.arcFromPBK);
+
+    KeInitResult init_result1;
+    ASSERT_EQ(OK, GetReturnError(sink->init(source_init_info1.key.pubKey.value(),
+                                            source_init_info1.identity, source_init_info1.nonce,
+                                            source_init_info1.version, &init_result1)));
+    KeInitResult init_result2;
+    ASSERT_EQ(OK, GetReturnError(sink->init(source_init_info2.key.pubKey.value(),
+                                            source_init_info2.identity, source_init_info2.nonce,
+                                            source_init_info2.version, &init_result2)));
+
+    // Two calls to init() should result in the same identity buf different nonces and session IDs.
+    ASSERT_EQ(init_result1.sessionInitiationInfo.identity,
+              init_result2.sessionInitiationInfo.identity);
+    ASSERT_NE(init_result1.sessionInitiationInfo.nonce, init_result2.sessionInitiationInfo.nonce);
+    ASSERT_NE(init_result1.sessionInfo.sessionId, init_result2.sessionInfo.sessionId);
+}
+
+INSTANTIATE_TEST_SUITE_P(PerInstance, AuthGraphSessionTest,
+                         testing::ValuesIn(AuthGraphSessionTest::build_params()),
+                         ::android::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AuthGraphSessionTest);
+
+}  // namespace aidl::android::hardware::security::authgraph::test
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/security/authgraph/default/Android.bp b/security/authgraph/default/Android.bp
new file mode 100644
index 0000000..9de3bc1
--- /dev/null
+++ b/security/authgraph/default/Android.bp
@@ -0,0 +1,46 @@
+//
+// Copyright (C) 2023 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.
+//
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "hardware_interfaces_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+rust_binary {
+    name: "android.hardware.security.authgraph-service.nonsecure",
+    relative_install_path: "hw",
+    vendor: true,
+    init_rc: ["authgraph.rc"],
+    vintf_fragments: ["authgraph.xml"],
+    defaults: [
+        "authgraph_use_latest_hal_aidl_rust",
+    ],
+    rustlibs: [
+        "libandroid_logger",
+        "libauthgraph_core",
+        "libauthgraph_boringssl",
+        "libauthgraph_hal",
+        "libbinder_rs",
+        "liblibc",
+        "liblog_rust",
+    ],
+    srcs: [
+        "src/main.rs",
+    ],
+}
diff --git a/security/authgraph/default/authgraph.rc b/security/authgraph/default/authgraph.rc
new file mode 100644
index 0000000..0222994
--- /dev/null
+++ b/security/authgraph/default/authgraph.rc
@@ -0,0 +1,5 @@
+service vendor.authgraph /vendor/bin/hw/android.hardware.security.authgraph-service.nonsecure
+    interface aidl android.hardware.security.authgraph.IAuthGraph/nonsecure
+    class hal
+    user nobody
+    group nobody
diff --git a/security/authgraph/default/authgraph.xml b/security/authgraph/default/authgraph.xml
new file mode 100644
index 0000000..9529a0a
--- /dev/null
+++ b/security/authgraph/default/authgraph.xml
@@ -0,0 +1,10 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.security.authgraph</name>
+        <version>1</version>
+        <interface>
+            <name>IAuthGraphKeyExchange</name>
+            <instance>nonsecure</instance>
+        </interface>
+    </hal>
+</manifest>
diff --git a/security/authgraph/default/src/main.rs b/security/authgraph/default/src/main.rs
new file mode 100644
index 0000000..2112e58
--- /dev/null
+++ b/security/authgraph/default/src/main.rs
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+//! Default implementation of the AuthGraph key exchange HAL.
+//!
+//! This implementation of the HAL is only intended to allow testing and policy compliance.  A real
+//! implementation of the AuthGraph HAL would be implemented in a secure environment, and would not
+//! be independently registered with service manager (a secure component that uses AuthGraph would
+//! expose an entrypoint that allowed retrieval of the specific IAuthGraphKeyExchange instance that
+//! is correlated with the component).
+
+use android_hardware_security_authgraph::aidl::android::hardware::security::authgraph::{
+    Arc::Arc, IAuthGraphKeyExchange::BnAuthGraphKeyExchange,
+    IAuthGraphKeyExchange::IAuthGraphKeyExchange, Identity::Identity, KeInitResult::KeInitResult,
+    Key::Key, PubKey::PubKey, SessionIdSignature::SessionIdSignature, SessionInfo::SessionInfo,
+    SessionInitiationInfo::SessionInitiationInfo,
+};
+use authgraph_boringssl as boring;
+use authgraph_core::{key::MillisecondsSinceEpoch, keyexchange as ke, traits};
+use authgraph_hal::{err_to_binder, Innto, TryInnto};
+use log::{error, info};
+use std::ffi::CString;
+use std::sync::Mutex;
+
+static SERVICE_NAME: &str = "android.hardware.security.authgraph.IAuthGraphKeyExchange";
+static SERVICE_INSTANCE: &str = "nonsecure";
+
+/// Local error type for failures in the HAL service.
+#[derive(Debug, Clone)]
+struct HalServiceError(String);
+
+impl From<String> for HalServiceError {
+    fn from(s: String) -> Self {
+        Self(s)
+    }
+}
+
+fn main() {
+    if let Err(e) = inner_main() {
+        panic!("HAL service failed: {:?}", e);
+    }
+}
+
+fn inner_main() -> Result<(), HalServiceError> {
+    // Initialize Android logging.
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("authgraph-hal-nonsecure")
+            .with_min_level(log::Level::Info)
+            .with_log_id(android_logger::LogId::System),
+    );
+    // Redirect panic messages to logcat.
+    std::panic::set_hook(Box::new(|panic_info| {
+        error!("{}", panic_info);
+    }));
+
+    info!("Insecure AuthGraph key exchange HAL service is starting.");
+
+    info!("Starting thread pool now.");
+    binder::ProcessState::start_thread_pool();
+
+    // Register the service
+    let service = AuthGraphService::new_as_binder();
+    let service_name = format!("{}/{}", SERVICE_NAME, SERVICE_INSTANCE);
+    binder::add_service(&service_name, service.as_binder()).map_err(|e| {
+        format!(
+            "Failed to register service {} because of {:?}.",
+            service_name, e
+        )
+    })?;
+
+    info!("Successfully registered AuthGraph HAL services.");
+    binder::ProcessState::join_thread_pool();
+    info!("AuthGraph HAL service is terminating."); // should not reach here
+    Ok(())
+}
+
+/// Non-secure implementation of the AuthGraph key exchange service.
+struct AuthGraphService {
+    imp: Mutex<traits::TraitImpl>,
+}
+
+impl AuthGraphService {
+    /// Create a new instance.
+    fn new() -> Self {
+        Self {
+            imp: Mutex::new(traits::TraitImpl {
+                aes_gcm: Box::new(boring::BoringAes),
+                ecdh: Box::new(boring::BoringEcDh),
+                ecdsa: Box::new(boring::BoringEcDsa),
+                hmac: Box::new(boring::BoringHmac),
+                hkdf: Box::new(boring::BoringHkdf),
+                sha256: Box::new(boring::BoringSha256),
+                rng: Box::new(boring::BoringRng),
+                device: Box::<boring::test_device::AgDevice>::default(),
+                clock: Some(Box::new(StdClock)),
+            }),
+        }
+    }
+
+    /// Create a new instance wrapped in a proxy object.
+    pub fn new_as_binder() -> binder::Strong<dyn IAuthGraphKeyExchange> {
+        BnAuthGraphKeyExchange::new_binder(Self::new(), binder::BinderFeatures::default())
+    }
+}
+
+impl binder::Interface for AuthGraphService {}
+
+/// Extract (and require) an unsigned public key as bytes from a [`PubKey`].
+fn unsigned_pub_key(pub_key: &PubKey) -> binder::Result<&[u8]> {
+    match pub_key {
+        PubKey::PlainKey(key) => Ok(&key.plainPubKey),
+        PubKey::SignedKey(_) => Err(binder::Status::new_exception(
+            binder::ExceptionCode::ILLEGAL_ARGUMENT,
+            Some(&CString::new("expected unsigned public key").unwrap()),
+        )),
+    }
+}
+
+/// This nonsecure implementation of the AuthGraph HAL interface directly calls the AuthGraph
+/// reference implementation library code; a real implementation requires the AuthGraph
+/// code to run in a secure environment, not within Android.
+impl IAuthGraphKeyExchange for AuthGraphService {
+    fn create(&self) -> binder::Result<SessionInitiationInfo> {
+        info!("create()");
+        let mut imp = self.imp.lock().unwrap();
+        let info = ke::create(&mut *imp).map_err(err_to_binder)?;
+        Ok(info.innto())
+    }
+    fn init(
+        &self,
+        peer_pub_key: &PubKey,
+        peer_id: &Identity,
+        peer_nonce: &[u8],
+        peer_version: i32,
+    ) -> binder::Result<KeInitResult> {
+        info!("init(v={peer_version})");
+        let mut imp = self.imp.lock().unwrap();
+        let peer_pub_key = unsigned_pub_key(peer_pub_key)?;
+        let result = ke::init(
+            &mut *imp,
+            peer_pub_key,
+            &peer_id.identity,
+            &peer_nonce,
+            peer_version,
+        )
+        .map_err(err_to_binder)?;
+        Ok(result.innto())
+    }
+
+    fn finish(
+        &self,
+        peer_pub_key: &PubKey,
+        peer_id: &Identity,
+        peer_signature: &SessionIdSignature,
+        peer_nonce: &[u8],
+        peer_version: i32,
+        own_key: &Key,
+    ) -> binder::Result<SessionInfo> {
+        info!("finish(v={peer_version})");
+        let mut imp = self.imp.lock().unwrap();
+        let peer_pub_key = unsigned_pub_key(peer_pub_key)?;
+        let own_key: Key = own_key.clone();
+        let own_key: authgraph_core::key::Key = own_key.try_innto()?;
+        let session_info = ke::finish(
+            &mut *imp,
+            peer_pub_key,
+            &peer_id.identity,
+            &peer_signature.signature,
+            &peer_nonce,
+            peer_version,
+            own_key,
+        )
+        .map_err(err_to_binder)?;
+        Ok(session_info.innto())
+    }
+
+    fn authenticationComplete(
+        &self,
+        peer_signature: &SessionIdSignature,
+        shared_keys: &[Arc; 2],
+    ) -> binder::Result<[Arc; 2]> {
+        info!("authComplete()");
+        let mut imp = self.imp.lock().unwrap();
+        let shared_keys = [shared_keys[0].arc.clone(), shared_keys[1].arc.clone()];
+        let arcs = ke::authentication_complete(&mut *imp, &peer_signature.signature, shared_keys)
+            .map_err(err_to_binder)?;
+        Ok(arcs.map(|arc| Arc { arc }))
+    }
+}
+
+/// Monotonic clock.
+#[derive(Default)]
+pub struct StdClock;
+
+impl traits::MonotonicClock for StdClock {
+    fn now(&self) -> authgraph_core::key::MillisecondsSinceEpoch {
+        let mut time = libc::timespec {
+            tv_sec: 0,  // libc::time_t
+            tv_nsec: 0, // libc::c_long
+        };
+        let rc =
+        // Safety: `time` is a valid structure.
+            unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut time as *mut libc::timespec) };
+        if rc < 0 {
+            log::warn!("failed to get time!");
+            return MillisecondsSinceEpoch(0);
+        }
+        // The types in `libc::timespec` may be different on different architectures,
+        // so allow conversion to `i64`.
+        #[allow(clippy::unnecessary_cast)]
+        MillisecondsSinceEpoch((time.tv_sec as i64 * 1000) + (time.tv_nsec as i64 / 1000 / 1000))
+    }
+}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
index 36f0106..aa7bf28 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
@@ -643,6 +643,8 @@
      * Tag::ATTESTATION_CHALLENGE is used to deliver a "challenge" value to the attested key
      * generation/import methods, which must place the value in the KeyDescription SEQUENCE of the
      * attestation extension.
+     * The challenge value may be up to 128 bytes. If the caller provides a bigger challenge,
+     * INVALID_INPUT_LENGTH error should be returned.
      *
      * Must never appear in KeyCharacteristics.
      */
diff --git a/tv/tuner/aidl/vts/functional/FilterTests.cpp b/tv/tuner/aidl/vts/functional/FilterTests.cpp
index 53afef7..533d0e6 100644
--- a/tv/tuner/aidl/vts/functional/FilterTests.cpp
+++ b/tv/tuner/aidl/vts/functional/FilterTests.cpp
@@ -305,13 +305,18 @@
     ndk::ScopedAStatus status;
 
     status = mFilters[filterId]->configureMonitorEvent(monitorEventTypes);
+    return AssertionResult(status.isOk());
+}
+
+AssertionResult FilterTests::testMonitorEvent(uint64_t filterId, uint32_t monitorEventTypes) {
+    EXPECT_TRUE(mFilterCallbacks[filterId]) << "Test with getNewlyOpenedFilterId first.";
     if (monitorEventTypes & static_cast<int32_t>(DemuxFilterMonitorEventType::SCRAMBLING_STATUS)) {
         mFilterCallbacks[filterId]->testFilterScramblingEvent();
     }
     if (monitorEventTypes & static_cast<int32_t>(DemuxFilterMonitorEventType::IP_CID_CHANGE)) {
         mFilterCallbacks[filterId]->testFilterIpCidEvent();
     }
-    return AssertionResult(status.isOk());
+    return AssertionResult(true);
 }
 
 AssertionResult FilterTests::startIdTest(int64_t filterId) {
diff --git a/tv/tuner/aidl/vts/functional/FilterTests.h b/tv/tuner/aidl/vts/functional/FilterTests.h
index f579441..f57093e 100644
--- a/tv/tuner/aidl/vts/functional/FilterTests.h
+++ b/tv/tuner/aidl/vts/functional/FilterTests.h
@@ -124,6 +124,7 @@
     AssertionResult configAvFilterStreamType(AvStreamType type, int64_t filterId);
     AssertionResult configIpFilterCid(int32_t ipCid, int64_t filterId);
     AssertionResult configureMonitorEvent(int64_t filterId, int32_t monitorEventTypes);
+    AssertionResult testMonitorEvent(uint64_t filterId, uint32_t monitorEventTypes);
     AssertionResult getFilterMQDescriptor(int64_t filterId, bool getMqDesc);
     AssertionResult startFilter(int64_t filterId);
     AssertionResult stopFilter(int64_t filterId);
diff --git a/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.cpp b/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.cpp
index 9db82c8..3664b6c 100644
--- a/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.cpp
+++ b/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.cpp
@@ -60,6 +60,11 @@
     }
     ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
     ASSERT_TRUE(mFilterTests.startFilter(filterId));
+    ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf, true /*testWithDemux*/));
+    if (filterConf.monitorEventTypes > 0) {
+        ASSERT_TRUE(mFilterTests.testMonitorEvent(filterId, filterConf.monitorEventTypes));
+    }
+    ASSERT_TRUE(mFrontendTests.stopTuneFrontend(true /*testWithDemux*/));
     ASSERT_TRUE(mFilterTests.stopFilter(filterId));
     ASSERT_TRUE(mFilterTests.closeFilter(filterId));
     ASSERT_TRUE(mDemuxTests.closeDemux());