Merge "Add supported value APIs to VHAL interface." into main
diff --git a/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv b/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv
index 56b7926..e2da90d 100644
--- a/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv
+++ b/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv
@@ -45,6 +45,8 @@
     PAUSED -> ACTIVE [label="start"];                 // consumer -> active
     PAUSED -> IDLE [label="flush"];                   // producer -> passive, buffer is cleared
     DRAINING -> IDLE [label="←IStreamCallback.onDrainReady"];
+    DRAINING -> DRAINING [label="←IStreamCallback.onDrainReady"];  // allowed for `DRAIN_EARLY_NOTIFY`
+    DRAINING -> IDLE [label="<empty buffer>"];        // allowed for `DRAIN_EARLY_NOTIFY`
     DRAINING -> TRANSFERRING [label="burst"];         // producer -> active
     DRAINING -> ACTIVE [label="burst"];               // full write
     DRAINING -> DRAIN_PAUSED [label="pause"];         // consumer -> passive (not consuming)
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index 51b6085..e96cf81 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -207,9 +207,9 @@
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
     }
     const auto& flags = portConfigIt->flags.value();
-    StreamContext::DebugParameters params{mDebug.streamTransientStateDelayMs,
-                                          mVendorDebug.forceTransientBurst,
-                                          mVendorDebug.forceSynchronousDrain};
+    StreamContext::DebugParameters params{
+            mDebug.streamTransientStateDelayMs, mVendorDebug.forceTransientBurst,
+            mVendorDebug.forceSynchronousDrain, mVendorDebug.forceDrainToDraining};
     std::unique_ptr<StreamContext::DataMQ> dataMQ = nullptr;
     std::shared_ptr<IStreamCallback> streamAsyncCallback = nullptr;
     std::shared_ptr<ISoundDose> soundDose;
@@ -1524,6 +1524,7 @@
 
 const std::string Module::VendorDebug::kForceTransientBurstName = "aosp.forceTransientBurst";
 const std::string Module::VendorDebug::kForceSynchronousDrainName = "aosp.forceSynchronousDrain";
+const std::string Module::VendorDebug::kForceDrainToDrainingName = "aosp.forceDrainToDraining";
 
 ndk::ScopedAStatus Module::getVendorParameters(const std::vector<std::string>& in_ids,
                                                std::vector<VendorParameter>* _aidl_return) {
@@ -1538,6 +1539,10 @@
             VendorParameter forceSynchronousDrain{.id = id};
             forceSynchronousDrain.ext.setParcelable(Boolean{mVendorDebug.forceSynchronousDrain});
             _aidl_return->push_back(std::move(forceSynchronousDrain));
+        } else if (id == VendorDebug::kForceDrainToDrainingName) {
+            VendorParameter forceDrainToDraining{.id = id};
+            forceDrainToDraining.ext.setParcelable(Boolean{mVendorDebug.forceDrainToDraining});
+            _aidl_return->push_back(std::move(forceDrainToDraining));
         } else {
             allParametersKnown = false;
             LOG(VERBOSE) << __func__ << ": " << mType << ": unrecognized parameter \"" << id << "\"";
@@ -1578,6 +1583,10 @@
             if (!extractParameter<Boolean>(p, &mVendorDebug.forceSynchronousDrain)) {
                 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
             }
+        } else if (p.id == VendorDebug::kForceDrainToDrainingName) {
+            if (!extractParameter<Boolean>(p, &mVendorDebug.forceDrainToDraining)) {
+                return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+            }
         } else {
             allParametersKnown = false;
             LOG(VERBOSE) << __func__ << ": " << mType << ": unrecognized parameter \"" << p.id
diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp
index 3d7f30c..4525f6a 100644
--- a/audio/aidl/default/Stream.cpp
+++ b/audio/aidl/default/Stream.cpp
@@ -382,8 +382,20 @@
 const std::string StreamOutWorkerLogic::kThreadName = "writer";
 
 StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
-    if (mState == StreamDescriptor::State::DRAINING ||
-        mState == StreamDescriptor::State::TRANSFERRING) {
+    if (mState == StreamDescriptor::State::DRAINING && mContext->getForceDrainToDraining() &&
+        mOnDrainReadyStatus == OnDrainReadyStatus::UNSENT) {
+        std::shared_ptr<IStreamCallback> asyncCallback = mContext->getAsyncCallback();
+        if (asyncCallback != nullptr) {
+            ndk::ScopedAStatus status = asyncCallback->onDrainReady();
+            if (!status.isOk()) {
+                LOG(ERROR) << __func__ << ": error from onDrainReady: " << status;
+            }
+            // This sets the timeout for moving into IDLE on next iterations.
+            switchToTransientState(StreamDescriptor::State::DRAINING);
+            mOnDrainReadyStatus = OnDrainReadyStatus::SENT;
+        }
+    } else if (mState == StreamDescriptor::State::DRAINING ||
+               mState == StreamDescriptor::State::TRANSFERRING) {
         if (auto stateDurationMs = std::chrono::duration_cast<std::chrono::milliseconds>(
                     std::chrono::steady_clock::now() - mTransientStateStart);
             stateDurationMs >= mTransientStateDelayMs) {
@@ -396,9 +408,12 @@
                 // drain or transfer completion. In the stub, we switch unconditionally.
                 if (mState == StreamDescriptor::State::DRAINING) {
                     mState = StreamDescriptor::State::IDLE;
-                    ndk::ScopedAStatus status = asyncCallback->onDrainReady();
-                    if (!status.isOk()) {
-                        LOG(ERROR) << __func__ << ": error from onDrainReady: " << status;
+                    if (mOnDrainReadyStatus != OnDrainReadyStatus::SENT) {
+                        ndk::ScopedAStatus status = asyncCallback->onDrainReady();
+                        if (!status.isOk()) {
+                            LOG(ERROR) << __func__ << ": error from onDrainReady: " << status;
+                        }
+                        mOnDrainReadyStatus = OnDrainReadyStatus::SENT;
                     }
                 } else {
                     mState = StreamDescriptor::State::ACTIVE;
@@ -537,6 +552,10 @@
                             mState = StreamDescriptor::State::IDLE;
                         } else {
                             switchToTransientState(StreamDescriptor::State::DRAINING);
+                            mOnDrainReadyStatus =
+                                    mode == StreamDescriptor::DrainMode::DRAIN_EARLY_NOTIFY
+                                            ? OnDrainReadyStatus::UNSENT
+                                            : OnDrainReadyStatus::IGNORE;
                         }
                     } else {
                         LOG(ERROR) << __func__ << ": drain failed: " << status;
diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h
index 7e32cf2..d03598a 100644
--- a/audio/aidl/default/include/core-impl/Module.h
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -148,8 +148,10 @@
     struct VendorDebug {
         static const std::string kForceTransientBurstName;
         static const std::string kForceSynchronousDrainName;
+        static const std::string kForceDrainToDrainingName;
         bool forceTransientBurst = false;
         bool forceSynchronousDrain = false;
+        bool forceDrainToDraining = false;
     };
     // ids of device ports created at runtime via 'connectExternalDevice'.
     // Also stores a list of ids of mix ports with dynamic profiles that were populated from
diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h
index f7b9269..8297fc5 100644
--- a/audio/aidl/default/include/core-impl/Stream.h
+++ b/audio/aidl/default/include/core-impl/Stream.h
@@ -78,6 +78,10 @@
         bool forceTransientBurst = false;
         // Force the "drain" command to be synchronous, going directly to the IDLE state.
         bool forceSynchronousDrain = false;
+        // Force the "drain early notify" command to keep the SM in the DRAINING state
+        // after sending 'onDrainReady' callback. The SM moves to IDLE after
+        // 'transientStateDelayMs'.
+        bool forceDrainToDraining = false;
     };
 
     StreamContext() = default;
@@ -119,6 +123,7 @@
     ::aidl::android::media::audio::common::AudioIoFlags getFlags() const { return mFlags; }
     bool getForceTransientBurst() const { return mDebugParameters.forceTransientBurst; }
     bool getForceSynchronousDrain() const { return mDebugParameters.forceSynchronousDrain; }
+    bool getForceDrainToDraining() const { return mDebugParameters.forceDrainToDraining; }
     size_t getFrameSize() const;
     int getInternalCommandCookie() const { return mInternalCommandCookie; }
     int32_t getMixPortHandle() const { return mMixPortHandle; }
@@ -301,6 +306,9 @@
     bool write(size_t clientSize, StreamDescriptor::Reply* reply);
 
     std::shared_ptr<IStreamOutEventCallback> mEventCallback;
+
+    enum OnDrainReadyStatus : int32_t { IGNORE /*used for DRAIN_ALL*/, UNSENT, SENT };
+    OnDrainReadyStatus mOnDrainReadyStatus = OnDrainReadyStatus::IGNORE;
 };
 using StreamOutWorker = StreamWorkerImpl<StreamOutWorkerLogic>;
 
diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
index 6bfba65..6bce107 100644
--- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
@@ -117,6 +117,10 @@
 using ndk::enum_range;
 using ndk::ScopedAStatus;
 
+static constexpr int32_t kAidlVersion1 = 1;
+static constexpr int32_t kAidlVersion2 = 2;
+static constexpr int32_t kAidlVersion3 = 3;
+
 template <typename T>
 std::set<int32_t> extractIds(const std::vector<T>& v) {
     std::set<int32_t> ids;
@@ -452,7 +456,6 @@
     // This is implemented by the 'StreamFixture' utility class.
     static constexpr int kNegativeTestBufferSizeFrames = 256;
     static constexpr int kDefaultLargeBufferSizeFrames = 48000;
-    static constexpr int32_t kAidlVersion3 = 3;
 
     void SetUpImpl(const std::string& moduleName, bool setUpDebug = true) {
         ASSERT_NO_FATAL_FAILURE(ConnectToService(moduleName, setUpDebug));
@@ -582,7 +585,7 @@
     std::unique_ptr<WithDebugFlags> debug;
     std::vector<AudioPort> initialPorts;
     std::vector<AudioRoute> initialRoutes;
-    int32_t aidlVersion;
+    int32_t aidlVersion = -1;
 };
 
 class WithDevicePortConnectedState {
@@ -1837,6 +1840,7 @@
 }
 
 TEST_P(AudioCoreModule, SetAudioPortConfigInvalidPortAudioGain) {
+    ASSERT_GE(aidlVersion, kAidlVersion1);
     if (aidlVersion < kAidlVersion3) {
         GTEST_SKIP() << "Skip for audio HAL version lower than " << kAidlVersion3;
     }
@@ -4021,6 +4025,7 @@
 
 enum {
     NAMED_CMD_NAME,
+    NAMED_CMD_MIN_INTERFACE_VERSION,
     NAMED_CMD_DELAY_MS,
     NAMED_CMD_STREAM_TYPE,
     NAMED_CMD_CMDS,
@@ -4028,7 +4033,7 @@
 };
 enum class StreamTypeFilter { ANY, SYNC, ASYNC };
 using NamedCommandSequence =
-        std::tuple<std::string, int /*cmdDelayMs*/, StreamTypeFilter,
+        std::tuple<std::string, int /*minInterfaceVersion*/, int /*cmdDelayMs*/, StreamTypeFilter,
                    std::shared_ptr<StateSequence>, bool /*validatePositionIncrease*/>;
 enum { PARAM_MODULE_NAME, PARAM_CMD_SEQ, PARAM_SETUP_SEQ };
 using StreamIoTestParameters =
@@ -4039,6 +4044,12 @@
   public:
     void SetUp() override {
         ASSERT_NO_FATAL_FAILURE(SetUpImpl(std::get<PARAM_MODULE_NAME>(GetParam())));
+        ASSERT_GE(aidlVersion, kAidlVersion1);
+        if (const int minVersion =
+                    std::get<NAMED_CMD_MIN_INTERFACE_VERSION>(std::get<PARAM_CMD_SEQ>(GetParam()));
+            aidlVersion < minVersion) {
+            GTEST_SKIP() << "Skip for audio HAL version lower than " << minVersion;
+        }
         ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
     }
 
@@ -4048,6 +4059,20 @@
         if (allPortConfigs.empty()) {
             GTEST_SKIP() << "No mix ports have attached devices";
         }
+        const auto& commandsAndStates =
+                std::get<NAMED_CMD_CMDS>(std::get<PARAM_CMD_SEQ>(GetParam()));
+        const bool validatePositionIncrease =
+                std::get<NAMED_CMD_VALIDATE_POS_INCREASE>(std::get<PARAM_CMD_SEQ>(GetParam()));
+        auto runStreamIoCommands = [&](const AudioPortConfig& portConfig) {
+            if (!std::get<PARAM_SETUP_SEQ>(GetParam())) {
+                ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates,
+                                                                    validatePositionIncrease));
+            } else {
+                ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates,
+                                                                    validatePositionIncrease));
+            }
+        };
+
         for (const auto& portConfig : allPortConfigs) {
             auto port = moduleConfig->getPort(portConfig.portId);
             ASSERT_TRUE(port.has_value());
@@ -4075,16 +4100,18 @@
             delayTransientStates.flags().streamTransientStateDelayMs =
                     std::get<NAMED_CMD_DELAY_MS>(std::get<PARAM_CMD_SEQ>(GetParam()));
             ASSERT_NO_FATAL_FAILURE(delayTransientStates.SetUp(module.get()));
-            const auto& commandsAndStates =
-                    std::get<NAMED_CMD_CMDS>(std::get<PARAM_CMD_SEQ>(GetParam()));
-            const bool validatePositionIncrease =
-                    std::get<NAMED_CMD_VALIDATE_POS_INCREASE>(std::get<PARAM_CMD_SEQ>(GetParam()));
-            if (!std::get<PARAM_SETUP_SEQ>(GetParam())) {
-                ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates,
-                                                                    validatePositionIncrease));
-            } else {
-                ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates,
-                                                                    validatePositionIncrease));
+            ASSERT_NO_FATAL_FAILURE(runStreamIoCommands(portConfig));
+            if (aidlVersion >= kAidlVersion3 && isNonBlocking && !IOTraits<Stream>::is_input) {
+                // Also try running the same sequence with "aosp.forceDrainToDraining" set.
+                // This will only work with the default implementation. When it works, the stream
+                // tries always to move to the 'DRAINING' state after an "early notify" drain.
+                // This helps to check more paths for our test scenarios.
+                WithModuleParameter forceDrainToDraining("aosp.forceDrainToDraining",
+                                                         Boolean{true});
+                if (forceDrainToDraining.SetUpNoChecks(module.get(), true /*failureExpected*/)
+                            .isOk()) {
+                    ASSERT_NO_FATAL_FAILURE(runStreamIoCommands(portConfig));
+                }
             }
             if (isNonBlocking) {
                 // Also try running the same sequence with "aosp.forceTransientBurst" set.
@@ -4094,13 +4121,7 @@
                 WithModuleParameter forceTransientBurst("aosp.forceTransientBurst", Boolean{true});
                 if (forceTransientBurst.SetUpNoChecks(module.get(), true /*failureExpected*/)
                             .isOk()) {
-                    if (!std::get<PARAM_SETUP_SEQ>(GetParam())) {
-                        ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq1(
-                                portConfig, commandsAndStates, validatePositionIncrease));
-                    } else {
-                        ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq2(
-                                portConfig, commandsAndStates, validatePositionIncrease));
-                    }
+                    ASSERT_NO_FATAL_FAILURE(runStreamIoCommands(portConfig));
                 }
             } else if (!IOTraits<Stream>::is_input) {
                 // Also try running the same sequence with "aosp.forceSynchronousDrain" set.
@@ -4111,13 +4132,7 @@
                                                           Boolean{true});
                 if (forceSynchronousDrain.SetUpNoChecks(module.get(), true /*failureExpected*/)
                             .isOk()) {
-                    if (!std::get<PARAM_SETUP_SEQ>(GetParam())) {
-                        ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq1(
-                                portConfig, commandsAndStates, validatePositionIncrease));
-                    } else {
-                        ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq2(
-                                portConfig, commandsAndStates, validatePositionIncrease));
-                    }
+                    ASSERT_NO_FATAL_FAILURE(runStreamIoCommands(portConfig));
                 }
             }
         }
@@ -4570,14 +4585,14 @@
     return std::make_shared<StateSequenceFollower>(std::move(d));
 }
 static const NamedCommandSequence kReadSeq =
-        std::make_tuple(std::string("Read"), 0, StreamTypeFilter::ANY, makeBurstCommands(true),
-                        true /*validatePositionIncrease*/);
+        std::make_tuple(std::string("Read"), kAidlVersion1, 0, StreamTypeFilter::ANY,
+                        makeBurstCommands(true), true /*validatePositionIncrease*/);
 static const NamedCommandSequence kWriteSyncSeq =
-        std::make_tuple(std::string("Write"), 0, StreamTypeFilter::SYNC, makeBurstCommands(true),
-                        true /*validatePositionIncrease*/);
+        std::make_tuple(std::string("Write"), kAidlVersion1, 0, StreamTypeFilter::SYNC,
+                        makeBurstCommands(true), true /*validatePositionIncrease*/);
 static const NamedCommandSequence kWriteAsyncSeq =
-        std::make_tuple(std::string("Write"), 0, StreamTypeFilter::ASYNC, makeBurstCommands(false),
-                        true /*validatePositionIncrease*/);
+        std::make_tuple(std::string("Write"), kAidlVersion1, 0, StreamTypeFilter::ASYNC,
+                        makeBurstCommands(false), true /*validatePositionIncrease*/);
 
 std::shared_ptr<StateSequence> makeAsyncDrainCommands(bool isInput) {
     using State = StreamDescriptor::State;
@@ -4606,10 +4621,10 @@
     return std::make_shared<StateSequenceFollower>(std::move(d));
 }
 static const NamedCommandSequence kWriteDrainAsyncSeq = std::make_tuple(
-        std::string("WriteDrain"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
-        makeAsyncDrainCommands(false), false /*validatePositionIncrease*/);
+        std::string("WriteDrain"), kAidlVersion1, kStreamTransientStateTransitionDelayMs,
+        StreamTypeFilter::ASYNC, makeAsyncDrainCommands(false), false /*validatePositionIncrease*/);
 static const NamedCommandSequence kDrainInSeq =
-        std::make_tuple(std::string("Drain"), 0, StreamTypeFilter::ANY,
+        std::make_tuple(std::string("Drain"), kAidlVersion1, 0, StreamTypeFilter::ANY,
                         makeAsyncDrainCommands(true), false /*validatePositionIncrease*/);
 
 std::shared_ptr<StateSequence> makeDrainOutCommands(bool isSync) {
@@ -4631,12 +4646,28 @@
     return std::make_shared<StateSequenceFollower>(std::move(d));
 }
 static const NamedCommandSequence kDrainOutSyncSeq =
-        std::make_tuple(std::string("Drain"), 0, StreamTypeFilter::SYNC, makeDrainOutCommands(true),
-                        false /*validatePositionIncrease*/);
+        std::make_tuple(std::string("Drain"), kAidlVersion1, 0, StreamTypeFilter::SYNC,
+                        makeDrainOutCommands(true), false /*validatePositionIncrease*/);
 static const NamedCommandSequence kDrainOutAsyncSeq =
-        std::make_tuple(std::string("Drain"), 0, StreamTypeFilter::ASYNC,
+        std::make_tuple(std::string("Drain"), kAidlVersion3, 0, StreamTypeFilter::ASYNC,
                         makeDrainOutCommands(false), false /*validatePositionIncrease*/);
 
+std::shared_ptr<StateSequence> makeDrainEarlyOutCommands() {
+    using State = StreamDescriptor::State;
+    auto d = std::make_unique<StateDag>();
+    StateDag::Node last = d->makeFinalNode(State::IDLE);
+    StateDag::Node draining = d->makeNode(State::DRAINING, kDrainReadyEvent, last);
+    draining.children().push_back(d->makeNode(State::DRAINING, kGetStatusCommand, last));
+    StateDag::Node active = d->makeNode(State::ACTIVE, kDrainOutEarlyCommand, draining);
+    StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
+    idle.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, active));
+    d->makeNode(State::STANDBY, kStartCommand, idle);
+    return std::make_shared<StateSequenceFollower>(std::move(d));
+}
+static const NamedCommandSequence kDrainEarlyOutAsyncSeq =
+        std::make_tuple(std::string("DrainEarly"), kAidlVersion3, 0, StreamTypeFilter::ASYNC,
+                        makeDrainEarlyOutCommands(), false /*validatePositionIncrease*/);
+
 std::shared_ptr<StateSequence> makeDrainPauseOutCommands(bool isSync) {
     using State = StreamDescriptor::State;
     auto d = std::make_unique<StateDag>();
@@ -4656,12 +4687,33 @@
     d->makeNode(State::STANDBY, kStartCommand, idle);
     return std::make_shared<StateSequenceFollower>(std::move(d));
 }
-static const NamedCommandSequence kDrainPauseOutSyncSeq = std::make_tuple(
-        std::string("DrainPause"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::SYNC,
-        makeDrainPauseOutCommands(true), false /*validatePositionIncrease*/);
-static const NamedCommandSequence kDrainPauseOutAsyncSeq = std::make_tuple(
-        std::string("DrainPause"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
-        makeDrainPauseOutCommands(false), false /*validatePositionIncrease*/);
+static const NamedCommandSequence kDrainPauseOutSyncSeq =
+        std::make_tuple(std::string("DrainPause"), kAidlVersion1,
+                        kStreamTransientStateTransitionDelayMs, StreamTypeFilter::SYNC,
+                        makeDrainPauseOutCommands(true), false /*validatePositionIncrease*/);
+static const NamedCommandSequence kDrainPauseOutAsyncSeq =
+        std::make_tuple(std::string("DrainPause"), kAidlVersion1,
+                        kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
+                        makeDrainPauseOutCommands(false), false /*validatePositionIncrease*/);
+
+std::shared_ptr<StateSequence> makeDrainEarlyPauseOutCommands() {
+    using State = StreamDescriptor::State;
+    auto d = std::make_unique<StateDag>();
+    StateDag::Node draining = d->makeNodes({std::make_pair(State::DRAINING, kPauseCommand),
+                                            std::make_pair(State::DRAIN_PAUSED, kStartCommand),
+                                            std::make_pair(State::DRAINING, kPauseCommand),
+                                            std::make_pair(State::DRAIN_PAUSED, kBurstCommand)},
+                                           State::TRANSFER_PAUSED);
+    StateDag::Node active = d->makeNode(State::ACTIVE, kDrainOutEarlyCommand, draining);
+    StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
+    idle.children().push_back(d->makeNode(State::TRANSFERRING, kDrainOutEarlyCommand, draining));
+    d->makeNode(State::STANDBY, kStartCommand, idle);
+    return std::make_shared<StateSequenceFollower>(std::move(d));
+}
+static const NamedCommandSequence kDrainEarlyPauseOutAsyncSeq =
+        std::make_tuple(std::string("DrainEarlyPause"), kAidlVersion3,
+                        kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
+                        makeDrainEarlyPauseOutCommands(), false /*validatePositionIncrease*/);
 
 // This sequence also verifies that the capture / presentation position is not reset on standby.
 std::shared_ptr<StateSequence> makeStandbyCommands(bool isInput, bool isSync) {
@@ -4703,14 +4755,15 @@
     return std::make_shared<StateSequenceFollower>(std::move(d));
 }
 static const NamedCommandSequence kStandbyInSeq =
-        std::make_tuple(std::string("Standby"), 0, StreamTypeFilter::ANY,
+        std::make_tuple(std::string("Standby"), kAidlVersion1, 0, StreamTypeFilter::ANY,
                         makeStandbyCommands(true, false), false /*validatePositionIncrease*/);
 static const NamedCommandSequence kStandbyOutSyncSeq =
-        std::make_tuple(std::string("Standby"), 0, StreamTypeFilter::SYNC,
+        std::make_tuple(std::string("Standby"), kAidlVersion1, 0, StreamTypeFilter::SYNC,
                         makeStandbyCommands(false, true), false /*validatePositionIncrease*/);
-static const NamedCommandSequence kStandbyOutAsyncSeq = std::make_tuple(
-        std::string("Standby"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
-        makeStandbyCommands(false, false), false /*validatePositionIncrease*/);
+static const NamedCommandSequence kStandbyOutAsyncSeq =
+        std::make_tuple(std::string("Standby"), kAidlVersion1,
+                        kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
+                        makeStandbyCommands(false, false), false /*validatePositionIncrease*/);
 
 std::shared_ptr<StateSequence> makePauseCommands(bool isInput, bool isSync) {
     using State = StreamDescriptor::State;
@@ -4745,14 +4798,15 @@
     return std::make_shared<StateSequenceFollower>(std::move(d));
 }
 static const NamedCommandSequence kPauseInSeq =
-        std::make_tuple(std::string("Pause"), 0, StreamTypeFilter::ANY,
+        std::make_tuple(std::string("Pause"), kAidlVersion1, 0, StreamTypeFilter::ANY,
                         makePauseCommands(true, false), false /*validatePositionIncrease*/);
 static const NamedCommandSequence kPauseOutSyncSeq =
-        std::make_tuple(std::string("Pause"), 0, StreamTypeFilter::SYNC,
+        std::make_tuple(std::string("Pause"), kAidlVersion1, 0, StreamTypeFilter::SYNC,
                         makePauseCommands(false, true), false /*validatePositionIncrease*/);
-static const NamedCommandSequence kPauseOutAsyncSeq = std::make_tuple(
-        std::string("Pause"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
-        makePauseCommands(false, false), false /*validatePositionIncrease*/);
+static const NamedCommandSequence kPauseOutAsyncSeq =
+        std::make_tuple(std::string("Pause"), kAidlVersion1, kStreamTransientStateTransitionDelayMs,
+                        StreamTypeFilter::ASYNC, makePauseCommands(false, false),
+                        false /*validatePositionIncrease*/);
 
 std::shared_ptr<StateSequence> makeFlushCommands(bool isInput, bool isSync) {
     using State = StreamDescriptor::State;
@@ -4780,14 +4834,15 @@
     return std::make_shared<StateSequenceFollower>(std::move(d));
 }
 static const NamedCommandSequence kFlushInSeq =
-        std::make_tuple(std::string("Flush"), 0, StreamTypeFilter::ANY,
+        std::make_tuple(std::string("Flush"), kAidlVersion1, 0, StreamTypeFilter::ANY,
                         makeFlushCommands(true, false), false /*validatePositionIncrease*/);
 static const NamedCommandSequence kFlushOutSyncSeq =
-        std::make_tuple(std::string("Flush"), 0, StreamTypeFilter::SYNC,
+        std::make_tuple(std::string("Flush"), kAidlVersion1, 0, StreamTypeFilter::SYNC,
                         makeFlushCommands(false, true), false /*validatePositionIncrease*/);
-static const NamedCommandSequence kFlushOutAsyncSeq = std::make_tuple(
-        std::string("Flush"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
-        makeFlushCommands(false, false), false /*validatePositionIncrease*/);
+static const NamedCommandSequence kFlushOutAsyncSeq =
+        std::make_tuple(std::string("Flush"), kAidlVersion1, kStreamTransientStateTransitionDelayMs,
+                        StreamTypeFilter::ASYNC, makeFlushCommands(false, false),
+                        false /*validatePositionIncrease*/);
 
 std::shared_ptr<StateSequence> makeDrainPauseFlushOutCommands(bool isSync) {
     using State = StreamDescriptor::State;
@@ -4807,13 +4862,13 @@
     return std::make_shared<StateSequenceFollower>(std::move(d));
 }
 static const NamedCommandSequence kDrainPauseFlushOutSyncSeq =
-        std::make_tuple(std::string("DrainPauseFlush"), kStreamTransientStateTransitionDelayMs,
-                        StreamTypeFilter::SYNC, makeDrainPauseFlushOutCommands(true),
-                        false /*validatePositionIncrease*/);
+        std::make_tuple(std::string("DrainPauseFlush"), kAidlVersion1,
+                        kStreamTransientStateTransitionDelayMs, StreamTypeFilter::SYNC,
+                        makeDrainPauseFlushOutCommands(true), false /*validatePositionIncrease*/);
 static const NamedCommandSequence kDrainPauseFlushOutAsyncSeq =
-        std::make_tuple(std::string("DrainPauseFlush"), kStreamTransientStateTransitionDelayMs,
-                        StreamTypeFilter::ASYNC, makeDrainPauseFlushOutCommands(false),
-                        false /*validatePositionIncrease*/);
+        std::make_tuple(std::string("DrainPauseFlush"), kAidlVersion1,
+                        kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
+                        makeDrainPauseFlushOutCommands(false), false /*validatePositionIncrease*/);
 
 // Note, this isn't the "official" enum printer, it is only used to make the test name suffix.
 std::string PrintStreamFilterToString(StreamTypeFilter filter) {
@@ -4851,9 +4906,10 @@
         AudioStreamIoOutTest, AudioStreamIoOut,
         testing::Combine(testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)),
                          testing::Values(kWriteSyncSeq, kWriteAsyncSeq, kWriteDrainAsyncSeq,
-                                         kDrainOutSyncSeq, kDrainPauseOutSyncSeq,
-                                         kDrainPauseOutAsyncSeq, kStandbyOutSyncSeq,
-                                         kStandbyOutAsyncSeq,
+                                         kDrainOutSyncSeq, kDrainOutAsyncSeq,
+                                         kDrainEarlyOutAsyncSeq, kDrainPauseOutSyncSeq,
+                                         kDrainPauseOutAsyncSeq, kDrainEarlyPauseOutAsyncSeq,
+                                         kStandbyOutSyncSeq, kStandbyOutAsyncSeq,
                                          kPauseOutSyncSeq,  // kPauseOutAsyncSeq,
                                          kFlushOutSyncSeq, kFlushOutAsyncSeq,
                                          kDrainPauseFlushOutSyncSeq, kDrainPauseFlushOutAsyncSeq),
diff --git a/automotive/can/1.0/default/libnetdevice/Android.bp b/automotive/can/1.0/default/libnetdevice/Android.bp
index affbeee..4131a65 100644
--- a/automotive/can/1.0/default/libnetdevice/Android.bp
+++ b/automotive/can/1.0/default/libnetdevice/Android.bp
@@ -23,11 +23,20 @@
     default_applicable_licenses: ["hardware_interfaces_license"],
 }
 
-cc_library_static {
-    name: "android.hardware.automotive.can@libnetdevice",
-    defaults: ["android.hardware.automotive.can@defaults"],
+cc_defaults {
+    name: "libnetdevice-common",
     host_supported: true,
     vendor_available: true,
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
+    ],
+    shared_libs: [
+        "libbase",
+        "libutils",
+    ],
     srcs: [
         "can.cpp",
         "common.cpp",
@@ -41,3 +50,14 @@
         "libnl++",
     ],
 }
+
+// TODO: migrate to "libnetdevice" and remove
+cc_library_static {
+    name: "android.hardware.automotive.can@libnetdevice",
+    defaults: ["libnetdevice-common"],
+}
+
+cc_library_static {
+    name: "libnetdevice",
+    defaults: ["libnetdevice-common"],
+}
diff --git a/automotive/can/1.0/default/libnetdevice/include/libnetdevice/libnetdevice.h b/automotive/can/1.0/default/libnetdevice/include/libnetdevice/libnetdevice.h
index 75655d5..15ff491 100644
--- a/automotive/can/1.0/default/libnetdevice/include/libnetdevice/libnetdevice.h
+++ b/automotive/can/1.0/default/libnetdevice/include/libnetdevice/libnetdevice.h
@@ -128,7 +128,8 @@
  * \param addr IPv4 address to set
  * \return true in case of success, false otherwise
  */
-bool setAddr4(std::string_view ifname, std::string_view addr);
+bool setAddr4(std::string_view ifname, std::string_view addr,
+              std::optional<uint8_t> prefixlen = std::nullopt);
 
 /**
  * Add new IPv4 address to a given interface.
diff --git a/automotive/can/1.0/default/libnetdevice/libnetdevice.cpp b/automotive/can/1.0/default/libnetdevice/libnetdevice.cpp
index 1830633..9bb1a57 100644
--- a/automotive/can/1.0/default/libnetdevice/libnetdevice.cpp
+++ b/automotive/can/1.0/default/libnetdevice/libnetdevice.cpp
@@ -93,14 +93,31 @@
     return addrn;
 }
 
-bool setAddr4(std::string_view ifname, std::string_view addr) {
-    auto ifr = ifreqs::fromName(ifname);
+static in_addr_t prefixLengthToIpv4Netmask(uint8_t prefixlen) {
+    in_addr_t zero = 0;
+    return htonl(~zero << (32 - prefixlen));
+}
 
-    struct sockaddr_in* ifrAddr = reinterpret_cast<sockaddr_in*>(&ifr.ifr_addr);
+bool setAddr4(std::string_view ifname, std::string_view addr, std::optional<uint8_t> prefixlen) {
+    auto ifr = ifreqs::fromName(ifname);
+    auto ifrAddr = reinterpret_cast<sockaddr_in*>(&ifr.ifr_addr);
     ifrAddr->sin_family = AF_INET;
     ifrAddr->sin_addr.s_addr = inetAddr(addr);
+    if (!ifreqs::send(SIOCSIFADDR, ifr)) return false;
 
-    return ifreqs::send(SIOCSIFADDR, ifr);
+    if (prefixlen.has_value()) {
+        if (*prefixlen < 0 || *prefixlen > 32) {
+            LOG(ERROR) << "Invalid prefix length: " << *prefixlen;
+            return false;
+        }
+        ifr = ifreqs::fromName(ifname);
+        auto ifrNetmask = reinterpret_cast<sockaddr_in*>(&ifr.ifr_netmask);
+        ifrNetmask->sin_family = AF_INET;
+        ifrNetmask->sin_addr.s_addr = prefixLengthToIpv4Netmask(*prefixlen);
+        if (!ifreqs::send(SIOCSIFNETMASK, ifr)) return false;
+    }
+
+    return true;
 }
 
 bool addAddr4(std::string_view ifname, std::string_view addr, uint8_t prefixlen) {
diff --git a/automotive/can/1.0/default/libnl++/Android.bp b/automotive/can/1.0/default/libnl++/Android.bp
index 5e3168a..ade4ae0 100644
--- a/automotive/can/1.0/default/libnl++/Android.bp
+++ b/automotive/can/1.0/default/libnl++/Android.bp
@@ -25,9 +25,18 @@
 
 cc_library_static {
     name: "libnl++",
-    defaults: ["android.hardware.automotive.can@defaults"],
     host_supported: true,
     vendor_available: true,
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
+    ],
+    shared_libs: [
+        "libbase",
+        "libutils",
+    ],
     srcs: [
         "protocols/common/Empty.cpp",
         "protocols/common/Error.cpp",
diff --git a/automotive/evs/aidl/Android.bp b/automotive/evs/aidl/Android.bp
index 75eb924..8983ae4 100644
--- a/automotive/evs/aidl/Android.bp
+++ b/automotive/evs/aidl/Android.bp
@@ -55,14 +55,14 @@
             version: "1",
             imports: [
                 "android.hardware.common-V2",
-                "android.hardware.graphics.common-V5",
+                "android.hardware.graphics.common-V6",
             ],
         },
         {
             version: "2",
             imports: [
                 "android.hardware.common-V2",
-                "android.hardware.graphics.common-V5",
+                "android.hardware.graphics.common-V6",
             ],
         },
 
diff --git a/camera/device/aidl/Android.bp b/camera/device/aidl/Android.bp
index 48ae34e..291ed0d 100644
--- a/camera/device/aidl/Android.bp
+++ b/camera/device/aidl/Android.bp
@@ -44,7 +44,7 @@
                 "android.hardware.common.fmq-V1",
                 "android.hardware.camera.common-V1",
                 "android.hardware.camera.metadata-V1",
-                "android.hardware.graphics.common-V5",
+                "android.hardware.graphics.common-V6",
             ],
         },
         {
@@ -54,7 +54,7 @@
                 "android.hardware.common.fmq-V1",
                 "android.hardware.camera.common-V1",
                 "android.hardware.camera.metadata-V2",
-                "android.hardware.graphics.common-V5",
+                "android.hardware.graphics.common-V6",
             ],
         },
         {
@@ -64,7 +64,7 @@
                 "android.hardware.common.fmq-V1",
                 "android.hardware.camera.common-V1",
                 "android.hardware.camera.metadata-V3",
-                "android.hardware.graphics.common-V5",
+                "android.hardware.graphics.common-V6",
             ],
         },
 
diff --git a/contexthub/aidl/default/Android.bp b/contexthub/aidl/default/Android.bp
index da173f9..c0b147c 100644
--- a/contexthub/aidl/default/Android.bp
+++ b/contexthub/aidl/default/Android.bp
@@ -30,6 +30,7 @@
     shared_libs: [
         "libbase",
         "libbinder_ndk",
+        "liblog",
         "android.hardware.contexthub-V4-ndk",
     ],
     export_include_dirs: ["include"],
@@ -51,6 +52,7 @@
     shared_libs: [
         "libbase",
         "libbinder_ndk",
+        "liblog",
         "android.hardware.contexthub-V4-ndk",
     ],
     static_libs: [
diff --git a/contexthub/aidl/default/ContextHub.cpp b/contexthub/aidl/default/ContextHub.cpp
index a915191..4ae9c09 100644
--- a/contexthub/aidl/default/ContextHub.cpp
+++ b/contexthub/aidl/default/ContextHub.cpp
@@ -16,10 +16,54 @@
 
 #include "contexthub-impl/ContextHub.h"
 
-namespace aidl::android::hardware::contexthub {
+#ifndef LOG_TAG
+#define LOG_TAG "CHRE"
+#endif
+
+#include <inttypes.h>
+#include <log/log.h>
 
 using ::ndk::ScopedAStatus;
 
+namespace aidl::android::hardware::contexthub {
+
+namespace {
+
+constexpr uint64_t kMockVendorHubId = 0x1234567812345678;
+constexpr uint64_t kMockVendorHub2Id = 0x0EADBEEFDEADBEEF;
+
+// Mock endpoints for the default implementation.
+// These endpoints just echo back any messages sent to them.
+constexpr size_t kMockEndpointCount = 4;
+const EndpointInfo kMockEndpointInfos[kMockEndpointCount] = {
+        {
+                .id = {.hubId = kMockVendorHubId, .id = UINT64_C(0x1)},
+                .type = EndpointInfo::EndpointType::GENERIC,
+                .name = "Mock Endpoint 1",
+                .version = 1,
+        },
+        {
+                .id = {.hubId = kMockVendorHubId, .id = UINT64_C(0x2)},
+                .type = EndpointInfo::EndpointType::GENERIC,
+                .name = "Mock Endpoint 2",
+                .version = 2,
+        },
+        {
+                .id = {.hubId = kMockVendorHub2Id, .id = UINT64_C(0x1)},
+                .type = EndpointInfo::EndpointType::GENERIC,
+                .name = "Mock Endpoint 3",
+                .version = 1,
+        },
+        {
+                .id = {.hubId = kMockVendorHub2Id, .id = UINT64_C(0x2)},
+                .type = EndpointInfo::EndpointType::GENERIC,
+                .name = "Mock Endpoint 4",
+                .version = 2,
+        },
+};
+
+}  // anonymous namespace
+
 ScopedAStatus ContextHub::getContextHubs(std::vector<ContextHubInfo>* out_contextHubInfos) {
     ContextHubInfo hub = {};
     hub.name = "Mock Context Hub";
@@ -112,7 +156,13 @@
     }
 }
 
-ScopedAStatus ContextHub::setTestMode(bool /* enable */) {
+ScopedAStatus ContextHub::setTestMode(bool enable) {
+    if (enable) {
+        std::unique_lock<std::mutex> lock(mEndpointMutex);
+        mEndpoints.clear();
+        mEndpointSessions.clear();
+        mEndpointCallback = nullptr;
+    }
     return ScopedAStatus::ok();
 }
 
@@ -137,6 +187,10 @@
 }
 
 ScopedAStatus ContextHub::getHubs(std::vector<HubInfo>* _aidl_return) {
+    if (_aidl_return == nullptr) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+
     ContextHubInfo hub = {};
     hub.name = "Mock Context Hub";
     hub.vendor = "AOSP";
@@ -158,61 +212,217 @@
     vendorHub.version = 42;
 
     HubInfo hubInfo2 = {};
-    hubInfo2.hubId = UINT64_C(0x1234567812345678);
+    hubInfo2.hubId = kMockVendorHubId;
     hubInfo2.hubDetails =
             HubInfo::HubDetails::make<HubInfo::HubDetails::Tag::vendorHubInfo>(vendorHub);
 
+    VendorHubInfo vendorHub2 = {};
+    vendorHub2.name = "Mock Vendor Hub 2";
+    vendorHub2.version = 24;
+
+    HubInfo hubInfo3 = {};
+    hubInfo3.hubId = kMockVendorHub2Id;
+    hubInfo3.hubDetails =
+            HubInfo::HubDetails::make<HubInfo::HubDetails::Tag::vendorHubInfo>(vendorHub2);
+
     _aidl_return->push_back(hubInfo1);
     _aidl_return->push_back(hubInfo2);
+    _aidl_return->push_back(hubInfo3);
 
     return ScopedAStatus::ok();
 };
 
-ScopedAStatus ContextHub::getEndpoints(std::vector<EndpointInfo>* /* _aidl_return */) {
-    return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ScopedAStatus ContextHub::getEndpoints(std::vector<EndpointInfo>* _aidl_return) {
+    if (_aidl_return == nullptr) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+
+    Service echoService;
+    echoService.format = Service::RpcFormat::CUSTOM;
+    echoService.serviceDescriptor = "ECHO";
+    echoService.majorVersion = 1;
+    echoService.minorVersion = 0;
+
+    for (const EndpointInfo& endpoint : kMockEndpointInfos) {
+        EndpointInfo endpointWithService(endpoint);
+        endpointWithService.services.push_back(echoService);
+        _aidl_return->push_back(std::move(endpointWithService));
+    }
+
+    return ScopedAStatus::ok();
 };
 
-ScopedAStatus ContextHub::registerEndpoint(const EndpointInfo& /* in_endpoint */) {
-    return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ScopedAStatus ContextHub::registerEndpoint(const EndpointInfo& in_endpoint) {
+    std::unique_lock<std::mutex> lock(mEndpointMutex);
+
+    for (const EndpointInfo& endpoint : mEndpoints) {
+        if ((endpoint.id.id == in_endpoint.id.id && endpoint.id.hubId == in_endpoint.id.hubId) ||
+            endpoint.name == in_endpoint.name) {
+            return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+    }
+    mEndpoints.push_back(in_endpoint);
+    return ScopedAStatus::ok();
 };
 
-ScopedAStatus ContextHub::unregisterEndpoint(const EndpointInfo& /* in_endpoint */) {
-    return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ScopedAStatus ContextHub::unregisterEndpoint(const EndpointInfo& in_endpoint) {
+    std::unique_lock<std::mutex> lock(mEndpointMutex);
+
+    for (auto it = mEndpoints.begin(); it != mEndpoints.end(); ++it) {
+        if (it->id.id == in_endpoint.id.id && it->id.hubId == in_endpoint.id.hubId) {
+            mEndpoints.erase(it);
+            return ScopedAStatus::ok();
+        }
+    }
+    return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
 };
 
 ScopedAStatus ContextHub::registerEndpointCallback(
-        const std::shared_ptr<IEndpointCallback>& /* in_callback */) {
-    return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+        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::vector<int32_t>* /* _aidl_return */) {
-    return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ScopedAStatus ContextHub::requestSessionIdRange(int32_t in_size,
+                                                std::vector<int32_t>* _aidl_return) {
+    constexpr int32_t kMaxSize = 1024;
+    if (in_size > kMaxSize || _aidl_return == nullptr) {
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+
+    {
+        std::lock_guard<std::mutex> lock(mEndpointMutex);
+        mMaxValidSessionId = in_size;
+    }
+
+    _aidl_return->push_back(0);
+    _aidl_return->push_back(in_size);
+    return ScopedAStatus::ok();
 };
 
 ScopedAStatus ContextHub::openEndpointSession(
-        int32_t /* in_sessionId */, const EndpointId& /* in_destination */,
-        const EndpointId& /* in_initiator */,
-        const std::optional<std::string>& /* in_serviceDescriptor */) {
-    return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+        int32_t in_sessionId, const EndpointId& in_destination, const EndpointId& in_initiator,
+        const std::optional<std::string>& in_serviceDescriptor) {
+    // We are not calling onCloseEndpointSession on failure because the remote endpoints (our
+    // mock endpoints) always accept the session.
+
+    std::shared_ptr<IEndpointCallback> callback = nullptr;
+    {
+        std::unique_lock<std::mutex> lock(mEndpointMutex);
+        if (in_sessionId > mMaxValidSessionId) {
+            ALOGE("openEndpointSession: session ID %" PRId32 " is invalid", in_sessionId);
+            return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+
+        for (const EndpointSession& session : mEndpointSessions) {
+            bool sessionAlreadyExists =
+                    (session.initiator == in_destination && session.peer == in_initiator) ||
+                    (session.peer == in_destination && session.initiator == in_initiator);
+            if (sessionAlreadyExists) {
+                ALOGD("openEndpointSession: session ID %" PRId32 " already exists", in_sessionId);
+                return (session.sessionId == in_sessionId &&
+                        session.serviceDescriptor == in_serviceDescriptor)
+                               ? ScopedAStatus::ok()
+                               : ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+            } else if (session.sessionId == in_sessionId) {
+                ALOGE("openEndpointSession: session ID %" PRId32 " is invalid: endpoint mismatch",
+                      in_sessionId);
+                return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+            }
+        }
+
+        // Verify the initiator and destination are valid endpoints
+        bool initiatorIsValid = findEndpoint(in_initiator, mEndpoints.begin(), mEndpoints.end());
+        if (!initiatorIsValid) {
+            ALOGE("openEndpointSession: initiator %" PRIu64 ":%" PRIu64 " is invalid",
+                  in_initiator.id, in_initiator.hubId);
+            return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+        bool destinationIsValid = findEndpoint(in_destination, &kMockEndpointInfos[0],
+                                               &kMockEndpointInfos[kMockEndpointCount]);
+        if (!destinationIsValid) {
+            ALOGE("openEndpointSession: destination %" PRIu64 ":%" PRIu64 " is invalid",
+                  in_destination.id, in_destination.hubId);
+            return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
+
+        mEndpointSessions.push_back({
+                .sessionId = in_sessionId,
+                .initiator = in_initiator,
+                .peer = in_destination,
+                .serviceDescriptor = in_serviceDescriptor,
+        });
+
+        if (mEndpointCallback != nullptr) {
+            callback = mEndpointCallback;
+        }
+    }
+
+    if (callback != nullptr) {
+        callback->onEndpointSessionOpenComplete(in_sessionId);
+    }
+    return ScopedAStatus::ok();
 };
 
-ScopedAStatus ContextHub::sendMessageToEndpoint(int32_t /* in_sessionId */,
-                                                const Message& /* in_msg */) {
-    return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ScopedAStatus ContextHub::sendMessageToEndpoint(int32_t in_sessionId, const Message& in_msg) {
+    bool foundSession = false;
+    std::shared_ptr<IEndpointCallback> callback = nullptr;
+    {
+        std::unique_lock<std::mutex> lock(mEndpointMutex);
+
+        for (const EndpointSession& session : mEndpointSessions) {
+            if (session.sessionId == in_sessionId) {
+                foundSession = true;
+                break;
+            }
+        }
+
+        if (mEndpointCallback != nullptr) {
+            callback = mEndpointCallback;
+        }
+    }
+
+    if (!foundSession) {
+        ALOGE("sendMessageToEndpoint: session ID %" PRId32 " is invalid", in_sessionId);
+        return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
+
+    if (callback != nullptr) {
+        if (in_msg.flags & Message::FLAG_REQUIRES_DELIVERY_STATUS) {
+            MessageDeliveryStatus msgStatus = {};
+            msgStatus.messageSequenceNumber = in_msg.sequenceNumber;
+            msgStatus.errorCode = ErrorCode::OK;
+            callback->onMessageDeliveryStatusReceived(in_sessionId, msgStatus);
+        }
+
+        // Echo the message back
+        callback->onMessageReceived(in_sessionId, in_msg);
+    }
+    return ScopedAStatus::ok();
 };
 
 ScopedAStatus ContextHub::sendMessageDeliveryStatusToEndpoint(
         int32_t /* in_sessionId */, const MessageDeliveryStatus& /* in_msgStatus */) {
-    return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    return ScopedAStatus::ok();
 };
 
-ScopedAStatus ContextHub::closeEndpointSession(int32_t /* in_sessionId */, Reason /* in_reason */) {
-    return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ScopedAStatus ContextHub::closeEndpointSession(int32_t in_sessionId, Reason /* in_reason */) {
+    std::unique_lock<std::mutex> lock(mEndpointMutex);
+
+    for (auto it = mEndpointSessions.begin(); it != mEndpointSessions.end(); ++it) {
+        if (it->sessionId == in_sessionId) {
+            mEndpointSessions.erase(it);
+            return ScopedAStatus::ok();
+        }
+    }
+    ALOGE("closeEndpointSession: session ID %" PRId32 " is invalid", in_sessionId);
+    return ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
 };
 
 ScopedAStatus ContextHub::endpointSessionOpenComplete(int32_t /* in_sessionId */) {
-    return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    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 5680a77..4968878 100644
--- a/contexthub/aidl/default/include/contexthub-impl/ContextHub.h
+++ b/contexthub/aidl/default/include/contexthub-impl/ContextHub.h
@@ -18,6 +18,7 @@
 
 #include <aidl/android/hardware/contexthub/BnContextHub.h>
 
+#include <mutex>
 #include <unordered_set>
 #include <vector>
 
@@ -72,10 +73,37 @@
     ::ndk::ScopedAStatus endpointSessionOpenComplete(int32_t in_sessionId) override;
 
   private:
+    struct EndpointSession {
+        int32_t sessionId;
+        EndpointId initiator;
+        EndpointId peer;
+        std::optional<std::string> serviceDescriptor;
+    };
+
     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;
 };
 
 }  // namespace contexthub
diff --git a/contexthub/aidl/vts/Android.bp b/contexthub/aidl/vts/Android.bp
index 62a319e..a19b6fd 100644
--- a/contexthub/aidl/vts/Android.bp
+++ b/contexthub/aidl/vts/Android.bp
@@ -33,7 +33,7 @@
         "libbinder",
     ],
     static_libs: [
-        "android.hardware.contexthub-V3-cpp",
+        "android.hardware.contexthub-V4-cpp",
         "VtsHalContexthubUtils",
     ],
     test_suites: [
diff --git a/contexthub/aidl/vts/VtsAidlHalContextHubTargetTest.cpp b/contexthub/aidl/vts/VtsAidlHalContextHubTargetTest.cpp
index fa427a5..95a96cd 100644
--- a/contexthub/aidl/vts/VtsAidlHalContextHubTargetTest.cpp
+++ b/contexthub/aidl/vts/VtsAidlHalContextHubTargetTest.cpp
@@ -20,8 +20,10 @@
 
 #include <android/hardware/contexthub/BnContextHub.h>
 #include <android/hardware/contexthub/BnContextHubCallback.h>
+#include <android/hardware/contexthub/BnEndpointCallback.h>
 #include <android/hardware/contexthub/IContextHub.h>
 #include <android/hardware/contexthub/IContextHubCallback.h>
+#include <android/hardware/contexthub/IEndpointCallback.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
 #include <log/log.h>
@@ -34,18 +36,25 @@
 using ::android::String16;
 using ::android::binder::Status;
 using ::android::hardware::contexthub::AsyncEventType;
+using ::android::hardware::contexthub::BnEndpointCallback;
 using ::android::hardware::contexthub::ContextHubInfo;
 using ::android::hardware::contexthub::ContextHubMessage;
+using ::android::hardware::contexthub::EndpointId;
+using ::android::hardware::contexthub::EndpointInfo;
 using ::android::hardware::contexthub::ErrorCode;
 using ::android::hardware::contexthub::HostEndpointInfo;
+using ::android::hardware::contexthub::HubInfo;
 using ::android::hardware::contexthub::IContextHub;
 using ::android::hardware::contexthub::IContextHubCallbackDefault;
+using ::android::hardware::contexthub::Message;
 using ::android::hardware::contexthub::MessageDeliveryStatus;
 using ::android::hardware::contexthub::NanoappBinary;
 using ::android::hardware::contexthub::NanoappInfo;
 using ::android::hardware::contexthub::NanoappRpcService;
 using ::android::hardware::contexthub::NanSessionRequest;
 using ::android::hardware::contexthub::NanSessionStateUpdate;
+using ::android::hardware::contexthub::Reason;
+using ::android::hardware::contexthub::Service;
 using ::android::hardware::contexthub::Setting;
 using ::android::hardware::contexthub::vts_utils::kNonExistentAppId;
 using ::android::hardware::contexthub::vts_utils::waitForCallback;
@@ -61,8 +70,14 @@
         contextHub = 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);
     }
 
+    virtual void TearDown() override { contextHub->setTestMode(/* enable= */ false); }
+
     uint32_t getHubId() { return std::get<1>(GetParam()); }
 
     void testSettingChanged(Setting setting);
@@ -465,6 +480,290 @@
     }
 }
 
+class TestEndpointCallback : public BnEndpointCallback {
+  public:
+    Status onEndpointStarted(const std::vector<EndpointInfo>& /* endpointInfos */) override {
+        return Status::ok();
+    }
+
+    Status onEndpointStopped(const std::vector<EndpointId>& /* endpointIds */,
+                             Reason /* reason */) override {
+        return Status::ok();
+    }
+
+    Status onMessageReceived(int32_t /* sessionId */, const Message& message) override {
+        mMessages.push_back(message);
+        return Status::ok();
+    }
+
+    Status onMessageDeliveryStatusReceived(int32_t /* sessionId */,
+                                           const MessageDeliveryStatus& /* msgStatus */) override {
+        return Status::ok();
+    }
+
+    Status onEndpointSessionOpenRequest(
+            int32_t /* sessionId */, const EndpointId& /* destination */,
+            const EndpointId& /* initiator */,
+            const std::optional<String16>& /* serviceDescriptor */) override {
+        return Status::ok();
+    }
+
+    Status onCloseEndpointSession(int32_t /* sessionId */, Reason /* reason */) override {
+        return Status::ok();
+    }
+
+    Status onEndpointSessionOpenComplete(int32_t /* sessionId */) override {
+        mWasOnEndpointSessionOpenCompleteCalled = true;
+        return Status::ok();
+    }
+
+    std::vector<Message> getMessages() { return mMessages; }
+
+    bool wasOnEndpointSessionOpenCompleteCalled() {
+        return mWasOnEndpointSessionOpenCompleteCalled;
+    }
+    void resetWasOnEndpointSessionOpenCompleteCalled() {
+        mWasOnEndpointSessionOpenCompleteCalled = false;
+    }
+
+  private:
+    std::vector<Message> mMessages;
+    bool mWasOnEndpointSessionOpenCompleteCalled = false;
+};
+
+TEST_P(ContextHubAidl, RegisterEndpoint) {
+    EndpointInfo endpointInfo;
+    endpointInfo.id.id = 1;
+    endpointInfo.id.hubId = 0xCAFECAFECAFECAFE;
+    endpointInfo.type = EndpointInfo::EndpointType::NATIVE;
+    endpointInfo.name = String16("Test host endpoint 1");
+    endpointInfo.version = 42;
+
+    Status status = contextHub->registerEndpoint(endpointInfo);
+    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());
+    }
+}
+
+TEST_P(ContextHubAidl, RegisterEndpointSameNameFailure) {
+    EndpointInfo endpointInfo;
+    endpointInfo.id.id = 2;
+    endpointInfo.id.hubId = 0xCAFECAFECAFECAFE;
+    endpointInfo.type = EndpointInfo::EndpointType::NATIVE;
+    endpointInfo.name = String16("Test host endpoint 2");
+    endpointInfo.version = 42;
+
+    EndpointInfo endpointInfo2;
+    endpointInfo2.id.id = 3;
+    endpointInfo2.id.hubId = 0xCAFECAFECAFECAFE;
+    endpointInfo2.type = EndpointInfo::EndpointType::NATIVE;
+    endpointInfo2.name = String16("Test host endpoint 2");
+    endpointInfo2.version = 42;
+
+    Status status = contextHub->registerEndpoint(endpointInfo);
+    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());
+    }
+
+    EXPECT_FALSE(contextHub->registerEndpoint(endpointInfo2).isOk());
+}
+
+TEST_P(ContextHubAidl, RegisterEndpointSameIdFailure) {
+    EndpointInfo endpointInfo;
+    endpointInfo.id.id = 4;
+    endpointInfo.id.hubId = 0xCAFECAFECAFECAFE;
+    endpointInfo.type = EndpointInfo::EndpointType::NATIVE;
+    endpointInfo.name = String16("Test host endpoint 4");
+    endpointInfo.version = 42;
+
+    EndpointInfo endpointInfo2;
+    endpointInfo2.id.id = 4;
+    endpointInfo2.id.hubId = 0xCAFECAFECAFECAFE;
+    endpointInfo2.type = EndpointInfo::EndpointType::NATIVE;
+    endpointInfo2.name = String16("Test host endpoint - same ID test");
+    endpointInfo2.version = 42;
+
+    Status status = contextHub->registerEndpoint(endpointInfo);
+    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());
+    }
+
+    EXPECT_FALSE(contextHub->registerEndpoint(endpointInfo2).isOk());
+}
+
+TEST_P(ContextHubAidl, UnregisterEndpoint) {
+    EndpointInfo endpointInfo;
+    endpointInfo.id.id = 6;
+    endpointInfo.id.hubId = 0xCAFECAFECAFECAFE;
+    endpointInfo.type = EndpointInfo::EndpointType::NATIVE;
+    endpointInfo.name = String16("Test host endpoint 6");
+    endpointInfo.version = 42;
+
+    Status status = contextHub->registerEndpoint(endpointInfo);
+    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());
+    }
+
+    EXPECT_TRUE(contextHub->unregisterEndpoint(endpointInfo).isOk());
+}
+
+TEST_P(ContextHubAidl, UnregisterEndpointNonexistent) {
+    EndpointInfo endpointInfo;
+    endpointInfo.id.id = 100;
+    endpointInfo.id.hubId = 0xCAFECAFECAFECAFE;
+    endpointInfo.type = EndpointInfo::EndpointType::NATIVE;
+    endpointInfo.name = String16("Test host endpoint 100");
+    endpointInfo.version = 42;
+
+    Status status = contextHub->unregisterEndpoint(endpointInfo);
+    if (status.exceptionCode() == Status::EX_UNSUPPORTED_OPERATION ||
+        status.transactionError() == android::UNKNOWN_TRANSACTION) {
+        GTEST_SKIP() << "Not supported -> old API; or not implemented";
+    } else {
+        EXPECT_FALSE(status.isOk());
+    }
+}
+
+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) {
+        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
+    EndpointInfo initiatorEndpoint;
+    initiatorEndpoint.id.id = 7;
+    initiatorEndpoint.id.hubId = 0xCAFECAFECAFECAFE;
+    initiatorEndpoint.type = EndpointInfo::EndpointType::NATIVE;
+    initiatorEndpoint.name = String16("Test host endpoint 7");
+    initiatorEndpoint.version = 42;
+    EXPECT_TRUE(contextHub->registerEndpoint(initiatorEndpoint).isOk());
+
+    // Find the destination, if it exists
+    std::vector<EndpointInfo> endpoints;
+    EXPECT_TRUE(contextHub->getEndpoints(&endpoints).isOk());
+    const EndpointInfo* destinationEndpoint = nullptr;
+    for (const EndpointInfo& endpoint : endpoints) {
+        for (const Service& service : endpoint.services) {
+            if (service.serviceDescriptor == String16("ECHO")) {
+                destinationEndpoint = &endpoint;
+                break;
+            }
+        }
+    }
+    if (destinationEndpoint == nullptr) {
+        return;  // no echo service endpoint -> just return
+    }
+
+    // Request the range
+    constexpr int32_t requestedRange = 100;
+    std::vector<int32_t> range;
+    ASSERT_TRUE(contextHub->requestSessionIdRange(requestedRange, &range).isOk());
+    EXPECT_EQ(range.size(), 2);
+    EXPECT_GE(range[1] - range[0] + 1, requestedRange);
+
+    // Open the session
+    cb->resetWasOnEndpointSessionOpenCompleteCalled();
+    int32_t sessionId = range[1] + 10;  // invalid
+    EXPECT_FALSE(contextHub
+                         ->openEndpointSession(sessionId, destinationEndpoint->id,
+                                               initiatorEndpoint.id,
+                                               /* in_serviceDescriptor= */ String16("ECHO"))
+                         .isOk());
+    EXPECT_FALSE(cb->wasOnEndpointSessionOpenCompleteCalled());
+}
+
+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) {
+        GTEST_SKIP() << "Not supported -> old API; or not implemented";
+    } else {
+        EXPECT_TRUE(status.isOk());
+    }
+
+    // Register the endpoint
+    EndpointInfo initiatorEndpoint;
+    initiatorEndpoint.id.id = 8;
+    initiatorEndpoint.id.hubId = 0xCAFECAFECAFECAFE;
+    initiatorEndpoint.type = EndpointInfo::EndpointType::NATIVE;
+    initiatorEndpoint.name = String16("Test host endpoint 7");
+    initiatorEndpoint.version = 42;
+    EXPECT_TRUE(contextHub->registerEndpoint(initiatorEndpoint).isOk());
+
+    // Find the destination, if it exists
+    std::vector<EndpointInfo> endpoints;
+    EXPECT_TRUE(contextHub->getEndpoints(&endpoints).isOk());
+    const EndpointInfo* destinationEndpoint = nullptr;
+    for (const EndpointInfo& endpoint : endpoints) {
+        for (const Service& service : endpoint.services) {
+            if (service.serviceDescriptor == String16("ECHO")) {
+                destinationEndpoint = &endpoint;
+                break;
+            }
+        }
+    }
+    if (destinationEndpoint == nullptr) {
+        return;  // no echo service endpoint -> just return
+    }
+
+    // Request the range
+    constexpr int32_t requestedRange = 100;
+    std::vector<int32_t> range;
+    ASSERT_TRUE(contextHub->requestSessionIdRange(requestedRange, &range).isOk());
+    EXPECT_EQ(range.size(), 2);
+    EXPECT_GE(range[1] - range[0] + 1, requestedRange);
+
+    // Open the session
+    cb->resetWasOnEndpointSessionOpenCompleteCalled();
+    int32_t sessionId = range[0];
+    ASSERT_TRUE(contextHub
+                        ->openEndpointSession(sessionId, destinationEndpoint->id,
+                                              initiatorEndpoint.id,
+                                              /* in_serviceDescriptor= */ String16("ECHO"))
+                        .isOk());
+    EXPECT_TRUE(cb->wasOnEndpointSessionOpenCompleteCalled());
+
+    // Send the message
+    Message message;
+    message.flags = 0;
+    message.sequenceNumber = 0;
+    message.content.push_back(42);
+    ASSERT_TRUE(contextHub->sendMessageToEndpoint(sessionId, message).isOk());
+
+    // Check for echo
+    EXPECT_FALSE(cb->getMessages().empty());
+    EXPECT_EQ(cb->getMessages().back().content.back(), 42);
+}
+
 std::string PrintGeneratedTest(const testing::TestParamInfo<ContextHubAidl::ParamType>& info) {
     return std::string("CONTEXT_HUB_ID_") + std::to_string(std::get<1>(info.param));
 }
diff --git a/graphics/Android.bp b/graphics/Android.bp
index d768ecf..2213f15 100644
--- a/graphics/Android.bp
+++ b/graphics/Android.bp
@@ -64,14 +64,14 @@
 aidl_interface_defaults {
     name: "android.hardware.graphics.common-latest",
     imports: [
-        "android.hardware.graphics.common-V5",
+        "android.hardware.graphics.common-V6",
     ],
 }
 
 rust_defaults {
     name: "android.hardware.graphics.common-latest-rust",
     rustlibs: [
-        "android.hardware.graphics.common-V5-rust",
+        "android.hardware.graphics.common-V6-rust",
     ],
 }
 
@@ -80,7 +80,7 @@
     target: {
         linux: {
             static_libs: [
-                "android.hardware.graphics.common-V5-ndk",
+                "android.hardware.graphics.common-V6-ndk",
             ],
         },
     },
@@ -91,7 +91,7 @@
     target: {
         linux: {
             shared_libs: [
-                "android.hardware.graphics.common-V5-ndk",
+                "android.hardware.graphics.common-V6-ndk",
             ],
         },
     },
diff --git a/graphics/allocator/aidl/Android.bp b/graphics/allocator/aidl/Android.bp
index 30b341c..3f74b23 100644
--- a/graphics/allocator/aidl/Android.bp
+++ b/graphics/allocator/aidl/Android.bp
@@ -45,7 +45,7 @@
             version: "2",
             imports: [
                 "android.hardware.common-V2",
-                "android.hardware.graphics.common-V5",
+                "android.hardware.graphics.common-V6",
             ],
         },
 
diff --git a/graphics/composer/aidl/Android.bp b/graphics/composer/aidl/Android.bp
index f4264eb..655188d 100644
--- a/graphics/composer/aidl/Android.bp
+++ b/graphics/composer/aidl/Android.bp
@@ -59,21 +59,21 @@
         {
             version: "1",
             imports: [
-                "android.hardware.graphics.common-V5",
+                "android.hardware.graphics.common-V6",
                 "android.hardware.common-V2",
             ],
         },
         {
             version: "2",
             imports: [
-                "android.hardware.graphics.common-V5",
+                "android.hardware.graphics.common-V6",
                 "android.hardware.common-V2",
             ],
         },
         {
             version: "3",
             imports: [
-                "android.hardware.graphics.common-V5",
+                "android.hardware.graphics.common-V6",
                 "android.hardware.common-V2",
             ],
         },
diff --git a/graphics/mapper/4.0/utils/vts/Android.bp b/graphics/mapper/4.0/utils/vts/Android.bp
index c5f124c..1be460e 100644
--- a/graphics/mapper/4.0/utils/vts/Android.bp
+++ b/graphics/mapper/4.0/utils/vts/Android.bp
@@ -48,7 +48,7 @@
     ],
     export_static_lib_headers: [
         "android.hardware.graphics.allocator@4.0",
-        "android.hardware.graphics.common-V5-ndk",
+        "android.hardware.graphics.common-V6-ndk",
         "android.hardware.graphics.mapper@4.0",
     ],
     export_include_dirs: ["include"],
diff --git a/neuralnetworks/aidl/Android.bp b/neuralnetworks/aidl/Android.bp
index 9589750..45b34e6 100644
--- a/neuralnetworks/aidl/Android.bp
+++ b/neuralnetworks/aidl/Android.bp
@@ -43,28 +43,28 @@
             version: "1",
             imports: [
                 "android.hardware.common-V2",
-                "android.hardware.graphics.common-V5",
+                "android.hardware.graphics.common-V6",
             ],
         },
         {
             version: "2",
             imports: [
                 "android.hardware.common-V2",
-                "android.hardware.graphics.common-V5",
+                "android.hardware.graphics.common-V6",
             ],
         },
         {
             version: "3",
             imports: [
                 "android.hardware.common-V2",
-                "android.hardware.graphics.common-V5",
+                "android.hardware.graphics.common-V6",
             ],
         },
         {
             version: "4",
             imports: [
                 "android.hardware.common-V2",
-                "android.hardware.graphics.common-V5",
+                "android.hardware.graphics.common-V6",
             ],
         },
 
diff --git a/nfc/aidl/aidl_api/android.hardware.nfc/current/android/hardware/nfc/ProtocolDiscoveryConfig.aidl b/nfc/aidl/aidl_api/android.hardware.nfc/current/android/hardware/nfc/ProtocolDiscoveryConfig.aidl
index 021dfe2..2df0d35 100644
--- a/nfc/aidl/aidl_api/android.hardware.nfc/current/android/hardware/nfc/ProtocolDiscoveryConfig.aidl
+++ b/nfc/aidl/aidl_api/android.hardware.nfc/current/android/hardware/nfc/ProtocolDiscoveryConfig.aidl
@@ -43,4 +43,5 @@
   byte discoveryPollKovio;
   byte discoveryPollBPrime;
   byte discoveryListenBPrime;
+  byte protocolChineseId;
 }
diff --git a/nfc/aidl/android/hardware/nfc/ProtocolDiscoveryConfig.aidl b/nfc/aidl/android/hardware/nfc/ProtocolDiscoveryConfig.aidl
index f8e3228..021e307 100644
--- a/nfc/aidl/android/hardware/nfc/ProtocolDiscoveryConfig.aidl
+++ b/nfc/aidl/android/hardware/nfc/ProtocolDiscoveryConfig.aidl
@@ -33,4 +33,5 @@
     byte discoveryPollKovio;
     byte discoveryPollBPrime;
     byte discoveryListenBPrime;
+    byte protocolChineseId;
 }
diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
index 48b12a4..51afa12 100644
--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp
@@ -149,7 +149,7 @@
     // The multiplier should never be higher than the AIDL version, but can be less
     // (for example, if the implementation is from an earlier version but the HAL service
     // uses the default libraries and so reports the current AIDL version).
-    EXPECT_TRUE((attestation_version / 100) <= aidl_version);
+    EXPECT_LE((attestation_version / 100), aidl_version);
 }
 
 bool avb_verification_enabled() {
@@ -1142,13 +1142,12 @@
                 int openssl_padding = RSA_NO_PADDING;
                 switch (padding) {
                     case PaddingMode::NONE:
-                        ASSERT_TRUE(data_size <= key_len);
+                        ASSERT_LE(data_size, key_len);
                         ASSERT_EQ(key_len, signature.size());
                         openssl_padding = RSA_NO_PADDING;
                         break;
                     case PaddingMode::RSA_PKCS1_1_5_SIGN:
-                        ASSERT_TRUE(data_size + kPkcs1UndigestedSignaturePaddingOverhead <=
-                                    key_len);
+                        ASSERT_LE(data_size + kPkcs1UndigestedSignaturePaddingOverhead, key_len);
                         openssl_padding = RSA_PKCS1_PADDING;
                         break;
                     default:
@@ -1905,7 +1904,7 @@
         }
     }
 
-    // Verified boot key should be all 0's if the boot state is not verified or self signed
+    // Verified Boot key should be all zeroes if the boot state is "orange".
     std::string empty_boot_key(32, '\0');
     std::string verified_boot_key_str((const char*)verified_boot_key.data(),
                                       verified_boot_key.size());
@@ -2364,7 +2363,7 @@
         // ATTESTATION_IDS_NOT_PROVISIONED in this case.
         ASSERT_TRUE((tag == TAG_ATTESTATION_ID_IMEI || tag == TAG_ATTESTATION_ID_MEID ||
                      tag == TAG_ATTESTATION_ID_SECOND_IMEI))
-                << "incorrect error code on attestation ID mismatch";
+                << "incorrect error code on attestation ID mismatch for " << tag;
     } else {
         ADD_FAILURE() << "Error code " << result
                       << " returned on attestation ID mismatch, should be CANNOT_ATTEST_IDS";
diff --git a/security/keymint/aidl/vts/functional/KeyMintTest.cpp b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
index e32c2db..067db78 100644
--- a/security/keymint/aidl/vts/functional/KeyMintTest.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
@@ -2584,7 +2584,8 @@
     auto result = GenerateKey(
             AuthorizationSetBuilder().EcdsaKey(256).Digest(Digest::NONE).SetDefaultValidity());
     ASSERT_TRUE(result == ErrorCode::UNSUPPORTED_KEY_SIZE ||
-                result == ErrorCode::UNSUPPORTED_EC_CURVE);
+                result == ErrorCode::UNSUPPORTED_EC_CURVE)
+            << "unexpected result " << result;
 }
 
 /*
@@ -2605,7 +2606,7 @@
                                       .SigningKey()
                                       .Digest(Digest::NONE)
                                       .SetDefaultValidity());
-    ASSERT_TRUE(result == ErrorCode::INVALID_ARGUMENT);
+    ASSERT_EQ(result, ErrorCode::INVALID_ARGUMENT);
 }
 
 /*
@@ -3184,7 +3185,8 @@
     string result;
     ErrorCode finish_error_code = Finish(message, &result);
     EXPECT_TRUE(finish_error_code == ErrorCode::INVALID_INPUT_LENGTH ||
-                finish_error_code == ErrorCode::INVALID_ARGUMENT);
+                finish_error_code == ErrorCode::INVALID_ARGUMENT)
+            << "unexpected error code " << finish_error_code;
 
     // Very large message that should exceed the transfer buffer size of any reasonable TEE.
     message = string(128 * 1024, 'a');
@@ -3194,7 +3196,8 @@
                                               .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN)));
     finish_error_code = Finish(message, &result);
     EXPECT_TRUE(finish_error_code == ErrorCode::INVALID_INPUT_LENGTH ||
-                finish_error_code == ErrorCode::INVALID_ARGUMENT);
+                finish_error_code == ErrorCode::INVALID_ARGUMENT)
+            << "unexpected error code " << finish_error_code;
 }
 
 /*
@@ -3248,7 +3251,8 @@
                                                   .Digest(Digest::NONE)
                                                   .Digest(Digest::SHA1)
                                                   .Padding(PaddingMode::RSA_PKCS1_1_5_SIGN));
-    ASSERT_TRUE(result == ErrorCode::UNSUPPORTED_DIGEST || result == ErrorCode::INVALID_ARGUMENT);
+    ASSERT_TRUE(result == ErrorCode::UNSUPPORTED_DIGEST || result == ErrorCode::INVALID_ARGUMENT)
+            << "unexpected result " << result;
 
     ASSERT_EQ(ErrorCode::UNSUPPORTED_DIGEST,
               Begin(KeyPurpose::SIGN,
@@ -3421,7 +3425,8 @@
         }
 
         auto rc = DeleteKey();
-        ASSERT_TRUE(rc == ErrorCode::OK || rc == ErrorCode::UNIMPLEMENTED);
+        ASSERT_TRUE(rc == ErrorCode::OK || rc == ErrorCode::UNIMPLEMENTED)
+                << "unexpected result " << rc;
     }
 }
 
@@ -5705,7 +5710,8 @@
     // is checked against those values, and found absent.
     auto result = Begin(KeyPurpose::DECRYPT, params);
     EXPECT_TRUE(result == ErrorCode::UNSUPPORTED_MGF_DIGEST ||
-                result == ErrorCode::INCOMPATIBLE_MGF_DIGEST);
+                result == ErrorCode::INCOMPATIBLE_MGF_DIGEST)
+            << "unexpected result " << result;
 }
 
 /*
@@ -5970,14 +5976,16 @@
                                                      .BlockMode(BlockMode::ECB)
                                                      .Padding(PaddingMode::NONE));
     EXPECT_TRUE(result == ErrorCode::INCOMPATIBLE_BLOCK_MODE ||
-                result == ErrorCode::UNSUPPORTED_BLOCK_MODE);
+                result == ErrorCode::UNSUPPORTED_BLOCK_MODE)
+            << "unexpected result " << result;
 
     result = Begin(KeyPurpose::ENCRYPT, AuthorizationSetBuilder()
                                                 .BlockMode(BlockMode::ECB)
                                                 .Padding(PaddingMode::NONE)
                                                 .Padding(PaddingMode::PKCS7));
     EXPECT_TRUE(result == ErrorCode::INCOMPATIBLE_PADDING_MODE ||
-                result == ErrorCode::UNSUPPORTED_PADDING_MODE);
+                result == ErrorCode::UNSUPPORTED_PADDING_MODE)
+            << "unexpected result " << result;
 }
 
 /*
@@ -8760,7 +8768,8 @@
 // Re-enable and run at your own risk.
 TEST_P(DestroyAttestationIdsTest, DISABLED_DestroyTest) {
     auto result = DestroyAttestationIds();
-    EXPECT_TRUE(result == ErrorCode::OK || result == ErrorCode::UNIMPLEMENTED);
+    EXPECT_TRUE(result == ErrorCode::OK || result == ErrorCode::UNIMPLEMENTED)
+            << "unexpected result " << result;
 }
 
 INSTANTIATE_KEYMINT_AIDL_TEST(DestroyAttestationIdsTest);
diff --git a/staging/security/see/hwcrypto/aidl/Android.bp b/staging/security/see/hwcrypto/aidl/Android.bp
index 0a7e8be..2da59a4 100644
--- a/staging/security/see/hwcrypto/aidl/Android.bp
+++ b/staging/security/see/hwcrypto/aidl/Android.bp
@@ -28,4 +28,5 @@
             enabled: true,
         },
     },
+    frozen: false,
 }
diff --git a/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/IHwCryptoKey.aidl b/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/IHwCryptoKey.aidl
index 6837a2f..5b34572 100644
--- a/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/IHwCryptoKey.aidl
+++ b/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/IHwCryptoKey.aidl
@@ -40,10 +40,14 @@
   android.hardware.security.see.hwcrypto.IOpaqueKey importClearKey(in android.hardware.security.see.hwcrypto.types.ExplicitKeyMaterial keyMaterial, in android.hardware.security.see.hwcrypto.KeyPolicy newKeyPolicy);
   byte[] getCurrentDicePolicy();
   android.hardware.security.see.hwcrypto.IOpaqueKey keyTokenImport(in android.hardware.security.see.hwcrypto.types.OpaqueKeyToken requestedKey, in byte[] sealingDicePolicy);
+  android.hardware.security.see.hwcrypto.IOpaqueKey getKeyslotData(android.hardware.security.see.hwcrypto.IHwCryptoKey.KeySlot slotId);
   enum DeviceKeyId {
     DEVICE_BOUND_KEY,
     BATCH_KEY,
   }
+  enum KeySlot {
+    KEYMINT_SHARED_HMAC_KEY,
+  }
   union DiceBoundDerivationKey {
     android.hardware.security.see.hwcrypto.IOpaqueKey opaqueKey;
     android.hardware.security.see.hwcrypto.IHwCryptoKey.DeviceKeyId keyId;
diff --git a/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/IOpaqueKey.aidl b/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/IOpaqueKey.aidl
index c74e71f..88dbdf1 100644
--- a/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/IOpaqueKey.aidl
+++ b/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/IOpaqueKey.aidl
@@ -37,4 +37,5 @@
   android.hardware.security.see.hwcrypto.KeyPolicy getKeyPolicy();
   byte[] getPublicKey();
   android.hardware.security.see.hwcrypto.types.OpaqueKeyToken getShareableToken(in byte[] sealingDicePolicy);
+  void setProtectionId(in android.hardware.security.see.hwcrypto.types.ProtectionId protectionId, in android.hardware.security.see.hwcrypto.types.OperationType[] allowedOperations);
 }
diff --git a/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/OperationParameters.aidl b/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/OperationParameters.aidl
index 017e51c..e069610 100644
--- a/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/OperationParameters.aidl
+++ b/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/OperationParameters.aidl
@@ -35,4 +35,5 @@
 union OperationParameters {
   android.hardware.security.see.hwcrypto.types.SymmetricAuthOperationParameters symmetricAuthCrypto;
   android.hardware.security.see.hwcrypto.types.SymmetricOperationParameters symmetricCrypto;
+  android.hardware.security.see.hwcrypto.types.HmacOperationParameters hmac;
 }
diff --git a/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/ExplicitKeyMaterial.aidl b/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/ExplicitKeyMaterial.aidl
index 933fb67..9970678 100644
--- a/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/ExplicitKeyMaterial.aidl
+++ b/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/ExplicitKeyMaterial.aidl
@@ -34,4 +34,5 @@
 package android.hardware.security.see.hwcrypto.types;
 union ExplicitKeyMaterial {
   android.hardware.security.see.hwcrypto.types.AesKey aes;
+  android.hardware.security.see.hwcrypto.types.HmacKey hmac;
 }
diff --git a/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/HalErrorCode.aidl b/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/HalErrorCode.aidl
index cd8b3c6..742314c 100644
--- a/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/HalErrorCode.aidl
+++ b/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/HalErrorCode.aidl
@@ -41,4 +41,5 @@
   const int ALLOCATION_ERROR = (-5) /* -5 */;
   const int INVALID_KEY = (-6) /* -6 */;
   const int BAD_PARAMETER = (-7) /* -7 */;
+  const int UNAUTHORIZED = (-8) /* -8 */;
 }
diff --git a/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/HmacKey.aidl b/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/HmacKey.aidl
new file mode 100644
index 0000000..f8de94a
--- /dev/null
+++ b/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/HmacKey.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright 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.security.see.hwcrypto.types;
+union HmacKey {
+  byte[32] sha256 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+  byte[64] sha512;
+}
diff --git a/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/HmacOperationParameters.aidl b/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/HmacOperationParameters.aidl
new file mode 100644
index 0000000..532cd8d
--- /dev/null
+++ b/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/HmacOperationParameters.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright 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.security.see.hwcrypto.types;
+parcelable HmacOperationParameters {
+  android.hardware.security.see.hwcrypto.IOpaqueKey key;
+}
diff --git a/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/ProtectionId.aidl b/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/ProtectionId.aidl
new file mode 100644
index 0000000..1e304ab
--- /dev/null
+++ b/staging/security/see/hwcrypto/aidl/aidl_api/android.hardware.security.see/current/android/hardware/security/see/hwcrypto/types/ProtectionId.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright 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.security.see.hwcrypto.types;
+enum ProtectionId {
+  WIDEVINE_OUTPUT_BUFFER = 1,
+}
diff --git a/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/IHwCryptoKey.aidl b/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/IHwCryptoKey.aidl
index e472f4c..bb194a3 100644
--- a/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/IHwCryptoKey.aidl
+++ b/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/IHwCryptoKey.aidl
@@ -37,6 +37,19 @@
         DEVICE_BOUND_KEY,
         BATCH_KEY,
     }
+
+    /*
+     * Identifier for the requested key slot. The currently supported identifiers are:
+     *
+     * KEYMINT_SHARED_HMAC_KEY:
+     *      This is the shared HMAC key that will now be computed by HwCryptoKey after participating
+     *      in the ISharedSecret protocol that can be shared with KeyMint and authenticators. See
+     *      ISharedSecret.aidl for more information.
+     */
+    enum KeySlot {
+        KEYMINT_SHARED_HMAC_KEY,
+    }
+
     union DiceBoundDerivationKey {
         /*
          * Opaque to be used to derive the DICE bound key.
@@ -256,4 +269,21 @@
      *      success, service specific error based on <code>HalErrorCode</code> otherwise.
      */
     IOpaqueKey keyTokenImport(in OpaqueKeyToken requestedKey, in byte[] sealingDicePolicy);
+
+    /*
+     * getKeyslotData() - Gets the keyslot key material referenced by slotId.
+     *
+     * @slotId:
+     *      Identifier for the requested keyslot
+     *
+     * This interface is used to access device specific keys with known types and uses. Because the
+     * returned key is opaque, it can only be used through the different HwCrypto interfaces.
+     * Because the keys live in a global namespace the identity of the caller needs to be
+     * checked to verify that it has permission to accesses the requested key.
+     *
+     * Return:
+     *      Ok(IOpaqueKey) on success, UNAUTHORIZED if the caller cannot access the requested key,
+     *      another specific error code otherwise.
+     */
+    IOpaqueKey getKeyslotData(KeySlot slotId);
 }
diff --git a/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/IOpaqueKey.aidl b/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/IOpaqueKey.aidl
index eba4d1c..9a72639 100644
--- a/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/IOpaqueKey.aidl
+++ b/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/IOpaqueKey.aidl
@@ -18,6 +18,7 @@
 import android.hardware.security.see.hwcrypto.KeyPolicy;
 import android.hardware.security.see.hwcrypto.types.OpaqueKeyToken;
 import android.hardware.security.see.hwcrypto.types.OperationType;
+import android.hardware.security.see.hwcrypto.types.ProtectionId;
 
 interface IOpaqueKey {
     /*
@@ -65,4 +66,25 @@
      *      <code>HalErrorCode</code> otherwise.
      */
     OpaqueKeyToken getShareableToken(in byte[] sealingDicePolicy);
+
+    /*
+     * setProtectionId() - Sets the protectionID associated with the buffers where the operation
+     *                     will be performed. A protection ID serves as a limitation on the key so
+     *                     it can only operate on buffers with a matching protection ID.
+     *                     The client calling this functions needs to have the necessary permissions
+     *                     to read and/or write to this buffer. Setting this parameter means that
+     *                     if the key is shared with a different client, the client receiving the
+     *                     key will be limited in which buffers can be used to read/write data for
+     *                     this operation.
+     *
+     * @protectionId:
+     *      ID of the given use case to provide protection for. The method of protecting the buffer
+     *      will be platform dependent.
+     * @allowedOperations:
+     *      array of allowed operations. Allowed operations are either READ or WRITE.
+     *
+     * Return:
+     *      service specific error based on <code>HalErrorCode</code> on failure.
+     */
+    void setProtectionId(in ProtectionId protectionId, in OperationType[] allowedOperations);
 }
diff --git a/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/OperationParameters.aidl b/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/OperationParameters.aidl
index 9e2fc6c..a977f56 100644
--- a/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/OperationParameters.aidl
+++ b/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/OperationParameters.aidl
@@ -15,6 +15,7 @@
  */
 package android.hardware.security.see.hwcrypto;
 
+import android.hardware.security.see.hwcrypto.types.HmacOperationParameters;
 import android.hardware.security.see.hwcrypto.types.SymmetricAuthOperationParameters;
 import android.hardware.security.see.hwcrypto.types.SymmetricOperationParameters;
 
@@ -31,4 +32,9 @@
      * Parameters for non-authenticated symmetric cryptography (AES/TDES).
      */
     SymmetricOperationParameters symmetricCrypto;
+
+    /*
+     * Parameters for hash based message authenticated code operations.
+     */
+    HmacOperationParameters hmac;
 }
diff --git a/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/ExplicitKeyMaterial.aidl b/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/ExplicitKeyMaterial.aidl
index 4298ba9..3aa5611 100644
--- a/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/ExplicitKeyMaterial.aidl
+++ b/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/ExplicitKeyMaterial.aidl
@@ -16,10 +16,12 @@
 package android.hardware.security.see.hwcrypto.types;
 
 import android.hardware.security.see.hwcrypto.types.AesKey;
+import android.hardware.security.see.hwcrypto.types.HmacKey;
 
 /*
  * Type encapsulating a clear key.
  */
 union ExplicitKeyMaterial {
     AesKey aes;
+    HmacKey hmac;
 }
diff --git a/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/HalErrorCode.aidl b/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/HalErrorCode.aidl
index e8e8539..f536c0e 100644
--- a/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/HalErrorCode.aidl
+++ b/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/HalErrorCode.aidl
@@ -42,4 +42,7 @@
 
     /* Bad parameter supplied for the desired operation */
     const int BAD_PARAMETER = -7;
+
+    /* Caller is not authorized to make this call */
+    const int UNAUTHORIZED = -8;
 }
diff --git a/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/HmacKey.aidl b/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/HmacKey.aidl
new file mode 100644
index 0000000..a0b6ba7
--- /dev/null
+++ b/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/HmacKey.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright 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.security.see.hwcrypto.types;
+
+/*
+ * Type that represents an Hmac key.
+ */
+union HmacKey {
+    /*
+     * Raw Hmac key for use with sha256.
+     */
+    byte[32] sha256 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0};
+
+    /*
+     * Raw Hmac key for use with sha512.
+     */
+    byte[64] sha512;
+}
diff --git a/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/HmacOperationParameters.aidl b/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/HmacOperationParameters.aidl
new file mode 100644
index 0000000..da09a2c
--- /dev/null
+++ b/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/HmacOperationParameters.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 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.security.see.hwcrypto.types;
+
+import android.hardware.security.see.hwcrypto.IOpaqueKey;
+/*
+ * Data needed to perform HMAC operations.
+ */
+parcelable HmacOperationParameters {
+    /*
+     * Key to be used for the HMAC operation.
+     */
+    IOpaqueKey key;
+}
diff --git a/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/ProtectionId.aidl b/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/ProtectionId.aidl
new file mode 100644
index 0000000..8686882
--- /dev/null
+++ b/staging/security/see/hwcrypto/aidl/android/hardware/security/see/hwcrypto/types/ProtectionId.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright 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.security.see.hwcrypto.types;
+
+/*
+ * Enum describing the different types of protected buffers. Protected buffers are named by its
+ * corresponding use case and its underlaying implementation is platform dependant.
+ */
+enum ProtectionId {
+    /*
+     * ProtectionID used by HwCrypto to enable Keys that can be used for Widevine video buffers.
+     * These buffers should not be readable by non-trusted entities and HwCrypto should not allow
+     * any read access to them through its interface.
+     */
+    WIDEVINE_OUTPUT_BUFFER = 1,
+}