Merge "ExtCam: Fix numAttempt is not increased in the while-loop" into main
diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp
index 36fd0d0..a8e8c50 100644
--- a/audio/aidl/default/Android.bp
+++ b/audio/aidl/default/Android.bp
@@ -80,6 +80,7 @@
         "stub/ApeHeader.cpp",
         "stub/DriverStubImpl.cpp",
         "stub/ModuleStub.cpp",
+        "stub/StreamMmapStub.cpp",
         "stub/StreamOffloadStub.cpp",
         "stub/StreamStub.cpp",
         "usb/ModuleUsb.cpp",
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index 123a5ec..aa624ff 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -214,24 +214,33 @@
     StreamContext::DebugParameters params{mDebug.streamTransientStateDelayMs,
                                           mVendorDebug.forceTransientBurst,
                                           mVendorDebug.forceSynchronousDrain};
-    std::unique_ptr<StreamContext::DataMQ> dataMQ = nullptr;
-    std::shared_ptr<IStreamCallback> streamAsyncCallback = nullptr;
     std::shared_ptr<ISoundDose> soundDose;
     if (!getSoundDose(&soundDose).isOk()) {
         LOG(ERROR) << __func__ << ": could not create sound dose instance";
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     }
-    if (!hasMmapFlag(flags)) {
-        dataMQ = std::make_unique<StreamContext::DataMQ>(frameSize * in_bufferSizeFrames);
-        streamAsyncCallback = asyncCallback;
+    StreamContext temp;
+    if (hasMmapFlag(flags)) {
+        MmapBufferDescriptor mmapDesc;
+        RETURN_STATUS_IF_ERROR(
+                createMmapBuffer(*portConfigIt, in_bufferSizeFrames, frameSize, &mmapDesc));
+        temp = StreamContext(
+                std::make_unique<StreamContext::CommandMQ>(1, true /*configureEventFlagWord*/),
+                std::make_unique<StreamContext::ReplyMQ>(1, true /*configureEventFlagWord*/),
+                portConfigIt->format.value(), portConfigIt->channelMask.value(),
+                portConfigIt->sampleRate.value().value, flags, nominalLatencyMs,
+                portConfigIt->ext.get<AudioPortExt::mix>().handle, std::move(mmapDesc),
+                outEventCallback, mSoundDose.getInstance(), params);
+    } else {
+        temp = StreamContext(
+                std::make_unique<StreamContext::CommandMQ>(1, true /*configureEventFlagWord*/),
+                std::make_unique<StreamContext::ReplyMQ>(1, true /*configureEventFlagWord*/),
+                portConfigIt->format.value(), portConfigIt->channelMask.value(),
+                portConfigIt->sampleRate.value().value, flags, nominalLatencyMs,
+                portConfigIt->ext.get<AudioPortExt::mix>().handle,
+                std::make_unique<StreamContext::DataMQ>(frameSize * in_bufferSizeFrames),
+                asyncCallback, outEventCallback, mSoundDose.getInstance(), params);
     }
-    StreamContext temp(
-            std::make_unique<StreamContext::CommandMQ>(1, true /*configureEventFlagWord*/),
-            std::make_unique<StreamContext::ReplyMQ>(1, true /*configureEventFlagWord*/),
-            portConfigIt->format.value(), portConfigIt->channelMask.value(),
-            portConfigIt->sampleRate.value().value, flags, nominalLatencyMs,
-            portConfigIt->ext.get<AudioPortExt::mix>().handle, std::move(dataMQ),
-            streamAsyncCallback, outEventCallback, mSoundDose.getInstance(), params);
     if (temp.isValid()) {
         *out_context = std::move(temp);
     } else {
@@ -394,9 +403,10 @@
     return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
 }
 
-ndk::ScopedAStatus Module::createMmapBuffer(
-        const ::aidl::android::hardware::audio::core::StreamContext& context __unused,
-        ::aidl::android::hardware::audio::core::StreamDescriptor* desc __unused) {
+ndk::ScopedAStatus Module::createMmapBuffer(const AudioPortConfig& portConfig __unused,
+                                            int32_t bufferSizeFrames __unused,
+                                            int32_t frameSizeBytes __unused,
+                                            MmapBufferDescriptor* desc __unused) {
     LOG(ERROR) << __func__ << ": " << mType << ": is not implemented";
     return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
 }
@@ -977,9 +987,6 @@
     RETURN_STATUS_IF_ERROR(createStreamContext(in_args.portConfigId, in_args.bufferSizeFrames,
                                                nullptr, nullptr, &context));
     context.fillDescriptor(&_aidl_return->desc);
-    if (hasMmapFlag(context.getFlags())) {
-        RETURN_STATUS_IF_ERROR(createMmapBuffer(context, &_aidl_return->desc));
-    }
     std::shared_ptr<StreamIn> stream;
     RETURN_STATUS_IF_ERROR(createInputStream(std::move(context), in_args.sinkMetadata,
                                              getMicrophoneInfos(), &stream));
@@ -1027,9 +1034,6 @@
                                                isNonBlocking ? in_args.callback : nullptr,
                                                in_args.eventCallback, &context));
     context.fillDescriptor(&_aidl_return->desc);
-    if (hasMmapFlag(context.getFlags())) {
-        RETURN_STATUS_IF_ERROR(createMmapBuffer(context, &_aidl_return->desc));
-    }
     std::shared_ptr<StreamOut> stream;
     RETURN_STATUS_IF_ERROR(createOutputStream(std::move(context), in_args.sourceMetadata,
                                               in_args.offloadInfo, &stream));
diff --git a/audio/aidl/default/ModulePrimary.cpp b/audio/aidl/default/ModulePrimary.cpp
index 2a1dba9..6cb9251 100644
--- a/audio/aidl/default/ModulePrimary.cpp
+++ b/audio/aidl/default/ModulePrimary.cpp
@@ -21,18 +21,23 @@
 #include <android-base/logging.h>
 
 #include "core-impl/ModulePrimary.h"
+#include "core-impl/StreamMmapStub.h"
 #include "core-impl/StreamOffloadStub.h"
 #include "core-impl/StreamPrimary.h"
 #include "core-impl/Telephony.h"
 
 using aidl::android::hardware::audio::common::areAllBitPositionFlagsSet;
+using aidl::android::hardware::audio::common::hasMmapFlag;
 using aidl::android::hardware::audio::common::SinkMetadata;
 using aidl::android::hardware::audio::common::SourceMetadata;
+using aidl::android::hardware::audio::core::StreamDescriptor;
+using aidl::android::media::audio::common::AudioInputFlags;
 using aidl::android::media::audio::common::AudioIoFlags;
 using aidl::android::media::audio::common::AudioOffloadInfo;
 using aidl::android::media::audio::common::AudioOutputFlags;
 using aidl::android::media::audio::common::AudioPort;
 using aidl::android::media::audio::common::AudioPortConfig;
+using aidl::android::media::audio::common::AudioPortExt;
 using aidl::android::media::audio::common::MicrophoneInfo;
 
 namespace aidl::android::hardware::audio::core {
@@ -62,6 +67,11 @@
                                                     const SinkMetadata& sinkMetadata,
                                                     const std::vector<MicrophoneInfo>& microphones,
                                                     std::shared_ptr<StreamIn>* result) {
+    if (context.isMmap()) {
+        // "Stub" is used because there is no support for MMAP audio I/O on CVD.
+        return createStreamInstance<StreamInMmapStub>(result, std::move(context), sinkMetadata,
+                                                      microphones);
+    }
     return createStreamInstance<StreamInPrimary>(result, std::move(context), sinkMetadata,
                                                  microphones);
 }
@@ -69,26 +79,54 @@
 ndk::ScopedAStatus ModulePrimary::createOutputStream(
         StreamContext&& context, const SourceMetadata& sourceMetadata,
         const std::optional<AudioOffloadInfo>& offloadInfo, std::shared_ptr<StreamOut>* result) {
-    if (!areAllBitPositionFlagsSet(
-                context.getFlags().get<AudioIoFlags::output>(),
-                {AudioOutputFlags::COMPRESS_OFFLOAD, AudioOutputFlags::NON_BLOCKING})) {
-        return createStreamInstance<StreamOutPrimary>(result, std::move(context), sourceMetadata,
-                                                      offloadInfo);
-    } else {
+    if (context.isMmap()) {
+        // "Stub" is used because there is no support for MMAP audio I/O on CVD.
+        return createStreamInstance<StreamOutMmapStub>(result, std::move(context), sourceMetadata,
+                                                       offloadInfo);
+    } else if (areAllBitPositionFlagsSet(
+                       context.getFlags().get<AudioIoFlags::output>(),
+                       {AudioOutputFlags::COMPRESS_OFFLOAD, AudioOutputFlags::NON_BLOCKING})) {
         // "Stub" is used because there is no actual decoder. The stream just
         // extracts the clip duration from the media file header and simulates
         // playback over time.
         return createStreamInstance<StreamOutOffloadStub>(result, std::move(context),
                                                           sourceMetadata, offloadInfo);
     }
+    return createStreamInstance<StreamOutPrimary>(result, std::move(context), sourceMetadata,
+                                                  offloadInfo);
 }
 
-int32_t ModulePrimary::getNominalLatencyMs(const AudioPortConfig&) {
+ndk::ScopedAStatus ModulePrimary::createMmapBuffer(const AudioPortConfig& portConfig,
+                                                   int32_t bufferSizeFrames, int32_t frameSizeBytes,
+                                                   MmapBufferDescriptor* desc) {
+    const size_t bufferSizeBytes = static_cast<size_t>(bufferSizeFrames) * frameSizeBytes;
+    // The actual mmap buffer for I/O is created after the stream exits standby, via
+    // 'IStreamCommon.createMmapBuffer'. But we must return a valid file descriptor here because
+    // 'MmapBufferDescriptor' can not contain a "null" fd.
+    const std::string regionName =
+            std::string("mmap-sim-o-") +
+            std::to_string(portConfig.ext.get<AudioPortExt::Tag::mix>().handle);
+    int fd = ashmem_create_region(regionName.c_str(), bufferSizeBytes);
+    if (fd < 0) {
+        PLOG(ERROR) << __func__ << ": failed to create shared memory region of " << bufferSizeBytes
+                    << " bytes";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    desc->sharedMemory.fd = ndk::ScopedFileDescriptor(fd);
+    desc->sharedMemory.size = bufferSizeBytes;
+    desc->burstSizeFrames = bufferSizeFrames / 2;
+    desc->flags = 0;
+    LOG(DEBUG) << __func__ << ": " << desc->toString();
+    return ndk::ScopedAStatus::ok();
+}
+
+int32_t ModulePrimary::getNominalLatencyMs(const AudioPortConfig& portConfig) {
+    static constexpr int32_t kLowLatencyMs = 5;
     // 85 ms is chosen considering 4096 frames @ 48 kHz. This is the value which allows
     // the virtual Android device implementation to pass CTS. Hardware implementations
     // should have significantly lower latency.
-    static constexpr int32_t kLatencyMs = 85;
-    return kLatencyMs;
+    static constexpr int32_t kStandardLatencyMs = 85;
+    return hasMmapFlag(portConfig.flags.value()) ? kLowLatencyMs : kStandardLatencyMs;
 }
 
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp
index 2800bed..873fc48 100644
--- a/audio/aidl/default/Stream.cpp
+++ b/audio/aidl/default/Stream.cpp
@@ -65,18 +65,26 @@
     if (mReplyMQ) {
         desc->reply = mReplyMQ->dupeDesc();
     }
+    desc->frameSizeBytes = getFrameSize();
+    desc->bufferSizeFrames = getBufferSizeInFrames();
     if (mDataMQ) {
-        desc->frameSizeBytes = getFrameSize();
-        desc->bufferSizeFrames = getBufferSizeInFrames();
         desc->audio.set<StreamDescriptor::AudioBuffer::Tag::fmq>(mDataMQ->dupeDesc());
+    } else {
+        MmapBufferDescriptor mmapDesc;  // Move-only due to `fd`.
+        mmapDesc.sharedMemory.fd = mMmapBufferDesc.sharedMemory.fd.dup();
+        mmapDesc.sharedMemory.size = mMmapBufferDesc.sharedMemory.size;
+        mmapDesc.burstSizeFrames = mMmapBufferDesc.burstSizeFrames;
+        mmapDesc.flags = mMmapBufferDesc.flags;
+        desc->audio.set<StreamDescriptor::AudioBuffer::Tag::mmap>(std::move(mmapDesc));
     }
 }
 
 size_t StreamContext::getBufferSizeInFrames() const {
     if (mDataMQ) {
         return mDataMQ->getQuantumCount() * mDataMQ->getQuantumSize() / getFrameSize();
+    } else {
+        return mMmapBufferDesc.sharedMemory.size / getFrameSize();
     }
-    return 0;
 }
 
 size_t StreamContext::getFrameSize() const {
@@ -96,9 +104,13 @@
         LOG(ERROR) << "frame size is invalid";
         return false;
     }
-    if (!hasMmapFlag(mFlags) && mDataMQ && !mDataMQ->isValid()) {
+    if (!isMmap() && mDataMQ && !mDataMQ->isValid()) {
         LOG(ERROR) << "data FMQ is invalid";
         return false;
+    } else if (isMmap() &&
+               (mMmapBufferDesc.sharedMemory.fd.get() == -1 ||
+                mMmapBufferDesc.sharedMemory.size == 0 || mMmapBufferDesc.burstSizeFrames == 0)) {
+        LOG(ERROR) << "mmap info is invalid" << mMmapBufferDesc.toString();
     }
     return true;
 }
@@ -115,6 +127,7 @@
     mCommandMQ.reset();
     mReplyMQ.reset();
     mDataMQ.reset();
+    mMmapBufferDesc.sharedMemory.fd.set(-1);
 }
 
 pid_t StreamWorkerCommonLogic::getTid() const {
@@ -128,7 +141,7 @@
 std::string StreamWorkerCommonLogic::init() {
     if (mContext->getCommandMQ() == nullptr) return "Command MQ is null";
     if (mContext->getReplyMQ() == nullptr) return "Reply MQ is null";
-    if (!hasMmapFlag(mContext->getFlags())) {
+    if (!mContext->isMmap()) {
         StreamContext::DataMQ* const dataMQ = mContext->getDataMQ();
         if (dataMQ == nullptr) return "Data MQ is null";
         if (sizeof(DataBufferElement) != dataMQ->getQuantumSize()) {
@@ -167,7 +180,7 @@
     } else {
         reply->observable = reply->hardware = kUnknownPosition;
     }
-    if (hasMmapFlag(mContext->getFlags())) {
+    if (mContext->isMmap()) {
         if (auto status = mDriver->getMmapPositionAndLatency(&reply->hardware, &reply->latencyMs);
             status != ::android::OK) {
             reply->hardware = kUnknownPosition;
@@ -252,9 +265,8 @@
                     mState == StreamDescriptor::State::ACTIVE ||
                     mState == StreamDescriptor::State::PAUSED ||
                     mState == StreamDescriptor::State::DRAINING) {
-                    if (bool success = hasMmapFlag(mContext->getFlags())
-                                               ? readMmap(&reply)
-                                               : read(fmqByteCount, &reply);
+                    if (bool success =
+                                mContext->isMmap() ? readMmap(&reply) : read(fmqByteCount, &reply);
                         !success) {
                         mState = StreamDescriptor::State::ERROR;
                     }
@@ -548,9 +560,8 @@
                 if (mState != StreamDescriptor::State::ERROR &&
                     mState != StreamDescriptor::State::TRANSFERRING &&
                     mState != StreamDescriptor::State::TRANSFER_PAUSED) {
-                    if (bool success = hasMmapFlag(mContext->getFlags())
-                                               ? writeMmap(&reply)
-                                               : write(fmqByteCount, &reply);
+                    if (bool success = mContext->isMmap() ? writeMmap(&reply)
+                                                          : write(fmqByteCount, &reply);
                         !success) {
                         mState = StreamDescriptor::State::ERROR;
                     }
diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h
index 0661015..379264d 100644
--- a/audio/aidl/default/include/core-impl/Module.h
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -212,8 +212,8 @@
             const ::aidl::android::media::audio::common::AudioFormatDescription &format,
             int32_t latencyMs, int32_t sampleRateHz, int32_t *bufferSizeFrames);
     virtual ndk::ScopedAStatus createMmapBuffer(
-            const ::aidl::android::hardware::audio::core::StreamContext& context,
-            ::aidl::android::hardware::audio::core::StreamDescriptor* desc);
+            const ::aidl::android::media::audio::common::AudioPortConfig& portConfig,
+            int32_t bufferSizeFrames, int32_t frameSizeBytes, MmapBufferDescriptor* desc);
 
     // Utility and helper functions accessible to subclasses.
     static int32_t calculateBufferSizeFramesForPcm(int32_t latencyMs, int32_t sampleRateHz) {
diff --git a/audio/aidl/default/include/core-impl/ModulePrimary.h b/audio/aidl/default/include/core-impl/ModulePrimary.h
index a657dc5..c93deed 100644
--- a/audio/aidl/default/include/core-impl/ModulePrimary.h
+++ b/audio/aidl/default/include/core-impl/ModulePrimary.h
@@ -42,6 +42,9 @@
             const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
                     offloadInfo,
             std::shared_ptr<StreamOut>* result) override;
+    ndk::ScopedAStatus createMmapBuffer(
+            const ::aidl::android::media::audio::common::AudioPortConfig& portConfig,
+            int32_t bufferSizeFrames, int32_t frameSizeBytes, MmapBufferDescriptor* desc) override;
     int32_t getNominalLatencyMs(
             const ::aidl::android::media::audio::common::AudioPortConfig& portConfig) override;
 
diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h
index 376c684..bb790e9 100644
--- a/audio/aidl/default/include/core-impl/Stream.h
+++ b/audio/aidl/default/include/core-impl/Stream.h
@@ -104,6 +104,27 @@
           mOutEventCallback(outEventCallback),
           mStreamDataProcessor(streamDataProcessor),
           mDebugParameters(debugParameters) {}
+    StreamContext(std::unique_ptr<CommandMQ> commandMQ, std::unique_ptr<ReplyMQ> replyMQ,
+                  const ::aidl::android::media::audio::common::AudioFormatDescription& format,
+                  const ::aidl::android::media::audio::common::AudioChannelLayout& channelLayout,
+                  int sampleRate, const ::aidl::android::media::audio::common::AudioIoFlags& flags,
+                  int32_t nominalLatencyMs, int32_t mixPortHandle, MmapBufferDescriptor&& mmapDesc,
+                  std::shared_ptr<IStreamOutEventCallback> outEventCallback,
+                  std::weak_ptr<sounddose::StreamDataProcessorInterface> streamDataProcessor,
+                  DebugParameters debugParameters)
+        : mCommandMQ(std::move(commandMQ)),
+          mInternalCommandCookie(std::rand() | 1 /* make sure it's not 0 */),
+          mReplyMQ(std::move(replyMQ)),
+          mFormat(format),
+          mChannelLayout(channelLayout),
+          mSampleRate(sampleRate),
+          mFlags(flags),
+          mNominalLatencyMs(nominalLatencyMs),
+          mMixPortHandle(mixPortHandle),
+          mMmapBufferDesc(std::move(mmapDesc)),
+          mOutEventCallback(outEventCallback),
+          mStreamDataProcessor(streamDataProcessor),
+          mDebugParameters(debugParameters) {}
 
     void fillDescriptor(StreamDescriptor* desc);
     std::shared_ptr<IStreamCallback> getAsyncCallback() const { return mAsyncCallback; }
@@ -136,6 +157,7 @@
     bool isInput() const {
         return mFlags.getTag() == ::aidl::android::media::audio::common::AudioIoFlags::input;
     }
+    bool isMmap() const { return ::aidl::android::hardware::audio::common::hasMmapFlag(mFlags); }
     bool isValid() const;
     // 'reset' is called on a Binder thread when closing the stream. Does not use
     // locking because it only cleans MQ pointers which were also set on the Binder thread.
@@ -155,7 +177,9 @@
     ::aidl::android::media::audio::common::AudioIoFlags mFlags;
     int32_t mNominalLatencyMs;
     int32_t mMixPortHandle;
+    // Only one of `mDataMQ` or `mMapBufferDesc` can be active, depending on `isMmap`
     std::unique_ptr<DataMQ> mDataMQ;
+    MmapBufferDescriptor mMmapBufferDesc;
     std::shared_ptr<IStreamCallback> mAsyncCallback;
     std::shared_ptr<IStreamOutEventCallback> mOutEventCallback;  // Only used by output streams
     std::weak_ptr<sounddose::StreamDataProcessorInterface> mStreamDataProcessor;
diff --git a/audio/aidl/default/include/core-impl/StreamMmapStub.h b/audio/aidl/default/include/core-impl/StreamMmapStub.h
new file mode 100644
index 0000000..0332007
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/StreamMmapStub.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <mutex>
+#include <string>
+
+#include "core-impl/DriverStubImpl.h"
+#include "core-impl/Stream.h"
+
+namespace aidl::android::hardware::audio::core {
+
+namespace mmap {
+
+struct DspSimulatorState {
+    const bool isInput;
+    const int sampleRate;
+    const int frameSizeBytes;
+    const size_t bufferSizeBytes;
+    std::mutex lock;
+    // The lock is also used to prevent un-mapping while the memory is in use.
+    uint8_t* sharedMemory GUARDED_BY(lock) = nullptr;
+    StreamDescriptor::Position mmapPos GUARDED_BY(lock);
+};
+
+class DspSimulatorLogic : public ::android::hardware::audio::common::StreamLogic {
+  protected:
+    explicit DspSimulatorLogic(DspSimulatorState& sharedState) : mSharedState(sharedState) {}
+    std::string init() override;
+    Status cycle() override;
+
+  private:
+    DspSimulatorState& mSharedState;
+    uint32_t mCycleDurationUs = 0;
+    uint8_t* mMemBegin = nullptr;
+    uint8_t* mMemPos = nullptr;
+    int64_t mLastFrames = 0;
+};
+
+class DspSimulatorWorker
+    : public ::android::hardware::audio::common::StreamWorker<DspSimulatorLogic> {
+  public:
+    explicit DspSimulatorWorker(DspSimulatorState& sharedState)
+        : ::android::hardware::audio::common::StreamWorker<DspSimulatorLogic>(sharedState) {}
+};
+
+}  // namespace mmap
+
+class DriverMmapStubImpl : public DriverStubImpl {
+  public:
+    explicit DriverMmapStubImpl(const StreamContext& context);
+    ::android::status_t init(DriverCallbackInterface* callback) override;
+    ::android::status_t drain(StreamDescriptor::DrainMode drainMode) override;
+    ::android::status_t pause() override;
+    ::android::status_t start() override;
+    ::android::status_t transfer(void* buffer, size_t frameCount, size_t* actualFrameCount,
+                                 int32_t* latencyMs) override;
+    void shutdown() override;
+    ::android::status_t refinePosition(StreamDescriptor::Position* position) override;
+    ::android::status_t getMmapPositionAndLatency(StreamDescriptor::Position* position,
+                                                  int32_t* latency) override;
+
+  protected:
+    ::android::status_t initSharedMemory(int ashmemFd);
+
+  private:
+    ::android::status_t releaseSharedMemory() REQUIRES(mState.lock);
+    ::android::status_t startWorkerIfNeeded();
+
+    mmap::DspSimulatorState mState;
+    mmap::DspSimulatorWorker mDspWorker;
+    bool mDspWorkerStarted = false;
+};
+
+class StreamMmapStub : public StreamCommonImpl, public DriverMmapStubImpl {
+  public:
+    static const std::string kCreateMmapBufferName;
+
+    StreamMmapStub(StreamContext* context, const Metadata& metadata);
+    ~StreamMmapStub();
+
+    ndk::ScopedAStatus getVendorParameters(const std::vector<std::string>& in_ids,
+                                           std::vector<VendorParameter>* _aidl_return) override;
+    ndk::ScopedAStatus setVendorParameters(const std::vector<VendorParameter>& in_parameters,
+                                           bool in_async) override;
+
+  private:
+    ndk::ScopedAStatus createMmapBuffer(MmapBufferDescriptor* desc);
+
+    ndk::ScopedFileDescriptor mSharedMemoryFd;
+};
+
+class StreamInMmapStub final : public StreamIn, public StreamMmapStub {
+  public:
+    friend class ndk::SharedRefBase;
+    StreamInMmapStub(
+            StreamContext&& context,
+            const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata,
+            const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones);
+
+  private:
+    void onClose(StreamDescriptor::State) override { defaultOnClose(); }
+};
+
+class StreamOutMmapStub final : public StreamOut, public StreamMmapStub {
+  public:
+    friend class ndk::SharedRefBase;
+    StreamOutMmapStub(
+            StreamContext&& context,
+            const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata,
+            const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
+                    offloadInfo);
+
+  private:
+    void onClose(StreamDescriptor::State) override { defaultOnClose(); }
+};
+
+}  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/StreamOffloadStub.h b/audio/aidl/default/include/core-impl/StreamOffloadStub.h
index 24e98c2..09b88aa 100644
--- a/audio/aidl/default/include/core-impl/StreamOffloadStub.h
+++ b/audio/aidl/default/include/core-impl/StreamOffloadStub.h
@@ -19,12 +19,15 @@
 #include <mutex>
 #include <set>
 #include <string>
+#include <vector>
 
 #include "core-impl/DriverStubImpl.h"
 #include "core-impl/Stream.h"
 
 namespace aidl::android::hardware::audio::core {
 
+namespace offload {
+
 struct DspSimulatorState {
     static constexpr int64_t kSkipBufferNotifyFrames = -1;
 
@@ -55,9 +58,11 @@
         : ::android::hardware::audio::common::StreamWorker<DspSimulatorLogic>(sharedState) {}
 };
 
+}  // namespace offload
+
 class DriverOffloadStubImpl : public DriverStubImpl {
   public:
-    DriverOffloadStubImpl(const StreamContext& context);
+    explicit DriverOffloadStubImpl(const StreamContext& context);
     ::android::status_t init(DriverCallbackInterface* callback) override;
     ::android::status_t drain(StreamDescriptor::DrainMode drainMode) override;
     ::android::status_t flush() override;
@@ -71,8 +76,8 @@
     ::android::status_t startWorkerIfNeeded();
 
     const int64_t mBufferNotifyFrames;
-    DspSimulatorState mState;
-    DspSimulatorWorker mDspWorker;
+    offload::DspSimulatorState mState;
+    offload::DspSimulatorWorker mDspWorker;
     bool mDspWorkerStarted = false;
 };
 
diff --git a/audio/aidl/default/stub/DriverStubImpl.cpp b/audio/aidl/default/stub/DriverStubImpl.cpp
index 0d129e6..cb8ee70 100644
--- a/audio/aidl/default/stub/DriverStubImpl.cpp
+++ b/audio/aidl/default/stub/DriverStubImpl.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <cmath>
+#include <cstdlib>
 
 #define LOG_TAG "AHAL_Stream"
 #include <android-base/logging.h>
diff --git a/audio/aidl/default/stub/StreamMmapStub.cpp b/audio/aidl/default/stub/StreamMmapStub.cpp
new file mode 100644
index 0000000..f48aea4
--- /dev/null
+++ b/audio/aidl/default/stub/StreamMmapStub.cpp
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <unistd.h>
+#include <cstdlib>
+
+#define LOG_TAG "AHAL_MmapStream"
+#include <android-base/logging.h>
+#include <audio_utils/clock.h>
+#include <error/Result.h>
+#include <utils/SystemClock.h>
+
+#include "core-impl/StreamMmapStub.h"
+
+using aidl::android::hardware::audio::common::SinkMetadata;
+using aidl::android::hardware::audio::common::SourceMetadata;
+using aidl::android::media::audio::common::AudioOffloadInfo;
+using aidl::android::media::audio::common::MicrophoneInfo;
+
+namespace aidl::android::hardware::audio::core {
+
+namespace mmap {
+
+std::string DspSimulatorLogic::init() {
+    {
+        std::lock_guard l(mSharedState.lock);
+        mSharedState.mmapPos.timeNs = StreamDescriptor::Position::UNKNOWN;
+        mSharedState.mmapPos.frames = StreamDescriptor::Position::UNKNOWN;
+    }
+    // Progress in buffer size chunks to make sure that VTS tolerates infrequent position updates
+    // (see b/350998390).
+    mCycleDurationUs = (mSharedState.bufferSizeBytes / mSharedState.frameSizeBytes) *
+                       MICROS_PER_SECOND / mSharedState.sampleRate;
+    return "";
+}
+
+DspSimulatorLogic::Status DspSimulatorLogic::cycle() {
+    // Simulate DSP moving along in real time.
+    const int64_t timeBeginNs = ::android::uptimeNanos();
+    usleep(mCycleDurationUs);
+    int64_t newFrames;
+    std::lock_guard l(mSharedState.lock);
+    if (mMemBegin != mSharedState.sharedMemory) {
+        mMemBegin = mSharedState.sharedMemory;
+        if (mMemBegin != nullptr) mMemPos = mMemBegin;
+    }
+    if (mMemBegin != nullptr) {
+        mSharedState.mmapPos.timeNs = ::android::uptimeNanos();
+        newFrames = (mSharedState.mmapPos.timeNs - timeBeginNs) * mSharedState.sampleRate /
+                    NANOS_PER_SECOND;
+        // Restore the reported frames position to ensure continuity.
+        if (mSharedState.mmapPos.frames == StreamDescriptor::Position::UNKNOWN) {
+            mSharedState.mmapPos.frames = mLastFrames;
+        }
+        mSharedState.mmapPos.frames += newFrames;
+        mLastFrames = mSharedState.mmapPos.frames;
+        if (mSharedState.isInput) {
+            for (size_t i = 0; i < static_cast<size_t>(newFrames) * mSharedState.frameSizeBytes;
+                 ++i) {
+                *mMemPos++ = std::rand() % 255;
+                if (mMemPos >= mMemBegin + mSharedState.bufferSizeBytes) mMemPos = mMemBegin;
+            }
+        }
+    } else {
+        LOG(WARNING) << "No shared memory but the DSP is active";
+        mSharedState.mmapPos.timeNs = StreamDescriptor::Position::UNKNOWN;
+        mSharedState.mmapPos.frames = StreamDescriptor::Position::UNKNOWN;
+    }
+    return Status::CONTINUE;
+}
+
+}  // namespace mmap
+
+using mmap::DspSimulatorState;
+
+DriverMmapStubImpl::DriverMmapStubImpl(const StreamContext& context)
+    : DriverStubImpl(context, 0 /*asyncSleepTimeUs*/),
+      mState{mIsInput, mSampleRate, static_cast<int>(mFrameSizeBytes),
+             mBufferSizeFrames * mFrameSizeBytes},
+      mDspWorker(mState) {
+    LOG_IF(FATAL, !context.isMmap()) << "The steam must be used in MMAP mode";
+}
+
+::android::status_t DriverMmapStubImpl::init(DriverCallbackInterface* callback) {
+    RETURN_STATUS_IF_ERROR(DriverStubImpl::init(callback));
+    return ::android::OK;
+}
+
+::android::status_t DriverMmapStubImpl::drain(StreamDescriptor::DrainMode drainMode) {
+    RETURN_STATUS_IF_ERROR(DriverStubImpl::drain(drainMode));
+    mDspWorker.pause();
+    return ::android::OK;
+}
+
+::android::status_t DriverMmapStubImpl::pause() {
+    RETURN_STATUS_IF_ERROR(DriverStubImpl::pause());
+    mDspWorker.pause();
+    return ::android::OK;
+}
+
+::android::status_t DriverMmapStubImpl::start() {
+    RETURN_STATUS_IF_ERROR(DriverStubImpl::start());
+    RETURN_STATUS_IF_ERROR(startWorkerIfNeeded());
+    mDspWorker.resume();
+    return ::android::OK;
+}
+
+::android::status_t DriverMmapStubImpl::transfer(void*, size_t, size_t*, int32_t*) {
+    // Do not call into DriverStubImpl::transfer
+    if (!mIsInitialized) {
+        LOG(FATAL) << __func__ << ": must not happen for an uninitialized driver";
+    }
+    if (mIsStandby) {
+        LOG(FATAL) << __func__ << ": must not happen while in standby";
+    }
+    RETURN_STATUS_IF_ERROR(startWorkerIfNeeded());
+    mDspWorker.resume();
+    return ::android::OK;
+}
+
+void DriverMmapStubImpl::shutdown() {
+    LOG(DEBUG) << __func__ << ": stopping the DSP simulator worker";
+    mDspWorker.stop();
+    std::lock_guard l(mState.lock);
+    releaseSharedMemory();
+    DriverStubImpl::shutdown();
+}
+
+::android::status_t DriverMmapStubImpl::initSharedMemory(int ashmemFd) {
+    {
+        std::lock_guard l(mState.lock);
+        if (ashmemFd == -1) {
+            mState.sharedMemory = nullptr;
+            return ::android::BAD_VALUE;
+        }
+        RETURN_STATUS_IF_ERROR(releaseSharedMemory());
+    }
+    uint8_t* sharedMemory = static_cast<uint8_t*>(::mmap(
+            nullptr, mState.bufferSizeBytes, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0));
+    if (sharedMemory == reinterpret_cast<uint8_t*>(MAP_FAILED) || sharedMemory == nullptr) {
+        PLOG(ERROR) << "mmap failed for size " << mState.bufferSizeBytes << ", fd " << ashmemFd;
+        return ::android::NO_INIT;
+    }
+    std::lock_guard l(mState.lock);
+    mState.sharedMemory = sharedMemory;
+    return ::android::OK;
+}
+
+::android::status_t DriverMmapStubImpl::releaseSharedMemory() {
+    if (mState.sharedMemory != nullptr) {
+        LOG(DEBUG) << __func__ << ": unmapping shared memory";
+        if (munmap(mState.sharedMemory, mState.bufferSizeBytes) != 0) {
+            PLOG(ERROR) << "munmap failed for size " << mState.bufferSizeBytes;
+            return ::android::INVALID_OPERATION;
+        }
+        mState.sharedMemory = nullptr;
+    }
+    return ::android::OK;
+}
+
+::android::status_t DriverMmapStubImpl::startWorkerIfNeeded() {
+    if (!mDspWorkerStarted) {
+        // This is an "audio service thread," must have elevated priority.
+        if (!mDspWorker.start("dsp_sim", ANDROID_PRIORITY_URGENT_AUDIO)) {
+            return ::android::NO_INIT;
+        }
+        mDspWorkerStarted = true;
+    }
+    return ::android::OK;
+}
+
+::android::status_t DriverMmapStubImpl::refinePosition(StreamDescriptor::Position* position) {
+    std::lock_guard l(mState.lock);
+    *position = mState.mmapPos;
+    return ::android::OK;
+}
+
+::android::status_t DriverMmapStubImpl::getMmapPositionAndLatency(
+        StreamDescriptor::Position* position, int32_t* latencyMs) {
+    {
+        std::lock_guard l(mState.lock);
+        *position = mState.mmapPos;
+    }
+    const size_t latencyFrames = mBufferSizeFrames / 2;
+    if (position->frames != StreamDescriptor::Position::UNKNOWN) {
+        position->frames += latencyFrames;
+    }
+    *latencyMs = latencyFrames * MILLIS_PER_SECOND / mSampleRate;
+    return ::android::OK;
+}
+
+const std::string StreamMmapStub::kCreateMmapBufferName = "aosp.createMmapBuffer";
+
+StreamMmapStub::StreamMmapStub(StreamContext* context, const Metadata& metadata)
+    : StreamCommonImpl(context, metadata), DriverMmapStubImpl(getContext()) {}
+
+StreamMmapStub::~StreamMmapStub() {
+    cleanupWorker();
+}
+
+ndk::ScopedAStatus StreamMmapStub::getVendorParameters(const std::vector<std::string>& in_ids,
+                                                       std::vector<VendorParameter>* _aidl_return) {
+    std::vector<std::string> unprocessedIds;
+    for (const auto& id : in_ids) {
+        if (id == kCreateMmapBufferName) {
+            LOG(DEBUG) << __func__ << ": " << id;
+            MmapBufferDescriptor mmapDesc;
+            RETURN_STATUS_IF_ERROR(createMmapBuffer(&mmapDesc));
+            VendorParameter createMmapBuffer{.id = id};
+            createMmapBuffer.ext.setParcelable(mmapDesc);
+            LOG(DEBUG) << __func__ << ": returning " << mmapDesc.toString();
+            _aidl_return->push_back(std::move(createMmapBuffer));
+        } else {
+            unprocessedIds.push_back(id);
+        }
+    }
+    if (!unprocessedIds.empty()) {
+        return StreamCommonImpl::getVendorParameters(unprocessedIds, _aidl_return);
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus StreamMmapStub::setVendorParameters(
+        const std::vector<VendorParameter>& in_parameters, bool in_async) {
+    std::vector<VendorParameter> unprocessedParameters;
+    for (const auto& param : in_parameters) {
+        if (param.id == kCreateMmapBufferName) {
+            LOG(DEBUG) << __func__ << ": " << param.id;
+            // The value is irrelevant. The fact that this parameter can be "set" is an
+            // indication that the method can be used by the client via 'getVendorParameters'.
+        } else {
+            unprocessedParameters.push_back(param);
+        }
+    }
+    if (!unprocessedParameters.empty()) {
+        return StreamCommonImpl::setVendorParameters(unprocessedParameters, in_async);
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus StreamMmapStub::createMmapBuffer(MmapBufferDescriptor* desc) {
+    const size_t bufferSizeFrames = mContext.getBufferSizeInFrames();
+    const size_t bufferSizeBytes = static_cast<size_t>(bufferSizeFrames) * mContext.getFrameSize();
+    const std::string regionName =
+            std::string("mmap-sim-") + std::to_string(mContext.getMixPortHandle());
+    int fd = ashmem_create_region(regionName.c_str(), bufferSizeBytes);
+    if (fd < 0) {
+        PLOG(ERROR) << __func__ << ": failed to create shared memory region of " << bufferSizeBytes
+                    << " bytes";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    mSharedMemoryFd = ndk::ScopedFileDescriptor(fd);
+    if (initSharedMemory(mSharedMemoryFd.get()) != ::android::OK) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    desc->sharedMemory.fd = mSharedMemoryFd.dup();
+    desc->sharedMemory.size = bufferSizeBytes;
+    desc->burstSizeFrames = bufferSizeFrames / 2;
+    desc->flags = 0;
+    LOG(DEBUG) << __func__ << ": " << desc->toString();
+    return ndk::ScopedAStatus::ok();
+}
+
+StreamInMmapStub::StreamInMmapStub(StreamContext&& context, const SinkMetadata& sinkMetadata,
+                                   const std::vector<MicrophoneInfo>& microphones)
+    : StreamIn(std::move(context), microphones), StreamMmapStub(&mContextInstance, sinkMetadata) {}
+
+StreamOutMmapStub::StreamOutMmapStub(StreamContext&& context, const SourceMetadata& sourceMetadata,
+                                     const std::optional<AudioOffloadInfo>& offloadInfo)
+    : StreamOut(std::move(context), offloadInfo),
+      StreamMmapStub(&mContextInstance, sourceMetadata) {}
+
+}  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/stub/StreamOffloadStub.cpp b/audio/aidl/default/stub/StreamOffloadStub.cpp
index 155f76d..5f5f741 100644
--- a/audio/aidl/default/stub/StreamOffloadStub.cpp
+++ b/audio/aidl/default/stub/StreamOffloadStub.cpp
@@ -30,6 +30,8 @@
 
 namespace aidl::android::hardware::audio::core {
 
+namespace offload {
+
 std::string DspSimulatorLogic::init() {
     return "";
 }
@@ -90,6 +92,10 @@
     return Status::CONTINUE;
 }
 
+}  // namespace offload
+
+using offload::DspSimulatorState;
+
 DriverOffloadStubImpl::DriverOffloadStubImpl(const StreamContext& context)
     : DriverStubImpl(context, 0 /*asyncSleepTimeUs*/),
       mBufferNotifyFrames(static_cast<int64_t>(context.getBufferSizeInFrames()) / 2),
diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
index 806c93f..2c692f5 100644
--- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
@@ -27,6 +27,7 @@
 #include <set>
 #include <string>
 #include <string_view>
+#include <thread>
 #include <variant>
 #include <vector>
 
@@ -76,6 +77,7 @@
 using aidl::android::hardware::audio::core::IStreamIn;
 using aidl::android::hardware::audio::core::IStreamOut;
 using aidl::android::hardware::audio::core::ITelephony;
+using aidl::android::hardware::audio::core::MmapBufferDescriptor;
 using aidl::android::hardware::audio::core::ModuleDebug;
 using aidl::android::hardware::audio::core::StreamDescriptor;
 using aidl::android::hardware::audio::core::VendorParameter;
@@ -720,21 +722,8 @@
           mFlags(flags),
           mDataMQ(maybeCreateDataMQ(descriptor)),
           mIsMmapped(isMmapped(descriptor)),
-          mSharedMemoryFd(maybeGetMmapFd(descriptor)) {
-        if (isMmapped()) {
-            mSharedMemory = (int8_t*)mmap(nullptr, getBufferSizeBytes(), PROT_READ | PROT_WRITE,
-                                          MAP_SHARED, mSharedMemoryFd, 0);
-            if (mSharedMemory == MAP_FAILED) {
-                PLOG(ERROR) << __func__ << ": mmap() failed.";
-                mSharedMemory = nullptr;
-            }
-        }
-    }
-    ~StreamContext() {
-        if (mSharedMemory != nullptr) {
-            munmap(mSharedMemory, getBufferSizeBytes());
-        }
-    }
+          mMmapBurstSizeFrames(getMmapBurstSizeFrames(descriptor)),
+          mSharedMemoryFd(maybeGetMmapFd(descriptor)) {}
     void checkIsValid() const {
         EXPECT_NE(0UL, mFrameSizeBytes);
         ASSERT_NE(nullptr, mCommandMQ);
@@ -742,15 +731,14 @@
         ASSERT_NE(nullptr, mReplyMQ);
         EXPECT_TRUE(mReplyMQ->isValid());
         if (isMmapped()) {
-            ASSERT_NE(nullptr, mSharedMemory);
+            EXPECT_NE(0, mMmapBurstSizeFrames) << "MMAP burst size must not be zero";
         } else {
-            if (mDataMQ != nullptr) {
-                EXPECT_TRUE(mDataMQ->isValid());
-                EXPECT_GE(mDataMQ->getQuantumCount() * mDataMQ->getQuantumSize(),
-                          mFrameSizeBytes * mBufferSizeFrames)
-                        << "Data MQ actual buffer size is "
-                           "less than the buffer size as specified by the descriptor";
-            }
+            ASSERT_NE(nullptr, mDataMQ);
+            EXPECT_TRUE(mDataMQ->isValid());
+            EXPECT_GE(mDataMQ->getQuantumCount() * mDataMQ->getQuantumSize(),
+                      mFrameSizeBytes * mBufferSizeFrames)
+                    << "Data MQ actual buffer size is "
+                       "less than the buffer size as specified by the descriptor";
         }
     }
     size_t getBufferSizeBytes() const { return mFrameSizeBytes * mBufferSizeFrames; }
@@ -763,7 +751,8 @@
     ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
     int getSampleRate() const { return mConfig.sampleRate; }
     bool isMmapped() const { return mIsMmapped; }
-    int8_t* getMmapMemory() const { return mSharedMemory; }
+    int32_t getMmapBurstSizeFrames() const { return mMmapBurstSizeFrames; }
+    int getMmapFd() const { return mSharedMemoryFd; }
 
   private:
     static std::unique_ptr<DataMQ> maybeCreateDataMQ(const StreamDescriptor& descriptor) {
@@ -773,6 +762,13 @@
         }
         return nullptr;
     }
+    static int32_t getMmapBurstSizeFrames(const StreamDescriptor& descriptor) {
+        using Tag = StreamDescriptor::AudioBuffer::Tag;
+        if (descriptor.audio.getTag() == Tag::mmap) {
+            return descriptor.audio.get<Tag::mmap>().burstSizeFrames;
+        }
+        return -1;
+    }
     static bool isMmapped(const StreamDescriptor& descriptor) {
         using Tag = StreamDescriptor::AudioBuffer::Tag;
         return descriptor.audio.getTag() == Tag::mmap;
@@ -793,8 +789,75 @@
     const AudioIoFlags mFlags;
     std::unique_ptr<DataMQ> mDataMQ;
     const bool mIsMmapped;
-    const int32_t mSharedMemoryFd;
-    int8_t* mSharedMemory = nullptr;
+    const int32_t mMmapBurstSizeFrames;
+    const int32_t mSharedMemoryFd;  // owned by StreamDescriptor
+};
+
+struct StreamWorkerMethods {
+    virtual ~StreamWorkerMethods() = default;
+    virtual bool createMmapBuffer(MmapBufferDescriptor* desc) = 0;
+    virtual bool supportsCreateMmapBuffer() = 0;
+};
+
+class MmapSharedMemory {
+  public:
+    explicit MmapSharedMemory(const StreamContext& context, StreamWorkerMethods* stream)
+        : mStream(stream),
+          mBufferSizeBytes(context.getBufferSizeBytes()),
+          mSharedMemoryFd(::dup(context.getMmapFd())) {}
+    ~MmapSharedMemory() { releaseSharedMemory(); }
+
+    int8_t* getMmapMemory() {
+        if (mSharedMemory != nullptr) return mSharedMemory;
+        if (mSharedMemoryFd.get() != -1) {
+            int8_t* sharedMemory = (int8_t*)mmap(nullptr, mBufferSizeBytes, PROT_READ | PROT_WRITE,
+                                                 MAP_SHARED, mSharedMemoryFd.get(), 0);
+            if (sharedMemory != MAP_FAILED && sharedMemory != nullptr) {
+                mSharedMemory = sharedMemory;
+            } else {
+                PLOG(ERROR) << __func__ << ": mmap() failed, fd " << mSharedMemoryFd.get()
+                            << ", size " << mBufferSizeBytes;
+            }
+        } else {
+            LOG(WARNING) << __func__ << ": shared memory FD has not been set yet";
+        }
+        return mSharedMemory;
+    }
+    bool updateMmapSharedMemoryIfNeeded(StreamDescriptor::State state) {
+        if (mPreviousState == StreamDescriptor::State::STANDBY &&
+            state != StreamDescriptor::State::STANDBY && state != StreamDescriptor::State::ERROR) {
+            LOG(INFO) << "Mmap stream exited standby, update Mmap buffer";
+            MmapBufferDescriptor desc;
+            if (!mStream->createMmapBuffer(&desc)) return false;
+            updateMmapSharedMemoryFd(desc);
+        }
+        mPreviousState = state;
+        return true;
+    }
+
+  private:
+    static ndk::ScopedFileDescriptor getMmapFd(const MmapBufferDescriptor& desc) {
+        return desc.sharedMemory.fd.get() != -1 ? desc.sharedMemory.fd.dup()
+                                                : ndk::ScopedFileDescriptor{};
+    }
+    void releaseSharedMemory() {
+        if (mSharedMemory != nullptr) {
+            munmap(mSharedMemory, mBufferSizeBytes);
+        }
+        mSharedMemory = nullptr;
+    }
+    void updateMmapSharedMemoryFd(const MmapBufferDescriptor& desc) {
+        mSharedMemoryFd = getMmapFd(desc);
+        releaseSharedMemory();
+    }
+
+    StreamWorkerMethods* const mStream;
+    const size_t mBufferSizeBytes;
+    ndk::ScopedFileDescriptor mSharedMemoryFd;
+    // Maps on the worker thread, may unmap in the destructor on the main thread.
+    std::atomic<int8_t*> mSharedMemory = nullptr;
+    // 'STANDBY' is always the starting state for a stream.
+    StreamDescriptor::State mPreviousState = StreamDescriptor::State::STANDBY;
 };
 
 struct StreamEventReceiver {
@@ -984,15 +1047,18 @@
 class StreamCommonLogic : public StreamLogic {
   protected:
     StreamCommonLogic(const StreamContext& context, StreamLogicDriver* driver,
-                      StreamEventReceiver* eventReceiver)
+                      StreamWorkerMethods* stream, StreamEventReceiver* eventReceiver)
         : mCommandMQ(context.getCommandMQ()),
           mReplyMQ(context.getReplyMQ()),
           mDataMQ(context.getDataMQ()),
+          mMmap(context, stream),
           mData(context.getBufferSizeBytes()),
           mDriver(driver),
           mEventReceiver(eventReceiver),
           mIsMmapped(context.isMmapped()),
-          mSharedMemory(context.getMmapMemory()),
+          mMmapBurstSleep(mIsMmapped ? static_cast<double>(context.getMmapBurstSizeFrames()) /
+                                               context.getSampleRate()
+                                     : 0.0),
           mIsCompressOffload(context.getFlags().getTag() == AudioIoFlags::output &&
                              isBitPositionFlagSet(context.getFlags().get<AudioIoFlags::output>(),
                                                   AudioOutputFlags::COMPRESS_OFFLOAD)),
@@ -1008,7 +1074,9 @@
     bool isMmapped() const { return mIsMmapped; }
 
     std::string init() override {
-        LOG(DEBUG) << __func__;
+        LOG(DEBUG) << __func__ << ": isMmapped? " << mIsMmapped << ", MmapBurstSleep "
+                   << mMmapBurstSleep << ", isCompressOffload? " << mIsCompressOffload << ", "
+                   << mConfig.toString();
         return "";
     }
     const std::vector<int8_t>& getData() const { return mData; }
@@ -1054,32 +1122,40 @@
         return false;
     }
     bool readDataFromMmap(size_t readCount) {
-        if (mSharedMemory != nullptr) {
-            std::memcpy(mData.data(), mSharedMemory, readCount);
+        if (auto memory = mMmap.getMmapMemory(); memory != nullptr) {
+            std::memcpy(mData.data(), memory, readCount);
+            // Since MMap `burst` does not block, need to sleep here to get an updated position.
+            std::this_thread::sleep_for(mMmapBurstSleep);
             return true;
         }
-        LOG(ERROR) << __func__ << ": reading of " << readCount << " bytes from mmap failed";
+        LOG(ERROR) << __func__ << ": reading of " << readCount << " bytes from MMap failed";
         return false;
     }
     bool writeDataToMmap() {
-        if (mSharedMemory != nullptr) {
-            std::memcpy(mSharedMemory, mData.data(), mData.size());
+        if (auto memory = mMmap.getMmapMemory(); memory != nullptr) {
+            std::memcpy(memory, mData.data(), mData.size());
+            // Since MMap `burst` does not block, need to sleep here to get an updated position.
+            std::this_thread::sleep_for(mMmapBurstSleep);
             return true;
         }
-        LOG(ERROR) << __func__ << ": writing of " << mData.size() << " bytes to mmap failed";
+        LOG(ERROR) << __func__ << ": writing of " << mData.size() << " bytes to MMap failed";
         return false;
     }
+    bool updateMmapSharedMemoryIfNeeded(StreamDescriptor::State state) {
+        return isMmapped() ? mMmap.updateMmapSharedMemoryIfNeeded(state) : true;
+    }
 
   private:
     StreamContext::CommandMQ* mCommandMQ;
     StreamContext::ReplyMQ* mReplyMQ;
     StreamContext::DataMQ* mDataMQ;
+    MmapSharedMemory mMmap;
     std::vector<int8_t> mData;
     StreamLogicDriver* const mDriver;
     StreamEventReceiver* const mEventReceiver;
     int mLastEventSeq = StreamEventReceiver::kEventSeqInit;
     const bool mIsMmapped;
-    int8_t* mSharedMemory = nullptr;
+    const std::chrono::duration<double> mMmapBurstSleep;
     const bool mIsCompressOffload;
     const AudioConfigBase mConfig;
 };
@@ -1087,8 +1163,9 @@
 class StreamReaderLogic : public StreamCommonLogic {
   public:
     StreamReaderLogic(const StreamContext& context, StreamLogicDriver* driver,
-                      StreamEventReceiver* eventReceiver)
-        : StreamCommonLogic(context, driver, eventReceiver) {}
+                      StreamWorkerMethods* stream, StreamEventReceiver* eventReceiver)
+        : StreamCommonLogic(context, driver, stream, eventReceiver),
+          mMmapBurstSizeFrames(context.getMmapBurstSizeFrames()) {}
     // Should only be called after the worker has joined.
     const std::vector<int8_t>& getData() const { return StreamCommonLogic::getData(); }
 
@@ -1154,30 +1231,35 @@
         }
         const bool acceptedReply = getDriver()->processValidReply(reply);
         if (const size_t readCount =
-                    !isMmapped() ? getDataMQ()->availableToRead() : reply.fmqByteCount;
+                    !isMmapped() ? getDataMQ()->availableToRead()
+                                 : (command.getTag() == StreamDescriptor::Command::Tag::burst
+                                            ? mMmapBurstSizeFrames
+                                            : 0);
             readCount > 0) {
             fillData(-1);
             if (isMmapped() ? readDataFromMmap(readCount) : readDataFromMQ(readCount)) {
                 goto checkAcceptedReply;
             }
-            LOG(ERROR) << __func__ << ": reading of " << readCount << " data bytes from MQ failed";
+            LOG(ERROR) << __func__ << ": reading of " << readCount << " data bytes failed";
             return Status::ABORT;
         }  // readCount == 0
     checkAcceptedReply:
         if (acceptedReply) {
-            return Status::CONTINUE;
+            return updateMmapSharedMemoryIfNeeded(reply.state) ? Status::CONTINUE : Status::ABORT;
         }
         LOG(ERROR) << __func__ << ": unacceptable reply: " << reply.toString();
         return Status::ABORT;
     }
+
+    const int32_t mMmapBurstSizeFrames;
 };
 using StreamReader = StreamWorker<StreamReaderLogic>;
 
 class StreamWriterLogic : public StreamCommonLogic {
   public:
     StreamWriterLogic(const StreamContext& context, StreamLogicDriver* driver,
-                      StreamEventReceiver* eventReceiver)
-        : StreamCommonLogic(context, driver, eventReceiver) {}
+                      StreamWorkerMethods* stream, StreamEventReceiver* eventReceiver)
+        : StreamCommonLogic(context, driver, stream, eventReceiver) {}
     // Should only be called after the worker has joined.
     const std::vector<int8_t>& getData() const { return StreamCommonLogic::getData(); }
 
@@ -1293,7 +1375,7 @@
             return Status::ABORT;
         }
         if (getDriver()->processValidReply(reply)) {
-            return Status::CONTINUE;
+            return updateMmapSharedMemoryIfNeeded(reply.state) ? Status::CONTINUE : Status::ABORT;
         }
         LOG(ERROR) << __func__ << ": unacceptable reply: " << reply.toString();
         return Status::ABORT;
@@ -1381,7 +1463,7 @@
 };
 
 template <typename Stream>
-class WithStream {
+class WithStream : public StreamWorkerMethods {
   public:
     static ndk::ScopedAStatus callClose(std::shared_ptr<Stream> stream) {
         std::shared_ptr<IStreamCommon> common;
@@ -1421,6 +1503,7 @@
         const AudioConfigBase cfg{config.sampleRate->value, *config.channelMask, *config.format};
         mContext.emplace(mDescriptor, cfg, config.flags.value());
         ASSERT_NO_FATAL_FAILURE(mContext.value().checkIsValid());
+        ASSERT_IS_OK(mStream->getInterfaceVersion(&mInterfaceVersion));
     }
     void SetUp(IModule* module, long bufferSizeFrames) {
         ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module));
@@ -1432,13 +1515,66 @@
     std::shared_ptr<Stream> getSharedPointer() const { return mStream; }
     const AudioPortConfig& getPortConfig() const { return mPortConfig.get(); }
     int32_t getPortId() const { return mPortConfig.getId(); }
+    // StreamWorkerMethods
+    bool createMmapBuffer(MmapBufferDescriptor* desc) override {
+        std::shared_ptr<IStreamCommon> common;
+        ndk::ScopedAStatus status = mStream->getStreamCommon(&common);
+        if (!status.isOk()) {
+            LOG(ERROR) << __func__ << ": getStreamCommon failed: " << status.getMessage();
+            return false;
+        }
+        if (mInterfaceVersion <= kAidlVersion3) {
+            std::vector<VendorParameter> parameters;
+            ScopedAStatus result = common->getVendorParameters({kCreateMmapBuffer}, &parameters);
+            if (result.isOk() && parameters.size() == 1) {
+                std::optional<MmapBufferDescriptor> result;
+                binder_status_t status = parameters[0].ext.getParcelable(&result);
+                if (status == ::android::OK) {
+                    *desc = std::move(*result);
+                    return true;
+                } else {
+                    LOG(ERROR) << __func__ << ": failed to extract parcelable: " << status;
+                }
+            } else {
+                LOG(ERROR) << __func__
+                           << ": failed to call 'createMmapBuffer' via 'getVendorParameter': "
+                           << result.getMessage();
+            }
+        } else {
+            // TODO: Use common->createMmapBuffer after interface update.
+        }
+        return false;
+    }
+    bool supportsCreateMmapBuffer() override {
+        if (!mHasCreateMmapBuffer.has_value()) {
+            if (mInterfaceVersion > kAidlVersion3) {
+                mHasCreateMmapBuffer = true;
+            } else {
+                std::shared_ptr<IStreamCommon> common;
+                ndk::ScopedAStatus status = mStream->getStreamCommon(&common);
+                if (status.isOk()) {
+                    VendorParameter createMmapBuffer{.id = kCreateMmapBuffer};
+                    mHasCreateMmapBuffer =
+                            common->setVendorParameters({createMmapBuffer}, false).isOk();
+                } else {
+                    LOG(ERROR) << __func__ << ": getStreamCommon failed: " << status.getMessage();
+                    return false;
+                }
+            }
+        }
+        return mHasCreateMmapBuffer.value();
+    }
 
   private:
+    static constexpr const char* kCreateMmapBuffer = "aosp.createMmapBuffer";
+
     WithAudioPortConfig mPortConfig;
     std::shared_ptr<Stream> mStream;
     StreamDescriptor mDescriptor;
     std::optional<StreamContext> mContext;
     std::shared_ptr<DefaultStreamCallback> mStreamCallback;
+    int32_t mInterfaceVersion = -1;
+    std::optional<bool> mHasCreateMmapBuffer;
 };
 
 SinkMetadata GenerateSinkMetadata(const AudioPortConfig& portConfig) {
@@ -3103,6 +3239,7 @@
     const StreamContext* getStreamContext() const { return mStream->getContext(); }
     StreamEventReceiver* getStreamEventReceiver() { return mStream->getEventReceiver(); }
     std::shared_ptr<Stream> getStreamSharedPointer() const { return mStream->getSharedPointer(); }
+    StreamWorkerMethods* getStreamWorkerMethods() const { return mStream.get(); }
     const std::string& skipTestReason() const { return mSkipTestReason; }
 
   private:
@@ -3315,12 +3452,11 @@
 static bool skipStreamIoTestForMixPortConfig(const AudioPortConfig& portConfig) {
     return (portConfig.flags.value().getTag() == AudioIoFlags::input &&
             isAnyBitPositionFlagSet(portConfig.flags.value().template get<AudioIoFlags::input>(),
-                                    {AudioInputFlags::MMAP_NOIRQ, AudioInputFlags::VOIP_TX,
-                                     AudioInputFlags::HW_HOTWORD, AudioInputFlags::HOTWORD_TAP})) ||
+                                    {AudioInputFlags::VOIP_TX, AudioInputFlags::HW_HOTWORD,
+                                     AudioInputFlags::HOTWORD_TAP})) ||
            (portConfig.flags.value().getTag() == AudioIoFlags::output &&
             (isAnyBitPositionFlagSet(portConfig.flags.value().template get<AudioIoFlags::output>(),
-                                     {AudioOutputFlags::MMAP_NOIRQ, AudioOutputFlags::VOIP_RX,
-                                      AudioOutputFlags::INCALL_MUSIC}) ||
+                                     {AudioOutputFlags::VOIP_RX, AudioOutputFlags::INCALL_MUSIC}) ||
              (isBitPositionFlagSet(portConfig.flags.value().template get<AudioIoFlags::output>(),
                                    AudioOutputFlags::COMPRESS_OFFLOAD) &&
               !getMediaFileInfoForConfig(portConfig))));
@@ -3331,6 +3467,12 @@
     return device.type.type == AudioDeviceType::IN_ECHO_REFERENCE;
 }
 
+// MMap implementation on the HAL version <= 3 was not test compliant,
+// unless the stream provides 'createMmapBuffer'
+static bool skipStreamIoTestForStream(const StreamContext* context, StreamWorkerMethods* stream) {
+    return context->isMmapped() && !stream->supportsCreateMmapBuffer();
+}
+
 template <typename Stream>
 class StreamFixtureWithWorker {
   public:
@@ -3376,7 +3518,8 @@
                 makeBurstCommands(mIsSync, burstCount, standbyInputWhenDone),
                 context->getFrameSizeBytes(), context->isMmapped());
         mWorker = std::make_unique<typename IOTraits<Stream>::Worker>(
-                *context, mWorkerDriver.get(), mStream->getStreamEventReceiver());
+                *context, mWorkerDriver.get(), mStream->getStreamWorkerMethods(),
+                mStream->getStreamEventReceiver());
         LOG(DEBUG) << __func__ << ": starting " << IOTraits<Stream>::directionStr << " worker...";
         ASSERT_TRUE(mWorker->start());
     }
@@ -3419,6 +3562,10 @@
         if (skipStreamIoTestForMixPortConfig(mStream->getPortConfig())) {
             mSkipTestReason = "Mix port config is not supported for stream I/O tests";
         }
+        if (skipStreamIoTestForStream(mStream->getStreamContext(),
+                                      mStream->getStreamWorkerMethods())) {
+            mSkipTestReason = "Stream can not be used in I/O tests";
+        }
     }
 
     const bool mIsSync;
@@ -3774,6 +3921,7 @@
             ASSERT_EQ("", stream.skipTestReason());
             StreamLogicDriverInvalidCommand driver(seq.second);
             typename IOTraits<Stream>::Worker worker(*stream.getStreamContext(), &driver,
+                                                     stream.getStreamWorkerMethods(),
                                                      stream.getStreamEventReceiver());
             LOG(DEBUG) << __func__ << ": starting worker...";
             ASSERT_TRUE(worker.start());
@@ -4373,11 +4521,15 @@
         ASSERT_NO_FATAL_FAILURE(
                 stream.SetUpStreamForMixPortConfig(module.get(), moduleConfig.get(), portConfig));
         if (skipStreamIoTestForDevice(stream.getDevice())) return;
+        if (skipStreamIoTestForStream(stream.getStreamContext(), stream.getStreamWorkerMethods())) {
+            return;
+        }
         ASSERT_EQ("", stream.skipTestReason());
         StreamLogicDefaultDriver driver(commandsAndStates,
                                         stream.getStreamContext()->getFrameSizeBytes(),
                                         stream.getStreamContext()->isMmapped());
         typename IOTraits<Stream>::Worker worker(*stream.getStreamContext(), &driver,
+                                                 stream.getStreamWorkerMethods(),
                                                  stream.getStreamEventReceiver());
 
         LOG(DEBUG) << __func__ << ": starting worker...";
@@ -4407,10 +4559,14 @@
         if (skipStreamIoTestForDevice(stream.getDevice())) return;
         ASSERT_EQ("", stream.skipTestReason());
         ASSERT_NO_FATAL_FAILURE(stream.TeardownPatchSetUpStream(module.get()));
+        if (skipStreamIoTestForStream(stream.getStreamContext(), stream.getStreamWorkerMethods())) {
+            return;
+        }
         StreamLogicDefaultDriver driver(commandsAndStates,
                                         stream.getStreamContext()->getFrameSizeBytes(),
                                         stream.getStreamContext()->isMmapped());
         typename IOTraits<Stream>::Worker worker(*stream.getStreamContext(), &driver,
+                                                 stream.getStreamWorkerMethods(),
                                                  stream.getStreamEventReceiver());
         ASSERT_NO_FATAL_FAILURE(stream.ReconnectPatch(module.get()));
 
@@ -4788,7 +4944,7 @@
 // Allow optional routing via the TRANSFERRING state on bursts.
 StateDag::Node makeAsyncBurstCommands(StateDag* d, size_t burstCount, StateDag::Node last) {
     using State = StreamDescriptor::State;
-    std::reference_wrapper<std::remove_reference_t<StateDag::Node>> prev = last;
+    std::reference_wrapper<StateDag::value_type> prev = last;
     for (size_t i = 0; i < burstCount; ++i) {
         StateDag::Node active = d->makeNode(State::ACTIVE, kBurstCommand, prev);
         active.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, prev));
diff --git a/audio/aidl/vts/VtsHalAudioEffectTargetTestTemplate.xml b/audio/aidl/vts/VtsHalAudioEffectTargetTestTemplate.xml
index 4170b4c..c92e852 100644
--- a/audio/aidl/vts/VtsHalAudioEffectTargetTestTemplate.xml
+++ b/audio/aidl/vts/VtsHalAudioEffectTargetTestTemplate.xml
@@ -33,6 +33,6 @@
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp" />
         <option name="module-name" value="{MODULE}" />
-        <option name="native-test-timeout" value="30m" />
+        <option name="native-test-timeout" value="10m" />
     </test>
 </configuration>
diff --git a/audio/aidl/vts/VtsHalDynamicsProcessingTest.cpp b/audio/aidl/vts/VtsHalDynamicsProcessingTest.cpp
index 2ce7b51..98f7d79 100644
--- a/audio/aidl/vts/VtsHalDynamicsProcessingTest.cpp
+++ b/audio/aidl/vts/VtsHalDynamicsProcessingTest.cpp
@@ -465,6 +465,7 @@
                                                  float fullScaleSineDb) {
     ASSERT_NO_FATAL_FAILURE(SetUpDynamicsProcessingEffect());
     SKIP_TEST_IF_DATA_UNSUPPORTED(mDescriptor.common.flags);
+    mInput.resize(kFrameCount * mChannelCount);
     ASSERT_NO_FATAL_FAILURE(
             generateSineWave(testFrequencies, mInput, 1.0, kSamplingFrequency, mChannelLayout));
     mInputDb = calculateDb(mInput);
@@ -722,13 +723,10 @@
       public DynamicsProcessingTestHelper {
   public:
     DynamicsProcessingInputGainDataTest()
-        : DynamicsProcessingTestHelper((GetParam()), AudioChannelLayout::LAYOUT_MONO) {
-        mInput.resize(kFrameCount * mChannelCount);
-    }
+        : DynamicsProcessingTestHelper((GetParam()), AudioChannelLayout::LAYOUT_MONO) {}
 
     void SetUp() override {
-        ASSERT_NO_FATAL_FAILURE(
-                setUpDataTest({static_cast<int>(kInputFrequency)}, kSineFullScaleDb));
+        ASSERT_NO_FATAL_FAILURE(setUpDataTest({kInputFrequency}, kSineFullScaleDb));
     }
 
     void TearDown() override { TearDownDynamicsProcessingEffect(); }
@@ -851,15 +849,12 @@
     : public ::testing::TestWithParam<LimiterConfigDataTestParams>,
       public DynamicsProcessingTestHelper {
   public:
-    DynamicsProcessingLimiterConfigDataTest()
-        : DynamicsProcessingTestHelper(GetParam(), AudioChannelLayout::LAYOUT_MONO) {
-        mBufferSize = kFrameCount * mChannelCount;
-        mInput.resize(mBufferSize);
-    }
+    DynamicsProcessingLimiterConfigDataTest(LimiterConfigDataTestParams param = GetParam(),
+                                            int32_t layout = AudioChannelLayout::LAYOUT_MONO)
+        : DynamicsProcessingTestHelper(param, layout) {}
 
     void SetUp() override {
-        ASSERT_NO_FATAL_FAILURE(
-                setUpDataTest({static_cast<int>(kInputFrequency)}, kSineFullScaleDb));
+        ASSERT_NO_FATAL_FAILURE(setUpDataTest({kInputFrequency}, kSineFullScaleDb));
     }
 
     void TearDown() override { TearDownDynamicsProcessingEffect(); }
@@ -876,12 +871,35 @@
         ratio = inputOverThreshold / outputOverThreshold;
     }
 
-    void setLimiterParamsAndProcess(std::vector<float>& input, std::vector<float>& output) {
+    void setLimiterParamsAndProcess(std::vector<float>& input, std::vector<float>& output,
+                                    bool isEngineLimiterEnabled = true) {
+        mEngineConfigPreset.limiterInUse = isEngineLimiterEnabled;
         addEngineConfig(mEngineConfigPreset);
         addLimiterConfig(mLimiterConfigList);
         EXPECT_NO_FATAL_FAILURE(setParamsAndProcess(input, output));
     }
 
+    void testEnableDisableConfiguration(bool isLimiterEnabled, bool isEngineLimiterEnabled) {
+        cleanUpLimiterConfig();
+        std::vector<float> output(mInput.size());
+        for (int i = 0; i < mChannelCount; i++) {
+            // Set non-default values
+            fillLimiterConfig(mLimiterConfigList, i, isLimiterEnabled, kDefaultLinkerGroup,
+                              5 /*attack time*/, 5 /*release time*/, 10 /*ratio*/,
+                              -20 /*threshold*/, 5 /*postgain*/);
+        }
+        ASSERT_NO_FATAL_FAILURE(setLimiterParamsAndProcess(mInput, output, isEngineLimiterEnabled));
+        float outputdB = calculateDb(output, kStartIndex);
+        if (isAllParamsValid()) {
+            if (isLimiterEnabled && isEngineLimiterEnabled) {
+                EXPECT_GT(std::abs(mInputDb - outputdB), kMinDifferenceDb)
+                        << "Input level: " << mInputDb << " Output level: " << outputdB;
+            } else {
+                EXPECT_NEAR(mInputDb, outputdB, kLimiterTestToleranceDb);
+            }
+        }
+    }
+
     void cleanUpLimiterConfig() {
         CleanUp();
         mLimiterConfigList.clear();
@@ -892,8 +910,9 @@
     static constexpr float kDefaultRatio = 4;
     static constexpr float kDefaultThreshold = -10;
     static constexpr float kDefaultPostGain = 0;
-    static constexpr float kInputFrequency = 1000;
     static constexpr float kLimiterTestToleranceDb = 0.05;
+    static constexpr float kMinDifferenceDb = 5;
+    const std::vector<bool> kEnableValues = {true, false, true};
     std::vector<DynamicsProcessing::LimiterConfig> mLimiterConfigList;
     int mBufferSize;
 };
@@ -975,25 +994,16 @@
 }
 
 TEST_P(DynamicsProcessingLimiterConfigDataTest, LimiterEnableDisable) {
-    std::vector<bool> limiterEnableValues = {false, true};
-    std::vector<float> output(mInput.size());
-    for (bool isEnabled : limiterEnableValues) {
-        cleanUpLimiterConfig();
-        for (int i = 0; i < mChannelCount; i++) {
-            // Set non-default values
-            fillLimiterConfig(mLimiterConfigList, i, isEnabled, kDefaultLinkerGroup,
-                              5 /*attack time*/, 5 /*release time*/, 10 /*ratio*/,
-                              -10 /*threshold*/, 5 /*postgain*/);
-        }
-        ASSERT_NO_FATAL_FAILURE(setLimiterParamsAndProcess(mInput, output));
-        if (!isAllParamsValid()) {
-            continue;
-        }
-        if (isEnabled) {
-            EXPECT_NE(mInputDb, calculateDb(output, kStartIndex));
-        } else {
-            EXPECT_NEAR(mInputDb, calculateDb(output, kStartIndex), kLimiterTestToleranceDb);
-        }
+    for (bool isLimiterEnabled : kEnableValues) {
+        ASSERT_NO_FATAL_FAILURE(
+                testEnableDisableConfiguration(isLimiterEnabled, true /*Engine Enabled*/));
+    }
+}
+
+TEST_P(DynamicsProcessingLimiterConfigDataTest, LimiterEnableDisableViaEngine) {
+    for (bool isEngineLimiterEnabled : kEnableValues) {
+        ASSERT_NO_FATAL_FAILURE(
+                testEnableDisableConfiguration(true /*Limiter Enabled*/, isEngineLimiterEnabled));
     }
 }
 
@@ -1010,6 +1020,103 @@
                          });
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DynamicsProcessingLimiterConfigDataTest);
 
+class DynamicsProcessingLimiterLinkerDataTest : public DynamicsProcessingLimiterConfigDataTest {
+  public:
+    DynamicsProcessingLimiterLinkerDataTest()
+        : DynamicsProcessingLimiterConfigDataTest(GetParam(), AudioChannelLayout::LAYOUT_STEREO) {}
+
+    void calculateExpectedOutputDb(std::vector<float>& expectedOutputDb) {
+        std::vector<float> inputDbValues = calculateStereoDb(mInput, kStartIndex);
+        ASSERT_EQ(inputDbValues.size(), kRatioThresholdPairValues.size());
+        EXPECT_NEAR(inputDbValues[0], inputDbValues[1], kToleranceDb);
+        for (size_t i = 0; i < kRatioThresholdPairValues.size(); i++) {
+            const auto& [ratio, threshold] = kRatioThresholdPairValues[i];
+            expectedOutputDb.push_back((inputDbValues[i] - threshold) / ratio + threshold);
+        }
+    }
+
+    std::vector<float> calculateStereoDb(const std::vector<float>& input,
+                                         size_t startSamplePos = 0) {
+        std::vector<float> leftChannel;
+        std::vector<float> rightChannel;
+        for (size_t i = 0; i < input.size(); i += 2) {
+            leftChannel.push_back(input[i]);
+            if (i + 1 < input.size()) {
+                rightChannel.push_back(input[i + 1]);
+            }
+        }
+        return {calculateDb(leftChannel, startSamplePos),
+                calculateDb(rightChannel, startSamplePos)};
+    }
+
+    void setLinkGroupAndProcess(std::vector<float>& output, bool hasSameLinkGroup) {
+        for (int i = 0; i < mChannelCount; i++) {
+            const auto& [ratio, threshold] = kRatioThresholdPairValues[i];
+            ASSERT_NE(ratio, 0);
+            int linkGroup = hasSameLinkGroup ? kDefaultLinkerGroup : i;
+            fillLimiterConfig(mLimiterConfigList, i, true, linkGroup, kDefaultAttackTime,
+                              kDefaultReleaseTime, ratio, threshold, kDefaultPostGain);
+        }
+
+        ASSERT_NO_FATAL_FAILURE(setLimiterParamsAndProcess(mInput, output));
+
+        if (!isAllParamsValid()) {
+            GTEST_SKIP() << "Invalid parameters. Skipping the test\n";
+        }
+    }
+
+    const std::vector<std::pair<float, float>> kRatioThresholdPairValues = {{2, -10}, {5, -20}};
+};
+
+TEST_P(DynamicsProcessingLimiterLinkerDataTest, SameLinkGroupDifferentConfigs) {
+    std::vector<float> output(mInput.size());
+
+    ASSERT_NO_FATAL_FAILURE(setLinkGroupAndProcess(output, true));
+
+    std::vector<float> outputDbValues = calculateStereoDb(output, kStartIndex);
+
+    std::vector<float> expectedOutputDbValues;
+    ASSERT_NO_FATAL_FAILURE(calculateExpectedOutputDb(expectedOutputDbValues));
+
+    // Verify that the actual output dB is same as the calculated maximum attenuation.
+    float expectedOutputDb = std::min(expectedOutputDbValues[0], expectedOutputDbValues[1]);
+    EXPECT_NEAR(outputDbValues[0], expectedOutputDb, kToleranceDb);
+    EXPECT_NEAR(outputDbValues[1], expectedOutputDb, kToleranceDb);
+}
+
+TEST_P(DynamicsProcessingLimiterLinkerDataTest, DifferentLinkGroupDifferentConfigs) {
+    std::vector<float> output(mInput.size());
+
+    ASSERT_NO_FATAL_FAILURE(setLinkGroupAndProcess(output, false));
+
+    std::vector<float> outputDbValues = calculateStereoDb(output, kStartIndex);
+
+    std::vector<float> expectedOutputDbValues;
+    ASSERT_NO_FATAL_FAILURE(calculateExpectedOutputDb(expectedOutputDbValues));
+
+    // Verify that both channels have different compression levels
+    EXPECT_GT(abs(expectedOutputDbValues[0] - expectedOutputDbValues[1]), kMinDifferenceDb)
+            << "Left channel level: " << expectedOutputDbValues[0]
+            << " Right channel level: " << expectedOutputDbValues[1];
+
+    // Verify that the actual output and the calculated dB values are same
+    EXPECT_NEAR(outputDbValues[0], expectedOutputDbValues[0], kToleranceDb);
+    EXPECT_NEAR(outputDbValues[1], expectedOutputDbValues[1], kToleranceDb);
+}
+
+INSTANTIATE_TEST_SUITE_P(DynamicsProcessingTest, DynamicsProcessingLimiterLinkerDataTest,
+                         testing::ValuesIn(EffectFactoryHelper::getAllEffectDescriptors(
+                                 IFactory::descriptor, getEffectTypeUuidDynamicsProcessing())),
+                         [](const auto& info) {
+                             auto descriptor = info.param;
+                             std::string name = getPrefix(descriptor.second);
+                             std::replace_if(
+                                     name.begin(), name.end(),
+                                     [](const char c) { return !std::isalnum(c); }, '_');
+                             return name;
+                         });
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DynamicsProcessingLimiterLinkerDataTest);
+
 /**
  * Test DynamicsProcessing ChannelConfig
  */
@@ -1215,7 +1322,6 @@
   public:
     DynamicsProcessingEqBandConfigDataTest()
         : DynamicsProcessingTestHelper(GetParam(), AudioChannelLayout::LAYOUT_MONO) {
-        mInput.resize(kFrameCount * mChannelCount);
         mBinOffsets.resize(mMultitoneTestFrequencies.size());
     }
 
@@ -1444,7 +1550,6 @@
   public:
     DynamicsProcessingMbcBandConfigDataTest()
         : DynamicsProcessingTestHelper(GetParam(), AudioChannelLayout::LAYOUT_MONO) {
-        mInput.resize(kFrameCount * mChannelCount);
         mBinOffsets.resize(mMultitoneTestFrequencies.size());
     }
 
diff --git a/radio/aidl/android/hardware/radio/network/SecurityAlgorithm.aidl b/radio/aidl/android/hardware/radio/network/SecurityAlgorithm.aidl
index 451eaa9..5825395 100644
--- a/radio/aidl/android/hardware/radio/network/SecurityAlgorithm.aidl
+++ b/radio/aidl/android/hardware/radio/network/SecurityAlgorithm.aidl
@@ -88,8 +88,12 @@
     ENCR_AES_CBC = 100,
     AUTH_HMAC_SHA2_256_128 = 101,
 
-    /** Unknown */
+    /** Misc placeholders */
+    // Unknown is now reserved for cases involving DRBs where an integrity algorithm
+    // is not assigned (which is different from a null algorithm being used) but a
+    // notification based solely on the integrity algorithm should not be triggered.
     UNKNOWN = 113,
+    // Currently unused
     OTHER = 114,
 
     /** For proprietary algorithms */
diff --git a/radio/aidl/compat/libradiocompat/network/RadioNetwork.cpp b/radio/aidl/compat/libradiocompat/network/RadioNetwork.cpp
index b5aee5c..41200f9 100644
--- a/radio/aidl/compat/libradiocompat/network/RadioNetwork.cpp
+++ b/radio/aidl/compat/libradiocompat/network/RadioNetwork.cpp
@@ -41,7 +41,7 @@
     if (mHal1_6) {
         mHal1_6->getAllowedNetworkTypesBitmap(serial);
     } else {
-        mHal1_5->getPreferredNetworkType(serial);
+        mHal1_5->getPreferredNetworkTypeBitmap(serial);
     }
     return ok();
 }
@@ -166,7 +166,7 @@
     if (mHal1_6) {
         mHal1_6->setAllowedNetworkTypesBitmap(serial, raf);
     } else {
-        mHal1_5->setPreferredNetworkType(serial, getNetworkTypeFromRaf(raf));
+        mHal1_5->setPreferredNetworkTypeBitmap(serial, raf);
     }
     return ok();
 }
diff --git a/radio/aidl/compat/libradiocompat/network/RadioResponse-network.cpp b/radio/aidl/compat/libradiocompat/network/RadioResponse-network.cpp
index 5a98eb2..428070c 100644
--- a/radio/aidl/compat/libradiocompat/network/RadioResponse-network.cpp
+++ b/radio/aidl/compat/libradiocompat/network/RadioResponse-network.cpp
@@ -55,9 +55,10 @@
 }
 
 Return<void> RadioResponse::getPreferredNetworkTypeBitmapResponse(
-        const V1_0::RadioResponseInfo& info, hidl_bitfield<V1_4::RadioAccessFamily>) {
+        const V1_0::RadioResponseInfo& info,
+        hidl_bitfield<V1_4::RadioAccessFamily> networkTypeBitmap) {
     LOG_CALL << info.serial;
-    LOG(ERROR) << "IRadio HAL 1.4 not supported";
+    networkCb()->getAllowedNetworkTypesBitmapResponse(toAidl(info), networkTypeBitmap);
     return {};
 }
 
@@ -290,7 +291,7 @@
 Return<void> RadioResponse::setPreferredNetworkTypeBitmapResponse(
         const V1_0::RadioResponseInfo& info) {
     LOG_CALL << info.serial;
-    LOG(ERROR) << "IRadio HAL 1.4 not supported";
+    networkCb()->setAllowedNetworkTypesBitmapResponse(toAidl(info));
     return {};
 }
 
diff --git a/secure_element/1.0/vts/functional/VtsHalSecureElementV1_0TargetTest.cpp b/secure_element/1.0/vts/functional/VtsHalSecureElementV1_0TargetTest.cpp
index 1623960..5df39ed 100644
--- a/secure_element/1.0/vts/functional/VtsHalSecureElementV1_0TargetTest.cpp
+++ b/secure_element/1.0/vts/functional/VtsHalSecureElementV1_0TargetTest.cpp
@@ -209,4 +209,15 @@
 INSTANTIATE_TEST_SUITE_P(
         PerInstance, SecureElementHidlTest,
         testing::ValuesIn(android::hardware::getAllHalInstanceNames(ISecureElement::descriptor)),
-        android::hardware::PrintInstanceNameToString);
\ No newline at end of file
+        android::hardware::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    std::system("svc nfc disable"); /* Turn off NFC */
+    sleep(5);
+    int status = RUN_ALL_TESTS();
+    LOG(INFO) << "Test result = " << status;
+    std::system("svc nfc enable"); /* Turn on NFC */
+    sleep(5);
+    return status;
+}
diff --git a/secure_element/1.1/vts/functional/VtsHalSecureElementV1_1TargetTest.cpp b/secure_element/1.1/vts/functional/VtsHalSecureElementV1_1TargetTest.cpp
index d7e4546..106ee29 100644
--- a/secure_element/1.1/vts/functional/VtsHalSecureElementV1_1TargetTest.cpp
+++ b/secure_element/1.1/vts/functional/VtsHalSecureElementV1_1TargetTest.cpp
@@ -94,3 +94,14 @@
         PerInstance, SecureElementHidlTest,
         testing::ValuesIn(android::hardware::getAllHalInstanceNames(ISecureElement::descriptor)),
         android::hardware::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    std::system("svc nfc disable"); /* Turn off NFC */
+    sleep(5);
+    int status = RUN_ALL_TESTS();
+    LOG(INFO) << "Test result = " << status;
+    std::system("svc nfc enable"); /* Turn on NFC */
+    sleep(5);
+    return status;
+}
diff --git a/secure_element/1.2/vts/functional/VtsHalSecureElementV1_2TargetTest.cpp b/secure_element/1.2/vts/functional/VtsHalSecureElementV1_2TargetTest.cpp
index 26b2ded..98c8a9c 100644
--- a/secure_element/1.2/vts/functional/VtsHalSecureElementV1_2TargetTest.cpp
+++ b/secure_element/1.2/vts/functional/VtsHalSecureElementV1_2TargetTest.cpp
@@ -108,3 +108,14 @@
         PerInstance, SecureElementHidlTest,
         testing::ValuesIn(android::hardware::getAllHalInstanceNames(ISecureElement::descriptor)),
         android::hardware::PrintInstanceNameToString);
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    std::system("svc nfc disable"); /* Turn off NFC */
+    sleep(5);
+    int status = RUN_ALL_TESTS();
+    LOG(INFO) << "Test result = " << status;
+    std::system("svc nfc enable"); /* Turn on NFC */
+    sleep(5);
+    return status;
+}
diff --git a/secure_element/aidl/vts/VtsHalSecureElementTargetTest.cpp b/secure_element/aidl/vts/VtsHalSecureElementTargetTest.cpp
index 9678da4..da69b37 100644
--- a/secure_element/aidl/vts/VtsHalSecureElementTargetTest.cpp
+++ b/secure_element/aidl/vts/VtsHalSecureElementTargetTest.cpp
@@ -320,5 +320,10 @@
     ::testing::InitGoogleTest(&argc, argv);
     ABinderProcess_setThreadPoolMaxThreadCount(1);
     ABinderProcess_startThreadPool();
-    return RUN_ALL_TESTS();
+    std::system("/system/bin/svc nfc disable"); /* Turn off NFC */
+    sleep(5);
+    int status = RUN_ALL_TESTS();
+    std::system("/system/bin/svc nfc enable"); /* Turn on NFC */
+    sleep(5);
+    return status;
 }
diff --git a/security/keymint/aidl/Android.bp b/security/keymint/aidl/Android.bp
index 5236e90..195e47b 100644
--- a/security/keymint/aidl/Android.bp
+++ b/security/keymint/aidl/Android.bp
@@ -52,7 +52,7 @@
         },
 
     ],
-
+    min_sdk_version: "35",
 }
 
 // An aidl_interface_defaults that includes the latest KeyMint AIDL interface.
diff --git a/security/rkp/README.md b/security/rkp/README.md
index 43a00fb..ef52c0c 100644
--- a/security/rkp/README.md
+++ b/security/rkp/README.md
@@ -240,28 +240,35 @@
 
 ### Support for Android Virtualization Framework
 
-The Android Virtualization Framwork (AVF) relies on RKP to provision keys for VMs. A
-privileged vm, the RKP VM, is reponsible for generating and managing the keys for client
-VMs that run virtualized workloads. See the following for more background information on the
-RKP VM:
-*    [rkp-vm](https://android.googlesource.com/platform/packages/modules/Virtualization/+/main/service_vm/README.md#rkp-vm-remote-key-provisioning-virtual-machine)
-*    [rkp-service](https://source.android.com/docs/core/ota/modular-system/remote-key-provisioning#stack-architecture)
+The Android Virtualization Framework (AVF) relies on RKP to provision keys for
+VMs. There are a privileged set of VMs that RKP will recognise and provision
+keys to for specific applications, like Widevine, and for services, like
+[VM attestation][vm-attestation]. These privileged VMs are identified by their
+DICE chain through a combination of the [RKP VM marker][rkp-vm-marker]
+(key `-70006`) and the component name.
 
-It is important to distinquish the RKP VM from other components, such as KeyMint. An
-[RKP VM marker](https://pigweed.googlesource.com/open-dice/+/HEAD/docs/android.md#configuration-descriptor)
-(key `-70006`) is used for this purpose. The existence or absence of this marker is used to
-identify the type of component decribed by a given DICE chain.
+[vm-attestation]: http://android.googlesource.com/platform/packages/modules/Virtualization/+/main/docs/vm_remote_attestation.md
+[rkp-vm-marker]: https://pigweed.googlesource.com/open-dice/+/HEAD/docs/android.md#configuration-descriptor
 
-The following describes which certificate types may be request based on the RKP VM marker:
-1. "rkp-vm": If a DICE chain has zero or more certificates without the RKP VM
-   marker followed by one or more certificates with the marker, then that chain
-   describes an RKP VM. If there are further certificates without the RKP VM
-   marker, then the chain does not describe an RKP VM.
+If a DICE chain begins from the root with zero or more certificates without
+the RKP VM marker, followed by only certificates with the marker up to and
+including the leaf certificate, then that chain describes a VM that RKP might
+provision keys to. Implementations must include the first RKP VM marker as early
+as possible after the point of divergence between TEE and non-TEE components in
+the DICE chain, prior to loading the Android Bootloader (ABL).
 
-   Implementations must include the first RKP VM marker as early as possible
-   after the point of divergence between TEE and non-TEE components in the DICE
-   chain, prior to loading the Android Bootloader (ABL).
-2. "widevine" or "keymint": If there are no certificates with the RKP VM
-   marker then it describes a TEE component.
-3. None: Any component described by a DICE chain that does not match the above
-   two categories.
+The component name of the leaf certificate then identifies the kind of keys for
+RKP to provision:
+
+*   "rkp-vm": for VM attestation keys managed by the [service VM][service-vm]
+*   "keymint": for Android attestation keys
+*   "widevine": for Widevine keys
+
+[service-vm]: https://android.googlesource.com/platform/packages/modules/Virtualization/+/main/service_vm/README.md#rkp-vm-remote-key-provisioning-virtual-machine
+
+If there are no certificates with the RKP VM marker in the DICE chain then it
+describes a TEE component that can be provisioned with Widevine and Android
+attestation keys.
+
+Any remaining DICE chains describe a component to which RKP will not provision
+keys.
\ No newline at end of file
diff --git a/security/rkp/aidl/Android.bp b/security/rkp/aidl/Android.bp
index adc63f6..df8a0ef 100644
--- a/security/rkp/aidl/Android.bp
+++ b/security/rkp/aidl/Android.bp
@@ -35,6 +35,7 @@
                 "//apex_available:platform",
                 "com.android.virt",
             ],
+            min_sdk_version: "35",
         },
     },
     versions_with_info: [
diff --git a/security/secureclock/aidl/Android.bp b/security/secureclock/aidl/Android.bp
index d7e7b43..1d4ec58 100644
--- a/security/secureclock/aidl/Android.bp
+++ b/security/secureclock/aidl/Android.bp
@@ -28,4 +28,5 @@
         },
     },
     versions: ["1"],
+    min_sdk_version: "35",
 }
diff --git a/security/see/authmgr/aidl/README.md b/security/see/authmgr/aidl/README.md
index 97b2b1d..d7bb5e4 100644
--- a/security/see/authmgr/aidl/README.md
+++ b/security/see/authmgr/aidl/README.md
@@ -16,6 +16,9 @@
 requirements that are specific to Android release versions.
 
 ### Android 16
-If implementing `IAuthMgrAuthorization` in Android 16 only one AuthMgr Backend is
+- If implementing `IAuthMgrAuthorization` in Android 16 only one AuthMgr Backend is
 supported and dynamic service discovery is not supported. The AuthMgr Backend
-service must be exposed on secure partition ID 0x8001 over VSOCK port 1.
\ No newline at end of file
+service must be exposed on secure partition ID 0x8001 over VSOCK port 1.
+
+- AuthMgr Front Ends must implement the "android.16" profile as described in the
+[Android Profile for DICE](https://pigweed.googlesource.com/open-dice/+/HEAD/docs/android.md#versions)
\ No newline at end of file
diff --git a/sensors/aidl/vts/VtsAidlHalSensorsTargetTest.cpp b/sensors/aidl/vts/VtsAidlHalSensorsTargetTest.cpp
index b1590f9..77e2916 100644
--- a/sensors/aidl/vts/VtsAidlHalSensorsTargetTest.cpp
+++ b/sensors/aidl/vts/VtsAidlHalSensorsTargetTest.cpp
@@ -616,8 +616,18 @@
 }
 
 TEST_P(SensorsAidlTest, InjectSensorEventData) {
-    std::vector<SensorInfo> sensors = getInjectEventSensors();
-    if (sensors.size() == 0) {
+    std::vector<SensorInfo> sensorsAll = getInjectEventSensors();
+    std::vector<SensorInfo> sensors3d;
+
+    for (const auto& s : sensorsAll) {
+        if ((s.type == SensorType::ACCELEROMETER) || (s.type == SensorType::GYROSCOPE) ||
+            (s.type == SensorType::MAGNETIC_FIELD)) {
+            sensors3d.push_back(s);
+        }
+    }
+
+    // Here we only test for the sensors with vec3 data type
+    if (sensors3d.size() == 0) {
         return;
     }
 
@@ -645,7 +655,7 @@
     Event::EventPayload::Vec3 data = {1, 2, 3, SensorStatus::ACCURACY_HIGH};
     injectedEvent.payload.set<Event::EventPayload::Tag::vec3>(data);
 
-    for (const auto& s : sensors) {
+    for (const auto& s : sensors3d) {
         additionalInfoEvent.sensorHandle = s.sensorHandle;
         ASSERT_TRUE(getSensors()->injectSensorData(additionalInfoEvent).isOk());
 
@@ -655,10 +665,10 @@
     }
 
     // Wait for events to be written back to the Event FMQ
-    callback.waitForEvents(sensors, std::chrono::milliseconds(1000) /* timeout */);
+    callback.waitForEvents(sensors3d, std::chrono::milliseconds(1000) /* timeout */);
     getEnvironment()->unregisterCallback();
 
-    for (const auto& s : sensors) {
+    for (const auto& s : sensors3d) {
         auto events = callback.getEvents(s.sensorHandle);
         if (events.empty()) {
             FAIL() << "Received no events";
diff --git a/uwb/aidl/vts/VtsHalUwbTargetTest.cpp b/uwb/aidl/vts/VtsHalUwbTargetTest.cpp
index 2b09f7e..c75160c 100644
--- a/uwb/aidl/vts/VtsHalUwbTargetTest.cpp
+++ b/uwb/aidl/vts/VtsHalUwbTargetTest.cpp
@@ -202,8 +202,26 @@
 }
 
 TEST_P(UwbAidl, ChipSendUciMessage_GetDeviceInfo) {
-    const auto iuwb_chip = getAnyChipAndOpen();
+    std::promise<void> open_cb_promise;
+    std::future<void> open_cb_future{open_cb_promise.get_future()};
+    std::promise<void> init_cb_promise;
+    std::future<void> init_cb_future{init_cb_promise.get_future()};
+    std::shared_ptr<UwbClientCallback> callback = ndk::SharedRefBase::make<UwbClientCallback>(
+            [](auto /* data */) {},
+            [&init_cb_promise, &open_cb_promise](auto event, auto /* status */) {
+                if (event == UwbEvent::OPEN_CPLT) {
+                    open_cb_promise.set_value();
+                }
+                if (event == UwbEvent::POST_INIT_CPLT) {
+                    init_cb_promise.set_value();
+                }
+            });
+    std::chrono::milliseconds timeout{1000};
+    const auto iuwb_chip = getAnyChip();
+    EXPECT_TRUE(iuwb_chip->open(callback).isOk());
+    EXPECT_EQ(open_cb_future.wait_for(timeout), std::future_status::ready);
     EXPECT_TRUE(iuwb_chip->coreInit().isOk());
+    EXPECT_EQ(init_cb_future.wait_for(timeout), std::future_status::ready);
 
     std::vector<uint8_t> uciMessage = {0x20, 0x02, 0x00, 0x00}; /** CoreGetDeviceInfo */
     int32_t* return_status = new int32_t;
diff --git a/wifi/aidl/vts/functional/wifi_sta_iface_aidl_test.cpp b/wifi/aidl/vts/functional/wifi_sta_iface_aidl_test.cpp
index f659bf6..1e6d555 100644
--- a/wifi/aidl/vts/functional/wifi_sta_iface_aidl_test.cpp
+++ b/wifi/aidl/vts/functional/wifi_sta_iface_aidl_test.cpp
@@ -21,6 +21,7 @@
 #include <aidl/Gtest.h>
 #include <aidl/Vintf.h>
 #include <aidl/android/hardware/wifi/BnWifi.h>
+#include <aidl/android/hardware/wifi/BnWifiStaIfaceEventCallback.h>
 #include <android/binder_manager.h>
 #include <android/binder_status.h>
 #include <binder/IServiceManager.h>
@@ -29,6 +30,7 @@
 
 #include "wifi_aidl_test_utils.h"
 
+using aidl::android::hardware::wifi::BnWifiStaIfaceEventCallback;
 using aidl::android::hardware::wifi::CachedScanData;
 using aidl::android::hardware::wifi::IWifi;
 using aidl::android::hardware::wifi::IWifiStaIface;
@@ -379,6 +381,66 @@
     }
 }
 
+class WifiStaIfaceEventCallback : public BnWifiStaIfaceEventCallback {
+  public:
+    WifiStaIfaceEventCallback() = default;
+
+    ::ndk::ScopedAStatus onBackgroundFullScanResult(
+            int32_t /* in_cmdId */, int32_t /* in_bucketsScanned */,
+            const ::aidl::android::hardware::wifi::StaScanResult& /* in_result */) override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onBackgroundScanFailure(int32_t /* in_cmdId */) override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onBackgroundScanResults(
+            int32_t /* in_cmdId */,
+            const std::vector<::aidl::android::hardware::wifi::StaScanData>& /* in_scanDatas */)
+            override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onRssiThresholdBreached(int32_t /* in_cmdId */,
+                                                 const std::array<uint8_t, 6>& /* in_currBssid */,
+                                                 int32_t /* in_currRssi */) override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onTwtFailure(int32_t /*in_cmdId*/,
+                                      ::aidl::android::hardware::wifi::IWifiStaIfaceEventCallback::
+                                              TwtErrorCode /* in_error */) override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onTwtSessionCreate(
+            int32_t /* in_cmdId */,
+            const ::aidl::android::hardware::wifi::TwtSession& /* in_twtSession */) override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onTwtSessionUpdate(
+            int32_t /* in_cmdId */,
+            const ::aidl::android::hardware::wifi::TwtSession& /* in_twtSession */) override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onTwtSessionTeardown(
+            int32_t /* in_cmdId */, int32_t /* in_twtSessionId */,
+            ::aidl::android::hardware::wifi::IWifiStaIfaceEventCallback::
+                    TwtTeardownReasonCode /* in_reasonCode */) override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onTwtSessionStats(
+            int32_t /* in_cmdId */, int32_t /* in_twtSessionId */,
+            const ::aidl::android::hardware::wifi::TwtSessionStats& /* in_twtSessionStats */)
+            override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onTwtSessionSuspend(int32_t /* in_cmdId */,
+                                             int32_t /* in_twtSessionId */) override {
+        return ndk::ScopedAStatus::ok();
+    }
+    ::ndk::ScopedAStatus onTwtSessionResume(int32_t /* in_cmdId */,
+                                            int32_t /* in_twtSessionId */) override {
+        return ndk::ScopedAStatus::ok();
+    }
+};
+
 TEST_P(WifiStaIfaceAidlTest, TwtGetCapabilities) {
     if (interface_version_ < 2) {
         GTEST_SKIP() << "TwtGetCapabilities is available as of sta_iface V2";
@@ -414,6 +476,10 @@
     if (!twt_capabilities.isTwtRequesterSupported) {
         GTEST_SKIP() << "TWT is not supported";
     }
+    const std::shared_ptr<WifiStaIfaceEventCallback> callback =
+            ndk::SharedRefBase::make<WifiStaIfaceEventCallback>();
+    ASSERT_NE(callback, nullptr);
+    EXPECT_TRUE(wifi_sta_iface_->registerEventCallback(callback).isOk());
 
     TwtRequest twtRequest;
     twtRequest.mloLinkId = 0;