Merge "[Lut vts] add readback test" into main
diff --git a/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp b/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
index ea59771..cef0ea6 100644
--- a/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
+++ b/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
@@ -77,17 +77,15 @@
 }
 
 ::android::status_t StreamRemoteSubmix::drain(StreamDescriptor::DrainMode) {
-    usleep(1000);
     return ::android::OK;
 }
 
 ::android::status_t StreamRemoteSubmix::flush() {
-    usleep(1000);
-    return ::android::OK;
+    // TODO(b/372951987): consider if this needs to be done from 'StreamInWorkerLogic::cycle'.
+    return mIsInput ? standby() : ::android::OK;
 }
 
 ::android::status_t StreamRemoteSubmix::pause() {
-    usleep(1000);
     return ::android::OK;
 }
 
diff --git a/audio/aidl/default/r_submix/SubmixRoute.cpp b/audio/aidl/default/r_submix/SubmixRoute.cpp
index 325a012..445b1d3 100644
--- a/audio/aidl/default/r_submix/SubmixRoute.cpp
+++ b/audio/aidl/default/r_submix/SubmixRoute.cpp
@@ -134,10 +134,10 @@
 // - the peer input is in standby AFTER having been active.
 // We DO block if:
 // - the input was never activated to avoid discarding first frames in the pipe in case capture
-// start was delayed
+//   start was delayed
 bool SubmixRoute::shouldBlockWrite() {
     std::lock_guard guard(mLock);
-    return (mStreamInOpen || (mStreamInStandby && (mReadCounterFrames != 0)));
+    return mStreamInOpen && (!mStreamInStandby || mReadCounterFrames == 0);
 }
 
 long SubmixRoute::updateReadCounterFrames(size_t frameCount) {
diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
index 93c2a61..750e54d 100644
--- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
@@ -938,6 +938,8 @@
         LOG(DEBUG) << __func__;
         return "";
     }
+    const std::vector<int8_t>& getData() const { return mData; }
+    void fillData(int8_t filler) { std::fill(mData.begin(), mData.end(), filler); }
     std::optional<StreamDescriptor::Command> maybeGetNextCommand(int* actualSize = nullptr) {
         TransitionTrigger trigger = mDriver->getNextTrigger(mData.size(), actualSize);
         if (StreamEventReceiver::Event* expEvent =
@@ -1007,6 +1009,8 @@
     StreamReaderLogic(const StreamContext& context, StreamLogicDriver* driver,
                       StreamEventReceiver* eventReceiver)
         : StreamCommonLogic(context, driver, eventReceiver) {}
+    // Should only be called after the worker has joined.
+    const std::vector<int8_t>& getData() const { return StreamCommonLogic::getData(); }
 
   protected:
     Status cycle() override {
@@ -1072,6 +1076,7 @@
         if (const size_t readCount =
                     !isMmapped() ? getDataMQ()->availableToRead() : reply.fmqByteCount;
             readCount > 0) {
+            fillData(-1);
             if (isMmapped() ? readDataFromMmap(readCount) : readDataFromMQ(readCount)) {
                 goto checkAcceptedReply;
             }
@@ -1093,6 +1098,8 @@
     StreamWriterLogic(const StreamContext& context, StreamLogicDriver* driver,
                       StreamEventReceiver* eventReceiver)
         : StreamCommonLogic(context, driver, eventReceiver) {}
+    // Should only be called after the worker has joined.
+    const std::vector<int8_t>& getData() const { return StreamCommonLogic::getData(); }
 
   protected:
     Status cycle() override {
@@ -1109,6 +1116,14 @@
             return Status::ABORT;
         }
         if (actualSize != 0) {
+            if (command.getTag() == StreamDescriptor::Command::burst) {
+                fillData(mBurstIteration);
+                if (mBurstIteration < std::numeric_limits<int8_t>::max()) {
+                    mBurstIteration++;
+                } else {
+                    mBurstIteration = 0;
+                }
+            }
             if (isMmapped() ? !writeDataToMmap() : !writeDataToMQ()) {
                 return Status::ABORT;
             }
@@ -1167,6 +1182,9 @@
         LOG(ERROR) << __func__ << ": unacceptable reply: " << reply.toString();
         return Status::ABORT;
     }
+
+  private:
+    int8_t mBurstIteration = 1;
 };
 using StreamWriter = StreamWorker<StreamWriterLogic>;
 
@@ -2859,10 +2877,12 @@
         ASSERT_NO_FATAL_FAILURE(mStream->SetUpStream(module, getMinimumStreamBufferSizeFrames()));
     }
 
-    void SetUpStreamForDevicePort(IModule* module, ModuleConfig* moduleConfig,
-                                  const AudioPort& devicePort, bool connectedOnly = false) {
-        ASSERT_NO_FATAL_FAILURE(
-                SetUpPortConfigForDevicePort(module, moduleConfig, devicePort, connectedOnly));
+    void SetUpStreamForDevicePort(
+            IModule* module, ModuleConfig* moduleConfig, const AudioPort& devicePort,
+            bool connectedOnly = false,
+            const std::optional<AudioDeviceAddress>& connectionAddress = std::nullopt) {
+        ASSERT_NO_FATAL_FAILURE(SetUpPortConfigForDevicePort(module, moduleConfig, devicePort,
+                                                             connectedOnly, connectionAddress));
         if (!mSkipTestReason.empty()) return;
         ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
     }
@@ -2898,6 +2918,23 @@
         if (!mSkipTestReason.empty()) return;
         ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
     }
+    void SetUpStreamForNewMixPortConfig(IModule* module, ModuleConfig*,
+                                        const AudioPortConfig& existingMixPortConfig,
+                                        const AudioPortConfig& existingDevicePortConfig) {
+        auto mixPortConfig = existingMixPortConfig;
+        mixPortConfig.id = 0;
+        mMixPortConfig = std::make_unique<WithAudioPortConfig>(mixPortConfig);
+        ASSERT_NO_FATAL_FAILURE(mMixPortConfig->SetUp(module));
+        mDevicePortConfig = std::make_unique<WithAudioPortConfig>(existingDevicePortConfig);
+        ASSERT_NO_FATAL_FAILURE(mDevicePortConfig->SetUp(module));
+        mDevice = existingDevicePortConfig.ext.get<AudioPortExt::device>().device;
+        mPatch = std::make_unique<WithAudioPatch>(mIsInput, mMixPortConfig->get(),
+                                                  mDevicePortConfig->get());
+        ASSERT_NO_FATAL_FAILURE(mPatch->SetUp(module));
+        mStream = std::make_unique<WithStream<Stream>>(mMixPortConfig->get());
+        ASSERT_NO_FATAL_FAILURE(mStream->SetUpPortConfig(module));
+        ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
+    }
     void SetUpPatchForMixPortConfig(IModule* module, ModuleConfig* moduleConfig,
                                     const AudioPortConfig& mixPortConfig) {
         constexpr bool connectedOnly = true;
@@ -2929,6 +2966,7 @@
     }
 
     const AudioDevice& getDevice() const { return mDevice; }
+    const AudioPortConfig& getDevicePortConfig() const { return mDevicePortConfig->get(); }
     int32_t getMinimumStreamBufferSizeFrames() const {
         return mPatch->getMinimumStreamBufferSizeFrames();
     }
@@ -2944,7 +2982,8 @@
   private:
     void SetUpDevicePort(IModule* module, ModuleConfig* moduleConfig,
                          const std::set<int32_t>& devicePortIds, bool connectedOnly,
-                         std::optional<AudioPort>* connectedDevicePort) {
+                         std::optional<AudioPort>* connectedDevicePort,
+                         const std::optional<AudioDeviceAddress>& connectionAddress) {
         const auto attachedDevicePorts = moduleConfig->getAttachedDevicePorts();
         if (auto it = findAny<AudioPort>(attachedDevicePorts, devicePortIds);
             it != attachedDevicePorts.end()) {
@@ -2961,7 +3000,12 @@
             const auto externalDevicePorts = moduleConfig->getExternalDevicePorts();
             if (auto it = findAny<AudioPort>(externalDevicePorts, devicePortIds);
                 it != externalDevicePorts.end()) {
-                AudioPort portWithData = GenerateUniqueDeviceAddress(*it);
+                AudioPort portWithData = *it;
+                if (connectionAddress.has_value()) {
+                    portWithData.ext.get<AudioPortExt::Tag::device>().device.address =
+                            *connectionAddress;
+                }
+                portWithData = GenerateUniqueDeviceAddress(portWithData);
                 mPortConnected = std::make_unique<WithDevicePortConnectedState>(portWithData);
                 ASSERT_NO_FATAL_FAILURE(mPortConnected->SetUp(module, moduleConfig));
                 *connectedDevicePort = mPortConnected->get();
@@ -2980,9 +3024,9 @@
             LOG(DEBUG) << __func__ << ": " << mSkipTestReason;
             return;
         };
-        ASSERT_NO_FATAL_FAILURE(SetUpDevicePort(module, moduleConfig,
-                                                extractIds<AudioPort>(devicePorts), connectedOnly,
-                                                connectedDevicePort));
+        ASSERT_NO_FATAL_FAILURE(SetUpDevicePort(
+                module, moduleConfig, extractIds<AudioPort>(devicePorts), connectedOnly,
+                connectedDevicePort, std::nullopt /*connectionAddress*/));
         if (!connectedDevicePort->has_value()) {
             mSkipTestReason = std::string("Unable to find a device port pair for mix port id ")
                                       .append(std::to_string(mixPort.id));
@@ -2990,11 +3034,14 @@
             return;
         }
     }
-    void SetUpPortConfigForDevicePort(IModule* module, ModuleConfig* moduleConfig,
-                                      const AudioPort& devicePort, bool connectedOnly) {
+    void SetUpPortConfigForDevicePort(
+            IModule* module, ModuleConfig* moduleConfig, const AudioPort& devicePort,
+            bool connectedOnly,
+            const std::optional<AudioDeviceAddress>& connectionAddress = std::nullopt) {
         std::optional<AudioPort> connectedDevicePort;
         ASSERT_NO_FATAL_FAILURE(SetUpDevicePort(module, moduleConfig, {devicePort.id},
-                                                connectedOnly, &connectedDevicePort));
+                                                connectedOnly, &connectedDevicePort,
+                                                connectionAddress));
         if (!connectedDevicePort.has_value()) {
             mSkipTestReason = std::string("Device port id ")
                                       .append(std::to_string(devicePort.id))
@@ -3135,7 +3182,8 @@
 };
 
 // Defined later together with state transition sequences.
-std::shared_ptr<StateSequence> makeBurstCommands(bool isSync);
+std::shared_ptr<StateSequence> makeBurstCommands(bool isSync, size_t burstCount = 10,
+                                                 bool standbyInputWhenDone = false);
 
 // Certain types of ports can not be used without special preconditions.
 static bool skipStreamIoTestForMixPortConfig(const AudioPortConfig& portConfig) {
@@ -3160,10 +3208,11 @@
   public:
     explicit StreamFixtureWithWorker(bool isSync) : mIsSync(isSync) {}
 
-    void SetUp(IModule* module, ModuleConfig* moduleConfig, const AudioPort& devicePort) {
+    void SetUp(IModule* module, ModuleConfig* moduleConfig, const AudioPort& devicePort,
+               const std::optional<AudioDeviceAddress>& connectionAddress = std::nullopt) {
         mStream = std::make_unique<StreamFixture<Stream>>();
-        ASSERT_NO_FATAL_FAILURE(
-                mStream->SetUpStreamForDevicePort(module, moduleConfig, devicePort));
+        ASSERT_NO_FATAL_FAILURE(mStream->SetUpStreamForDevicePort(
+                module, moduleConfig, devicePort, false /*connectedOnly*/, connectionAddress));
         MaybeSetSkipTestReason();
     }
 
@@ -3175,26 +3224,42 @@
         MaybeSetSkipTestReason();
     }
 
-    void SendBurstCommands(bool validatePosition = true) {
-        ASSERT_NO_FATAL_FAILURE(StartWorkerToSendBurstCommands());
+    void SetUp(IModule* module, ModuleConfig* moduleConfig,
+               const AudioPortConfig& existingMixPortConfig,
+               const AudioPortConfig& existingDevicePortConfig) {
+        mStream = std::make_unique<StreamFixture<Stream>>();
+        ASSERT_NO_FATAL_FAILURE(mStream->SetUpStreamForNewMixPortConfig(
+                module, moduleConfig, existingMixPortConfig, existingDevicePortConfig));
+        MaybeSetSkipTestReason();
+    }
+
+    void SendBurstCommands(bool validatePosition = true, size_t burstCount = 10,
+                           bool standbyInputWhenDone = false) {
+        ASSERT_NO_FATAL_FAILURE(StartWorkerToSendBurstCommands(burstCount, standbyInputWhenDone));
         ASSERT_NO_FATAL_FAILURE(JoinWorkerAfterBurstCommands(validatePosition));
     }
 
-    void StartWorkerToSendBurstCommands() {
+    void StartWorkerToSendBurstCommands(size_t burstCount = 10, bool standbyInputWhenDone = false) {
+        if (!IOTraits<Stream>::is_input) {
+            ASSERT_FALSE(standbyInputWhenDone) << "Only supported for input";
+        }
         const StreamContext* context = mStream->getStreamContext();
         mWorkerDriver = std::make_unique<StreamLogicDefaultDriver>(
-                makeBurstCommands(mIsSync), context->getFrameSizeBytes(), context->isMmapped());
+                makeBurstCommands(mIsSync, burstCount, standbyInputWhenDone),
+                context->getFrameSizeBytes(), context->isMmapped());
         mWorker = std::make_unique<typename IOTraits<Stream>::Worker>(
                 *context, mWorkerDriver.get(), mStream->getStreamEventReceiver());
         LOG(DEBUG) << __func__ << ": starting " << IOTraits<Stream>::directionStr << " worker...";
         ASSERT_TRUE(mWorker->start());
     }
 
-    void JoinWorkerAfterBurstCommands(bool validatePosition = true) {
-        // Must call 'prepareToClose' before attempting to join because the stream may be stuck.
-        std::shared_ptr<IStreamCommon> common;
-        ASSERT_IS_OK(mStream->getStream()->getStreamCommon(&common));
-        ASSERT_IS_OK(common->prepareToClose());
+    void JoinWorkerAfterBurstCommands(bool validatePosition = true,
+                                      bool callPrepareToClose = true) {
+        if (callPrepareToClose) {
+            std::shared_ptr<IStreamCommon> common;
+            ASSERT_IS_OK(mStream->getStream()->getStreamCommon(&common));
+            ASSERT_IS_OK(common->prepareToClose());
+        }
         LOG(DEBUG) << __func__ << ": joining " << IOTraits<Stream>::directionStr << " worker...";
         mWorker->join();
         EXPECT_FALSE(mWorker->hasError()) << mWorker->getError();
@@ -3205,6 +3270,7 @@
             EXPECT_FALSE(mWorkerDriver->hasObservableRetrogradePosition());
             EXPECT_FALSE(mWorkerDriver->hasHardwareRetrogradePosition());
         }
+        mLastData = mWorker->getData();
         mWorker.reset();
         mWorkerDriver.reset();
     }
@@ -3212,6 +3278,9 @@
     void TeardownPatch() { mStream->TeardownPatch(); }
 
     const AudioDevice& getDevice() const { return mStream->getDevice(); }
+    const AudioPortConfig& getDevicePortConfig() const { return mStream->getDevicePortConfig(); }
+    const std::vector<int8_t>& getLastData() const { return mLastData; }
+    const AudioPortConfig& getPortConfig() const { return mStream->getPortConfig(); }
     Stream* getStream() const { return mStream->getStream(); }
     std::string skipTestReason() const {
         return !mSkipTestReason.empty() ? mSkipTestReason : mStream->skipTestReason();
@@ -3229,6 +3298,7 @@
     std::unique_ptr<StreamFixture<Stream>> mStream;
     std::unique_ptr<StreamLogicDefaultDriver> mWorkerDriver;
     std::unique_ptr<typename IOTraits<Stream>::Worker> mWorker;
+    std::vector<int8_t> mLastData;
 };
 
 template <typename Stream>
@@ -4576,15 +4646,20 @@
 
 // TODO: Add async test cases for input once it is implemented.
 
-std::shared_ptr<StateSequence> makeBurstCommands(bool isSync) {
+std::shared_ptr<StateSequence> makeBurstCommands(bool isSync, size_t burstCount,
+                                                 bool standbyInputWhenDone) {
     using State = StreamDescriptor::State;
     auto d = std::make_unique<StateDag>();
-    StateDag::Node last = d->makeFinalNode(State::ACTIVE);
+    StateDag::Node active = d->makeFinalNode(State::ACTIVE);
+    StateDag::Node paused = d->makeNodes({std::make_pair(State::ACTIVE, kPauseCommand),
+                                          std::make_pair(State::PAUSED, kFlushCommand)},
+                                         State::STANDBY);
+    StateDag::Node& last = standbyInputWhenDone ? paused : active;
     if (isSync) {
         StateDag::Node idle = d->makeNode(
                 State::IDLE, kBurstCommand,
                 // Use several bursts to ensure that the driver starts reporting the position.
-                d->makeNodes(State::ACTIVE, kBurstCommand, 10, last));
+                d->makeNodes(State::ACTIVE, kBurstCommand, burstCount, last));
         d->makeNode(State::STANDBY, kStartCommand, idle);
     } else {
         StateDag::Node active2 = d->makeNode(State::ACTIVE, kBurstCommand, last);
@@ -4949,49 +5024,69 @@
   public:
     WithRemoteSubmix() : mStream(true /*isSync*/) {}
     explicit WithRemoteSubmix(AudioDeviceAddress address)
-        : mStream(true /*isSync*/), mAddress(address) {}
+        : mStream(true /*isSync*/), mAddress(address) {
+        LOG(DEBUG) << __func__ << ": Creating " << IOTraits<Stream>::directionStr
+                   << " stream for: " << mAddress.value_or(AudioDeviceAddress{}).toString();
+    }
     WithRemoteSubmix(const WithRemoteSubmix&) = delete;
     WithRemoteSubmix& operator=(const WithRemoteSubmix&) = delete;
+    ~WithRemoteSubmix() {
+        LOG(DEBUG) << __func__ << ": Deleting " << IOTraits<Stream>::directionStr
+                   << " stream for: " << mAddress.value_or(AudioDeviceAddress{}).toString();
+    }
 
-    static std::optional<AudioPort> getRemoteSubmixAudioPort(
-            ModuleConfig* moduleConfig,
-            const std::optional<AudioDeviceAddress>& address = std::nullopt) {
+    static std::optional<AudioPort> getRemoteSubmixAudioPort(ModuleConfig* moduleConfig) {
         auto ports =
                 moduleConfig->getRemoteSubmixPorts(IOTraits<Stream>::is_input, true /*singlePort*/);
         if (ports.empty()) return {};
-        AudioPort port = ports.front();
-        if (address) {
-            port.ext.template get<AudioPortExt::Tag::device>().device.address = address.value();
-        }
-        return port;
+        return ports.front();
     }
 
     void SetUp(IModule* module, ModuleConfig* moduleConfig) {
-        auto devicePort = getRemoteSubmixAudioPort(moduleConfig, mAddress);
+        auto devicePort = getRemoteSubmixAudioPort(moduleConfig);
         ASSERT_TRUE(devicePort.has_value()) << "Device port for remote submix device not found";
-        ASSERT_NO_FATAL_FAILURE(mStream.SetUp(module, moduleConfig, *devicePort));
+        ASSERT_NO_FATAL_FAILURE(mStream.SetUp(module, moduleConfig, *devicePort, mAddress));
         mAddress = mStream.getDevice().address;
     }
-
-    void StartWorkerToSendBurstCommands() {
-        ASSERT_NO_FATAL_FAILURE(mStream.StartWorkerToSendBurstCommands());
+    void SetUp(IModule* module, ModuleConfig* moduleConfig,
+               const AudioPortConfig& existingMixPortConfig,
+               const AudioPortConfig& existingDevicePortConfig) {
+        ASSERT_NO_FATAL_FAILURE(mStream.SetUp(module, moduleConfig, existingMixPortConfig,
+                                              existingDevicePortConfig));
+        mAddress = mStream.getDevice().address;
+    }
+    void StartWorkerToSendBurstCommands(size_t burstCount = 10, bool standbyInputWhenDone = false) {
+        ASSERT_NO_FATAL_FAILURE(
+                mStream.StartWorkerToSendBurstCommands(burstCount, standbyInputWhenDone));
     }
 
-    void JoinWorkerAfterBurstCommands() {
-        ASSERT_NO_FATAL_FAILURE(mStream.JoinWorkerAfterBurstCommands());
+    void JoinWorkerAfterBurstCommands(bool callPrepareToCloseBeforeJoin) {
+        ASSERT_NO_FATAL_FAILURE(mStream.JoinWorkerAfterBurstCommands(
+                true /*validatePositionIncrease*/, callPrepareToCloseBeforeJoin));
     }
 
-    void SendBurstCommands() {
-        ASSERT_NO_FATAL_FAILURE(mStream.StartWorkerToSendBurstCommands());
-        ASSERT_NO_FATAL_FAILURE(mStream.JoinWorkerAfterBurstCommands());
+    void JoinWorkerAfterBurstCommands(bool validatePositionIncrease,
+                                      bool callPrepareToCloseBeforeJoin) {
+        ASSERT_NO_FATAL_FAILURE(mStream.JoinWorkerAfterBurstCommands(validatePositionIncrease,
+                                                                     callPrepareToCloseBeforeJoin));
+    }
+
+    void SendBurstCommands(bool callPrepareToCloseBeforeJoin, size_t burstCount = 10,
+                           bool standbyInputWhenDone = false) {
+        ASSERT_NO_FATAL_FAILURE(StartWorkerToSendBurstCommands(burstCount, standbyInputWhenDone));
+        // When 'burstCount == 0', there is no "previous" frame count, thus the check for
+        // the position increase fails.
+        ASSERT_NO_FATAL_FAILURE(JoinWorkerAfterBurstCommands(
+                burstCount > 0 /*validatePositionIncrease*/, callPrepareToCloseBeforeJoin));
     }
 
     std::optional<AudioDeviceAddress> getAudioDeviceAddress() const { return mAddress; }
+    const AudioPortConfig& getDevicePortConfig() const { return mStream.getDevicePortConfig(); }
+    int8_t getLastBurstIteration() const { return mStream.getLastData()[0]; }
+    const AudioPortConfig& getPortConfig() const { return mStream.getPortConfig(); }
     std::string skipTestReason() const { return mStream.skipTestReason(); }
 
   private:
-    void SetUp(IModule* module, ModuleConfig* moduleConfig, const AudioPort& devicePort) {}
-
     StreamFixtureWithWorker<Stream> mStream;
     std::optional<AudioDeviceAddress> mAddress;
 };
@@ -5007,77 +5102,130 @@
         }
         ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
     }
+
+    void TearDown() override {
+        streamIn.reset();
+        streamOut.reset();
+    }
+
+    void CreateOutputStream() {
+        streamOut = std::make_unique<WithRemoteSubmix<IStreamOut>>();
+        ASSERT_NO_FATAL_FAILURE(streamOut->SetUp(module.get(), moduleConfig.get()));
+        // Note: any issue with connection attempts is considered as a problem.
+        ASSERT_EQ("", streamOut->skipTestReason());
+        ASSERT_TRUE(streamOut->getAudioDeviceAddress().has_value());
+    }
+
+    void CreateInputStream(const std::optional<AudioDeviceAddress>& address = std::nullopt) {
+        if (address.has_value()) {
+            streamIn = std::make_unique<WithRemoteSubmix<IStreamIn>>(address.value());
+        } else {
+            ASSERT_TRUE(streamOut->getAudioDeviceAddress().has_value());
+            streamIn = std::make_unique<WithRemoteSubmix<IStreamIn>>(
+                    streamOut->getAudioDeviceAddress().value());
+        }
+        ASSERT_NO_FATAL_FAILURE(streamIn->SetUp(module.get(), moduleConfig.get()));
+        ASSERT_EQ("", streamIn->skipTestReason());
+        auto inAddress = streamIn->getAudioDeviceAddress();
+        ASSERT_TRUE(inAddress.has_value());
+        if (address.has_value()) {
+            if (address.value() != AudioDeviceAddress{}) {
+                ASSERT_EQ(address.value(), inAddress.value());
+            }
+        } else {
+            ASSERT_EQ(streamOut->getAudioDeviceAddress().value(), inAddress.value());
+        }
+    }
+
+    std::unique_ptr<WithRemoteSubmix<IStreamOut>> streamOut;
+    std::unique_ptr<WithRemoteSubmix<IStreamIn>> streamIn;
 };
 
 TEST_P(AudioModuleRemoteSubmix, OutputDoesNotBlockWhenNoInput) {
-    WithRemoteSubmix<IStreamOut> streamOut;
-    ASSERT_NO_FATAL_FAILURE(streamOut.SetUp(module.get(), moduleConfig.get()));
-    // Note: here and in other tests any issue with connection attempts is considered as a problem.
-    ASSERT_EQ("", streamOut.skipTestReason());
-    ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommands());
+    ASSERT_NO_FATAL_FAILURE(CreateOutputStream());
+    ASSERT_NO_FATAL_FAILURE(streamOut->SendBurstCommands(false /*callPrepareToCloseBeforeJoin*/));
 }
 
-TEST_P(AudioModuleRemoteSubmix, OutputDoesNotBlockWhenInputStuck) {
-    WithRemoteSubmix<IStreamOut> streamOut;
-    ASSERT_NO_FATAL_FAILURE(streamOut.SetUp(module.get(), moduleConfig.get()));
-    ASSERT_EQ("", streamOut.skipTestReason());
-    auto address = streamOut.getAudioDeviceAddress();
-    ASSERT_TRUE(address.has_value());
+TEST_P(AudioModuleRemoteSubmix, OutputDoesNotBlockWhenInputInStandby) {
+    if (int32_t version; module->getInterfaceVersion(&version).isOk() && version < 3) {
+        GTEST_SKIP() << "Default remote submix implementation <V3 could not pass this test";
+    }
+    ASSERT_NO_FATAL_FAILURE(CreateOutputStream());
+    ASSERT_NO_FATAL_FAILURE(CreateInputStream());
+    ASSERT_NO_FATAL_FAILURE(streamOut->StartWorkerToSendBurstCommands());
+    // Send just 1 burst command. This triggers the condition "input is in standby after
+    // being active." The output must flush the fifo before writing to avoid being blocked.
+    ASSERT_NO_FATAL_FAILURE(
+            streamIn->StartWorkerToSendBurstCommands(1, true /*stanbyInputWhenDone*/));
+    // The output must be able to close without shutting down the pipe first (due to a call
+    // to 'prepareToClose').
+    ASSERT_NO_FATAL_FAILURE(
+            streamOut->JoinWorkerAfterBurstCommands(false /*callPrepareToCloseBeforeJoin*/));
+    ASSERT_NO_FATAL_FAILURE(
+            streamIn->JoinWorkerAfterBurstCommands(false /*callPrepareToCloseBeforeJoin*/));
+}
 
-    WithRemoteSubmix<IStreamIn> streamIn(address.value());
-    ASSERT_NO_FATAL_FAILURE(streamIn.SetUp(module.get(), moduleConfig.get()));
-    ASSERT_EQ("", streamIn.skipTestReason());
+TEST_P(AudioModuleRemoteSubmix, BlockedOutputUnblocksOnClose) {
+    ASSERT_NO_FATAL_FAILURE(CreateOutputStream());
+    ASSERT_NO_FATAL_FAILURE(CreateInputStream());
+    ASSERT_NO_FATAL_FAILURE(streamOut->StartWorkerToSendBurstCommands());
+    // Send just 3 burst command, but do not enter standby. This is a stalled input.
+    ASSERT_NO_FATAL_FAILURE(streamIn->StartWorkerToSendBurstCommands(3));
+    ASSERT_NO_FATAL_FAILURE(
+            streamOut->JoinWorkerAfterBurstCommands(true /*callPrepareToCloseBeforeJoin*/));
+    ASSERT_NO_FATAL_FAILURE(
+            streamIn->JoinWorkerAfterBurstCommands(false /*callPrepareToCloseBeforeJoin*/));
+}
 
-    ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommands());
+TEST_P(AudioModuleRemoteSubmix, OutputBlocksUntilInputStarts) {
+    ASSERT_NO_FATAL_FAILURE(CreateOutputStream());
+    ASSERT_NO_FATAL_FAILURE(CreateInputStream());
+    ASSERT_NO_FATAL_FAILURE(streamOut->StartWorkerToSendBurstCommands());
+    // Read the head of the pipe and check that it starts with the first output burst, that is,
+    // the contents of the very first write has not been superseded due to pipe overflow.
+    // The burstCount is '0' because the very first burst is used to exit from the 'IDLE' state,
+    // see 'makeBurstCommands'.
+    ASSERT_NO_FATAL_FAILURE(streamIn->SendBurstCommands(false /*callPrepareToCloseBeforeJoin*/, 0,
+                                                        true /*standbyInputWhenDone*/));
+    EXPECT_EQ(1, streamIn->getLastBurstIteration());
+    ASSERT_NO_FATAL_FAILURE(
+            streamOut->JoinWorkerAfterBurstCommands(true /*callPrepareToCloseBeforeJoin*/));
 }
 
 TEST_P(AudioModuleRemoteSubmix, OutputAndInput) {
-    WithRemoteSubmix<IStreamOut> streamOut;
-    ASSERT_NO_FATAL_FAILURE(streamOut.SetUp(module.get(), moduleConfig.get()));
-    ASSERT_EQ("", streamOut.skipTestReason());
-    auto address = streamOut.getAudioDeviceAddress();
-    ASSERT_TRUE(address.has_value());
-
-    WithRemoteSubmix<IStreamIn> streamIn(address.value());
-    ASSERT_NO_FATAL_FAILURE(streamIn.SetUp(module.get(), moduleConfig.get()));
-    ASSERT_EQ("", streamIn.skipTestReason());
-
+    ASSERT_NO_FATAL_FAILURE(CreateOutputStream());
+    ASSERT_NO_FATAL_FAILURE(CreateInputStream());
     // Start writing into the output stream.
-    ASSERT_NO_FATAL_FAILURE(streamOut.StartWorkerToSendBurstCommands());
+    ASSERT_NO_FATAL_FAILURE(streamOut->StartWorkerToSendBurstCommands());
     // Simultaneously, read from the input stream.
-    ASSERT_NO_FATAL_FAILURE(streamIn.SendBurstCommands());
-    ASSERT_NO_FATAL_FAILURE(streamOut.JoinWorkerAfterBurstCommands());
+    ASSERT_NO_FATAL_FAILURE(streamIn->SendBurstCommands(false /*callPrepareToCloseBeforeJoin*/));
+    ASSERT_NO_FATAL_FAILURE(
+            streamOut->JoinWorkerAfterBurstCommands(false /*callPrepareToCloseBeforeJoin*/));
 }
 
 TEST_P(AudioModuleRemoteSubmix, OpenInputMultipleTimes) {
-    WithRemoteSubmix<IStreamOut> streamOut;
-    ASSERT_NO_FATAL_FAILURE(streamOut.SetUp(module.get(), moduleConfig.get()));
-    ASSERT_EQ("", streamOut.skipTestReason());
-    auto address = streamOut.getAudioDeviceAddress();
-    ASSERT_TRUE(address.has_value());
-
-    const size_t streamInCount = 3;
-    std::vector<std::unique_ptr<WithRemoteSubmix<IStreamIn>>> streamIns(streamInCount);
-    for (size_t i = 0; i < streamInCount; i++) {
-        streamIns[i] = std::make_unique<WithRemoteSubmix<IStreamIn>>(address.value());
-        ASSERT_NO_FATAL_FAILURE(streamIns[i]->SetUp(module.get(), moduleConfig.get()));
+    ASSERT_NO_FATAL_FAILURE(CreateOutputStream());
+    ASSERT_NO_FATAL_FAILURE(CreateInputStream());
+    ASSERT_NO_FATAL_FAILURE(streamOut->StartWorkerToSendBurstCommands());
+    ASSERT_NO_FATAL_FAILURE(streamIn->SendBurstCommands(false /*callPrepareToCloseBeforeJoin*/, 1,
+                                                        true /*standbyInputWhenDone*/));
+    // For the new stream, only create a new mix port config and a new patch.
+    const size_t extraStreamInCount = 2;
+    std::vector<std::unique_ptr<WithRemoteSubmix<IStreamIn>>> streamIns(extraStreamInCount);
+    for (size_t i = 0; i < extraStreamInCount; i++) {
+        streamIns[i] = std::make_unique<WithRemoteSubmix<IStreamIn>>();
+        ASSERT_NO_FATAL_FAILURE(streamIns[i]->SetUp(module.get(), moduleConfig.get(),
+                                                    streamIn->getPortConfig(),
+                                                    streamIn->getDevicePortConfig()));
         ASSERT_EQ("", streamIns[i]->skipTestReason());
+        const auto inAddress = streamIns[i]->getAudioDeviceAddress();
+        ASSERT_TRUE(inAddress.has_value());
+        ASSERT_EQ(streamOut->getAudioDeviceAddress().value(), inAddress.value());
+        ASSERT_NO_FATAL_FAILURE(streamIns[i]->SendBurstCommands(
+                false /*callPrepareToCloseBeforeJoin*/, 1, true /*standbyInputWhenDone*/));
     }
-    // Start writing into the output stream.
-    ASSERT_NO_FATAL_FAILURE(streamOut.StartWorkerToSendBurstCommands());
-    // Simultaneously, read from input streams.
-    for (size_t i = 0; i < streamInCount; i++) {
-        ASSERT_NO_FATAL_FAILURE(streamIns[i]->StartWorkerToSendBurstCommands());
-    }
-    for (size_t i = 0; i < streamInCount; i++) {
-        ASSERT_NO_FATAL_FAILURE(streamIns[i]->JoinWorkerAfterBurstCommands());
-    }
-    ASSERT_NO_FATAL_FAILURE(streamOut.JoinWorkerAfterBurstCommands());
-    // Clean up input streams in the reverse order because the device connection is owned
-    // by the first one.
-    for (size_t i = streamInCount; i != 0; --i) {
-        streamIns[i - 1].reset();
-    }
+    ASSERT_NO_FATAL_FAILURE(
+            streamOut->JoinWorkerAfterBurstCommands(false /*callPrepareToCloseBeforeJoin*/));
 }
 
 INSTANTIATE_TEST_SUITE_P(AudioModuleRemoteSubmixTest, AudioModuleRemoteSubmix,
diff --git a/automotive/vehicle/aidl/impl/3/utils/common/test/PendingRequestPoolTest.cpp b/automotive/vehicle/aidl/impl/3/utils/common/test/PendingRequestPoolTest.cpp
index 734c739..cc690d7 100644
--- a/automotive/vehicle/aidl/impl/3/utils/common/test/PendingRequestPoolTest.cpp
+++ b/automotive/vehicle/aidl/impl/3/utils/common/test/PendingRequestPoolTest.cpp
@@ -57,7 +57,7 @@
 
   private:
     // Test timeout is 0.1s.
-    static const int64_t TEST_TIMEOUT = 100000000;
+    static constexpr int64_t TEST_TIMEOUT = 100000000;
 
     std::unique_ptr<PendingRequestPool> mPool;
 };
diff --git a/automotive/vehicle/aidl/impl/current/hardware/include/IVehicleHardware.h b/automotive/vehicle/aidl/impl/current/hardware/include/IVehicleHardware.h
index f46a1c8..9122955 100644
--- a/automotive/vehicle/aidl/impl/current/hardware/include/IVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/current/hardware/include/IVehicleHardware.h
@@ -303,6 +303,8 @@
     //
     // If the propertyId's supported values are static, then must do nothing.
     //
+    // If some of the [propId, areaId]s are already subscribed, then do nothing.
+    //
     // This is only called for [propId, areaId] that has non-null {@code HasSupportedValueInfo}.
     //
     // Client must implement (override) this function if at least one [propId, areaId]'s
diff --git a/automotive/vehicle/aidl/impl/current/utils/common/test/PendingRequestPoolTest.cpp b/automotive/vehicle/aidl/impl/current/utils/common/test/PendingRequestPoolTest.cpp
index 734c739..cc690d7 100644
--- a/automotive/vehicle/aidl/impl/current/utils/common/test/PendingRequestPoolTest.cpp
+++ b/automotive/vehicle/aidl/impl/current/utils/common/test/PendingRequestPoolTest.cpp
@@ -57,7 +57,7 @@
 
   private:
     // Test timeout is 0.1s.
-    static const int64_t TEST_TIMEOUT = 100000000;
+    static constexpr int64_t TEST_TIMEOUT = 100000000;
 
     std::unique_ptr<PendingRequestPool> mPool;
 };
diff --git a/automotive/vehicle/aidl/impl/current/vhal/include/DefaultVehicleHal.h b/automotive/vehicle/aidl/impl/current/vhal/include/DefaultVehicleHal.h
index 8785bcd..02dbe9e 100644
--- a/automotive/vehicle/aidl/impl/current/vhal/include/DefaultVehicleHal.h
+++ b/automotive/vehicle/aidl/impl/current/vhal/include/DefaultVehicleHal.h
@@ -233,9 +233,9 @@
             std::function<void(const std::unordered_map<int32_t, aidlvhal::VehiclePropConfig>&)>
                     callback) const EXCLUDES(mConfigLock);
 
-    android::base::Result<const aidlvhal::VehicleAreaConfig*> getAreaConfigForPropIdAreaId(
+    android::base::Result<aidlvhal::VehicleAreaConfig> getAreaConfigForPropIdAreaId(
             int32_t propId, int32_t areaId) const;
-    android::base::Result<const aidlvhal::HasSupportedValueInfo*> getHasSupportedValueInfo(
+    android::base::Result<aidlvhal::HasSupportedValueInfo> getHasSupportedValueInfo(
             int32_t propId, int32_t areaId) const;
     // Puts the property change events into a queue so that they can handled in batch.
     static void batchPropertyChangeEvent(
diff --git a/automotive/vehicle/aidl/impl/current/vhal/include/SubscriptionManager.h b/automotive/vehicle/aidl/impl/current/vhal/include/SubscriptionManager.h
index cac1901..59c21aa 100644
--- a/automotive/vehicle/aidl/impl/current/vhal/include/SubscriptionManager.h
+++ b/automotive/vehicle/aidl/impl/current/vhal/include/SubscriptionManager.h
@@ -118,8 +118,19 @@
                        std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropError>>
     getSubscribedClientsForErrorEvents(const std::vector<SetValueErrorEvent>& errorEvents);
 
-    // Returns the number of subscribed clients.
-    size_t countClients();
+    // Subscribes to supported values change.
+    VhalResult<void> subscribeSupportedValueChange(const CallbackType& callback,
+                                                   const std::vector<PropIdAreaId>& propIdAreaIds);
+
+    // Unsubscribes to supported values change.
+    VhalResult<void> unsubscribeSupportedValueChange(
+            ClientIdType client, const std::vector<PropIdAreaId>& propIdAreaIds);
+
+    // Returns the number of subscribed property change clients.
+    size_t countPropertyChangeClients();
+
+    // Returns the number of subscribed supported value change clients.
+    size_t countSupportedValueChangeClients();
 
     // Checks whether the sample rate is valid.
     static bool checkSampleRateHz(float sampleRateHz);
@@ -160,6 +171,11 @@
                        std::unordered_set<VehiclePropValue, VehiclePropValueHashPropIdAreaId,
                                           VehiclePropValueEqualPropIdAreaId>>
             mContSubValuesByCallback GUARDED_BY(mLock);
+    std::unordered_map<PropIdAreaId, std::unordered_map<ClientIdType, CallbackType>,
+                       PropIdAreaIdHash>
+            mSupportedValueChangeClientsByPropIdAreaId GUARDED_BY(mLock);
+    std::unordered_map<ClientIdType, std::unordered_set<PropIdAreaId, PropIdAreaIdHash>>
+            mSupportedValueChangePropIdAreaIdsByClient GUARDED_BY(mLock);
 
     VhalResult<void> addContinuousSubscriberLocked(const ClientIdType& clientId,
                                                    const PropIdAreaId& propIdAreaId,
@@ -180,6 +196,9 @@
     VhalResult<void> unsubscribePropIdAreaIdLocked(SubscriptionManager::ClientIdType clientId,
                                                    const PropIdAreaId& propIdAreaId)
             REQUIRES(mLock);
+    VhalResult<void> unsubscribeSupportedValueChangeLocked(
+            SubscriptionManager::ClientIdType clientId,
+            const std::vector<PropIdAreaId>& propIdAreaIds) REQUIRES(mLock);
 
     // Checks whether the manager is empty. For testing purpose.
     bool isEmpty();
diff --git a/automotive/vehicle/aidl/impl/current/vhal/src/DefaultVehicleHal.cpp b/automotive/vehicle/aidl/impl/current/vhal/src/DefaultVehicleHal.cpp
index 509d1c3..25af50e 100644
--- a/automotive/vehicle/aidl/impl/current/vhal/src/DefaultVehicleHal.cpp
+++ b/automotive/vehicle/aidl/impl/current/vhal/src/DefaultVehicleHal.cpp
@@ -82,9 +82,6 @@
 
 using VhalPropIdAreaId = ::aidl::android::hardware::automotive::vehicle::PropIdAreaId;
 
-#define propIdtoString(PROP_ID) \
-    aidl::android::hardware::automotive::vehicle::toString(static_cast<VehicleProperty>(PROP_ID))
-
 std::string toString(const std::unordered_set<int64_t>& values) {
     std::string str = "";
     for (auto it = values.begin(); it != values.end(); it++) {
@@ -972,25 +969,25 @@
     return ScopedAStatus::ok();
 }
 
-Result<const VehicleAreaConfig*> DefaultVehicleHal::getAreaConfigForPropIdAreaId(
-        int32_t propId, int32_t areaId) const {
+Result<VehicleAreaConfig> DefaultVehicleHal::getAreaConfigForPropIdAreaId(int32_t propId,
+                                                                          int32_t areaId) const {
     auto result = getConfig(propId);
     if (!result.ok()) {
-        return Error() << "Failed to get property config for propertyId: " << propIdtoString(propId)
+        return Error() << "Failed to get property config for propertyId: " << propIdToString(propId)
                        << ", error: " << result.error();
     }
     const VehiclePropConfig& config = result.value();
     const VehicleAreaConfig* areaConfig = getAreaConfig(propId, areaId, config);
     if (areaConfig == nullptr) {
-        return Error() << "AreaId config not found for propertyId: " << propIdtoString(propId)
+        return Error() << "AreaId config not found for propertyId: " << propIdToString(propId)
                        << ", areaId: " << areaId;
     }
-    return areaConfig;
+    return *areaConfig;
 }
 
-Result<const HasSupportedValueInfo*> DefaultVehicleHal::getHasSupportedValueInfo(
-        int32_t propId, int32_t areaId) const {
-    Result<const VehicleAreaConfig*> propIdAreaIdConfigResult =
+Result<HasSupportedValueInfo> DefaultVehicleHal::getHasSupportedValueInfo(int32_t propId,
+                                                                          int32_t areaId) const {
+    Result<VehicleAreaConfig> propIdAreaIdConfigResult =
             getAreaConfigForPropIdAreaId(propId, areaId);
     if (!isGlobalProp(propId) && !propIdAreaIdConfigResult.ok()) {
         // For global property, it is possible that no config exists.
@@ -998,11 +995,11 @@
     }
     if (propIdAreaIdConfigResult.has_value()) {
         auto areaConfig = propIdAreaIdConfigResult.value();
-        if (areaConfig->hasSupportedValueInfo.has_value()) {
-            return &(areaConfig->hasSupportedValueInfo.value());
+        if (areaConfig.hasSupportedValueInfo.has_value()) {
+            return areaConfig.hasSupportedValueInfo.value();
         }
     }
-    return Error() << "property: " << propIdtoString(propId) << ", areaId: " << areaId
+    return Error() << "property: " << propIdToString(propId) << ", areaId: " << areaId
                    << " does not support this operation because hasSupportedValueInfo is null";
 }
 
@@ -1026,7 +1023,7 @@
             continue;
         }
 
-        const auto& hasSupportedValueInfo = *(hasSupportedValueInfoResult.value());
+        const auto& hasSupportedValueInfo = hasSupportedValueInfoResult.value();
         if (hasSupportedValueInfo.hasSupportedValuesList) {
             toHardwarePropIdAreaIds.push_back(PropIdAreaId{.propId = propId, .areaId = areaId});
             toHardwareRequestCounters.push_back(requestCounter);
@@ -1087,7 +1084,7 @@
             continue;
         }
 
-        const auto& hasSupportedValueInfo = *(hasSupportedValueInfoResult.value());
+        const auto& hasSupportedValueInfo = hasSupportedValueInfoResult.value();
         if (hasSupportedValueInfo.hasMinSupportedValue ||
             hasSupportedValueInfo.hasMaxSupportedValue) {
             toHardwarePropIdAreaIds.push_back(PropIdAreaId{.propId = propId, .areaId = areaId});
@@ -1130,15 +1127,65 @@
 }
 
 ScopedAStatus DefaultVehicleHal::registerSupportedValueChangeCallback(
-        const std::shared_ptr<IVehicleCallback>&, const std::vector<VhalPropIdAreaId>&) {
-    // TODO(b/381020465): Add relevant implementation.
-    return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+        const std::shared_ptr<IVehicleCallback>& callback,
+        const std::vector<VhalPropIdAreaId>& vhalPropIdAreaIds) {
+    std::vector<PropIdAreaId> propIdAreaIdsToSubscribe;
+    for (size_t i = 0; i < vhalPropIdAreaIds.size(); i++) {
+        const auto& vhalPropIdAreaId = vhalPropIdAreaIds.at(i);
+        int32_t propId = vhalPropIdAreaId.propId;
+        int32_t areaId = vhalPropIdAreaId.areaId;
+        auto hasSupportedValueInfoResult = getHasSupportedValueInfo(propId, areaId);
+        if (!hasSupportedValueInfoResult.ok()) {
+            ALOGE("registerSupportedValueChangeCallback not supported: %s",
+                  hasSupportedValueInfoResult.error().message().c_str());
+            return toScopedAStatus(hasSupportedValueInfoResult, StatusCode::INVALID_ARG);
+        }
+        const auto& hasSupportedValueInfo = hasSupportedValueInfoResult.value();
+        if (!hasSupportedValueInfo.hasMinSupportedValue &&
+            !hasSupportedValueInfo.hasMaxSupportedValue &&
+            !hasSupportedValueInfo.hasSupportedValuesList) {
+            ALOGW("registerSupportedValueChangeCallback: do nothing for property: %s, "
+                  "areaId: %" PRId32
+                  ", no min/max supported values or supported values list"
+                  " specified",
+                  propIdToString(propId).c_str(), areaId);
+            continue;
+        }
+        propIdAreaIdsToSubscribe.push_back(PropIdAreaId{.propId = propId, .areaId = areaId});
+    }
+    if (propIdAreaIdsToSubscribe.empty()) {
+        return ScopedAStatus::ok();
+    }
+    auto result =
+            mSubscriptionManager->subscribeSupportedValueChange(callback, propIdAreaIdsToSubscribe);
+    if (!result.ok()) {
+        ALOGW("registerSupportedValueChangeCallback: failed to subscribe supported value change"
+              " for %s, error: %s",
+              fmt::format("{}", propIdAreaIdsToSubscribe).c_str(),
+              result.error().message().c_str());
+        return toScopedAStatus(result);
+    }
+    return ScopedAStatus::ok();
 }
 
 ScopedAStatus DefaultVehicleHal::unregisterSupportedValueChangeCallback(
-        const std::shared_ptr<IVehicleCallback>&, const std::vector<VhalPropIdAreaId>&) {
-    // TODO(b/381020465): Add relevant implementation.
-    return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+        const std::shared_ptr<IVehicleCallback>& callback,
+        const std::vector<VhalPropIdAreaId>& vhalPropIdAreaIds) {
+    std::vector<PropIdAreaId> propIdAreaIds;
+    for (const auto& vhalPropIdAreaId : vhalPropIdAreaIds) {
+        propIdAreaIds.push_back(
+                PropIdAreaId{.propId = vhalPropIdAreaId.propId, .areaId = vhalPropIdAreaId.areaId});
+    }
+
+    auto result = mSubscriptionManager->unsubscribeSupportedValueChange(callback->asBinder().get(),
+                                                                        propIdAreaIds);
+    if (!result.ok()) {
+        ALOGW("unregisterSupportedValueChangeCallback: failed to unsubscribe supported value change"
+              " for %s, error: %s",
+              fmt::format("{}", propIdAreaIds).c_str(), result.error().message().c_str());
+        return toScopedAStatus(result);
+    }
+    return ScopedAStatus::ok();
 }
 
 IVehicleHardware* DefaultVehicleHal::getHardware() {
@@ -1256,12 +1303,14 @@
         dprintf(fd, "Currently have %zu getValues clients\n", mGetValuesClients.size());
         dprintf(fd, "Currently have %zu setValues clients\n", mSetValuesClients.size());
         dprintf(fd, "Currently have %zu subscribe clients\n", countSubscribeClients());
+        dprintf(fd, "Currently have %zu supported values change subscribe clients\n",
+                mSubscriptionManager->countSupportedValueChangeClients());
     }
     return STATUS_OK;
 }
 
 size_t DefaultVehicleHal::countSubscribeClients() {
-    return mSubscriptionManager->countClients();
+    return mSubscriptionManager->countPropertyChangeClients();
 }
 
 }  // namespace vehicle
diff --git a/automotive/vehicle/aidl/impl/current/vhal/src/SubscriptionManager.cpp b/automotive/vehicle/aidl/impl/current/vhal/src/SubscriptionManager.cpp
index 2d09e02..f790033 100644
--- a/automotive/vehicle/aidl/impl/current/vhal/src/SubscriptionManager.cpp
+++ b/automotive/vehicle/aidl/impl/current/vhal/src/SubscriptionManager.cpp
@@ -17,6 +17,7 @@
 #include "SubscriptionManager.h"
 
 #include <VehicleUtils.h>
+#include <android-base/format.h>
 #include <android-base/stringprintf.h>
 #include <utils/Log.h>
 #include <utils/SystemClock.h>
@@ -65,6 +66,8 @@
 
     mClientsByPropIdAreaId.clear();
     mSubscribedPropsByClient.clear();
+    mSupportedValueChangeClientsByPropIdAreaId.clear();
+    mSupportedValueChangePropIdAreaIdsByClient.clear();
 }
 
 bool SubscriptionManager::checkSampleRateHz(float sampleRateHz) {
@@ -166,8 +169,7 @@
                                     /*enableVur*/ false));
         status != StatusCode::OK) {
         return StatusError(status)
-               << StringPrintf("failed subscribe for prop: %s, areaId: %" PRId32,
-                               propIdToString(propId).c_str(), areaId);
+               << fmt::format("failed subscribe for propIdAreaId: {}", propIdAreaId);
     }
     return {};
 }
@@ -379,16 +381,111 @@
 
     if (mSubscribedPropsByClient.find(clientId) == mSubscribedPropsByClient.end()) {
         ALOGW("No property was subscribed for this client, unsubscribe does nothing");
-        return {};
+    } else {
+        auto& propIdAreaIds = mSubscribedPropsByClient[clientId];
+        for (auto const& propIdAreaId : propIdAreaIds) {
+            if (auto result = unsubscribePropIdAreaIdLocked(clientId, propIdAreaId); !result.ok()) {
+                return result;
+            }
+        }
+        mSubscribedPropsByClient.erase(clientId);
     }
 
-    auto& subscriptions = mSubscribedPropsByClient[clientId];
-    for (auto const& propIdAreaId : subscriptions) {
-        if (auto result = unsubscribePropIdAreaIdLocked(clientId, propIdAreaId); !result.ok()) {
+    if (mSupportedValueChangePropIdAreaIdsByClient.find(clientId) ==
+        mSupportedValueChangePropIdAreaIdsByClient.end()) {
+        ALOGW("No supported value change was subscribed for this client, unsubscribe does nothing");
+    } else {
+        const auto& propIdAreaIds = mSupportedValueChangePropIdAreaIdsByClient[clientId];
+        if (auto result = unsubscribeSupportedValueChangeLocked(
+                    clientId,
+                    std::vector<PropIdAreaId>(propIdAreaIds.begin(), propIdAreaIds.end()));
+            !result.ok()) {
             return result;
         }
     }
-    mSubscribedPropsByClient.erase(clientId);
+    return {};
+}
+
+VhalResult<void> SubscriptionManager::subscribeSupportedValueChange(
+        const std::shared_ptr<IVehicleCallback>& callback,
+        const std::vector<PropIdAreaId>& propIdAreaIds) {
+    // Need to make sure this whole operation is guarded by a lock so that our internal state is
+    // consistent with IVehicleHardware state.
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+
+    ClientIdType clientId = callback->asBinder().get();
+
+    // It is possible that some of the [propId, areaId]s are already subscribed, IVehicleHardware
+    // will ignore them.
+    if (auto status = mVehicleHardware->subscribeSupportedValueChange(propIdAreaIds);
+        status != StatusCode::OK) {
+        return StatusError(status)
+               << fmt::format("failed to call subscribeSupportedValueChange for propIdAreaIds: {}",
+                              propIdAreaIds);
+    }
+    for (const auto& propIdAreaId : propIdAreaIds) {
+        mSupportedValueChangeClientsByPropIdAreaId[propIdAreaId][clientId] = callback;
+        // mSupportedValueChangePropIdAreaIdsByClient[clientId] is a set so this will ignore
+        // duplicate [propId, areaId].
+        mSupportedValueChangePropIdAreaIdsByClient[clientId].insert(propIdAreaId);
+    }
+    return {};
+}
+
+VhalResult<void> SubscriptionManager::unsubscribeSupportedValueChange(
+        SubscriptionManager::ClientIdType clientId,
+        const std::vector<PropIdAreaId>& propIdAreaIds) {
+    // Need to make sure this whole operation is guarded by a lock so that our internal state is
+    // consistent with IVehicleHardware state.
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+
+    return unsubscribeSupportedValueChangeLocked(clientId, propIdAreaIds);
+}
+
+VhalResult<void> SubscriptionManager::unsubscribeSupportedValueChangeLocked(
+        SubscriptionManager::ClientIdType clientId,
+        const std::vector<PropIdAreaId>& propIdAreaIds) {
+    std::vector<PropIdAreaId> propIdAreaIdsToUnsubscribe;
+
+    // Check which [propId, areaId] needs to be unsubscribed from the hardware.
+    for (const auto& propIdAreaId : propIdAreaIds) {
+        auto it = mSupportedValueChangeClientsByPropIdAreaId.find(propIdAreaId);
+        if (it != mSupportedValueChangeClientsByPropIdAreaId.end()) {
+            const auto& clients = it->second;
+            if (clients.size() == 1 && clients.find(clientId) != clients.end()) {
+                // This callback is the only client registered for [propId, areaId].
+                // Unregister it should unregister the [propId, areaId].
+                propIdAreaIdsToUnsubscribe.push_back(propIdAreaId);
+            }
+        }
+    }
+
+    // Send the unsubscribe request.
+    if (!propIdAreaIdsToUnsubscribe.empty()) {
+        if (auto status =
+                    mVehicleHardware->unsubscribeSupportedValueChange(propIdAreaIdsToUnsubscribe);
+            status != StatusCode::OK) {
+            return StatusError(status) << fmt::format(
+                           "failed to call unsubscribeSupportedValueChange for "
+                           "propIdAreaIds: {}",
+                           propIdAreaIdsToUnsubscribe);
+        }
+    }
+
+    // Remove internal book-keeping.
+    for (const auto& propIdAreaId : propIdAreaIds) {
+        if (mSupportedValueChangeClientsByPropIdAreaId.find(propIdAreaId) !=
+            mSupportedValueChangeClientsByPropIdAreaId.end()) {
+            mSupportedValueChangeClientsByPropIdAreaId[propIdAreaId].erase(clientId);
+        }
+        if (mSupportedValueChangeClientsByPropIdAreaId.empty()) {
+            mSupportedValueChangeClientsByPropIdAreaId.erase(propIdAreaId);
+        }
+        mSupportedValueChangePropIdAreaIdsByClient[clientId].erase(propIdAreaId);
+        if (mSupportedValueChangePropIdAreaIdsByClient[clientId].empty()) {
+            mSupportedValueChangePropIdAreaIdsByClient.erase(clientId);
+        }
+    }
     return {};
 }
 
@@ -486,14 +583,21 @@
 
 bool SubscriptionManager::isEmpty() {
     std::scoped_lock<std::mutex> lockGuard(mLock);
-    return mSubscribedPropsByClient.empty() && mClientsByPropIdAreaId.empty();
+    return mSubscribedPropsByClient.empty() && mClientsByPropIdAreaId.empty() &&
+           mSupportedValueChangeClientsByPropIdAreaId.empty() &&
+           mSupportedValueChangePropIdAreaIdsByClient.empty();
 }
 
-size_t SubscriptionManager::countClients() {
+size_t SubscriptionManager::countPropertyChangeClients() {
     std::scoped_lock<std::mutex> lockGuard(mLock);
     return mSubscribedPropsByClient.size();
 }
 
+size_t SubscriptionManager::countSupportedValueChangeClients() {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+    return mSupportedValueChangePropIdAreaIdsByClient.size();
+}
+
 }  // namespace vehicle
 }  // namespace automotive
 }  // namespace hardware
diff --git a/automotive/vehicle/aidl/impl/current/vhal/test/DefaultVehicleHalTest.cpp b/automotive/vehicle/aidl/impl/current/vhal/test/DefaultVehicleHalTest.cpp
index 4df5ba3..0526f7d 100644
--- a/automotive/vehicle/aidl/impl/current/vhal/test/DefaultVehicleHalTest.cpp
+++ b/automotive/vehicle/aidl/impl/current/vhal/test/DefaultVehicleHalTest.cpp
@@ -2418,6 +2418,205 @@
     ASSERT_EQ(result.status, StatusCode::INVALID_ARG);
 }
 
+TEST_F(DefaultVehicleHalTest, testRegisterSupportedValueChangeCallback) {
+    auto testConfigs = std::vector<VehiclePropConfig>(
+            {VehiclePropConfig{
+                     .prop = testInt32VecProp(1),
+                     .areaConfigs =
+                             {
+                                     {.areaId = 0,
+                                      .hasSupportedValueInfo =
+                                              HasSupportedValueInfo{
+                                                      .hasMinSupportedValue = false,
+                                                      .hasMaxSupportedValue = false,
+                                                      .hasSupportedValuesList = false,
+                                              }},
+                             },
+             },
+             VehiclePropConfig{
+                     .prop = testInt32VecWindowProp(2),
+                     .areaConfigs =
+                             {
+                                     {.areaId = 2,
+                                      .hasSupportedValueInfo =
+                                              HasSupportedValueInfo{
+                                                      .hasMinSupportedValue = true,
+                                                      .hasMaxSupportedValue = false,
+                                                      .hasSupportedValuesList = false,
+                                              }},
+                             },
+             }});
+
+    auto hardware = std::make_unique<MockVehicleHardware>();
+    MockVehicleHardware* hardwarePtr = hardware.get();
+    hardware->setPropertyConfigs(testConfigs);
+
+    auto vhal = ndk::SharedRefBase::make<DefaultVehicleHal>(std::move(hardware));
+    std::shared_ptr<IVehicle> client = IVehicle::fromBinder(vhal->asBinder());
+
+    // This request is ignored because it does not have supported value info.
+    auto propIdAreaId1 = VhalPropIdAreaId{.propId = testInt32VecProp(1), .areaId = 0};
+    auto propIdAreaId2 = VhalPropIdAreaId{.propId = testInt32VecWindowProp(2), .areaId = 2};
+    auto status = client->registerSupportedValueChangeCallback(
+            getCallbackClient(), std::vector<VhalPropIdAreaId>{propIdAreaId1, propIdAreaId2});
+
+    ASSERT_TRUE(status.isOk()) << "Get non-okay status from registerSupportedValueChangeCallback"
+                               << status.getMessage();
+    ASSERT_THAT(hardwarePtr->getSubscribedSupportedValueChangePropIdAreaIds(),
+                ElementsAre(PropIdAreaId{.propId = testInt32VecWindowProp(2), .areaId = 2}));
+}
+
+TEST_F(DefaultVehicleHalTest, testRegisterSupportedValueChangeCallback_invalidRequest) {
+    auto testConfigs = std::vector<VehiclePropConfig>({VehiclePropConfig{
+            .prop = testInt32VecProp(1),
+            .areaConfigs =
+                    {
+                            {.areaId = 0, .hasSupportedValueInfo = std::nullopt},
+                    },
+    }});
+    auto hardware = std::make_unique<MockVehicleHardware>();
+    hardware->setPropertyConfigs(testConfigs);
+
+    auto vhal = ndk::SharedRefBase::make<DefaultVehicleHal>(std::move(hardware));
+    std::shared_ptr<IVehicle> client = IVehicle::fromBinder(vhal->asBinder());
+
+    auto propIdAreaId1 = VhalPropIdAreaId{.propId = testInt32VecProp(1), .areaId = 0};
+    auto status = client->registerSupportedValueChangeCallback(
+            getCallbackClient(), std::vector<VhalPropIdAreaId>{propIdAreaId1});
+
+    ASSERT_FALSE(status.isOk()) << "registerSupportedValueChangeCallback must return error if one "
+                                   "of the requested [propId, areaId]"
+                                   " does not have supportedValueInfo";
+}
+
+TEST_F(DefaultVehicleHalTest, testRegisterSupportedValueChangeCallback_errorStatusFromHardware) {
+    auto testConfigs = std::vector<VehiclePropConfig>({VehiclePropConfig{
+            .prop = testInt32VecWindowProp(2),
+            .areaConfigs =
+                    {
+                            {.areaId = 2,
+                             .hasSupportedValueInfo =
+                                     HasSupportedValueInfo{
+                                             .hasMinSupportedValue = true,
+                                             .hasMaxSupportedValue = false,
+                                             .hasSupportedValuesList = false,
+                                     }},
+                    },
+    }});
+
+    auto hardware = std::make_unique<MockVehicleHardware>();
+    hardware->setStatus("subscribeSupportedValueChange", StatusCode::INTERNAL_ERROR);
+    hardware->setPropertyConfigs(testConfigs);
+
+    auto vhal = ndk::SharedRefBase::make<DefaultVehicleHal>(std::move(hardware));
+    std::shared_ptr<IVehicle> client = IVehicle::fromBinder(vhal->asBinder());
+
+    auto propIdAreaId = VhalPropIdAreaId{.propId = testInt32VecWindowProp(2), .areaId = 2};
+    auto status = client->registerSupportedValueChangeCallback(
+            getCallbackClient(), std::vector<VhalPropIdAreaId>{propIdAreaId});
+
+    ASSERT_FALSE(status.isOk()) << "registerSupportedValueChangeCallback must return error if "
+                                   "VehicleHardware returns error";
+}
+
+TEST_F(DefaultVehicleHalTest, testUnregisterSupportedValueChangeCallback) {
+    auto testConfigs = std::vector<VehiclePropConfig>(
+            {VehiclePropConfig{
+                     .prop = testInt32VecProp(1),
+                     .areaConfigs =
+                             {
+                                     {.areaId = 0,
+                                      .hasSupportedValueInfo =
+                                              HasSupportedValueInfo{
+                                                      .hasMinSupportedValue = false,
+                                                      .hasMaxSupportedValue = false,
+                                                      .hasSupportedValuesList = true,
+                                              }},
+                             },
+             },
+             VehiclePropConfig{
+                     .prop = testInt32VecWindowProp(2),
+                     .areaConfigs =
+                             {
+                                     {.areaId = 2,
+                                      .hasSupportedValueInfo =
+                                              HasSupportedValueInfo{
+                                                      .hasMinSupportedValue = true,
+                                                      .hasMaxSupportedValue = false,
+                                                      .hasSupportedValuesList = false,
+                                              }},
+                             },
+             }});
+
+    auto hardware = std::make_unique<MockVehicleHardware>();
+    MockVehicleHardware* hardwarePtr = hardware.get();
+    hardware->setPropertyConfigs(testConfigs);
+
+    auto vhal = ndk::SharedRefBase::make<DefaultVehicleHal>(std::move(hardware));
+    std::shared_ptr<IVehicle> client = IVehicle::fromBinder(vhal->asBinder());
+
+    auto propIdAreaId1 = VhalPropIdAreaId{.propId = testInt32VecProp(1), .areaId = 0};
+    auto propIdAreaId2 = VhalPropIdAreaId{.propId = testInt32VecWindowProp(2), .areaId = 2};
+    auto status = client->registerSupportedValueChangeCallback(
+            getCallbackClient(), std::vector<VhalPropIdAreaId>{propIdAreaId1, propIdAreaId2});
+
+    ASSERT_TRUE(status.isOk()) << "Get non-okay status from registerSupportedValueChangeCallback"
+                               << status.getMessage();
+
+    status = client->unregisterSupportedValueChangeCallback(
+            getCallbackClient(), std::vector<VhalPropIdAreaId>{propIdAreaId1, propIdAreaId2});
+
+    ASSERT_TRUE(status.isOk()) << "Get non-okay status from unregisterSupportedValueChangeCallback"
+                               << status.getMessage();
+
+    ASSERT_TRUE(hardwarePtr->getSubscribedSupportedValueChangePropIdAreaIds().empty())
+            << "All registered [propId, areaId]s must be unregistered";
+}
+
+TEST_F(DefaultVehicleHalTest, testUnregisterSupportedValueChangeCallback_ignoreUnregistered) {
+    auto testConfigs = std::vector<VehiclePropConfig>(
+            {VehiclePropConfig{
+                     .prop = testInt32VecProp(1),
+                     .areaConfigs =
+                             {
+                                     {.areaId = 0,
+                                      .hasSupportedValueInfo =
+                                              HasSupportedValueInfo{
+                                                      .hasMinSupportedValue = false,
+                                                      .hasMaxSupportedValue = false,
+                                                      .hasSupportedValuesList = true,
+                                              }},
+                             },
+             },
+             VehiclePropConfig{
+                     .prop = testInt32VecWindowProp(2),
+                     .areaConfigs =
+                             {
+                                     {.areaId = 2,
+                                      .hasSupportedValueInfo =
+                                              HasSupportedValueInfo{
+                                                      .hasMinSupportedValue = true,
+                                                      .hasMaxSupportedValue = false,
+                                                      .hasSupportedValuesList = false,
+                                              }},
+                             },
+             }});
+
+    auto hardware = std::make_unique<MockVehicleHardware>();
+    MockVehicleHardware* hardwarePtr = hardware.get();
+    hardware->setPropertyConfigs(testConfigs);
+
+    auto vhal = ndk::SharedRefBase::make<DefaultVehicleHal>(std::move(hardware));
+    std::shared_ptr<IVehicle> client = IVehicle::fromBinder(vhal->asBinder());
+
+    auto propIdAreaId1 = VhalPropIdAreaId{.propId = testInt32VecProp(1), .areaId = 0};
+    auto propIdAreaId2 = VhalPropIdAreaId{.propId = testInt32VecWindowProp(2), .areaId = 2};
+    auto status = client->unregisterSupportedValueChangeCallback(
+            getCallbackClient(), std::vector<VhalPropIdAreaId>{propIdAreaId1, propIdAreaId2});
+
+    ASSERT_TRUE(status.isOk());
+}
+
 }  // namespace vehicle
 }  // namespace automotive
 }  // namespace hardware
diff --git a/automotive/vehicle/aidl/impl/current/vhal/test/MockVehicleHardware.cpp b/automotive/vehicle/aidl/impl/current/vhal/test/MockVehicleHardware.cpp
index ae2b5a2..11f1835 100644
--- a/automotive/vehicle/aidl/impl/current/vhal/test/MockVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/current/vhal/test/MockVehicleHardware.cpp
@@ -310,6 +310,40 @@
     return mEventBatchingWindow;
 }
 
+StatusCode MockVehicleHardware::subscribeSupportedValueChange(
+        const std::vector<PropIdAreaId>& propIdAreaIds) {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+    if (auto it = mStatusByFunctions.find(__func__); it != mStatusByFunctions.end()) {
+        if (StatusCode status = it->second; status != StatusCode::OK) {
+            return status;
+        }
+    }
+    for (const auto& propIdAreaId : propIdAreaIds) {
+        mSubscribedSupportedValueChangePropIdAreaIds.insert(propIdAreaId);
+    }
+    return StatusCode::OK;
+}
+
+StatusCode MockVehicleHardware::unsubscribeSupportedValueChange(
+        const std::vector<PropIdAreaId>& propIdAreaIds) {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+    if (auto it = mStatusByFunctions.find(__func__); it != mStatusByFunctions.end()) {
+        if (StatusCode status = it->second; status != StatusCode::OK) {
+            return status;
+        }
+    }
+    for (const auto& propIdAreaId : propIdAreaIds) {
+        mSubscribedSupportedValueChangePropIdAreaIds.erase(propIdAreaId);
+    }
+    return StatusCode::OK;
+}
+
+std::unordered_set<PropIdAreaId, PropIdAreaIdHash>
+MockVehicleHardware::getSubscribedSupportedValueChangePropIdAreaIds() {
+    std::scoped_lock<std::mutex> lockGuard(mLock);
+    return mSubscribedSupportedValueChangePropIdAreaIds;
+}
+
 template <class ResultType>
 StatusCode MockVehicleHardware::returnResponse(
         std::shared_ptr<const std::function<void(std::vector<ResultType>)>> callback,
diff --git a/automotive/vehicle/aidl/impl/current/vhal/test/MockVehicleHardware.h b/automotive/vehicle/aidl/impl/current/vhal/test/MockVehicleHardware.h
index d1e9771..e7359db 100644
--- a/automotive/vehicle/aidl/impl/current/vhal/test/MockVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/current/vhal/test/MockVehicleHardware.h
@@ -71,6 +71,10 @@
     getSupportedValuesLists(const std::vector<PropIdAreaId>& propIdAreaIds) override;
     std::vector<aidl::android::hardware::automotive::vehicle::MinMaxSupportedValueResult>
     getMinMaxSupportedValues(const std::vector<PropIdAreaId>& propIdAreaIds) override;
+    aidl::android::hardware::automotive::vehicle::StatusCode subscribeSupportedValueChange(
+            const std::vector<PropIdAreaId>& propIdAreaIds) override;
+    aidl::android::hardware::automotive::vehicle::StatusCode unsubscribeSupportedValueChange(
+            const std::vector<PropIdAreaId>& propIdAreaIds) override;
 
     // Test functions.
     void setPropertyConfigs(
@@ -114,10 +118,13 @@
     std::vector<aidl::android::hardware::automotive::vehicle::SubscribeOptions>
     getSubscribeOptions();
     void clearSubscribeOptions();
-    // Whether getAllPropertyConfigs() has been called, which blocks all all property configs
+    // Whether getAllPropertyConfigs() has been called, which blocks on all property configs
     // being ready.
     bool getAllPropertyConfigsCalled();
 
+    std::unordered_set<PropIdAreaId, PropIdAreaIdHash>
+    getSubscribedSupportedValueChangePropIdAreaIds();
+
   private:
     mutable std::mutex mLock;
     mutable std::condition_variable mCv;
@@ -174,6 +181,9 @@
     std::shared_ptr<RecurrentTimer> mRecurrentTimer;
     std::unordered_map<int32_t, std::unordered_map<int32_t, std::shared_ptr<std::function<void()>>>>
             mRecurrentActions GUARDED_BY(mLock);
+
+    std::unordered_set<PropIdAreaId, PropIdAreaIdHash> mSubscribedSupportedValueChangePropIdAreaIds
+            GUARDED_BY(mLock);
 };
 
 }  // namespace vehicle
diff --git a/boot/aidl/default/Android.bp b/boot/aidl/default/Android.bp
index c1d3c57..2fd2dad 100644
--- a/boot/aidl/default/Android.bp
+++ b/boot/aidl/default/Android.bp
@@ -57,7 +57,7 @@
     name: "android.hardware.boot-service.default_recovery",
     defaults: ["android.hardware.boot-service_common"],
     init_rc: ["android.hardware.boot-service.default_recovery.rc"],
-    vintf_fragments: ["android.hardware.boot-service.default.xml"],
+    vintf_fragment_modules: ["android.hardware.boot-service.default.xml.recovery"],
     recovery: true,
 
     shared_libs: [
@@ -77,11 +77,16 @@
     installable: false,
 }
 
-prebuilt_etc {
+vintf_fragment {
+    name: "android.hardware.boot-service.default.xml.recovery",
+    src: "android.hardware.boot-service.default.xml",
+    recovery: true,
+}
+
+vintf_fragment {
     name: "android.hardware.boot-service.default.xml",
     src: "android.hardware.boot-service.default.xml",
-    sub_dir: "vintf",
-    installable: false,
+    vendor: true,
 }
 
 apex {
@@ -98,6 +103,8 @@
     ],
     prebuilts: [
         "android.hardware.boot-service.default.rc",
+    ],
+    vintf_fragment_modules: [
         "android.hardware.boot-service.default.xml",
     ],
 }
diff --git a/contexthub/aidl/aidl_api/android.hardware.contexthub/current/android/hardware/contexthub/IContextHub.aidl b/contexthub/aidl/aidl_api/android.hardware.contexthub/current/android/hardware/contexthub/IContextHub.aidl
index 2646d15..2940745 100644
--- a/contexthub/aidl/aidl_api/android.hardware.contexthub/current/android/hardware/contexthub/IContextHub.aidl
+++ b/contexthub/aidl/aidl_api/android.hardware.contexthub/current/android/hardware/contexthub/IContextHub.aidl
@@ -51,14 +51,6 @@
   void sendMessageDeliveryStatusToHub(in int contextHubId, in android.hardware.contexthub.MessageDeliveryStatus messageDeliveryStatus);
   List<android.hardware.contexthub.HubInfo> getHubs();
   List<android.hardware.contexthub.EndpointInfo> getEndpoints();
-  void registerEndpoint(in android.hardware.contexthub.EndpointInfo endpoint);
-  void unregisterEndpoint(in android.hardware.contexthub.EndpointInfo endpoint);
-  void registerEndpointCallback(in android.hardware.contexthub.IEndpointCallback callback);
-  int[2] requestSessionIdRange(int size);
-  void openEndpointSession(int sessionId, in android.hardware.contexthub.EndpointId destination, in android.hardware.contexthub.EndpointId initiator, in @nullable String serviceDescriptor);
-  void sendMessageToEndpoint(int sessionId, in android.hardware.contexthub.Message msg);
-  void sendMessageDeliveryStatusToEndpoint(int sessionId, in android.hardware.contexthub.MessageDeliveryStatus msgStatus);
-  void closeEndpointSession(int sessionId, in android.hardware.contexthub.Reason reason);
-  void endpointSessionOpenComplete(int sessionId);
+  @PropagateAllowBlocking android.hardware.contexthub.IEndpointCommunication registerEndpointHub(in android.hardware.contexthub.IEndpointCallback callback, in android.hardware.contexthub.HubInfo hubInfo);
   const int EX_CONTEXT_HUB_UNSPECIFIED = (-1) /* -1 */;
 }
diff --git a/contexthub/aidl/aidl_api/android.hardware.contexthub/current/android/hardware/contexthub/IEndpointCommunication.aidl b/contexthub/aidl/aidl_api/android.hardware.contexthub/current/android/hardware/contexthub/IEndpointCommunication.aidl
new file mode 100644
index 0000000..8742415
--- /dev/null
+++ b/contexthub/aidl/aidl_api/android.hardware.contexthub/current/android/hardware/contexthub/IEndpointCommunication.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 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.contexthub;
+@VintfStability
+interface IEndpointCommunication {
+  void registerEndpoint(in android.hardware.contexthub.EndpointInfo endpoint);
+  void unregisterEndpoint(in android.hardware.contexthub.EndpointInfo endpoint);
+  int[2] requestSessionIdRange(int size);
+  void openEndpointSession(int sessionId, in android.hardware.contexthub.EndpointId destination, in android.hardware.contexthub.EndpointId initiator, in @nullable String serviceDescriptor);
+  void sendMessageToEndpoint(int sessionId, in android.hardware.contexthub.Message msg);
+  void sendMessageDeliveryStatusToEndpoint(int sessionId, in android.hardware.contexthub.MessageDeliveryStatus msgStatus);
+  void closeEndpointSession(int sessionId, in android.hardware.contexthub.Reason reason);
+  void endpointSessionOpenComplete(int sessionId);
+  void unregister();
+}
diff --git a/contexthub/aidl/android/hardware/contexthub/IContextHub.aidl b/contexthub/aidl/android/hardware/contexthub/IContextHub.aidl
index eb6d051..3fb452c 100644
--- a/contexthub/aidl/android/hardware/contexthub/IContextHub.aidl
+++ b/contexthub/aidl/android/hardware/contexthub/IContextHub.aidl
@@ -18,19 +18,16 @@
 
 import android.hardware.contexthub.ContextHubInfo;
 import android.hardware.contexthub.ContextHubMessage;
-import android.hardware.contexthub.EndpointId;
 import android.hardware.contexthub.EndpointInfo;
 import android.hardware.contexthub.HostEndpointInfo;
 import android.hardware.contexthub.HubInfo;
 import android.hardware.contexthub.IContextHubCallback;
 import android.hardware.contexthub.IEndpointCallback;
-import android.hardware.contexthub.Message;
+import android.hardware.contexthub.IEndpointCommunication;
 import android.hardware.contexthub.MessageDeliveryStatus;
 import android.hardware.contexthub.NanSessionStateUpdate;
 import android.hardware.contexthub.NanoappBinary;
 import android.hardware.contexthub.NanoappInfo;
-import android.hardware.contexthub.Reason;
-import android.hardware.contexthub.Service;
 import android.hardware.contexthub.Setting;
 
 @VintfStability
@@ -271,125 +268,22 @@
     List<EndpointInfo> getEndpoints();
 
     /**
-     * Publishes an endpoint from the calling side (e.g. Android). Endpoints must be registered
-     * prior to starting a session.
+     * Registers a new hub for endpoint communication which will receive events for its endpoints
+     * over the given callback. Returns an interface for the hub to register endpoints, start
+     * sessions, and send messages.
+     *
+     * It is valid for the same callback to be registered for multiple hubs, as the
+     * IEndpointCallback events provide sufficient information to determine which hub the event is
+     * intended for:
+     * * session ids are allocated to a specific hub and are unique
+     * * endpoints are identified by hub and endpoint id
+     *
+     * @param callback Interface to send endpoint events targeting the caller
+     * @param hubInfo Details of the hub being registered
+     * @return Interface for the hub to interact with other endpoint hubs
+     *
+     * @throws EX_ILLEGAL_STATE if hubInfo.hubId has already been registered
      */
-    void registerEndpoint(in EndpointInfo endpoint);
-
-    /**
-     * Teardown an endpoint from the calling side (e.g. Android). This endpoint must have already
-     * been published via registerEndpoint().
-     */
-    void unregisterEndpoint(in EndpointInfo endpoint);
-
-    /**
-     * Attaches a callback interface to receive events targeted at endpoints registered by the
-     * caller.
-     */
-    void registerEndpointCallback(in IEndpointCallback callback);
-
-    /**
-     * Request a range of session IDs for the caller to use when initiating sessions. This may be
-     * called more than once, but typical usage is to request a large enough range to accommodate
-     * the maximum expected number of concurrent sessions, but not overly large as to limit other
-     * clients.
-     *
-     * @param size The number of sessionId reserved for host-initiated sessions. This number should
-     *         be less than or equal to 1024.
-     *
-     * @return An array with two elements representing the smallest and largest possible session id
-     *         available for host.
-     *
-     * @throws EX_ILLEGAL_ARGUMENT if the size is invalid.
-     * @throws EX_SERVICE_SPECIFIC if the id range requested cannot be allocated.
-     */
-    int[2] requestSessionIdRange(int size);
-
-    /**
-     * Request to open a session for communication between an endpoint previously registered by the
-     * caller and a target endpoint found in getEndpoints(), optionally scoped to a service
-     * published by the target endpoint.
-     *
-     * Upon returning from this function, the session is in pending state, and the final result will
-     * be given by an asynchronous call to onEndpointSessionOpenComplete() on success, or
-     * onCloseEndpointSession() on failure.
-     *
-     * @param sessionId Caller-allocated session identifier, which must be unique across all active
-     *         sessions, and must fall in a range allocated via requestSessionIdRange().
-     * @param destination The EndpointId representing the destination side of the session.
-     * @param initiator The EndpointId representing the initiating side of the session, which
-     *         must've already been published through registerEndpoint().
-     * @param serviceDescriptor Descriptor for the service specification for scoping this session
-     *         (nullable). Null indicates a fully custom marshalling scheme. The value should match
-     *         a published descriptor for both destination and initiator.
-     *
-     * @throws EX_ILLEGAL_ARGUMENT if any of the arguments are invalid, or the combination of the
-     *         arguments is invalid.
-     * @throws EX_SERVICE_SPECIFIC on other errors
-     *         - EX_CONTEXT_HUB_UNSPECIFIED if the request failed for other reasons.
-     */
-    void openEndpointSession(int sessionId, in EndpointId destination, in EndpointId initiator,
-            in @nullable String serviceDescriptor);
-
-    /**
-     * Send a message from one endpoint to another on the (currently open) session.
-     *
-     * @param sessionId The integer representing the communication session, previously set in
-     *         openEndpointSession() or onEndpointSessionOpenRequest().
-     * @param msg The Message object representing a message to endpoint from the endpoint on host.
-     *
-     * @throws EX_ILLEGAL_ARGUMENT if any of the arguments are invalid, or the combination of the
-     *         arguments is invalid.
-     * @throws EX_SERVICE_SPECIFIC on other errors
-     *         - EX_CONTEXT_HUB_UNSPECIFIED if the request failed for other reasons.
-     */
-    void sendMessageToEndpoint(int sessionId, in Message msg);
-
-    /**
-     * Sends a message delivery status to the endpoint in response to receiving a Message with flag
-     * FLAG_REQUIRES_DELIVERY_STATUS. Each message with the flag should have a MessageDeliveryStatus
-     * response. This method sends the message delivery status back to the remote endpoint for a
-     * session.
-     *
-     * @param sessionId The integer representing the communication session, previously set in
-     *         openEndpointSession() or onEndpointSessionOpenRequest().
-     * @param msgStatus The MessageDeliveryStatus object representing the delivery status for a
-     *         specific message (identified by the sequenceNumber) within the session.
-     *
-     * @throws EX_UNSUPPORTED_OPERATION if ContextHubInfo.supportsReliableMessages is false for
-     *          the hub involved in this session.
-     */
-    void sendMessageDeliveryStatusToEndpoint(int sessionId, in MessageDeliveryStatus msgStatus);
-
-    /**
-     * Closes a session previously opened by openEndpointSession() or requested via
-     * onEndpointSessionOpenRequest(). Processing of session closure must be ordered/synchronized
-     * with message delivery, such that if this session was open, any messages previously passed to
-     * sendMessageToEndpoint() that are still in-flight must still be delivered before the session
-     * is closed. Any in-flight messages to the endpoint that requested to close the session will
-     * not be delivered.
-     *
-     * @param sessionId The integer representing the communication session, previously set in
-     *         openEndpointSession() or onEndpointSessionOpenRequest().
-     * @param reason The reason for this close endpoint session request.
-     *
-     * @throws EX_ILLEGAL_ARGUMENT if any of the arguments are invalid, or the combination of the
-     *         arguments is invalid.
-     * @throws EX_SERVICE_SPECIFIC on other errors
-     *         - EX_CONTEXT_HUB_UNSPECIFIED if the request failed for other reasons.
-     */
-    void closeEndpointSession(int sessionId, in Reason reason);
-
-    /**
-     * Notifies the HAL that the session requested by onEndpointSessionOpenRequest is ready to use.
-     *
-     * @param sessionId The integer representing the communication session, previously set in
-     *         onEndpointSessionOpenRequest(). This id is assigned by the HAL.
-     *
-     * @throws EX_ILLEGAL_ARGUMENT if any of the arguments are invalid, or the combination of the
-     *         arguments is invalid.
-     * @throws EX_SERVICE_SPECIFIC on other errors
-     *         - EX_CONTEXT_HUB_UNSPECIFIED if the request failed for other reasons.
-     */
-    void endpointSessionOpenComplete(int sessionId);
+    @PropagateAllowBlocking
+    IEndpointCommunication registerEndpointHub(in IEndpointCallback callback, in HubInfo hubInfo);
 }
diff --git a/contexthub/aidl/android/hardware/contexthub/IEndpointCommunication.aidl b/contexthub/aidl/android/hardware/contexthub/IEndpointCommunication.aidl
new file mode 100644
index 0000000..e5045ba
--- /dev/null
+++ b/contexthub/aidl/android/hardware/contexthub/IEndpointCommunication.aidl
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2024 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.contexthub;
+
+import android.hardware.contexthub.EndpointId;
+import android.hardware.contexthub.EndpointInfo;
+import android.hardware.contexthub.IEndpointCallback;
+import android.hardware.contexthub.Message;
+import android.hardware.contexthub.MessageDeliveryStatus;
+import android.hardware.contexthub.Reason;
+import android.hardware.contexthub.Service;
+
+@VintfStability
+interface IEndpointCommunication {
+    /**
+     * Publishes an endpoint from the calling side (e.g. Android). Endpoints must be registered
+     * prior to starting a session.
+     */
+    void registerEndpoint(in EndpointInfo endpoint);
+
+    /**
+     * Teardown an endpoint from the calling side (e.g. Android). This endpoint must have already
+     * been published via registerEndpoint().
+     */
+    void unregisterEndpoint(in EndpointInfo endpoint);
+
+    /**
+     * Request a range of session IDs for the caller to use when initiating sessions. This may be
+     * called more than once, but typical usage is to request a large enough range to accommodate
+     * the maximum expected number of concurrent sessions, but not overly large as to limit other
+     * clients.
+     *
+     * @param size The number of sessionId reserved for host-initiated sessions. This number should
+     *         be less than or equal to 1024.
+     *
+     * @return An array with two elements representing the smallest and largest possible session id
+     *         available for host.
+     *
+     * @throws EX_ILLEGAL_ARGUMENT if the size is invalid.
+     * @throws EX_SERVICE_SPECIFIC if the id range requested cannot be allocated.
+     */
+    int[2] requestSessionIdRange(int size);
+
+    /**
+     * Request to open a session for communication between an endpoint previously registered by the
+     * caller and a target endpoint found in getEndpoints(), optionally scoped to a service
+     * published by the target endpoint.
+     *
+     * Upon returning from this function, the session is in pending state, and the final result will
+     * be given by an asynchronous call to onEndpointSessionOpenComplete() on success, or
+     * onCloseEndpointSession() on failure.
+     *
+     * @param sessionId Caller-allocated session identifier, which must be unique across all active
+     *         sessions, and must fall in a range allocated via requestSessionIdRange().
+     * @param destination The EndpointId representing the destination side of the session.
+     * @param initiator The EndpointId representing the initiating side of the session, which
+     *         must've already been published through registerEndpoint().
+     * @param serviceDescriptor Descriptor for the service specification for scoping this session
+     *         (nullable). Null indicates a fully custom marshalling scheme. The value should match
+     *         a published descriptor for both destination and initiator.
+     *
+     * @throws EX_ILLEGAL_ARGUMENT if any of the arguments are invalid, or the combination of the
+     *         arguments is invalid.
+     * @throws EX_SERVICE_SPECIFIC on other errors
+     *         - EX_CONTEXT_HUB_UNSPECIFIED if the request failed for other reasons.
+     */
+    void openEndpointSession(int sessionId, in EndpointId destination, in EndpointId initiator,
+            in @nullable String serviceDescriptor);
+
+    /**
+     * Send a message from one endpoint to another on the (currently open) session.
+     *
+     * @param sessionId The integer representing the communication session, previously set in
+     *         openEndpointSession() or onEndpointSessionOpenRequest().
+     * @param msg The Message object representing a message to endpoint from the endpoint on host.
+     *
+     * @throws EX_ILLEGAL_ARGUMENT if any of the arguments are invalid, or the combination of the
+     *         arguments is invalid.
+     * @throws EX_SERVICE_SPECIFIC on other errors
+     *         - EX_CONTEXT_HUB_UNSPECIFIED if the request failed for other reasons.
+     */
+    void sendMessageToEndpoint(int sessionId, in Message msg);
+
+    /**
+     * Sends a message delivery status to the endpoint in response to receiving a Message with flag
+     * FLAG_REQUIRES_DELIVERY_STATUS. Each message with the flag should have a MessageDeliveryStatus
+     * response. This method sends the message delivery status back to the remote endpoint for a
+     * session.
+     *
+     * @param sessionId The integer representing the communication session, previously set in
+     *         openEndpointSession() or onEndpointSessionOpenRequest().
+     * @param msgStatus The MessageDeliveryStatus object representing the delivery status for a
+     *         specific message (identified by the sequenceNumber) within the session.
+     *
+     * @throws EX_UNSUPPORTED_OPERATION if ContextHubInfo.supportsReliableMessages is false for
+     *          the hub involved in this session.
+     */
+    void sendMessageDeliveryStatusToEndpoint(int sessionId, in MessageDeliveryStatus msgStatus);
+
+    /**
+     * Closes a session previously opened by openEndpointSession() or requested via
+     * onEndpointSessionOpenRequest(). Processing of session closure must be ordered/synchronized
+     * with message delivery, such that if this session was open, any messages previously passed to
+     * sendMessageToEndpoint() that are still in-flight must still be delivered before the session
+     * is closed. Any in-flight messages to the endpoint that requested to close the session will
+     * not be delivered.
+     *
+     * @param sessionId The integer representing the communication session, previously set in
+     *         openEndpointSession() or onEndpointSessionOpenRequest().
+     * @param reason The reason for this close endpoint session request.
+     *
+     * @throws EX_ILLEGAL_ARGUMENT if any of the arguments are invalid, or the combination of the
+     *         arguments is invalid.
+     * @throws EX_SERVICE_SPECIFIC on other errors
+     *         - EX_CONTEXT_HUB_UNSPECIFIED if the request failed for other reasons.
+     */
+    void closeEndpointSession(int sessionId, in Reason reason);
+
+    /**
+     * Notifies the HAL that the session requested by onEndpointSessionOpenRequest is ready to use.
+     *
+     * @param sessionId The integer representing the communication session, previously set in
+     *         onEndpointSessionOpenRequest(). This id is assigned by the HAL.
+     *
+     * @throws EX_ILLEGAL_ARGUMENT if any of the arguments are invalid, or the combination of the
+     *         arguments is invalid.
+     * @throws EX_SERVICE_SPECIFIC on other errors
+     *         - EX_CONTEXT_HUB_UNSPECIFIED if the request failed for other reasons.
+     */
+    void endpointSessionOpenComplete(int sessionId);
+
+    /**
+     * Unregisters this hub. Subsequent calls on this interface will fail.
+     *
+     * @throws EX_ILLEGAL_STATE if this interface was already unregistered.
+     */
+    void unregister();
+}
diff --git a/contexthub/aidl/default/ContextHub.cpp b/contexthub/aidl/default/ContextHub.cpp
index 19d9639..433617e 100644
--- a/contexthub/aidl/default/ContextHub.cpp
+++ b/contexthub/aidl/default/ContextHub.cpp
@@ -150,10 +150,11 @@
 
 ScopedAStatus ContextHub::setTestMode(bool enable) {
     if (enable) {
-        std::unique_lock<std::mutex> lock(mEndpointMutex);
-        mEndpoints.clear();
-        mEndpointSessions.clear();
-        mEndpointCallback = nullptr;
+        std::lock_guard lock(mHostHubsLock);
+        for (auto& [id, hub] : mIdToHostHub) {
+            hub->mActive = false;
+        }
+        mIdToHostHub.clear();
     }
     return ScopedAStatus::ok();
 }
@@ -227,7 +228,23 @@
     return ScopedAStatus::ok();
 };
 
-ScopedAStatus ContextHub::registerEndpoint(const EndpointInfo& in_endpoint) {
+ScopedAStatus ContextHub::registerEndpointHub(
+        const std::shared_ptr<IEndpointCallback>& in_callback, const HubInfo& in_hubInfo,
+        std::shared_ptr<IEndpointCommunication>* _aidl_return) {
+    std::lock_guard lock(mHostHubsLock);
+    if (mIdToHostHub.count(in_hubInfo.hubId)) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    auto hub = ndk::SharedRefBase::make<HubInterface>(*this, in_callback, in_hubInfo);
+    mIdToHostHub.insert({in_hubInfo.hubId, hub});
+    *_aidl_return = std::move(hub);
+    return ScopedAStatus::ok();
+}
+
+ScopedAStatus ContextHub::HubInterface::registerEndpoint(const EndpointInfo& in_endpoint) {
+    if (!mActive) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
     std::unique_lock<std::mutex> lock(mEndpointMutex);
 
     for (const EndpointInfo& endpoint : mEndpoints) {
@@ -240,7 +257,10 @@
     return ScopedAStatus::ok();
 };
 
-ScopedAStatus ContextHub::unregisterEndpoint(const EndpointInfo& in_endpoint) {
+ScopedAStatus ContextHub::HubInterface::unregisterEndpoint(const EndpointInfo& in_endpoint) {
+    if (!mActive) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
     std::unique_lock<std::mutex> lock(mEndpointMutex);
 
     for (auto it = mEndpoints.begin(); it != mEndpoints.end(); ++it) {
@@ -252,41 +272,47 @@
     return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
 };
 
-ScopedAStatus ContextHub::registerEndpointCallback(
-        const std::shared_ptr<IEndpointCallback>& in_callback) {
-    std::unique_lock<std::mutex> lock(mEndpointMutex);
-
-    mEndpointCallback = in_callback;
-    return ScopedAStatus::ok();
-};
-
-ScopedAStatus ContextHub::requestSessionIdRange(int32_t in_size,
-                                                std::array<int32_t, 2>* _aidl_return) {
+ScopedAStatus ContextHub::HubInterface::requestSessionIdRange(
+        int32_t in_size, std::array<int32_t, 2>* _aidl_return) {
+    if (!mActive) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
     constexpr int32_t kMaxSize = 1024;
     if (in_size > kMaxSize || _aidl_return == nullptr) {
         return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
     }
 
+    uint16_t base = 0;
     {
-        std::lock_guard<std::mutex> lock(mEndpointMutex);
-        mMaxValidSessionId = in_size;
+        std::lock_guard lock(mHal.mHostHubsLock);
+        if (static_cast<int32_t>(USHRT_MAX) - mHal.mNextSessionIdBase + 1 < in_size) {
+            return ScopedAStatus::fromServiceSpecificError(EX_CONTEXT_HUB_UNSPECIFIED);
+        }
+        base = mHal.mNextSessionIdBase;
+        mHal.mNextSessionIdBase += in_size;
     }
 
-    (*_aidl_return)[0] = 0;
-    (*_aidl_return)[1] = in_size;
+    {
+        std::lock_guard<std::mutex> lock(mEndpointMutex);
+        (*_aidl_return)[0] = mBaseSessionId = base;
+        (*_aidl_return)[1] = mMaxSessionId = base + (in_size - 1);
+    }
     return ScopedAStatus::ok();
 };
 
-ScopedAStatus ContextHub::openEndpointSession(
+ScopedAStatus ContextHub::HubInterface::openEndpointSession(
         int32_t in_sessionId, const EndpointId& in_destination, const EndpointId& in_initiator,
         const std::optional<std::string>& in_serviceDescriptor) {
+    if (!mActive) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
     // We are not calling onCloseEndpointSession on failure because the remote endpoints (our
     // mock endpoints) always accept the session.
 
     std::weak_ptr<IEndpointCallback> callback;
     {
         std::unique_lock<std::mutex> lock(mEndpointMutex);
-        if (in_sessionId > mMaxValidSessionId) {
+        if (in_sessionId < mBaseSessionId || in_sessionId > mMaxSessionId) {
             ALOGE("openEndpointSession: session ID %" PRId32 " is invalid", in_sessionId);
             return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
         }
@@ -346,7 +372,11 @@
     return ScopedAStatus::ok();
 };
 
-ScopedAStatus ContextHub::sendMessageToEndpoint(int32_t in_sessionId, const Message& in_msg) {
+ScopedAStatus ContextHub::HubInterface::sendMessageToEndpoint(int32_t in_sessionId,
+                                                              const Message& in_msg) {
+    if (!mActive) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
     std::weak_ptr<IEndpointCallback> callback;
     {
         std::unique_lock<std::mutex> lock(mEndpointMutex);
@@ -393,12 +423,19 @@
     return ScopedAStatus::ok();
 };
 
-ScopedAStatus ContextHub::sendMessageDeliveryStatusToEndpoint(
+ScopedAStatus ContextHub::HubInterface::sendMessageDeliveryStatusToEndpoint(
         int32_t /* in_sessionId */, const MessageDeliveryStatus& /* in_msgStatus */) {
+    if (!mActive) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
     return ScopedAStatus::ok();
 };
 
-ScopedAStatus ContextHub::closeEndpointSession(int32_t in_sessionId, Reason /* in_reason */) {
+ScopedAStatus ContextHub::HubInterface::closeEndpointSession(int32_t in_sessionId,
+                                                             Reason /* in_reason */) {
+    if (!mActive) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
     std::unique_lock<std::mutex> lock(mEndpointMutex);
 
     for (auto it = mEndpointSessions.begin(); it != mEndpointSessions.end(); ++it) {
@@ -411,8 +448,20 @@
     return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
 };
 
-ScopedAStatus ContextHub::endpointSessionOpenComplete(int32_t /* in_sessionId */) {
+ScopedAStatus ContextHub::HubInterface::endpointSessionOpenComplete(int32_t /* in_sessionId */) {
+    if (!mActive) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
     return ScopedAStatus::ok();
 };
 
+ScopedAStatus ContextHub::HubInterface::unregister() {
+    if (!mActive.exchange(false)) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    std::lock_guard lock(mHal.mHostHubsLock);
+    mHal.mIdToHostHub.erase(kInfo.hubId);
+    return ScopedAStatus::ok();
+}
+
 }  // namespace aidl::android::hardware::contexthub
diff --git a/contexthub/aidl/default/include/contexthub-impl/ContextHub.h b/contexthub/aidl/default/include/contexthub-impl/ContextHub.h
index 6da8bf2..65e84bb 100644
--- a/contexthub/aidl/default/include/contexthub-impl/ContextHub.h
+++ b/contexthub/aidl/default/include/contexthub-impl/ContextHub.h
@@ -17,7 +17,9 @@
 #pragma once
 
 #include <aidl/android/hardware/contexthub/BnContextHub.h>
+#include <aidl/android/hardware/contexthub/BnEndpointCommunication.h>
 
+#include <atomic>
 #include <mutex>
 #include <unordered_set>
 #include <vector>
@@ -56,54 +58,79 @@
 
     ::ndk::ScopedAStatus getHubs(std::vector<HubInfo>* _aidl_return) override;
     ::ndk::ScopedAStatus getEndpoints(std::vector<EndpointInfo>* _aidl_return) override;
-    ::ndk::ScopedAStatus registerEndpoint(const EndpointInfo& in_endpoint) override;
-    ::ndk::ScopedAStatus unregisterEndpoint(const EndpointInfo& in_endpoint) override;
-    ::ndk::ScopedAStatus registerEndpointCallback(
-            const std::shared_ptr<IEndpointCallback>& in_callback) override;
-    ::ndk::ScopedAStatus requestSessionIdRange(int32_t in_size,
-                                               std::array<int32_t, 2>* _aidl_return) override;
-    ::ndk::ScopedAStatus openEndpointSession(
-            int32_t in_sessionId, const EndpointId& in_destination, const EndpointId& in_initiator,
-            const std::optional<std::string>& in_serviceDescriptor) override;
-    ::ndk::ScopedAStatus sendMessageToEndpoint(int32_t in_sessionId,
-                                               const Message& in_msg) override;
-    ::ndk::ScopedAStatus sendMessageDeliveryStatusToEndpoint(
-            int32_t in_sessionId, const MessageDeliveryStatus& in_msgStatus) override;
-    ::ndk::ScopedAStatus closeEndpointSession(int32_t in_sessionId, Reason in_reason) override;
-    ::ndk::ScopedAStatus endpointSessionOpenComplete(int32_t in_sessionId) override;
+    ::ndk::ScopedAStatus registerEndpointHub(
+            const std::shared_ptr<IEndpointCallback>& in_callback, const HubInfo& in_hubInfo,
+            std::shared_ptr<IEndpointCommunication>* _aidl_return) override;
 
   private:
-    struct EndpointSession {
-        int32_t sessionId;
-        EndpointId initiator;
-        EndpointId peer;
-        std::optional<std::string> serviceDescriptor;
+    class HubInterface : public BnEndpointCommunication {
+      public:
+        HubInterface(ContextHub& hal, const std::shared_ptr<IEndpointCallback>& in_callback,
+                     const HubInfo& in_hubInfo)
+            : mHal(hal), mEndpointCallback(in_callback), kInfo(in_hubInfo) {}
+        ~HubInterface() = default;
+
+        ::ndk::ScopedAStatus registerEndpoint(const EndpointInfo& in_endpoint) override;
+        ::ndk::ScopedAStatus unregisterEndpoint(const EndpointInfo& in_endpoint) override;
+        ::ndk::ScopedAStatus requestSessionIdRange(int32_t in_size,
+                                                   std::array<int32_t, 2>* _aidl_return) override;
+        ::ndk::ScopedAStatus openEndpointSession(
+                int32_t in_sessionId, const EndpointId& in_destination,
+                const EndpointId& in_initiator,
+                const std::optional<std::string>& in_serviceDescriptor) override;
+        ::ndk::ScopedAStatus sendMessageToEndpoint(int32_t in_sessionId,
+                                                   const Message& in_msg) override;
+        ::ndk::ScopedAStatus sendMessageDeliveryStatusToEndpoint(
+                int32_t in_sessionId, const MessageDeliveryStatus& in_msgStatus) override;
+        ::ndk::ScopedAStatus closeEndpointSession(int32_t in_sessionId, Reason in_reason) override;
+        ::ndk::ScopedAStatus endpointSessionOpenComplete(int32_t in_sessionId) override;
+        ::ndk::ScopedAStatus unregister() override;
+
+      private:
+        friend class ContextHub;
+
+        struct EndpointSession {
+            int32_t sessionId;
+            EndpointId initiator;
+            EndpointId peer;
+            std::optional<std::string> serviceDescriptor;
+        };
+
+        //! Finds an endpoint in the range defined by the endpoints
+        //! @return whether the endpoint was found
+        template <typename Iter>
+        bool findEndpoint(const EndpointId& target, const Iter& begin, const Iter& end) {
+            for (auto iter = begin; iter != end; ++iter) {
+                if (iter->id.id == target.id && iter->id.hubId == target.hubId) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        //! Endpoint storage and information
+        ContextHub& mHal;
+        std::shared_ptr<IEndpointCallback> mEndpointCallback;
+        const HubInfo kInfo;
+
+        std::atomic<bool> mActive = true;
+
+        std::mutex mEndpointMutex;
+        std::vector<EndpointInfo> mEndpoints;
+        std::vector<EndpointSession> mEndpointSessions;
+        uint16_t mBaseSessionId;
+        uint16_t mMaxSessionId;
     };
 
     static constexpr uint32_t kMockHubId = 0;
 
-    //! Finds an endpoint in the range defined by the endpoints
-    //! @return whether the endpoint was found
-    template <typename Iter>
-    bool findEndpoint(const EndpointId& target, const Iter& begin, const Iter& end) {
-        for (auto iter = begin; iter != end; ++iter) {
-            if (iter->id.id == target.id && iter->id.hubId == target.hubId) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     std::shared_ptr<IContextHubCallback> mCallback;
 
     std::unordered_set<char16_t> mConnectedHostEndpoints;
 
-    //! Endpoint storage and information
-    std::mutex mEndpointMutex;
-    std::vector<EndpointInfo> mEndpoints;
-    std::vector<EndpointSession> mEndpointSessions;
-    std::shared_ptr<IEndpointCallback> mEndpointCallback;
-    int32_t mMaxValidSessionId = 0;
+    std::mutex mHostHubsLock;
+    std::unordered_map<int64_t, std::shared_ptr<HubInterface>> mIdToHostHub;
+    int32_t mNextSessionIdBase = 0;
 };
 
 }  // namespace contexthub
diff --git a/contexthub/aidl/vts/VtsAidlHalContextHubTargetTest.cpp b/contexthub/aidl/vts/VtsAidlHalContextHubTargetTest.cpp
index aa611ce..d7859d9 100644
--- a/contexthub/aidl/vts/VtsAidlHalContextHubTargetTest.cpp
+++ b/contexthub/aidl/vts/VtsAidlHalContextHubTargetTest.cpp
@@ -24,6 +24,7 @@
 #include <android/hardware/contexthub/IContextHub.h>
 #include <android/hardware/contexthub/IContextHubCallback.h>
 #include <android/hardware/contexthub/IEndpointCallback.h>
+#include <android/hardware/contexthub/IEndpointCommunication.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
 #include <log/log.h>
@@ -46,6 +47,7 @@
 using ::android::hardware::contexthub::HubInfo;
 using ::android::hardware::contexthub::IContextHub;
 using ::android::hardware::contexthub::IContextHubCallbackDefault;
+using ::android::hardware::contexthub::IEndpointCommunication;
 using ::android::hardware::contexthub::Message;
 using ::android::hardware::contexthub::MessageDeliveryStatus;
 using ::android::hardware::contexthub::NanoappBinary;
@@ -62,34 +64,71 @@
 // 6612b522-b717-41c8-b48d-c0b1cc64e142
 constexpr std::array<uint8_t, 16> kUuid = {0x66, 0x12, 0xb5, 0x22, 0xb7, 0x17, 0x41, 0xc8,
                                            0xb4, 0x8d, 0xc0, 0xb1, 0xcc, 0x64, 0xe1, 0x42};
+
 const String16 kName{"VtsAidlHalContextHubTargetTest"};
 
 const String16 kEchoServiceName{"android.hardware.contexthub.test.EchoService"};
 
+constexpr int64_t kDefaultHubId = 1;
+
+class TestEndpointCallback;
 class ContextHubAidl : public testing::TestWithParam<std::tuple<std::string, int32_t>> {
   public:
-    virtual void SetUp() override {
-        contextHub = android::waitForDeclaredService<IContextHub>(
+    void SetUp() override {
+        mContextHub = android::waitForDeclaredService<IContextHub>(
                 String16(std::get<0>(GetParam()).c_str()));
-        ASSERT_NE(contextHub, nullptr);
-
-        // Best effort enable test mode - this may not be supported on older HALS, so we
-        // ignore the return value.
-        contextHub->setTestMode(/* enable= */ true);
+        ASSERT_NE(mContextHub, nullptr);
+        mEndpointCb = sp<TestEndpointCallback>::make();
     }
 
-    virtual void TearDown() override { contextHub->setTestMode(/* enable= */ false); }
-
     uint32_t getHubId() { return std::get<1>(GetParam()); }
 
+    Status registerHub(int64_t id, sp<IEndpointCommunication>* hubInterface) {
+        HubInfo info;
+        info.hubId = id;
+        return mContextHub->registerEndpointHub(mEndpointCb, info, hubInterface);
+    }
+
+    bool registerDefaultHub() {
+        Status status = registerHub(kDefaultHubId, &mHubInterface);
+        if (status.exceptionCode() == Status::EX_UNSUPPORTED_OPERATION ||
+            status.transactionError() == android::UNKNOWN_TRANSACTION) {
+            return false;
+        }
+        EXPECT_TRUE(status.isOk());
+        EXPECT_NE(mHubInterface, nullptr);
+        if (!mHubInterface) {
+            return false;
+        }
+        return true;
+    }
+
     void testSettingChanged(Setting setting);
 
-    sp<IContextHub> contextHub;
+    sp<IContextHub> mContextHub;
+    sp<TestEndpointCallback> mEndpointCb;
+    sp<IEndpointCommunication> mHubInterface;
+};
+
+class ContextHubAidlWithTestMode : public ContextHubAidl {
+  public:
+    void SetUp() override {
+        ContextHubAidl::SetUp();
+
+        // Best effort enable test mode - this may not be supported on older HALS, so we
+        // ignore the return value.
+        mContextHub->setTestMode(/* enable= */ true);
+    }
+
+    void TearDown() override {
+        mContextHub->setTestMode(/* enable= */ false);
+        ContextHubAidl::TearDown();
+    }
 };
 
 TEST_P(ContextHubAidl, TestGetHubs) {
     std::vector<ContextHubInfo> hubs;
-    ASSERT_TRUE(contextHub->getContextHubs(&hubs).isOk());
+    ASSERT_TRUE(mContextHub->getContextHubs(&hubs).isOk());
 
     ALOGD("System reports %zu hubs", hubs.size());
 
@@ -111,7 +150,7 @@
 }
 
 TEST_P(ContextHubAidl, TestEnableTestMode) {
-    Status status = contextHub->setTestMode(true);
+    Status status = mContextHub->setTestMode(true);
     if (status.exceptionCode() == Status::EX_UNSUPPORTED_OPERATION ||
         status.transactionError() == android::UNKNOWN_TRANSACTION) {
         GTEST_SKIP() << "Not supported -> old API; or not implemented";
@@ -121,7 +160,7 @@
 }
 
 TEST_P(ContextHubAidl, TestDisableTestMode) {
-    Status status = contextHub->setTestMode(false);
+    Status status = mContextHub->setTestMode(false);
     if (status.exceptionCode() == Status::EX_UNSUPPORTED_OPERATION ||
         status.transactionError() == android::UNKNOWN_TRANSACTION) {
         GTEST_SKIP() << "Not supported -> old API; or not implemented";
@@ -170,7 +209,7 @@
 
 TEST_P(ContextHubAidl, TestRegisterCallback) {
     sp<EmptyContextHubCallback> cb = sp<EmptyContextHubCallback>::make();
-    ASSERT_TRUE(contextHub->registerCallback(getHubId(), cb).isOk());
+    ASSERT_TRUE(mContextHub->registerCallback(getHubId(), cb).isOk());
 }
 
 // Helper callback that puts the async appInfo callback data into a promise
@@ -219,8 +258,8 @@
 // Calls queryApps() and checks the returned metadata
 TEST_P(ContextHubAidl, TestQueryApps) {
     sp<QueryAppsCallback> cb = sp<QueryAppsCallback>::make();
-    ASSERT_TRUE(contextHub->registerCallback(getHubId(), cb).isOk());
-    ASSERT_TRUE(contextHub->queryNanoapps(getHubId()).isOk());
+    ASSERT_TRUE(mContextHub->registerCallback(getHubId(), cb).isOk());
+    ASSERT_TRUE(mContextHub->queryNanoapps(getHubId()).isOk());
 
     std::vector<NanoappInfo> appInfoList;
     ASSERT_TRUE(waitForCallback(cb->promise.get_future(), &appInfoList));
@@ -241,7 +280,7 @@
 // Calls getPreloadedNanoappsIds() and verifies there are preloaded nanoapps
 TEST_P(ContextHubAidl, TestGetPreloadedNanoappIds) {
     std::vector<int64_t> preloadedNanoappIds;
-    Status status = contextHub->getPreloadedNanoappIds(getHubId(), &preloadedNanoappIds);
+    Status status = mContextHub->getPreloadedNanoappIds(getHubId(), &preloadedNanoappIds);
     if (status.exceptionCode() == Status::EX_UNSUPPORTED_OPERATION ||
         status.transactionError() == android::UNKNOWN_TRANSACTION) {
         GTEST_SKIP() << "Not supported -> old API; or not implemented";
@@ -304,7 +343,7 @@
   public:
     virtual void SetUp() override {
         ContextHubAidl::SetUp();
-        ASSERT_TRUE(contextHub->registerCallback(getHubId(), cb).isOk());
+        ASSERT_TRUE(mContextHub->registerCallback(getHubId(), cb).isOk());
     }
 
     sp<TransactionResultCallback> cb = sp<TransactionResultCallback>::make();
@@ -318,7 +357,7 @@
     std::fill(message.messageBody.begin(), message.messageBody.end(), 0);
 
     ALOGD("Sending message to non-existent nanoapp");
-    ASSERT_TRUE(contextHub->sendMessageToHub(getHubId(), message).isOk());
+    ASSERT_TRUE(mContextHub->sendMessageToHub(getHubId(), message).isOk());
 }
 
 TEST_P(ContextHubTransactionTest, TestLoadEmptyNanoapp) {
@@ -332,7 +371,7 @@
     emptyApp.targetChreApiMinorVersion = 0;
 
     ALOGD("Loading empty nanoapp");
-    bool success = contextHub->loadNanoapp(getHubId(), emptyApp, cb->expectedTransactionId).isOk();
+    bool success = mContextHub->loadNanoapp(getHubId(), emptyApp, cb->expectedTransactionId).isOk();
     if (success) {
         bool transactionSuccess;
         ASSERT_TRUE(waitForCallback(cb->promise.get_future(), &transactionSuccess));
@@ -345,7 +384,7 @@
 
     ALOGD("Unloading nonexistent nanoapp");
     bool success =
-            contextHub->unloadNanoapp(getHubId(), kNonExistentAppId, cb->expectedTransactionId)
+            mContextHub->unloadNanoapp(getHubId(), kNonExistentAppId, cb->expectedTransactionId)
                     .isOk();
     if (success) {
         bool transactionSuccess;
@@ -359,7 +398,7 @@
 
     ALOGD("Enabling nonexistent nanoapp");
     bool success =
-            contextHub->enableNanoapp(getHubId(), kNonExistentAppId, cb->expectedTransactionId)
+            mContextHub->enableNanoapp(getHubId(), kNonExistentAppId, cb->expectedTransactionId)
                     .isOk();
     if (success) {
         bool transactionSuccess;
@@ -373,7 +412,7 @@
 
     ALOGD("Disabling nonexistent nanoapp");
     bool success =
-            contextHub->disableNanoapp(getHubId(), kNonExistentAppId, cb->expectedTransactionId)
+            mContextHub->disableNanoapp(getHubId(), kNonExistentAppId, cb->expectedTransactionId)
                     .isOk();
     if (success) {
         bool transactionSuccess;
@@ -386,10 +425,10 @@
     // In VTS, we only test that sending the values doesn't cause things to blow up - GTS tests
     // verify the expected E2E behavior in CHRE
     sp<EmptyContextHubCallback> cb = sp<EmptyContextHubCallback>::make();
-    ASSERT_TRUE(contextHub->registerCallback(getHubId(), cb).isOk());
+    ASSERT_TRUE(mContextHub->registerCallback(getHubId(), cb).isOk());
 
-    ASSERT_TRUE(contextHub->onSettingChanged(setting, true /* enabled */).isOk());
-    ASSERT_TRUE(contextHub->onSettingChanged(setting, false /* enabled */).isOk());
+    ASSERT_TRUE(mContextHub->onSettingChanged(setting, true /* enabled */).isOk());
+    ASSERT_TRUE(mContextHub->onSettingChanged(setting, false /* enabled */).isOk());
 }
 
 TEST_P(ContextHubAidl, TestOnLocationSettingChanged) {
@@ -444,27 +483,27 @@
     hostEndpointInfo.type = HostEndpointInfo::Type::NATIVE;
     hostEndpointInfo.hostEndpointId = kHostEndpointId;
 
-    ASSERT_TRUE(contextHub->onHostEndpointConnected(hostEndpointInfo).isOk());
-    ASSERT_TRUE(contextHub->onHostEndpointDisconnected(kHostEndpointId).isOk());
+    ASSERT_TRUE(mContextHub->onHostEndpointConnected(hostEndpointInfo).isOk());
+    ASSERT_TRUE(mContextHub->onHostEndpointDisconnected(kHostEndpointId).isOk());
 }
 
 TEST_P(ContextHubTransactionTest, TestInvalidHostConnection) {
     constexpr char16_t kHostEndpointId = 1;
 
-    ASSERT_TRUE(contextHub->onHostEndpointDisconnected(kHostEndpointId).isOk());
+    ASSERT_TRUE(mContextHub->onHostEndpointDisconnected(kHostEndpointId).isOk());
 }
 
 TEST_P(ContextHubTransactionTest, TestNanSessionStateChange) {
     NanSessionStateUpdate update;
     update.state = true;
-    Status status = contextHub->onNanSessionStateChanged(update);
+    Status status = mContextHub->onNanSessionStateChanged(update);
     if (status.exceptionCode() == Status::EX_UNSUPPORTED_OPERATION ||
         status.transactionError() == android::UNKNOWN_TRANSACTION) {
         GTEST_SKIP() << "Not supported -> old API; or not implemented";
     } else {
         ASSERT_TRUE(status.isOk());
         update.state = false;
-        ASSERT_TRUE(contextHub->onNanSessionStateChanged(update).isOk());
+        ASSERT_TRUE(mContextHub->onNanSessionStateChanged(update).isOk());
     }
 }
 
@@ -473,7 +512,7 @@
     messageDeliveryStatus.messageSequenceNumber = 123;
     messageDeliveryStatus.errorCode = ErrorCode::OK;
 
-    Status status = contextHub->sendMessageDeliveryStatusToHub(getHubId(), messageDeliveryStatus);
+    Status status = mContextHub->sendMessageDeliveryStatusToHub(getHubId(), messageDeliveryStatus);
     if (status.exceptionCode() == Status::EX_UNSUPPORTED_OPERATION ||
         status.transactionError() == android::UNKNOWN_TRANSACTION) {
         GTEST_SKIP() << "Not supported -> old API; or not implemented";
@@ -546,7 +585,30 @@
     bool mWasOnEndpointSessionOpenCompleteCalled = false;
 };
 
-TEST_P(ContextHubAidl, RegisterEndpoint) {
+TEST_P(ContextHubAidlWithTestMode, RegisterHub) {
+    if (!registerDefaultHub()) {
+        GTEST_SKIP() << "Not supported -> old API; or not implemented";
+    }
+
+    sp<IEndpointCommunication> hub2;
+    Status status = registerHub(kDefaultHubId + 1, &hub2);
+    EXPECT_TRUE(status.isOk());
+
+    sp<IEndpointCommunication> hub3;
+    status = registerHub(kDefaultHubId + 1, &hub3);
+    ASSERT_FALSE(status.isOk());
+    EXPECT_EQ(status.exceptionCode(), Status::EX_ILLEGAL_STATE);
+
+    hub2->unregister();
+    status = registerHub(kDefaultHubId + 1, &hub3);
+    EXPECT_TRUE(status.isOk());
+}
+
+TEST_P(ContextHubAidlWithTestMode, RegisterEndpoint) {
+    if (!registerDefaultHub()) {
+        GTEST_SKIP() << "Not supported -> old API; or not implemented";
+    }
+
     EndpointInfo endpointInfo;
     endpointInfo.id.id = 1;
     endpointInfo.id.hubId = 0xCAFECAFECAFECAFE;
@@ -554,7 +616,7 @@
     endpointInfo.name = String16("Test host endpoint 1");
     endpointInfo.version = 42;
 
-    Status status = contextHub->registerEndpoint(endpointInfo);
+    Status status = mHubInterface->registerEndpoint(endpointInfo);
     if (status.exceptionCode() == Status::EX_UNSUPPORTED_OPERATION ||
         status.transactionError() == android::UNKNOWN_TRANSACTION) {
         GTEST_SKIP() << "Not supported -> old API; or not implemented";
@@ -563,7 +625,11 @@
     }
 }
 
-TEST_P(ContextHubAidl, RegisterEndpointSameNameFailure) {
+TEST_P(ContextHubAidlWithTestMode, RegisterEndpointSameNameFailure) {
+    if (!registerDefaultHub()) {
+        GTEST_SKIP() << "Not supported -> old API; or not implemented";
+    }
+
     EndpointInfo endpointInfo;
     endpointInfo.id.id = 2;
     endpointInfo.id.hubId = 0xCAFECAFECAFECAFE;
@@ -578,7 +644,7 @@
     endpointInfo2.name = String16("Test host endpoint 2");
     endpointInfo2.version = 42;
 
-    Status status = contextHub->registerEndpoint(endpointInfo);
+    Status status = mHubInterface->registerEndpoint(endpointInfo);
     if (status.exceptionCode() == Status::EX_UNSUPPORTED_OPERATION ||
         status.transactionError() == android::UNKNOWN_TRANSACTION) {
         GTEST_SKIP() << "Not supported -> old API; or not implemented";
@@ -586,10 +652,14 @@
         EXPECT_TRUE(status.isOk());
     }
 
-    EXPECT_FALSE(contextHub->registerEndpoint(endpointInfo2).isOk());
+    EXPECT_FALSE(mHubInterface->registerEndpoint(endpointInfo2).isOk());
 }
 
-TEST_P(ContextHubAidl, RegisterEndpointSameIdFailure) {
+TEST_P(ContextHubAidlWithTestMode, RegisterEndpointSameIdFailure) {
+    if (!registerDefaultHub()) {
+        GTEST_SKIP() << "Not supported -> old API; or not implemented";
+    }
+
     EndpointInfo endpointInfo;
     endpointInfo.id.id = 4;
     endpointInfo.id.hubId = 0xCAFECAFECAFECAFE;
@@ -604,7 +674,7 @@
     endpointInfo2.name = String16("Test host endpoint - same ID test");
     endpointInfo2.version = 42;
 
-    Status status = contextHub->registerEndpoint(endpointInfo);
+    Status status = mHubInterface->registerEndpoint(endpointInfo);
     if (status.exceptionCode() == Status::EX_UNSUPPORTED_OPERATION ||
         status.transactionError() == android::UNKNOWN_TRANSACTION) {
         GTEST_SKIP() << "Not supported -> old API; or not implemented";
@@ -612,10 +682,14 @@
         EXPECT_TRUE(status.isOk());
     }
 
-    EXPECT_FALSE(contextHub->registerEndpoint(endpointInfo2).isOk());
+    EXPECT_FALSE(mHubInterface->registerEndpoint(endpointInfo2).isOk());
 }
 
-TEST_P(ContextHubAidl, UnregisterEndpoint) {
+TEST_P(ContextHubAidlWithTestMode, UnregisterEndpoint) {
+    if (!registerDefaultHub()) {
+        GTEST_SKIP() << "Not supported -> old API; or not implemented";
+    }
+
     EndpointInfo endpointInfo;
     endpointInfo.id.id = 6;
     endpointInfo.id.hubId = 0xCAFECAFECAFECAFE;
@@ -623,7 +697,7 @@
     endpointInfo.name = String16("Test host endpoint 6");
     endpointInfo.version = 42;
 
-    Status status = contextHub->registerEndpoint(endpointInfo);
+    Status status = mHubInterface->registerEndpoint(endpointInfo);
     if (status.exceptionCode() == Status::EX_UNSUPPORTED_OPERATION ||
         status.transactionError() == android::UNKNOWN_TRANSACTION) {
         GTEST_SKIP() << "Not supported -> old API; or not implemented";
@@ -631,10 +705,14 @@
         EXPECT_TRUE(status.isOk());
     }
 
-    EXPECT_TRUE(contextHub->unregisterEndpoint(endpointInfo).isOk());
+    EXPECT_TRUE(mHubInterface->unregisterEndpoint(endpointInfo).isOk());
 }
 
-TEST_P(ContextHubAidl, UnregisterEndpointNonexistent) {
+TEST_P(ContextHubAidlWithTestMode, UnregisterEndpointNonexistent) {
+    if (!registerDefaultHub()) {
+        GTEST_SKIP() << "Not supported -> old API; or not implemented";
+    }
+
     EndpointInfo endpointInfo;
     endpointInfo.id.id = 100;
     endpointInfo.id.hubId = 0xCAFECAFECAFECAFE;
@@ -642,7 +720,7 @@
     endpointInfo.name = String16("Test host endpoint 100");
     endpointInfo.version = 42;
 
-    Status status = contextHub->unregisterEndpoint(endpointInfo);
+    Status status = mHubInterface->unregisterEndpoint(endpointInfo);
     if (status.exceptionCode() == Status::EX_UNSUPPORTED_OPERATION ||
         status.transactionError() == android::UNKNOWN_TRANSACTION) {
         GTEST_SKIP() << "Not supported -> old API; or not implemented";
@@ -651,25 +729,9 @@
     }
 }
 
-TEST_P(ContextHubAidl, RegisterCallback) {
-    auto cb = sp<TestEndpointCallback>::make();
-    Status status = contextHub->registerEndpointCallback(cb);
-    if (status.exceptionCode() == Status::EX_UNSUPPORTED_OPERATION ||
-        status.transactionError() == android::UNKNOWN_TRANSACTION) {
+TEST_P(ContextHubAidlWithTestMode, OpenEndpointSessionInvalidRange) {
+    if (!registerDefaultHub()) {
         GTEST_SKIP() << "Not supported -> old API; or not implemented";
-    } else {
-        EXPECT_TRUE(status.isOk());
-    }
-}
-
-TEST_P(ContextHubAidl, OpenEndpointSessionInvalidRange) {
-    auto cb = sp<TestEndpointCallback>::make();
-    Status status = contextHub->registerEndpointCallback(cb);
-    if (status.exceptionCode() == Status::EX_UNSUPPORTED_OPERATION ||
-        status.transactionError() == android::UNKNOWN_TRANSACTION) {
-        GTEST_SKIP() << "Not supported -> old API; or not implemented";
-    } else {
-        EXPECT_TRUE(status.isOk());
     }
 
     // Register the endpoint
@@ -679,11 +741,11 @@
     initiatorEndpoint.type = EndpointInfo::EndpointType::NATIVE;
     initiatorEndpoint.name = String16("Test host endpoint 7");
     initiatorEndpoint.version = 42;
-    EXPECT_TRUE(contextHub->registerEndpoint(initiatorEndpoint).isOk());
+    EXPECT_TRUE(mHubInterface->registerEndpoint(initiatorEndpoint).isOk());
 
     // Find the destination, if it exists
     std::vector<EndpointInfo> endpoints;
-    EXPECT_TRUE(contextHub->getEndpoints(&endpoints).isOk());
+    EXPECT_TRUE(mContextHub->getEndpoints(&endpoints).isOk());
     const EndpointInfo* destinationEndpoint = nullptr;
     for (const EndpointInfo& endpoint : endpoints) {
         for (const Service& service : endpoint.services) {
@@ -700,30 +762,24 @@
     // Request the range
     constexpr int32_t requestedRange = 100;
     std::array<int32_t, 2> range;
-    ASSERT_TRUE(contextHub->requestSessionIdRange(requestedRange, &range).isOk());
+    ASSERT_TRUE(mHubInterface->requestSessionIdRange(requestedRange, &range).isOk());
     EXPECT_EQ(range.size(), 2);
     EXPECT_GE(range[1] - range[0] + 1, requestedRange);
 
     // Open the session
     int32_t sessionId = range[1] + 10;  // invalid
-    EXPECT_FALSE(contextHub
+    EXPECT_FALSE(mHubInterface
                          ->openEndpointSession(sessionId, destinationEndpoint->id,
                                                initiatorEndpoint.id,
                                                /* in_serviceDescriptor= */ kEchoServiceName)
                          .isOk());
 }
 
-TEST_P(ContextHubAidl, OpenEndpointSessionAndSendMessageEchoesBack) {
-    auto cb = sp<TestEndpointCallback>::make();
-    Status status = contextHub->registerEndpointCallback(cb);
-    if (status.exceptionCode() == Status::EX_UNSUPPORTED_OPERATION ||
-        status.transactionError() == android::UNKNOWN_TRANSACTION) {
+TEST_P(ContextHubAidlWithTestMode, OpenEndpointSessionAndSendMessageEchoesBack) {
+    if (!registerDefaultHub()) {
         GTEST_SKIP() << "Not supported -> old API; or not implemented";
-    } else {
-        EXPECT_TRUE(status.isOk());
     }
-
-    std::unique_lock<std::mutex> lock(cb->getMutex());
+    std::unique_lock<std::mutex> lock(mEndpointCb->getMutex());
 
     // Register the endpoint
     EndpointInfo initiatorEndpoint;
@@ -732,11 +788,11 @@
     initiatorEndpoint.type = EndpointInfo::EndpointType::NATIVE;
     initiatorEndpoint.name = String16("Test host endpoint 7");
     initiatorEndpoint.version = 42;
-    EXPECT_TRUE(contextHub->registerEndpoint(initiatorEndpoint).isOk());
+    EXPECT_TRUE(mHubInterface->registerEndpoint(initiatorEndpoint).isOk());
 
     // Find the destination, if it exists
     std::vector<EndpointInfo> endpoints;
-    EXPECT_TRUE(contextHub->getEndpoints(&endpoints).isOk());
+    EXPECT_TRUE(mContextHub->getEndpoints(&endpoints).isOk());
     const EndpointInfo* destinationEndpoint = nullptr;
     for (const EndpointInfo& endpoint : endpoints) {
         for (const Service& service : endpoint.services) {
@@ -753,32 +809,32 @@
     // Request the range
     constexpr int32_t requestedRange = 100;
     std::array<int32_t, 2> range;
-    ASSERT_TRUE(contextHub->requestSessionIdRange(requestedRange, &range).isOk());
+    ASSERT_TRUE(mHubInterface->requestSessionIdRange(requestedRange, &range).isOk());
     EXPECT_EQ(range.size(), 2);
     EXPECT_GE(range[1] - range[0] + 1, requestedRange);
 
     // Open the session
-    cb->resetWasOnEndpointSessionOpenCompleteCalled();
+    mEndpointCb->resetWasOnEndpointSessionOpenCompleteCalled();
     int32_t sessionId = range[0];
-    ASSERT_TRUE(contextHub
+    ASSERT_TRUE(mHubInterface
                         ->openEndpointSession(sessionId, destinationEndpoint->id,
                                               initiatorEndpoint.id,
                                               /* in_serviceDescriptor= */ kEchoServiceName)
                         .isOk());
-    cb->getCondVar().wait(lock);
-    EXPECT_TRUE(cb->wasOnEndpointSessionOpenCompleteCalled());
+    mEndpointCb->getCondVar().wait(lock);
+    EXPECT_TRUE(mEndpointCb->wasOnEndpointSessionOpenCompleteCalled());
 
     // Send the message
     Message message;
     message.flags = 0;
     message.sequenceNumber = 0;
     message.content.push_back(42);
-    ASSERT_TRUE(contextHub->sendMessageToEndpoint(sessionId, message).isOk());
+    ASSERT_TRUE(mHubInterface->sendMessageToEndpoint(sessionId, message).isOk());
 
     // Check for echo
-    cb->getCondVar().wait(lock);
-    EXPECT_FALSE(cb->getMessages().empty());
-    EXPECT_EQ(cb->getMessages().back().content.back(), 42);
+    mEndpointCb->getCondVar().wait(lock);
+    EXPECT_FALSE(mEndpointCb->getMessages().empty());
+    EXPECT_EQ(mEndpointCb->getMessages().back().content.back(), 42);
 }
 
 std::string PrintGeneratedTest(const testing::TestParamInfo<ContextHubAidl::ParamType>& info) {
@@ -789,13 +845,17 @@
 INSTANTIATE_TEST_SUITE_P(ContextHub, ContextHubAidl, testing::ValuesIn(generateContextHubMapping()),
                          PrintGeneratedTest);
 
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ContextHubAidlWithTestMode);
+INSTANTIATE_TEST_SUITE_P(ContextHub, ContextHubAidlWithTestMode,
+                         testing::ValuesIn(generateContextHubMapping()), PrintGeneratedTest);
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ContextHubTransactionTest);
 INSTANTIATE_TEST_SUITE_P(ContextHub, ContextHubTransactionTest,
                          testing::ValuesIn(generateContextHubMapping()), PrintGeneratedTest);
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
-    ProcessState::self()->setThreadPoolMaxThreadCount(1);
+    ProcessState::self()->setThreadPoolMaxThreadCount(2);
     ProcessState::self()->startThreadPool();
     return RUN_ALL_TESTS();
 }
diff --git a/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/SupportInfo.aidl b/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/SupportInfo.aidl
index c90125c..d2fcae2 100644
--- a/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/SupportInfo.aidl
+++ b/power/aidl/aidl_api/android.hardware.power/current/android/hardware/power/SupportInfo.aidl
@@ -55,5 +55,10 @@
     boolean isGpuSupported;
     int cpuMinIntervalMillis;
     int gpuMinIntervalMillis;
+    int cpuMinCalculationWindowMillis = 50;
+    int cpuMaxCalculationWindowMillis = 10000;
+    int gpuMinCalculationWindowMillis = 50;
+    int gpuMaxCalculationWindowMillis = 10000;
+    int cpuMaxTidCount = 5;
   }
 }
diff --git a/power/aidl/android/hardware/power/CpuHeadroomParams.aidl b/power/aidl/android/hardware/power/CpuHeadroomParams.aidl
index fab8f43..83a46ae 100644
--- a/power/aidl/android/hardware/power/CpuHeadroomParams.aidl
+++ b/power/aidl/android/hardware/power/CpuHeadroomParams.aidl
@@ -35,9 +35,8 @@
     CalculationType calculationType = CalculationType.MIN;
 
     /**
-     * The calculation rolling window size in milliseconds.
-     * The device should support a superset of [50, 10000] and try to use the closest feasible
-     * window size to the provided value param.
+     * The device should support the range specified in SupportInfo#HeadroomSupportInfo and try to
+     * use the closest feasible window size to the provided value param.
      */
     int calculationWindowMillis = 1000;
 
@@ -50,6 +49,8 @@
      * This should handle all the cases including but not limited to core affinity and app cpuset
      * that change the available CPU cores for the caller. And the HAL should check that the TIDs
      * have the same core affinity.
+     *
+     * The device should support the maximum TID count specified SupportInfo#HeadroomSupportInfo.
      */
     int[] tids;
 }
diff --git a/power/aidl/android/hardware/power/GpuHeadroomParams.aidl b/power/aidl/android/hardware/power/GpuHeadroomParams.aidl
index 68848d8..2d64bd3 100644
--- a/power/aidl/android/hardware/power/GpuHeadroomParams.aidl
+++ b/power/aidl/android/hardware/power/GpuHeadroomParams.aidl
@@ -35,8 +35,8 @@
     CalculationType calculationType = CalculationType.MIN;
 
     /**
-     * The device should support a superset of [50, 10000] and try to use the closest feasible
-     * window size to the provided value param.
+     * The device should support the range specified in SupportInfo#HeadroomSupportInfo and try to
+     * use the closest feasible window size to the provided value param.
      */
     int calculationWindowMillis = 1000;
 }
diff --git a/power/aidl/android/hardware/power/SupportInfo.aidl b/power/aidl/android/hardware/power/SupportInfo.aidl
index 55287cb..011dc18 100644
--- a/power/aidl/android/hardware/power/SupportInfo.aidl
+++ b/power/aidl/android/hardware/power/SupportInfo.aidl
@@ -139,5 +139,39 @@
          * than the interval.
          */
         int gpuMinIntervalMillis;
+
+        /**
+         * Minimum calculation window size for getCpuHeadroom in milliseconds.
+         *
+         * This should be no larger than 50ms.
+         */
+        int cpuMinCalculationWindowMillis = 50;
+
+        /**
+         * Maximum calculation window size for getCpuHeadroom in milliseconds.
+         *
+         * This should be no smaller than 10000ms.
+         */
+        int cpuMaxCalculationWindowMillis = 10000;
+
+        /**
+         * Minimum calculation window size for getGpuHeadroom in milliseconds.
+         *
+         * This should be no larger than 50ms.
+         */
+        int gpuMinCalculationWindowMillis = 50;
+
+        /**
+         * Maximum calculation window size for getGpuHeadroom in milliseconds.
+         *
+         * This should be no smaller than 10000ms.
+         */
+        int gpuMaxCalculationWindowMillis = 10000;
+
+        /**
+         * Maximum number of TIDs this device can support.
+         * This should be no smaller than 5.
+         */
+        int cpuMaxTidCount = 5;
     }
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio/current/android/hardware/radio/RadioAccessFamily.aidl b/radio/aidl/aidl_api/android.hardware.radio/current/android/hardware/radio/RadioAccessFamily.aidl
index 471916f..f44385a 100644
--- a/radio/aidl/aidl_api/android.hardware.radio/current/android/hardware/radio/RadioAccessFamily.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio/current/android/hardware/radio/RadioAccessFamily.aidl
@@ -80,5 +80,4 @@
    */
   LTE_CA = (1 << android.hardware.radio.RadioTechnology.LTE_CA) /* 524288 */,
   NR = (1 << android.hardware.radio.RadioTechnology.NR) /* 1048576 */,
-  NB_IOT_NTN = (1 << android.hardware.radio.RadioTechnology.NB_IOT_NTN) /* 2097152 */,
 }
diff --git a/radio/aidl/aidl_api/android.hardware.radio/current/android/hardware/radio/RadioTechnology.aidl b/radio/aidl/aidl_api/android.hardware.radio/current/android/hardware/radio/RadioTechnology.aidl
index 201e694..7aae601 100644
--- a/radio/aidl/aidl_api/android.hardware.radio/current/android/hardware/radio/RadioTechnology.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio/current/android/hardware/radio/RadioTechnology.aidl
@@ -80,5 +80,4 @@
    */
   LTE_CA,
   NR,
-  NB_IOT_NTN,
 }
diff --git a/radio/aidl/android/hardware/radio/RadioAccessFamily.aidl b/radio/aidl/android/hardware/radio/RadioAccessFamily.aidl
index 8c10bb9..9588ed9 100644
--- a/radio/aidl/android/hardware/radio/RadioAccessFamily.aidl
+++ b/radio/aidl/android/hardware/radio/RadioAccessFamily.aidl
@@ -55,8 +55,4 @@
      * 5G NR. This is only use in 5G Standalone mode.
      */
     NR = 1 << RadioTechnology.NR,
-    /**
-     * 3GPP NB-IOT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology.
-     */
-    NB_IOT_NTN = 1 << RadioTechnology.NB_IOT_NTN,
 }
diff --git a/radio/aidl/android/hardware/radio/RadioTechnology.aidl b/radio/aidl/android/hardware/radio/RadioTechnology.aidl
index 843bfd0..de93a2b 100644
--- a/radio/aidl/android/hardware/radio/RadioTechnology.aidl
+++ b/radio/aidl/android/hardware/radio/RadioTechnology.aidl
@@ -62,8 +62,4 @@
      * 5G NR. This is only used in 5G Standalone mode.
      */
     NR,
-    /**
-     * 3GPP NB-IOT (Narrowband Internet of Things) over Non-Terrestrial-Networks technology.
-     */
-    NB_IOT_NTN,
 }
diff --git a/security/keymint/support/remote_prov_utils.cpp b/security/keymint/support/remote_prov_utils.cpp
index eaeec32..5afaf31 100644
--- a/security/keymint/support/remote_prov_utils.cpp
+++ b/security/keymint/support/remote_prov_utils.cpp
@@ -870,7 +870,7 @@
     }
 
     auto csr = hwtrust::Csr::validate(encodedCsr, *diceChainKind, false /*isFactory*/,
-                                      false /*allowAnyMode*/, deviceSuffix(instanceName));
+                                      true /*allowAnyMode*/, deviceSuffix(instanceName));
     if (!csr.ok()) {
         return csr.error().message();
     }
@@ -904,7 +904,7 @@
     }
 
     auto csr1 = hwtrust::Csr::validate(encodedCsr1, *diceChainKind, false /*isFactory*/,
-                                       false /*allowAnyMode*/, deviceSuffix(instanceName1));
+                                       true /*allowAnyMode*/, deviceSuffix(instanceName1));
     if (!csr1.ok()) {
         return csr1.error().message();
     }
@@ -921,7 +921,7 @@
     }
 
     auto csr2 = hwtrust::Csr::validate(encodedCsr2, *diceChainKind, false /*isFactory*/,
-                                       false /*allowAnyMode*/, deviceSuffix(instanceName2));
+                                       true /*allowAnyMode*/, deviceSuffix(instanceName2));
     if (!csr2.ok()) {
         return csr2.error().message();
     }
@@ -952,7 +952,7 @@
     }
 
     auto csr = hwtrust::Csr::validate(encodedCsr, *diceChainKind, false /*isFactory*/,
-                                      false /*allowAnyMode*/, deviceSuffix(DEFAULT_INSTANCE_NAME));
+                                      true /*allowAnyMode*/, deviceSuffix(DEFAULT_INSTANCE_NAME));
     if (!csr.ok()) {
         return csr.error().message();
     }
diff --git a/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see.hwcrypto/current/android/hardware/security/see/hwcrypto/IHwCryptoKey.aidl b/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see.hwcrypto/current/android/hardware/security/see/hwcrypto/IHwCryptoKey.aidl
index b31a06c..99eb761 100644
--- a/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see.hwcrypto/current/android/hardware/security/see/hwcrypto/IHwCryptoKey.aidl
+++ b/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see.hwcrypto/current/android/hardware/security/see/hwcrypto/IHwCryptoKey.aidl
@@ -64,7 +64,7 @@
     int keySizeBytes;
   }
   union DerivedKeyPolicy {
-    android.hardware.security.see.hwcrypto.IHwCryptoKey.ClearKeyPolicy clearKey;
+    android.hardware.security.see.hwcrypto.IHwCryptoKey.ClearKeyPolicy clearKeyPolicy;
     byte[] opaqueKey;
   }
   parcelable DerivedKeyParameters {
diff --git a/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see.hwcrypto/current/android/hardware/security/see/hwcrypto/IHwCryptoOperations.aidl b/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see.hwcrypto/current/android/hardware/security/see/hwcrypto/IHwCryptoOperations.aidl
index 7c87dd3..3adb2f9 100644
--- a/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see.hwcrypto/current/android/hardware/security/see/hwcrypto/IHwCryptoOperations.aidl
+++ b/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see.hwcrypto/current/android/hardware/security/see/hwcrypto/IHwCryptoOperations.aidl
@@ -34,5 +34,5 @@
 package android.hardware.security.see.hwcrypto;
 @VintfStability
 interface IHwCryptoOperations {
-  android.hardware.security.see.hwcrypto.CryptoOperationResult[] processCommandList(inout android.hardware.security.see.hwcrypto.CryptoOperationSet[] operations, out android.hardware.security.see.hwcrypto.CryptoOperationErrorAdditionalInfo additionalErrorInfo);
+  android.hardware.security.see.hwcrypto.CryptoOperationResult[] processCommandList(inout android.hardware.security.see.hwcrypto.CryptoOperationSet[] operations);
 }
diff --git a/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/IHwCryptoKey.aidl b/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/IHwCryptoKey.aidl
index 97a4c37..93d6cbc 100644
--- a/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/IHwCryptoKey.aidl
+++ b/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/IHwCryptoKey.aidl
@@ -107,10 +107,12 @@
          * If used we will derive a clear key and pass it back as an array of bytes on
          * <code>HwCryptoKeyMaterial::explicitKey</code>.
          */
-        ClearKeyPolicy clearKey;
+        ClearKeyPolicy clearKeyPolicy;
 
         /*
          * Policy for the newly derived opaque key. Defines how the key can be used and its type.
+         * Its definition can be found in <code>KeyPolicy.cddl</code>, which is basically a CBOR
+         * serialization of the file <code>KeyPolicy.aidl</code>.
          */
         byte[] opaqueKey;
     }
@@ -154,11 +156,14 @@
      *     Key to be used to derive the new key using HKDF.
      *
      * @return:
-     *     A DiceCurrentBoundKeyResult containint the versioned key tied the current client version
+     *     A DiceCurrentBoundKeyResult containing the versioned key tied the current client version
      *     on success.
      *
      * @throws:
-     *      ServiceSpecificException based on <code>HalErrorCode</code> if any error occurs.
+     *      ServiceSpecificException based on <code>HalErrorCode</code> if any error occurs,
+     *      in particular:
+     *          - BAD_PARAMETER if an invalid DeviceKeyId is requested.
+     *          - INVALID_KEY if an opaque key is provided that is not suitable for key derivation.
      */
     DiceCurrentBoundKeyResult deriveCurrentDicePolicyBoundKey(
             in DiceBoundDerivationKey derivationKey);
@@ -184,7 +189,11 @@
      *      success.
      *
      * @throws:
-     *      ServiceSpecificException based on <code>HalErrorCode</code> if any error occurs.
+     *      ServiceSpecificException based on <code>HalErrorCode</code> if any error occurs,
+     *      in particular:
+     *          - BAD_PARAMETER if an invalid DeviceKeyId is requested or if dicePolicyForKeyVersion
+     *            is not a valid encrypted DICE policy.
+     *          - INVALID_KEY if an opaque key is provided that is not suitable for key derivation.
      */
     DiceBoundKeyResult deriveDicePolicyBoundKey(
             in DiceBoundDerivationKey derivationKey, in byte[] dicePolicyForKeyVersion);
@@ -197,10 +206,15 @@
      *      file for more information.
      *
      * @return:
-     *      A HwCryptoKeyMaterial containing the derived key on success.
+     *      A <code>DerivedKey</code> containing the derived key on success.
      *
      * @throws:
-     *      ServiceSpecificException based on <code>HalErrorCode</code> if any error occurs.
+     *      ServiceSpecificException based on <code>HalErrorCode</code> if any error occurs,
+     *      in particular:
+     *          - BAD_PARAMETER if an invalid key policy is provided or if the key policy conflicts
+     *            with the requested key.
+     *          - SERIALIZATION_ERROR if the provided key policy is not a valid CBOR key policy.
+     *          - INVALID_KEY if an opaque key is provided that is not suitable for key derivation.
      */
     DerivedKey deriveKey(in DerivedKeyParameters parameters);
 
@@ -233,7 +247,11 @@
      *      IOpaqueKey on success.
      *
      * @throws:
-     *      ServiceSpecificException based on <code>HalErrorCode</code> if any error occurs.
+     *      ServiceSpecificException based on <code>HalErrorCode</code> if any error occurs,
+     *      in particular:
+     *          - BAD_PARAMETER if an invalid Key policy is provided or if the key policy conflicts
+     *            with provided key material.
+     *          - ALLOCATION_ERROR if the system runs out of memory while carring out the operation.
      */
     IOpaqueKey importClearKey(in ExplicitKeyMaterial keyMaterial, in KeyPolicy newKeyPolicy);
 
@@ -248,7 +266,9 @@
      * passing the receiver DICE policy to insure that only that receiver can import the key.
      *
      * @return:
-     *      byte[] on success, which is the caller encrypted DICE policy.
+     *      byte[] on success, which is the caller encrypted DICE policy. The DICE policy follows
+     *      the structure defined on DicePolicy.cddl, located under
+     *      hardware/interfaces/security/authgraph/aidl/android/hardware/security/authgraph/
      */
     byte[] getCurrentDicePolicy();
 
@@ -266,10 +286,14 @@
      *      DICE policy used to seal the exported key.
      *
      * @return:
-     *      An IOpaqueKey that can be directly be used on the local HWCrypto service on success.
+     *      An IOpaqueKey that can be directly used on the local HWCrypto service on success.
      *
      * @throws:
-     *      ServiceSpecificException based on <code>HalErrorCode</code> if any error occurs.
+     *      ServiceSpecificException based on <code>HalErrorCode</code> if any error occurs,
+     *      in particular:
+     *          - BAD_PARAMETER if an invalid encrypted sealing DICE policy is provided.
+     *          - ALLOCATION_ERROR if the system runs out of memory while carring out the operation.
+     *          - UNAUTHORIZED if the sealingDicePolicy do not match the caller.
      */
     IOpaqueKey keyTokenImport(in OpaqueKeyToken requestedKey, in byte[] sealingDicePolicy);
 
@@ -287,8 +311,9 @@
      *      An IOpaqueKey corresponding to the requested key slot on success.
      *
      * @throws:
-     *      ServiceSpecificException <code>UNAUTHORIZED</code> if the caller cannot access the
-     *      requested key, another specific error based on <code>HalErrorCode</code> otherwise.
+     *      ServiceSpecificException based on <code>HalErrorCode</code> if any error occurs,
+     *      in particular:
+     *          - UNAUTHORIZED if the caller cannot access the requested key.
      */
     IOpaqueKey getKeyslotData(KeySlot slotId);
 }
diff --git a/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/IHwCryptoOperations.aidl b/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/IHwCryptoOperations.aidl
index 9df6d67..dbe4d80 100644
--- a/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/IHwCryptoOperations.aidl
+++ b/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/IHwCryptoOperations.aidl
@@ -31,19 +31,21 @@
      *
      * @param operations:
      *      Parameter containing 1 or more set of commands to execute. Additionally, each set can
-     *      also contain a context on which the commands will be executed.
-     *
-     * @param additionalErrorInfo:
-     *      Structure containing additional info when errors are encountered. Only valid if the
-     *      function failed its execution.
+     *      also contain a context on which the commands will be executed. The parameter has type
+     *      inout because it can contain buffers used to write the output of the operation.
      *
      * @return:
      *      CryptoOperationResult[] on success, which can contain a context to continue executing
      *      each of the provided operations sets.
      *
      * @throws:
-     *      ServiceSpecificException based on <code>HalErrorCode</code> if any error occurs.
+     *      ServiceSpecificException based on <code>HalErrorCode</code> if any error occurs,
+     *      in particular:
+     *          - INVALID_KEY if the provided key is not compatible with the operation requested.
+     *          - BAD_STATE if the provided <code>CryptoOperationSet</code> contains operations that
+     *            cannot be carried out in the current server state.
+     *          - UNSUPPORTED if the requested operation is not supported by the server.
+     *          - ALLOCATION_ERROR if the system runs out of memory while carring out the operation.
      */
-    CryptoOperationResult[] processCommandList(inout CryptoOperationSet[] operations,
-            out CryptoOperationErrorAdditionalInfo additionalErrorInfo);
+    CryptoOperationResult[] processCommandList(inout CryptoOperationSet[] operations);
 }
diff --git a/tv/mediaquality/aidl/vts/functional/VtsHalMediaQualityTest.cpp b/tv/mediaquality/aidl/vts/functional/VtsHalMediaQualityTest.cpp
index f785cad..a01d4b0 100644
--- a/tv/mediaquality/aidl/vts/functional/VtsHalMediaQualityTest.cpp
+++ b/tv/mediaquality/aidl/vts/functional/VtsHalMediaQualityTest.cpp
@@ -35,6 +35,7 @@
 #include <android/binder_manager.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
+#include <log/log.h>
 #include <future>
 
 using aidl::android::hardware::graphics::common::PixelFormat;
@@ -63,6 +64,407 @@
 #define ASSERT_OK(ret) ASSERT_TRUE(ret.isOk())
 #define EXPECT_OK(ret) EXPECT_TRUE(ret.isOk())
 
+void validateParameterRange0To100(int value) {
+    EXPECT_GE(value, 0);
+    EXPECT_LE(value, 100);
+}
+
+void validateParameterRange0To2047(int value) {
+    EXPECT_GE(value, 0);
+    EXPECT_LE(value, 2047);
+}
+
+void validateColorTemperature(int value) {
+    EXPECT_GE(value, -100);
+    EXPECT_LE(value, 100);
+}
+
+void validatePictureParameter(const PictureParameter& param) {
+    switch (param.getTag()) {
+        case PictureParameter::Tag::brightness: {
+            ALOGD("[validatePictureParameter] validate brightness value");
+            float value = param.get<PictureParameter::Tag::brightness>();
+            EXPECT_TRUE(value >= 0.0f && value <= 1.0f);
+            break;
+        }
+        case PictureParameter::Tag::contrast: {
+            ALOGD("[validatePictureParameter] validate contrast value");
+            int value = param.get<PictureParameter::Tag::contrast>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::sharpness: {
+            ALOGD("[validatePictureParameter] validate sharpness value");
+            int value = param.get<PictureParameter::Tag::sharpness>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::saturation: {
+            ALOGD("[validatePictureParameter] validate saturation value");
+            int value = param.get<PictureParameter::Tag::saturation>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::hue: {
+            ALOGD("[validatePictureParameter] validate hue value");
+            int value = param.get<PictureParameter::Tag::hue>();
+            EXPECT_GE(value, -50);
+            EXPECT_LE(value, 50);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerBrightness: {
+            ALOGD("[validatePictureParameter] validate colorTunerBrightness value");
+            int value = param.get<PictureParameter::Tag::colorTunerBrightness>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerSaturation: {
+            ALOGD("[validatePictureParameter] validate colorTunerSaturation value");
+            int value = param.get<PictureParameter::Tag::colorTunerSaturation>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerHue: {
+            ALOGD("[validatePictureParameter] validate colorTunerHue value");
+            int value = param.get<PictureParameter::Tag::colorTunerHue>();
+            EXPECT_GE(value, -50);
+            EXPECT_LE(value, 50);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerRedOffset: {
+            ALOGD("[validatePictureParameter] validate colorTunerRedOffset value");
+            int value = param.get<PictureParameter::Tag::colorTunerRedOffset>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerGreenOffset: {
+            ALOGD("[validatePictureParameter] validate colorTunerGreenOffset value");
+            int value = param.get<PictureParameter::Tag::colorTunerGreenOffset>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerBlueOffset: {
+            ALOGD("[validatePictureParameter] validate colorTunerBlueOffset value");
+            int value = param.get<PictureParameter::Tag::colorTunerBlueOffset>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerRedGain: {
+            ALOGD("[validatePictureParameter] validate colorTunerRedGain value");
+            int value = param.get<PictureParameter::Tag::colorTunerRedGain>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerGreenGain: {
+            ALOGD("[validatePictureParameter] validate colorTunerGreenGain value");
+            int value = param.get<PictureParameter::Tag::colorTunerGreenGain>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerBlueGain: {
+            ALOGD("[validatePictureParameter] validate colorTunerBlueGain value");
+            int value = param.get<PictureParameter::Tag::colorTunerBlueGain>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::panelInitMaxLuminceNits: {
+            ALOGD("[validatePictureParameter] validate panelInitMaxLuminceNits value");
+            int value = param.get<PictureParameter::Tag::panelInitMaxLuminceNits>();
+            EXPECT_GE(value, 0);
+            EXPECT_LE(value, 10000);
+            break;
+        }
+        case PictureParameter::Tag::colorTemperatureRedGain: {
+            ALOGD("[validatePictureParameter] validate colorTemperatureRedGain value");
+            int value = param.get<PictureParameter::Tag::colorTemperatureRedGain>();
+            validateColorTemperature(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTemperatureGreenGain: {
+            ALOGD("[validatePictureParameter] validate colorTemperatureGreenGain value");
+            int value = param.get<PictureParameter::Tag::colorTemperatureGreenGain>();
+            validateColorTemperature(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTemperatureBlueGain: {
+            ALOGD("[validatePictureParameter] validate colorTemperatureBlueGain value");
+            int value = param.get<PictureParameter::Tag::colorTemperatureBlueGain>();
+            validateColorTemperature(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTemperatureRedOffset: {
+            ALOGD("[validatePictureParameter] validate ccolorTemperatureRedOffset value");
+            int value = param.get<PictureParameter::Tag::colorTemperatureRedOffset>();
+            validateColorTemperature(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTemperatureGreenOffset: {
+            ALOGD("[validatePictureParameter] validate colorTemperatureGreenOffset value");
+            int value = param.get<PictureParameter::Tag::colorTemperatureGreenOffset>();
+            validateColorTemperature(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTemperatureBlueOffset: {
+            ALOGD("[validatePictureParameter] validate colorTemperatureBlueOffset value");
+            int value = param.get<PictureParameter::Tag::colorTemperatureBlueOffset>();
+            validateColorTemperature(value);
+            break;
+        }
+        case PictureParameter::Tag::elevenPointRed: {
+            ALOGD("[validatePictureParameter] validate elevenPointRed value");
+            std::array<int, 11> elevenPointValues =
+                    param.get<PictureParameter::Tag::elevenPointRed>();
+            for (int value : elevenPointValues) {
+                validateParameterRange0To100(value);
+            }
+            break;
+        }
+        case PictureParameter::Tag::elevenPointGreen: {
+            ALOGD("[validatePictureParameter] validate elevenPointGreen value");
+            std::array<int, 11> elevenPointValues =
+                    param.get<PictureParameter::Tag::elevenPointGreen>();
+            for (int value : elevenPointValues) {
+                validateParameterRange0To100(value);
+            }
+            break;
+        }
+        case PictureParameter::Tag::elevenPointBlue: {
+            ALOGD("[validatePictureParameter] validate elevenPointBlue value");
+            std::array<int, 11> elevenPointValues =
+                    param.get<PictureParameter::Tag::elevenPointBlue>();
+            for (int value : elevenPointValues) {
+                validateParameterRange0To100(value);
+            }
+            break;
+        }
+        case PictureParameter::Tag::osdRedGain: {
+            ALOGD("[validatePictureParameter] validate osdRedGain value");
+            int value = param.get<PictureParameter::Tag::osdRedGain>();
+            validateParameterRange0To2047(value);
+            break;
+        }
+        case PictureParameter::Tag::osdGreenGain: {
+            ALOGD("[validatePictureParameter] validate osdGreenGain value");
+            int value = param.get<PictureParameter::Tag::osdGreenGain>();
+            validateParameterRange0To2047(value);
+            break;
+        }
+        case PictureParameter::Tag::osdBlueGain: {
+            ALOGD("[validatePictureParameter] validate osdBlueGain value");
+            int value = param.get<PictureParameter::Tag::osdBlueGain>();
+            validateParameterRange0To2047(value);
+            break;
+        }
+        case PictureParameter::Tag::osdRedOffset: {
+            ALOGD("[validatePictureParameter] validate osdRedOffset value");
+            int value = param.get<PictureParameter::Tag::osdRedOffset>();
+            validateParameterRange0To2047(value);
+            break;
+        }
+        case PictureParameter::Tag::osdGreenOffset: {
+            ALOGD("[validatePictureParameter] validate osdGreenOffset value");
+            int value = param.get<PictureParameter::Tag::osdGreenOffset>();
+            validateParameterRange0To2047(value);
+            break;
+        }
+        case PictureParameter::Tag::osdBlueOffset: {
+            ALOGD("[validatePictureParameter] validate osdBlueOffset value");
+            int value = param.get<PictureParameter::Tag::osdBlueOffset>();
+            validateParameterRange0To2047(value);
+            break;
+        }
+        case PictureParameter::Tag::osdHue: {
+            ALOGD("[validatePictureParameter] validate osdHue value");
+            int value = param.get<PictureParameter::Tag::osdHue>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::osdSaturation: {
+            ALOGD("[validatePictureParameter] validate osdSaturation value");
+            int value = param.get<PictureParameter::Tag::osdSaturation>();
+            EXPECT_GE(value, 0);
+            EXPECT_LE(value, 255);
+            break;
+        }
+        case PictureParameter::Tag::osdContrast: {
+            ALOGD("[validatePictureParameter] validate osdContrast value");
+            int value = param.get<PictureParameter::Tag::osdContrast>();
+            validateParameterRange0To2047(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerHueRed: {
+            ALOGD("[validatePictureParameter] validate colorTunerHueRed value");
+            int value = param.get<PictureParameter::Tag::colorTunerHueRed>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerHueGreen: {
+            ALOGD("[validatePictureParameter] validate colorTunerHueGreen value");
+            int value = param.get<PictureParameter::Tag::colorTunerHueGreen>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerHueBlue: {
+            ALOGD("[validatePictureParameter] validate colorTunerHueBlue value");
+            int value = param.get<PictureParameter::Tag::colorTunerHueBlue>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerHueCyan: {
+            ALOGD("[validatePictureParameter] validate colorTunerHueCyan value");
+            int value = param.get<PictureParameter::Tag::colorTunerHueCyan>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerHueMagenta: {
+            ALOGD("[validatePictureParameter] validate colorTunerHueMagenta value");
+            int value = param.get<PictureParameter::Tag::colorTunerHueMagenta>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerHueYellow: {
+            ALOGD("[validatePictureParameter] validate colorTunerHueYellow value");
+            int value = param.get<PictureParameter::Tag::colorTunerHueYellow>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerHueFlesh: {
+            ALOGD("[validatePictureParameter] validate colorTunerHueFlesh value");
+            int value = param.get<PictureParameter::Tag::colorTunerHueFlesh>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerSaturationRed: {
+            ALOGD("[validatePictureParameter] validate colorTunerSaturationRed value");
+            int value = param.get<PictureParameter::Tag::colorTunerSaturationRed>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerSaturationGreen: {
+            ALOGD("[validatePictureParameter] validate colorTunerSaturationGreen value");
+            int value = param.get<PictureParameter::Tag::colorTunerSaturationGreen>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerSaturationBlue: {
+            ALOGD("[validatePictureParameter] validate colorTunerSaturationBlue value");
+            int value = param.get<PictureParameter::Tag::colorTunerSaturationBlue>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerSaturationCyan: {
+            ALOGD("[validatePictureParameter] validate colorTunerSaturationCyan value");
+            int value = param.get<PictureParameter::Tag::colorTunerSaturationCyan>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerSaturationMagenta: {
+            ALOGD("[validatePictureParameter] validate colorTunerSaturationMagenta value");
+            int value = param.get<PictureParameter::Tag::colorTunerSaturationMagenta>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerSaturationYellow: {
+            ALOGD("[validatePictureParameter] validate colorTunerSaturationYellow value");
+            int value = param.get<PictureParameter::Tag::colorTunerSaturationYellow>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerSaturationFlesh: {
+            ALOGD("[validatePictureParameter] validate colorTunerSaturationFlesh value");
+            int value = param.get<PictureParameter::Tag::colorTunerSaturationFlesh>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerLuminanceRed: {
+            ALOGD("[validatePictureParameter] validate colorTunerLuminanceRed value");
+            int value = param.get<PictureParameter::Tag::colorTunerLuminanceRed>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerLuminanceGreen: {
+            ALOGD("[validatePictureParameter] validate colorTunerLuminanceGreen value");
+            int value = param.get<PictureParameter::Tag::colorTunerLuminanceGreen>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerLuminanceBlue: {
+            ALOGD("[validatePictureParameter] validate colorTunerLuminanceBlue value");
+            int value = param.get<PictureParameter::Tag::colorTunerLuminanceBlue>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerLuminanceCyan: {
+            ALOGD("[validatePictureParameter] validate colorTunerLuminanceCyan value");
+            int value = param.get<PictureParameter::Tag::colorTunerLuminanceCyan>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerLuminanceMagenta: {
+            ALOGD("[validatePictureParameter] validate colorTunerLuminanceMagenta value");
+            int value = param.get<PictureParameter::Tag::colorTunerLuminanceMagenta>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerLuminanceYellow: {
+            ALOGD("[validatePictureParameter] validate colorTunerLuminanceYellow value");
+            int value = param.get<PictureParameter::Tag::colorTunerLuminanceYellow>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case PictureParameter::Tag::colorTunerLuminanceFlesh: {
+            ALOGD("[validatePictureParameter] validate colorTunerLuminanceFlesh value");
+            int value = param.get<PictureParameter::Tag::colorTunerLuminanceFlesh>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        default:
+            ALOGD("Those parameters don't need to check.");
+            break;
+    }
+}
+
+void validateSoundParameter(const SoundParameter& param) {
+    switch (param.getTag()) {
+        case SoundParameter::Tag::balance: {
+            ALOGD("[validateSoundParameter] validate balance value");
+            int value = param.get<SoundParameter::Tag::balance>();
+            EXPECT_GE(value, -50);
+            EXPECT_LE(value, 50);
+            break;
+        }
+        case SoundParameter::Tag::bass: {
+            ALOGD("[validateSoundParameter] validate bass value");
+            int value = param.get<SoundParameter::Tag::bass>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case SoundParameter::Tag::treble: {
+            ALOGD("[validateSoundParameter] validate treble value");
+            int value = param.get<SoundParameter::Tag::treble>();
+            validateParameterRange0To100(value);
+            break;
+        }
+        case SoundParameter::Tag::speakersDelayMs: {
+            ALOGD("[validateSoundParameter] validate speakersDelayMs value");
+            int value = param.get<SoundParameter::Tag::speakersDelayMs>();
+            EXPECT_GE(value, 0);
+            EXPECT_LE(value, 250);
+            break;
+        }
+        case SoundParameter::Tag::digitalOutputDelayMs: {
+            ALOGD("[validateSoundParameter] validate digitalOutputDelayMs value");
+            int value = param.get<SoundParameter::Tag::digitalOutputDelayMs>();
+            EXPECT_GE(value, 0);
+            EXPECT_LE(value, 250);
+            break;
+        }
+        default:
+            ALOGD("Those parameters don't need to check.");
+            break;
+    }
+}
+
 class MediaQualityCallback : public BnMediaQualityCallback {
   public:
     explicit MediaQualityCallback(
@@ -83,7 +485,11 @@
             const std::function<void(const PictureProfile& pictureProfile)>&
                     on_hal_picture_profile_adjust)
         : on_hal_picture_profile_adjust_(on_hal_picture_profile_adjust) {}
+
     ScopedAStatus onPictureProfileAdjusted(const PictureProfile& pictureProfile) override {
+        for (const auto& param : pictureProfile.parameters.pictureParameters) {
+            validatePictureParameter(param);
+        }
         on_hal_picture_profile_adjust_(pictureProfile);
         return ScopedAStatus::ok();
     }
@@ -111,7 +517,11 @@
             const std::function<void(const SoundProfile& soundProfile)>&
                     on_hal_sound_profile_adjust)
         : on_hal_sound_profile_adjust_(on_hal_sound_profile_adjust) {}
+
     ScopedAStatus onSoundProfileAdjusted(const SoundProfile& soundProfile) override {
+        for (const auto& param : soundProfile.parameters.soundParameters) {
+            validateSoundParameter(param);
+        }
         on_hal_sound_profile_adjust_(soundProfile);
         return ScopedAStatus::ok();
     }
diff --git a/wifi/aidl/default/aidl_struct_util.cpp b/wifi/aidl/default/aidl_struct_util.cpp
index 6bd5a7f..6de150e 100644
--- a/wifi/aidl/default/aidl_struct_util.cpp
+++ b/wifi/aidl/default/aidl_struct_util.cpp
@@ -3883,7 +3883,8 @@
         return false;
     }
     *aidl_scan_result = {};
-    aidl_scan_result->timeStampInUs = ts_us - legacy_scan_result.age_ms * 1000;
+    aidl_scan_result->timeStampInUs =
+            ts_us - (static_cast<uint64_t>(legacy_scan_result.age_ms) * 1000);
     if (aidl_scan_result->timeStampInUs < 0) {
         aidl_scan_result->timeStampInUs = 0;
         return false;
diff --git a/wifi/aidl/vts/functional/wifi_chip_aidl_test.cpp b/wifi/aidl/vts/functional/wifi_chip_aidl_test.cpp
index b4cb030..4677952 100644
--- a/wifi/aidl/vts/functional/wifi_chip_aidl_test.cpp
+++ b/wifi/aidl/vts/functional/wifi_chip_aidl_test.cpp
@@ -38,6 +38,7 @@
 using aidl::android::hardware::wifi::IWifiP2pIface;
 using aidl::android::hardware::wifi::IWifiRttController;
 using aidl::android::hardware::wifi::WifiBand;
+using aidl::android::hardware::wifi::WifiChipCapabilities;
 using aidl::android::hardware::wifi::WifiDebugHostWakeReasonStats;
 using aidl::android::hardware::wifi::WifiDebugRingBufferStatus;
 using aidl::android::hardware::wifi::WifiDebugRingBufferVerboseLevel;
@@ -987,6 +988,29 @@
     EXPECT_EQ(instances.size(), 2);
 }
 
+/*
+ * GetWifiChipCapabilities
+ */
+TEST_P(WifiChipAidlTest, GetWifiChipCapabilities) {
+    WifiChipCapabilities chipCapabilities;
+    auto status = wifi_chip_->getWifiChipCapabilities(&chipCapabilities);
+    if (checkStatusCode(&status, WifiStatusCode::ERROR_NOT_SUPPORTED)) {
+        GTEST_SKIP() << "getWifiChipCapabilities() is not supported by vendor.";
+    }
+    EXPECT_TRUE(status.isOk());
+}
+
+/*
+ * SetMloMode
+ */
+TEST_P(WifiChipAidlTest, SetMloMode) {
+    auto status = wifi_chip_->setMloMode(IWifiChip::ChipMloMode::LOW_LATENCY);
+    if (checkStatusCode(&status, WifiStatusCode::ERROR_NOT_SUPPORTED)) {
+        GTEST_SKIP() << "setMloMode() is not supported by vendor.";
+    }
+    EXPECT_TRUE(status.isOk());
+}
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WifiChipAidlTest);
 INSTANTIATE_TEST_SUITE_P(WifiTest, WifiChipAidlTest,
                          testing::ValuesIn(android::getAidlHalInstanceNames(IWifi::descriptor)),
diff --git a/wifi/aidl/vts/functional/wifi_rtt_controller_aidl_test.cpp b/wifi/aidl/vts/functional/wifi_rtt_controller_aidl_test.cpp
index 1596602..aca1364 100644
--- a/wifi/aidl/vts/functional/wifi_rtt_controller_aidl_test.cpp
+++ b/wifi/aidl/vts/functional/wifi_rtt_controller_aidl_test.cpp
@@ -19,8 +19,10 @@
 #include <VtsCoreUtil.h>
 #include <aidl/Gtest.h>
 #include <aidl/Vintf.h>
+#include <aidl/android/hardware/wifi/Akm.h>
 #include <aidl/android/hardware/wifi/BnWifi.h>
 #include <aidl/android/hardware/wifi/BnWifiRttControllerEventCallback.h>
+#include <aidl/android/hardware/wifi/RttSecureConfig.h>
 #include <android-base/logging.h>
 #include <android/binder_manager.h>
 #include <android/binder_status.h>
@@ -29,6 +31,7 @@
 
 #include "wifi_aidl_test_utils.h"
 
+using aidl::android::hardware::wifi::Akm;
 using aidl::android::hardware::wifi::BnWifiRttControllerEventCallback;
 using aidl::android::hardware::wifi::IWifiRttController;
 using aidl::android::hardware::wifi::RttBw;
@@ -38,6 +41,7 @@
 using aidl::android::hardware::wifi::RttPreamble;
 using aidl::android::hardware::wifi::RttResponder;
 using aidl::android::hardware::wifi::RttResult;
+using aidl::android::hardware::wifi::RttSecureConfig;
 using aidl::android::hardware::wifi::RttType;
 using aidl::android::hardware::wifi::WifiChannelInfo;
 using aidl::android::hardware::wifi::WifiChannelWidthInMhz;
@@ -87,6 +91,15 @@
         return caps;
     }
 
+    int getMostSignificantSetBitMask(int n) {
+        if (n == 0) return 0;
+        int pos = std::numeric_limits<int>::digits - 1;
+        while ((n & (1 << pos)) == 0) {
+            pos--;
+        }
+        return 1 << pos;
+    }
+
     std::shared_ptr<IWifiRttController> wifi_rtt_controller_;
     int interface_version_;
 
@@ -161,6 +174,66 @@
 }
 
 /*
+ * Request80211azNtbSecureRangeMeasurement
+ * Tests the two sided 11az non-trigger based secure ranging - 802.11az NTB FTM protocol.
+ */
+TEST_P(WifiRttControllerAidlTest, Request80211azNtbSecureRangeMeasurement) {
+    if (interface_version_ < 3) {
+        GTEST_SKIP() << "Request80211azNtbRangeMeasurement is available as of RttController V3";
+    }
+
+    RttCapabilities caps = getCapabilities();
+    if (!caps.ntbInitiatorSupported) {
+        GTEST_SKIP() << "Skipping 11az NTB RTT since driver/fw does not support";
+    }
+    if (!caps.secureHeLtfSupported && !caps.rangingFrameProtectionSupported) {
+        GTEST_SKIP() << "Skipping 11az NTB secure RTT since driver/fw does not support";
+    }
+    if (!(caps.akmsSupported & Akm::PASN)) {
+        GTEST_SKIP() << "Skipping 11az NTB secure RTT since driver/fw does not support PASN";
+    }
+    if (!caps.cipherSuitesSupported) {
+        GTEST_SKIP()
+                << "Skipping 11az NTB secure RTT since driver/fw does not support Cipher Suites";
+    }
+
+    RttConfig config;
+    config.addr = {{0x00, 0x01, 0x02, 0x03, 0x04, 0x05}};
+    config.type = RttType::TWO_SIDED_11AZ_NTB_SECURE;
+    config.peer = RttPeerType::AP;
+    config.channel.width = WifiChannelWidthInMhz::WIDTH_80;
+    config.channel.centerFreq = 5180;
+    config.channel.centerFreq0 = 5210;
+    config.channel.centerFreq1 = 0;
+    config.bw = RttBw::BW_20MHZ;
+    config.preamble = RttPreamble::HT;
+    config.mustRequestLci = false;
+    config.mustRequestLcr = false;
+    config.numFramesPerBurst = 8;
+    config.numRetriesPerRttFrame = 0;
+    config.numRetriesPerFtmr = 0;
+    // 11az non-trigger based minimum measurement time in units of 100 microseconds.
+    config.ntbMinMeasurementTime = 2500;
+    // 11az non-trigger based maximum measurement time in units of 10 milliseconds.
+    config.ntbMaxMeasurementTime = 1500;
+    RttSecureConfig secureConfig;
+    // PASN is a must to test secure config; which does not need any password.
+    secureConfig.pasnConfig.baseAkm = Akm::PASN;
+    // Get the best Cipher suite supported by the chip.
+    secureConfig.pasnConfig.cipherSuite = getMostSignificantSetBitMask(caps.cipherSuitesSupported);
+    secureConfig.enableSecureHeLtf = caps.secureHeLtfSupported;
+    secureConfig.enableRangingFrameProtection = caps.rangingFrameProtectionSupported;
+    config.secureConfig = secureConfig;
+
+    int cmdId = 55;
+    std::vector<RttConfig> configs = {config};
+    EXPECT_TRUE(wifi_rtt_controller_->rangeRequest(cmdId, configs).isOk());
+
+    // Sleep for 2 seconds to wait for driver/firmware to complete RTT.
+    sleep(2);
+}
+
+/*
  * Request80211azNtbRangeMeasurement
  * Tests the two sided 11az non-trigger based ranging - 802.11az NTB FTM protocol.
  */
diff --git a/wifi/aidl/vts/functional/wifi_sta_iface_aidl_test.cpp b/wifi/aidl/vts/functional/wifi_sta_iface_aidl_test.cpp
index f659bf6..5e8e21f 100644
--- a/wifi/aidl/vts/functional/wifi_sta_iface_aidl_test.cpp
+++ b/wifi/aidl/vts/functional/wifi_sta_iface_aidl_test.cpp
@@ -65,6 +65,12 @@
         return features & static_cast<int32_t>(expected);
     }
 
+    bool isTwtSupported() {
+        TwtCapabilities twt_capabilities = {};
+        auto status = wifi_sta_iface_->twtGetCapabilities(&twt_capabilities);
+        return status.isOk() && twt_capabilities.isTwtRequesterSupported;
+    }
+
     ndk::ScopedAStatus createStaIface(std::shared_ptr<IWifiStaIface>* sta_iface) {
         std::shared_ptr<IWifiChip> wifi_chip = getWifiChip(getInstanceName());
         EXPECT_NE(nullptr, wifi_chip.get());
@@ -379,6 +385,9 @@
     }
 }
 
+/**
+ * TwtGetCapabilities
+ */
 TEST_P(WifiStaIfaceAidlTest, TwtGetCapabilities) {
     if (interface_version_ < 2) {
         GTEST_SKIP() << "TwtGetCapabilities is available as of sta_iface V2";
@@ -400,6 +409,9 @@
     EXPECT_GT(twt_capabilities.maxWakeIntervalUs, 0);
 }
 
+/**
+ * TwtSessionSetup
+ */
 TEST_P(WifiStaIfaceAidlTest, TwtSessionSetup) {
     if (interface_version_ < 2) {
         GTEST_SKIP() << "TwtSessionSetup is available as of sta_iface V2";
@@ -424,18 +436,14 @@
     EXPECT_TRUE(wifi_sta_iface_->twtSessionSetup(1, twtRequest).isOk());
 }
 
+/**
+ * TwtSessionGetStats
+ */
 TEST_P(WifiStaIfaceAidlTest, TwtSessionGetStats) {
     if (interface_version_ < 2) {
         GTEST_SKIP() << "TwtSessionGetStats is available as of sta_iface V2";
     }
-
-    TwtCapabilities twt_capabilities = {};
-    auto status = wifi_sta_iface_->twtGetCapabilities(&twt_capabilities);
-    if (checkStatusCode(&status, WifiStatusCode::ERROR_NOT_SUPPORTED)) {
-        GTEST_SKIP() << "twtGetCapabilities() is not supported by the vendor";
-    }
-    EXPECT_TRUE(status.isOk());
-    if (!twt_capabilities.isTwtRequesterSupported) {
+    if (!isTwtSupported()) {
         GTEST_SKIP() << "TWT is not supported";
     }
 
@@ -444,26 +452,77 @@
     EXPECT_TRUE(wifi_sta_iface_->twtSessionGetStats(1, 10).isOk());
 }
 
+/**
+ * TwtSessionTeardown
+ */
 TEST_P(WifiStaIfaceAidlTest, TwtSessionTeardown) {
     if (interface_version_ < 2) {
-        GTEST_SKIP() << "TwtSessionTeardown is available as of sta_iface V3";
+        GTEST_SKIP() << "TwtSessionTeardown is available as of sta_iface V2";
     }
-
-    TwtCapabilities twt_capabilities = {};
-    auto status = wifi_sta_iface_->twtGetCapabilities(&twt_capabilities);
-    if (checkStatusCode(&status, WifiStatusCode::ERROR_NOT_SUPPORTED)) {
-        GTEST_SKIP() << "twtGetCapabilities() is not supported by the vendor";
-    }
-    EXPECT_TRUE(status.isOk());
-    if (!twt_capabilities.isTwtRequesterSupported) {
+    if (!isTwtSupported()) {
         GTEST_SKIP() << "TWT is not supported";
     }
 
     // Expecting a IWifiStaIfaceEventCallback.onTwtFailure() with INVALID_PARAMS
-    // as  the error code.
+    // as the error code.
     EXPECT_TRUE(wifi_sta_iface_->twtSessionTeardown(1, 10).isOk());
 }
 
+/**
+ * TwtSessionUpdate
+ */
+TEST_P(WifiStaIfaceAidlTest, TwtSessionUpdate) {
+    if (interface_version_ < 2) {
+        GTEST_SKIP() << "TwtSessionUpdate is available as of sta_iface V2";
+    }
+    if (!isTwtSupported()) {
+        GTEST_SKIP() << "TWT is not supported";
+    }
+
+    TwtRequest twtRequest;
+    twtRequest.mloLinkId = 0;
+    twtRequest.minWakeDurationUs = 1000;
+    twtRequest.maxWakeDurationUs = 10000;
+    twtRequest.minWakeIntervalUs = 10000;
+    twtRequest.maxWakeIntervalUs = 100000;
+
+    // Expecting a IWifiStaIfaceEventCallback.onTwtFailure() with INVALID_PARAMS
+    // as the error code.
+    EXPECT_TRUE(wifi_sta_iface_->twtSessionUpdate(1, 10, twtRequest).isOk());
+}
+
+/**
+ * TwtSessionSuspend
+ */
+TEST_P(WifiStaIfaceAidlTest, TwtSessionSuspend) {
+    if (interface_version_ < 2) {
+        GTEST_SKIP() << "TwtSessionSuspend is available as of sta_iface V2";
+    }
+    if (!isTwtSupported()) {
+        GTEST_SKIP() << "TWT is not supported";
+    }
+
+    // Expecting a IWifiStaIfaceEventCallback.onTwtFailure() with INVALID_PARAMS
+    // as the error code.
+    EXPECT_TRUE(wifi_sta_iface_->twtSessionSuspend(1, 10).isOk());
+}
+
+/**
+ * TwtSessionResume
+ */
+TEST_P(WifiStaIfaceAidlTest, TwtSessionResume) {
+    if (interface_version_ < 2) {
+        GTEST_SKIP() << "TwtSessionResume is available as of sta_iface V2";
+    }
+    if (!isTwtSupported()) {
+        GTEST_SKIP() << "TWT is not supported";
+    }
+
+    // Expecting a IWifiStaIfaceEventCallback.onTwtFailure() with INVALID_PARAMS
+    // as the error code.
+    EXPECT_TRUE(wifi_sta_iface_->twtSessionResume(1, 10).isOk());
+}
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WifiStaIfaceAidlTest);
 INSTANTIATE_TEST_SUITE_P(WifiTest, WifiStaIfaceAidlTest,
                          testing::ValuesIn(android::getAidlHalInstanceNames(IWifi::descriptor)),
diff --git a/wifi/hostapd/aidl/vts/functional/VtsHalHostapdTargetTest.cpp b/wifi/hostapd/aidl/vts/functional/VtsHalHostapdTargetTest.cpp
index 590c58b..6723b1f 100644
--- a/wifi/hostapd/aidl/vts/functional/VtsHalHostapdTargetTest.cpp
+++ b/wifi/hostapd/aidl/vts/functional/VtsHalHostapdTargetTest.cpp
@@ -86,6 +86,8 @@
         isBridgedSupport = testing::checkSubstringInCommandOutput(
                 "/system/bin/cmd wifi get-softap-supported-features",
                 "wifi_softap_bridged_ap_supported");
+        isMloSupport = testing::checkSubstringInCommandOutput(
+                "/system/bin/cmd wifi get-softap-supported-features", "wifi_softap_mlo_supported");
     }
 
     virtual void TearDown() override {
@@ -100,6 +102,7 @@
     bool isAcsSupport;
     bool isWpa3SaeSupport;
     bool isBridgedSupport;
+    bool isMloSupport;
     int interface_version_;
 
     IfaceParams getIfaceParamsWithoutAcs(std::string iface_name) {
@@ -476,6 +479,34 @@
     EXPECT_TRUE(status.isOk());
 }
 
+/**
+ * AddAccessPointWithMloConfig and remove link should pass
+ */
+TEST_P(HostapdAidl, AddAccessPointWithMloConfigAndRemoveInstance) {
+    if (interface_version_ < 3) {
+        GTEST_SKIP() << "MLO SAP is available in IfaceParams as of Hostapd V3";
+    }
+    if (!isMloSupport) GTEST_SKIP() << "Missing MLO AP support";
+    std::shared_ptr<IWifiApIface> wifi_ap_iface = HostapdAidlTestUtils::setupMloApIface();
+    EXPECT_TRUE(wifi_ap_iface.get() != nullptr);
+    std::string br_name;
+    std::vector<std::string> instances;
+    bool uses_mlo;
+    EXPECT_TRUE(wifi_ap_iface->getName(&br_name).isOk());
+    EXPECT_TRUE(wifi_ap_iface->getBridgedInstances(&instances).isOk());
+    EXPECT_TRUE(wifi_ap_iface->usesMlo(&uses_mlo).isOk());
+    EXPECT_TRUE(uses_mlo);
+
+    IfaceParams iface_params = getIfaceParamsWithBridgedModeACS(br_name);
+    iface_params.instanceIdentities = {instances[0], instances[1]};
+    iface_params.usesMlo = uses_mlo;
+    iface_params.hwModeParams.enable80211AX = true;
+    iface_params.hwModeParams.enable80211BE = true;
+
+    EXPECT_TRUE(hostapd->addAccessPoint(iface_params, getSaeNwParams()).isOk());
+    EXPECT_TRUE(hostapd->removeLinkFromMultipleLinkBridgedApIface(br_name, instances[0]).isOk());
+}
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HostapdAidl);
 INSTANTIATE_TEST_SUITE_P(
     Hostapd, HostapdAidl,
diff --git a/wifi/hostapd/aidl/vts/functional/hostapd_aidl_test_utils.h b/wifi/hostapd/aidl/vts/functional/hostapd_aidl_test_utils.h
index 93540b2..3876998 100644
--- a/wifi/hostapd/aidl/vts/functional/hostapd_aidl_test_utils.h
+++ b/wifi/hostapd/aidl/vts/functional/hostapd_aidl_test_utils.h
@@ -77,4 +77,11 @@
     return ap_iface_name;
 }
 
+std::shared_ptr<IWifiApIface> setupMloApIface() {
+    std::shared_ptr<IWifiChip> wifi_chip = getWifiChip(kWifiInstanceName);
+    EXPECT_TRUE(wifi_chip.get() != nullptr);
+    return getWifiApOrBridgedApIface(
+            wifi_chip, generateApIfaceParams(IfaceConcurrencyType::AP_BRIDGED, true, 0));
+}
+
 }  // namespace HostapdAidlTestUtils
diff --git a/wifi/supplicant/aidl/vts/functional/Android.bp b/wifi/supplicant/aidl/vts/functional/Android.bp
index f94eb46..95ff6cd 100644
--- a/wifi/supplicant/aidl/vts/functional/Android.bp
+++ b/wifi/supplicant/aidl/vts/functional/Android.bp
@@ -137,3 +137,41 @@
         "vts",
     ],
 }
+
+cc_test {
+    name: "VtsHalWifiSupplicantP2pNetworkTargetTest",
+    defaults: [
+        "VtsHalTargetTestDefaults",
+        "use_libaidlvintf_gtest_helper_static",
+    ],
+    srcs: ["supplicant_p2p_network_aidl_test.cpp"],
+    shared_libs: [
+        "libbinder",
+        "libbinder_ndk",
+        "libvndksupport",
+    ],
+    static_libs: [
+        "android.hardware.wifi@1.0",
+        "android.hardware.wifi@1.1",
+        "android.hardware.wifi@1.2",
+        "android.hardware.wifi@1.3",
+        "android.hardware.wifi@1.4",
+        "android.hardware.wifi@1.5",
+        "android.hardware.wifi.common-V2-ndk",
+        "android.hardware.wifi.supplicant@1.0",
+        "android.hardware.wifi.supplicant@1.1",
+        "android.hardware.wifi.supplicant-V4-ndk",
+        "libwifi-system",
+        "libwifi-system-iface",
+        "VtsHalWifiV1_0TargetTestUtil",
+        "VtsHalWifiV1_5TargetTestUtil",
+        "VtsHalWifiSupplicantV1_0TargetTestUtil",
+        "android.hardware.wifi.common-V2-ndk",
+        "android.hardware.wifi-V3-ndk",
+        "VtsHalWifiTargetTestUtil",
+    ],
+    test_suites: [
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/wifi/supplicant/aidl/vts/functional/supplicant_p2p_iface_aidl_test.cpp b/wifi/supplicant/aidl/vts/functional/supplicant_p2p_iface_aidl_test.cpp
index 3638ac5..778e20a 100644
--- a/wifi/supplicant/aidl/vts/functional/supplicant_p2p_iface_aidl_test.cpp
+++ b/wifi/supplicant/aidl/vts/functional/supplicant_p2p_iface_aidl_test.cpp
@@ -38,6 +38,7 @@
 using aidl::android::hardware::wifi::supplicant::IfaceType;
 using aidl::android::hardware::wifi::supplicant::ISupplicant;
 using aidl::android::hardware::wifi::supplicant::ISupplicantP2pIface;
+using aidl::android::hardware::wifi::supplicant::ISupplicantP2pNetwork;
 using aidl::android::hardware::wifi::supplicant::MiracastMode;
 using aidl::android::hardware::wifi::supplicant::P2pAddGroupConfigurationParams;
 using aidl::android::hardware::wifi::supplicant::P2pConnectInfo;
@@ -398,8 +399,8 @@
  * SetWpsModelNumber
  */
 TEST_P(SupplicantP2pIfaceAidlTest, SetWpsModelNumber) {
-    const std::string modelNumber = "TestModelNumber";
-    EXPECT_TRUE(p2p_iface_->setWpsModelName(modelNumber).isOk());
+    const std::string modelNumber = "Model1234";
+    EXPECT_TRUE(p2p_iface_->setWpsModelNumber(modelNumber).isOk());
 }
 
 /*
@@ -981,6 +982,96 @@
     EXPECT_TRUE(p2p_iface_->reinvokePersistentGroup(params).isOk());
 }
 
+/*
+ * Test the P2P network management functions.
+ */
+TEST_P(SupplicantP2pIfaceAidlTest, ManageNetworks) {
+    std::shared_ptr<ISupplicantP2pNetwork> network;
+    EXPECT_TRUE(p2p_iface_->addNetwork(&network).isOk());
+    ASSERT_NE(network, nullptr);
+
+    std::vector<int32_t> networkList;
+    EXPECT_TRUE(p2p_iface_->listNetworks(&networkList).isOk());
+    ASSERT_FALSE(networkList.empty());
+
+    int networkId = networkList[0];
+    EXPECT_TRUE(p2p_iface_->getNetwork(networkId, &network).isOk());
+    ASSERT_NE(network, nullptr);
+    EXPECT_TRUE(p2p_iface_->removeNetwork(networkId).isOk());
+}
+
+/*
+ * Request and cancel service discovery
+ */
+TEST_P(SupplicantP2pIfaceAidlTest, RequestAndCancelServiceDiscovery) {
+    int64_t discoveryId;
+    std::vector<uint8_t> query = {0x11, 0x22, 0x33};
+    EXPECT_TRUE(p2p_iface_->requestServiceDiscovery(kTestMacAddr, query, &discoveryId).isOk());
+    EXPECT_TRUE(p2p_iface_->cancelServiceDiscovery(discoveryId).isOk());
+}
+
+/*
+ * Start and stop WPS
+ */
+TEST_P(SupplicantP2pIfaceAidlTest, StartAndStopWps) {
+    // Expected to fail with test values
+    std::string generatedPin;
+    EXPECT_FALSE(p2p_iface_->startWpsPbc(kTestGroupIfName, kTestMacAddr).isOk());
+    EXPECT_FALSE(
+            p2p_iface_->startWpsPinDisplay(kTestGroupIfName, kTestMacAddr, &generatedPin).isOk());
+    EXPECT_FALSE(p2p_iface_->startWpsPinKeypad(kTestGroupIfName, kTestConnectPin).isOk());
+    EXPECT_FALSE(p2p_iface_->cancelWps(kTestGroupIfName).isOk());
+}
+
+/*
+ * Create message and report handover for NFC Request
+ */
+TEST_P(SupplicantP2pIfaceAidlTest, CreateAndReportNfcRequest) {
+    std::vector<uint8_t> requestMsg;
+    EXPECT_TRUE(p2p_iface_->createNfcHandoverRequestMessage(&requestMsg).isOk());
+    EXPECT_FALSE(requestMsg.empty());
+    EXPECT_TRUE(p2p_iface_->reportNfcHandoverResponse(requestMsg).isOk());
+}
+
+/*
+ * Create message and report handover for NFC Select
+ */
+TEST_P(SupplicantP2pIfaceAidlTest, CreateAndReportNfcSelect) {
+    std::vector<uint8_t> selectMsg;
+    EXPECT_TRUE(p2p_iface_->createNfcHandoverSelectMessage(&selectMsg).isOk());
+    EXPECT_FALSE(selectMsg.empty());
+    EXPECT_TRUE(p2p_iface_->reportNfcHandoverInitiation(selectMsg).isOk());
+}
+
+/*
+ * RemoveClient
+ */
+TEST_P(SupplicantP2pIfaceAidlTest, RemoveClient) {
+    // Method returns success for any valid MAC address
+    EXPECT_TRUE(p2p_iface_->removeClient(kTestMacAddr, false).isOk());
+    // Returns failure for any invalid MAC address
+    std::vector<uint8_t> invalidMacAddr = {0x11, 0x22};
+    EXPECT_FALSE(p2p_iface_->removeClient(invalidMacAddr, false).isOk());
+}
+
+/*
+ * ConfigureEapolIpAddressAllocationParams
+ */
+TEST_P(SupplicantP2pIfaceAidlTest, ConfigureEapolIpAddressAllocationParams) {
+    if (interface_version_ < 2) {
+        GTEST_SKIP() << "ConfigureEapolIpAddressAllocationParams is available as of Supplicant V2";
+    }
+    // The IP addresses are IPV4 addresses and higher-order address bytes are in the
+    // lower-order int bytes (e.g. 192.168.1.1 is represented as 0x0101A8C0)
+    EXPECT_TRUE(p2p_iface_
+                        ->configureEapolIpAddressAllocationParams(0x0101A8C0, 0x00FFFFFF,
+                                                                  0x0501A8C0, 0x0801A8C0)
+                        .isOk());
+
+    // Clear the configuration.
+    EXPECT_TRUE(p2p_iface_->configureEapolIpAddressAllocationParams(0, 0, 0, 0).isOk());
+}
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SupplicantP2pIfaceAidlTest);
 INSTANTIATE_TEST_SUITE_P(Supplicant, SupplicantP2pIfaceAidlTest,
                          testing::ValuesIn(android::getAidlHalInstanceNames(
diff --git a/wifi/supplicant/aidl/vts/functional/supplicant_p2p_network_aidl_test.cpp b/wifi/supplicant/aidl/vts/functional/supplicant_p2p_network_aidl_test.cpp
new file mode 100644
index 0000000..c5a73f1
--- /dev/null
+++ b/wifi/supplicant/aidl/vts/functional/supplicant_p2p_network_aidl_test.cpp
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2025 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 <VtsCoreUtil.h>
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/wifi/supplicant/BnSupplicant.h>
+#include <android/binder_manager.h>
+#include <android/binder_status.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cutils/properties.h>
+
+#include "supplicant_test_utils.h"
+#include "wifi_aidl_test_utils.h"
+
+using aidl::android::hardware::wifi::supplicant::DebugLevel;
+using aidl::android::hardware::wifi::supplicant::IfaceType;
+using aidl::android::hardware::wifi::supplicant::ISupplicantP2pNetwork;
+using aidl::android::hardware::wifi::supplicant::MacAddress;
+using android::ProcessState;
+
+class SupplicantP2pNetworkAidlTest : public testing::TestWithParam<std::string> {
+  public:
+    void SetUp() override {
+        initializeService();
+        supplicant_ = getSupplicant(GetParam().c_str());
+        ASSERT_NE(supplicant_, nullptr);
+        ASSERT_TRUE(supplicant_->setDebugParams(DebugLevel::EXCESSIVE, true, true).isOk());
+
+        bool p2pEnabled = testing::deviceSupportsFeature("android.hardware.wifi.direct");
+        if (!p2pEnabled) {
+            GTEST_SKIP() << "Wi-Fi Direct is not supported, skip this test.";
+        }
+
+        EXPECT_TRUE(supplicant_->getP2pInterface(getP2pIfaceName(), &p2p_iface_).isOk());
+        ASSERT_NE(p2p_iface_, nullptr);
+        EXPECT_TRUE(p2p_iface_->addNetwork(&p2p_network_).isOk());
+        ASSERT_NE(p2p_network_, nullptr);
+    }
+
+    void TearDown() override {
+        stopSupplicantService();
+        startWifiFramework();
+    }
+
+  protected:
+    std::shared_ptr<ISupplicant> supplicant_;
+    std::shared_ptr<ISupplicantP2pIface> p2p_iface_;
+    std::shared_ptr<ISupplicantP2pNetwork> p2p_network_;
+};
+
+/*
+ * GetBssid
+ */
+TEST_P(SupplicantP2pNetworkAidlTest, GetBssid) {
+    std::vector<uint8_t> bssid;
+    EXPECT_TRUE(p2p_network_->getBssid(&bssid).isOk());
+}
+
+/*
+ * GetClientList
+ */
+TEST_P(SupplicantP2pNetworkAidlTest, GetClientList) {
+    // Expect failure if there are no clients
+    std::vector<MacAddress> clientList;
+    EXPECT_FALSE(p2p_network_->getClientList(&clientList).isOk());
+}
+
+/*
+ * GetId
+ */
+TEST_P(SupplicantP2pNetworkAidlTest, GetId) {
+    int networkId;
+    EXPECT_TRUE(p2p_network_->getId(&networkId).isOk());
+}
+
+/*
+ * GetInterfaceName
+ */
+TEST_P(SupplicantP2pNetworkAidlTest, GetInterfaceName) {
+    std::string expectedName = getP2pIfaceName();
+    std::string retrievedName;
+    EXPECT_TRUE(p2p_network_->getInterfaceName(&retrievedName).isOk());
+    EXPECT_EQ(retrievedName, expectedName);
+}
+
+/*
+ * GetSsid
+ */
+TEST_P(SupplicantP2pNetworkAidlTest, GetSsid) {
+    std::vector<uint8_t> ssid;
+    EXPECT_TRUE(p2p_network_->getSsid(&ssid).isOk());
+}
+
+/*
+ * GetType
+ */
+TEST_P(SupplicantP2pNetworkAidlTest, GetType) {
+    IfaceType ifaceType;
+    EXPECT_TRUE(p2p_network_->getType(&ifaceType).isOk());
+    EXPECT_EQ(ifaceType, IfaceType::P2P);
+}
+
+/*
+ * IsCurrent
+ */
+TEST_P(SupplicantP2pNetworkAidlTest, IsCurrent) {
+    bool isCurrent;
+    EXPECT_TRUE(p2p_network_->isCurrent(&isCurrent).isOk());
+    EXPECT_FALSE(isCurrent);
+}
+
+/*
+ * IsGroupOwner
+ */
+TEST_P(SupplicantP2pNetworkAidlTest, IsGroupOwner) {
+    bool isGroupOwner;
+    EXPECT_TRUE(p2p_network_->isGroupOwner(&isGroupOwner).isOk());
+    EXPECT_FALSE(isGroupOwner);
+}
+
+/*
+ * IsPersistent
+ */
+TEST_P(SupplicantP2pNetworkAidlTest, IsPersistent) {
+    bool isPersistent;
+    EXPECT_TRUE(p2p_network_->isPersistent(&isPersistent).isOk());
+    EXPECT_FALSE(isPersistent);
+}
+
+/*
+ * SetClientList
+ */
+TEST_P(SupplicantP2pNetworkAidlTest, SetClientList) {
+    MacAddress client = {{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc}};
+    std::vector clientList = {client};
+    EXPECT_TRUE(p2p_network_->setClientList(clientList).isOk());
+}
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SupplicantP2pNetworkAidlTest);
+INSTANTIATE_TEST_SUITE_P(
+        Supplicant, SupplicantP2pNetworkAidlTest,
+        testing::ValuesIn(android::getAidlHalInstanceNames(ISupplicant::descriptor)),
+        android::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    ProcessState::self()->setThreadPoolMaxThreadCount(1);
+    ProcessState::self()->startThreadPool();
+    return RUN_ALL_TESTS();
+}
diff --git a/wifi/supplicant/aidl/vts/functional/supplicant_sta_iface_aidl_test.cpp b/wifi/supplicant/aidl/vts/functional/supplicant_sta_iface_aidl_test.cpp
index 18b8ccb..cc3e821 100644
--- a/wifi/supplicant/aidl/vts/functional/supplicant_sta_iface_aidl_test.cpp
+++ b/wifi/supplicant/aidl/vts/functional/supplicant_sta_iface_aidl_test.cpp
@@ -47,8 +47,13 @@
 using aidl::android::hardware::wifi::supplicant::QosCharacteristics;
 using aidl::android::hardware::wifi::supplicant::QosPolicyScsData;
 using aidl::android::hardware::wifi::supplicant::QosPolicyScsRequestStatus;
+using aidl::android::hardware::wifi::supplicant::SignalPollResult;
+using aidl::android::hardware::wifi::supplicant::UsdBaseConfig;
+using aidl::android::hardware::wifi::supplicant::UsdCapabilities;
 using aidl::android::hardware::wifi::supplicant::UsdMessageInfo;
+using aidl::android::hardware::wifi::supplicant::UsdPublishConfig;
 using aidl::android::hardware::wifi::supplicant::UsdServiceDiscoveryInfo;
+using aidl::android::hardware::wifi::supplicant::UsdSubscribeConfig;
 using aidl::android::hardware::wifi::supplicant::UsdTerminateReasonCode;
 using aidl::android::hardware::wifi::supplicant::WpaDriverCapabilitiesMask;
 using aidl::android::hardware::wifi::supplicant::WpsConfigMethods;
@@ -880,6 +885,95 @@
     EXPECT_EQ(1, responseList.size());
 }
 
+/*
+ * Verify that all USD methods check the Service Specific Info (SSI) length
+ * and fail if the provided SSI is too long.
+ */
+TEST_P(SupplicantStaIfaceAidlTest, InvalidUsdServiceSpecificInfo) {
+    if (interface_version_ < 4) {
+        GTEST_SKIP() << "USD is available as of Supplicant V4";
+    }
+
+    UsdCapabilities caps;
+    EXPECT_TRUE(sta_iface_->getUsdCapabilities(&caps).isOk());
+    if (!caps.isUsdPublisherSupported && !caps.isUsdSubscriberSupported) {
+        GTEST_SKIP() << "USD publish and subscribe are not supported";
+    }
+
+    int commandId = 123;
+    std::vector<uint8_t> invalidSsi(caps.maxLocalSsiLengthBytes + 1);
+    UsdBaseConfig invalidBaseConfig;
+    invalidBaseConfig.serviceSpecificInfo = invalidSsi;
+
+    if (caps.isUsdPublisherSupported) {
+        UsdPublishConfig publishConfig;
+        publishConfig.usdBaseConfig = invalidBaseConfig;
+        EXPECT_FALSE(sta_iface_->startUsdPublish(commandId, publishConfig).isOk());
+        EXPECT_FALSE(sta_iface_->updateUsdPublish(commandId, invalidSsi).isOk());
+    }
+
+    if (caps.isUsdSubscriberSupported) {
+        UsdSubscribeConfig subscribeConfig;
+        subscribeConfig.usdBaseConfig = invalidBaseConfig;
+        EXPECT_FALSE(sta_iface_->startUsdSubscribe(commandId, subscribeConfig).isOk());
+    }
+
+    UsdMessageInfo messageInfo;
+    messageInfo.message = invalidSsi;
+    EXPECT_FALSE(sta_iface_->sendUsdMessage(messageInfo).isOk());
+}
+
+/*
+ * Cancel a USD Publish and Subscribe session.
+ */
+TEST_P(SupplicantStaIfaceAidlTest, CancelUsdSession) {
+    if (interface_version_ < 4) {
+        GTEST_SKIP() << "USD is available as of Supplicant V4";
+    }
+
+    UsdCapabilities caps;
+    EXPECT_TRUE(sta_iface_->getUsdCapabilities(&caps).isOk());
+    if (!caps.isUsdPublisherSupported && !caps.isUsdSubscriberSupported) {
+        GTEST_SKIP() << "USD publish and subscribe are not supported";
+    }
+
+    int sessionId = 123;
+    if (caps.isUsdPublisherSupported) {
+        // Method is expected to succeed, even if the session does not exist.
+        EXPECT_TRUE(sta_iface_->cancelUsdPublish(sessionId).isOk());
+    }
+    if (caps.isUsdSubscriberSupported) {
+        EXPECT_TRUE(sta_iface_->cancelUsdSubscribe(sessionId).isOk());
+    }
+}
+
+/*
+ * GenerateSelfDppConfiguration
+ */
+TEST_P(SupplicantStaIfaceAidlTest, GenerateSelfDppConfiguration) {
+    if (!keyMgmtSupported(sta_iface_, KeyMgmtMask::DPP)) {
+        GTEST_SKIP() << "Missing DPP support";
+    }
+    const std::string ssid = "my_test_ssid";
+    const std::vector<uint8_t> eckey_in = {0x2, 0x3, 0x4};
+
+    // Expect to fail as this test requires a DPP AKM supported AP and a valid private EC
+    // key generated by wpa_supplicant.
+    EXPECT_FALSE(sta_iface_->generateSelfDppConfiguration(ssid, eckey_in).isOk());
+}
+
+/*
+ * getSignalPollResults
+ */
+TEST_P(SupplicantStaIfaceAidlTest, GetSignalPollResults) {
+    if (interface_version_ < 2) {
+        GTEST_SKIP() << "getSignalPollResults is available as of Supplicant V2";
+    }
+
+    std::vector<SignalPollResult> results;
+    EXPECT_TRUE(sta_iface_->getSignalPollResults(&results).isOk());
+}
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SupplicantStaIfaceAidlTest);
 INSTANTIATE_TEST_SUITE_P(Supplicant, SupplicantStaIfaceAidlTest,
                          testing::ValuesIn(android::getAidlHalInstanceNames(
diff --git a/wifi/supplicant/aidl/vts/functional/supplicant_sta_network_aidl_test.cpp b/wifi/supplicant/aidl/vts/functional/supplicant_sta_network_aidl_test.cpp
index 9bdd2f5..5e6069f 100644
--- a/wifi/supplicant/aidl/vts/functional/supplicant_sta_network_aidl_test.cpp
+++ b/wifi/supplicant/aidl/vts/functional/supplicant_sta_network_aidl_test.cpp
@@ -32,6 +32,7 @@
 using aidl::android::hardware::wifi::supplicant::AuthAlgMask;
 using aidl::android::hardware::wifi::supplicant::BnSupplicantStaNetworkCallback;
 using aidl::android::hardware::wifi::supplicant::DebugLevel;
+using aidl::android::hardware::wifi::supplicant::DppConnectionKeys;
 using aidl::android::hardware::wifi::supplicant::EapMethod;
 using aidl::android::hardware::wifi::supplicant::EapPhase2Method;
 using aidl::android::hardware::wifi::supplicant::GroupCipherMask;
@@ -837,6 +838,21 @@
 }
 
 /*
+ * SetDppKeys
+ */
+TEST_P(SupplicantStaNetworkAidlTest, SetDppKeys) {
+    if (!keyMgmtSupported(sta_iface_, KeyMgmtMask::DPP)) {
+        GTEST_SKIP() << "Missing DPP support";
+    }
+
+    DppConnectionKeys in_keys;
+    in_keys.connector = std::vector<uint8_t>({0x11, 0x22, 0x33, 0x44});
+    in_keys.cSign = std::vector<uint8_t>({0x55, 0x66, 0x77, 0x88});
+    in_keys.netAccessKey = std::vector<uint8_t>({0xaa, 0xbb, 0xcc, 0xdd});
+    EXPECT_TRUE(sta_network_->setDppKeys(in_keys).isOk());
+}
+
+/*
  * SetVendorData
  */
 TEST_P(SupplicantStaNetworkAidlTest, SetVendorData) {