Merge "Move NNAPI type information to types.spec"
diff --git a/audio/effect/all-versions/default/OWNERS b/audio/common/7.0/enums/OWNERS
similarity index 67%
rename from audio/effect/all-versions/default/OWNERS
rename to audio/common/7.0/enums/OWNERS
index 6fdc97c..24071af 100644
--- a/audio/effect/all-versions/default/OWNERS
+++ b/audio/common/7.0/enums/OWNERS
@@ -1,3 +1,2 @@
 elaurent@google.com
-krocard@google.com
 mnaganov@google.com
diff --git a/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h b/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h
index fe8eee1..88dd12e 100644
--- a/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h
+++ b/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h
@@ -212,6 +212,15 @@
     return isOutputDevice(stringToAudioDevice(device));
 }
 
+static inline bool isTelephonyDevice(AudioDevice device) {
+    return device == AudioDevice::AUDIO_DEVICE_OUT_TELEPHONY_TX ||
+           device == AudioDevice::AUDIO_DEVICE_IN_TELEPHONY_RX;
+}
+
+static inline bool isTelephonyDevice(const std::string& device) {
+    return isTelephonyDevice(stringToAudioDevice(device));
+}
+
 static inline bool maybeVendorExtension(const std::string& s) {
     // Only checks whether the string starts with the "vendor prefix".
     static const std::string vendorPrefix = "VX_";
@@ -260,6 +269,24 @@
     return stringToAudioUsage(usage) == AudioUsage::UNKNOWN;
 }
 
+static inline bool isLinearPcm(AudioFormat format) {
+    switch (format) {
+        case AudioFormat::AUDIO_FORMAT_PCM_16_BIT:
+        case AudioFormat::AUDIO_FORMAT_PCM_8_BIT:
+        case AudioFormat::AUDIO_FORMAT_PCM_32_BIT:
+        case AudioFormat::AUDIO_FORMAT_PCM_8_24_BIT:
+        case AudioFormat::AUDIO_FORMAT_PCM_FLOAT:
+        case AudioFormat::AUDIO_FORMAT_PCM_24_BIT_PACKED:
+            return true;
+        default:
+            return false;
+    }
+}
+
+static inline bool isLinearPcm(const std::string& format) {
+    return isLinearPcm(stringToAudioFormat(format));
+}
+
 }  // namespace android::audio::policy::configuration::V7_0
 
 #endif  // ANDROID_AUDIO_POLICY_CONFIGURATION_V7_0__ENUMS_H
diff --git a/audio/effect/all-versions/default/OWNERS b/audio/common/7.0/example/OWNERS
similarity index 67%
copy from audio/effect/all-versions/default/OWNERS
copy to audio/common/7.0/example/OWNERS
index 6fdc97c..24071af 100644
--- a/audio/effect/all-versions/default/OWNERS
+++ b/audio/common/7.0/example/OWNERS
@@ -1,3 +1,2 @@
 elaurent@google.com
-krocard@google.com
 mnaganov@google.com
diff --git a/audio/common/all-versions/OWNERS b/audio/common/all-versions/OWNERS
index 6fdc97c..24071af 100644
--- a/audio/common/all-versions/OWNERS
+++ b/audio/common/all-versions/OWNERS
@@ -1,3 +1,2 @@
 elaurent@google.com
-krocard@google.com
 mnaganov@google.com
diff --git a/audio/common/all-versions/default/OWNERS b/audio/common/all-versions/default/OWNERS
index 6fdc97c..24071af 100644
--- a/audio/common/all-versions/default/OWNERS
+++ b/audio/common/all-versions/default/OWNERS
@@ -1,3 +1,2 @@
 elaurent@google.com
-krocard@google.com
 mnaganov@google.com
diff --git a/audio/common/all-versions/test/OWNERS b/audio/common/all-versions/test/OWNERS
deleted file mode 100644
index 6a26ae7..0000000
--- a/audio/common/all-versions/test/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-yim@google.com
-zhuoyao@google.com
diff --git a/audio/core/all-versions/default/OWNERS b/audio/core/all-versions/OWNERS
similarity index 100%
rename from audio/core/all-versions/default/OWNERS
rename to audio/core/all-versions/OWNERS
diff --git a/audio/core/all-versions/vts/OWNERS b/audio/core/all-versions/vts/OWNERS
deleted file mode 100644
index 0ea4666..0000000
--- a/audio/core/all-versions/vts/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-elaurent@google.com
-krocard@google.com
-mnaganov@google.com
-yim@google.com
-zhuoyao@google.com
diff --git a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
index f87e5ed..b96cc83 100644
--- a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
+++ b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
@@ -77,7 +77,6 @@
                   .tags = {},
                   .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_MONO)}}};
 #endif
-        EventFlag* efGroup;
         for (auto microphone : microphones) {
 #if MAJOR_VERSION <= 6
             if (microphone.deviceAddress.device != AudioDevice::IN_BUILTIN_MIC) {
@@ -96,44 +95,15 @@
                                                             config, flags, initMetadata, cb);
                     },
                     config, &res, &suggestedConfig));
+            StreamReader reader(stream.get(), stream->getBufferSize());
+            ASSERT_TRUE(reader.start());
+            reader.pause();  // This ensures that at least one read has happened.
+            EXPECT_FALSE(reader.hasError());
+
             hidl_vec<MicrophoneInfo> activeMicrophones;
-            Result readRes;
-            typedef MessageQueue<IStreamIn::ReadParameters, kSynchronizedReadWrite> CommandMQ;
-            typedef MessageQueue<uint8_t, kSynchronizedReadWrite> DataMQ;
-            std::unique_ptr<CommandMQ> commandMQ;
-            std::unique_ptr<DataMQ> dataMQ;
-            size_t frameSize = stream->getFrameSize();
-            size_t frameCount = stream->getBufferSize() / frameSize;
-            ASSERT_OK(stream->prepareForReading(
-                    frameSize, frameCount, [&](auto r, auto& c, auto& d, auto&, auto) {
-                        readRes = r;
-                        if (readRes == Result::OK) {
-                            commandMQ.reset(new CommandMQ(c));
-                            dataMQ.reset(new DataMQ(d));
-                            if (dataMQ->isValid() && dataMQ->getEventFlagWord()) {
-                                EventFlag::createEventFlag(dataMQ->getEventFlagWord(), &efGroup);
-                            }
-                        }
-                    }));
-            ASSERT_OK(readRes);
-            IStreamIn::ReadParameters params;
-            params.command = IStreamIn::ReadCommand::READ;
-            ASSERT_TRUE(commandMQ != nullptr);
-            ASSERT_TRUE(commandMQ->isValid());
-            ASSERT_TRUE(commandMQ->write(&params));
-            efGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL));
-            uint32_t efState = 0;
-            efGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY), &efState);
-            if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY)) {
-                ASSERT_OK(stream->getActiveMicrophones(returnIn(res, activeMicrophones)));
-                ASSERT_OK(res);
-                ASSERT_NE(0U, activeMicrophones.size());
-            }
-            helper.close(true /*clear*/, &res);
+            ASSERT_OK(stream->getActiveMicrophones(returnIn(res, activeMicrophones)));
             ASSERT_OK(res);
-            if (efGroup) {
-                EventFlag::deleteEventFlag(&efGroup);
-            }
+            EXPECT_NE(0U, activeMicrophones.size());
         }
     }
 }
diff --git a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
index c1923f1..657b42d 100644
--- a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
+++ b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <android-base/chrono_utils.h>
+
 #include "Generators.h"
 
 // pull in all the <= 6.0 tests
@@ -487,3 +489,305 @@
                 << ::testing::PrintToString(metadata);
     }
 }
+
+static const std::vector<DeviceConfigParameter>& getOutputDevicePcmOnlyConfigParameters() {
+    static const std::vector<DeviceConfigParameter> parameters = [] {
+        auto allParams = getOutputDeviceConfigParameters();
+        std::vector<DeviceConfigParameter> pcmParams;
+        std::copy_if(allParams.begin(), allParams.end(), std::back_inserter(pcmParams), [](auto cfg) {
+            const auto& flags = std::get<PARAM_FLAGS>(cfg);
+            return xsd::isLinearPcm(std::get<PARAM_CONFIG>(cfg).base.format)
+                   // MMAP NOIRQ and HW A/V Sync profiles use special writing protocols.
+                   &&
+                   std::find_if(flags.begin(), flags.end(),
+                                [](const auto& flag) {
+                                    return flag == toString(xsd::AudioInOutFlag::
+                                                                    AUDIO_OUTPUT_FLAG_MMAP_NOIRQ) ||
+                                           flag == toString(xsd::AudioInOutFlag::
+                                                                    AUDIO_OUTPUT_FLAG_HW_AV_SYNC);
+                                }) == flags.end() &&
+                   !getCachedPolicyConfig()
+                            .getAttachedSinkDeviceForMixPort(
+                                    std::get<PARAM_DEVICE_NAME>(std::get<PARAM_DEVICE>(cfg)),
+                                    std::get<PARAM_PORT_NAME>(cfg))
+                            .empty();
+        });
+        return pcmParams;
+    }();
+    return parameters;
+}
+
+class PcmOnlyConfigOutputStreamTest : public OutputStreamTest {
+  public:
+    void TearDown() override {
+        releasePatchIfNeeded();
+        OutputStreamTest::TearDown();
+    }
+
+    bool canQueryPresentationPosition() const {
+        auto maybeSinkAddress =
+                getCachedPolicyConfig().getSinkDeviceForMixPort(getDeviceName(), getMixPortName());
+        // Returning 'true' when no sink is found so the test can fail later with a more clear
+        // problem description.
+        return !maybeSinkAddress.has_value() ||
+               !xsd::isTelephonyDevice(maybeSinkAddress.value().deviceType);
+    }
+
+    void createPatchIfNeeded() {
+        auto maybeSinkAddress =
+                getCachedPolicyConfig().getSinkDeviceForMixPort(getDeviceName(), getMixPortName());
+        ASSERT_TRUE(maybeSinkAddress.has_value())
+                << "No sink device found for mix port " << getMixPortName() << " (module "
+                << getDeviceName() << ")";
+        if (areAudioPatchesSupported()) {
+            AudioPortConfig source;
+            source.base.format.value(getConfig().base.format);
+            source.base.sampleRateHz.value(getConfig().base.sampleRateHz);
+            source.base.channelMask.value(getConfig().base.channelMask);
+            source.ext.mix({});
+            source.ext.mix().ioHandle = helper.getIoHandle();
+            source.ext.mix().useCase.stream({});
+            AudioPortConfig sink;
+            sink.ext.device(maybeSinkAddress.value());
+            EXPECT_OK(getDevice()->createAudioPatch(hidl_vec<AudioPortConfig>{source},
+                                                    hidl_vec<AudioPortConfig>{sink},
+                                                    returnIn(res, mPatchHandle)));
+            mHasPatch = res == Result::OK;
+        } else {
+            EXPECT_OK(stream->setDevices({maybeSinkAddress.value()}));
+        }
+    }
+
+    void releasePatchIfNeeded() {
+        if (areAudioPatchesSupported()) {
+            if (mHasPatch) {
+                EXPECT_OK(getDevice()->releaseAudioPatch(mPatchHandle));
+                mHasPatch = false;
+            }
+        } else {
+            EXPECT_OK(stream->setDevices({address}));
+        }
+    }
+
+    const std::string& getMixPortName() const { return std::get<PARAM_PORT_NAME>(GetParam()); }
+
+    void waitForPresentationPositionAdvance(StreamWriter& writer, uint64_t* firstPosition = nullptr,
+                                            uint64_t* lastPosition = nullptr) {
+        static constexpr int kWriteDurationUs = 50 * 1000;
+        static constexpr std::chrono::milliseconds kPositionChangeTimeout{10000};
+        uint64_t framesInitial;
+        TimeSpec ts;
+        // Starting / resuming of streams is asynchronous at HAL level.
+        // Sometimes HAL doesn't have enough information until the audio data actually gets
+        // consumed by the hardware.
+        do {
+            ASSERT_OK(stream->getPresentationPosition(returnIn(res, framesInitial, ts)));
+            ASSERT_RESULT(okOrInvalidState, res);
+        } while (res != Result::OK);
+        uint64_t frames = framesInitial;
+        bool timedOut = false;
+        for (android::base::Timer elapsed;
+             frames <= framesInitial && !writer.hasError() &&
+             !(timedOut = (elapsed.duration() >= kPositionChangeTimeout));) {
+            usleep(kWriteDurationUs);
+            ASSERT_OK(stream->getPresentationPosition(returnIn(res, frames, ts)));
+            ASSERT_RESULT(Result::OK, res);
+        }
+        EXPECT_FALSE(timedOut);
+        EXPECT_FALSE(writer.hasError());
+        EXPECT_GT(frames, framesInitial);
+        if (firstPosition) *firstPosition = framesInitial;
+        if (lastPosition) *lastPosition = frames;
+    }
+
+  private:
+    AudioPatchHandle mPatchHandle = {};
+    bool mHasPatch = false;
+};
+
+TEST_P(PcmOnlyConfigOutputStreamTest, Write) {
+    doc::test("Check that output streams opened for PCM output accepts audio data");
+    StreamWriter writer(stream.get(), stream->getBufferSize());
+    ASSERT_TRUE(writer.start());
+    EXPECT_TRUE(writer.waitForAtLeastOneCycle());
+}
+
+TEST_P(PcmOnlyConfigOutputStreamTest, PresentationPositionAdvancesWithWrites) {
+    doc::test("Check that the presentation position advances with writes");
+    if (!canQueryPresentationPosition()) {
+        GTEST_SKIP() << "Presentation position retrieval is not possible";
+    }
+
+    ASSERT_NO_FATAL_FAILURE(createPatchIfNeeded());
+    StreamWriter writer(stream.get(), stream->getBufferSize());
+    ASSERT_TRUE(writer.start());
+    ASSERT_TRUE(writer.waitForAtLeastOneCycle());
+    ASSERT_NO_FATAL_FAILURE(waitForPresentationPositionAdvance(writer));
+
+    writer.stop();
+    releasePatchIfNeeded();
+}
+
+TEST_P(PcmOnlyConfigOutputStreamTest, PresentationPositionPreservedOnStandby) {
+    doc::test("Check that the presentation position does not reset on standby");
+    if (!canQueryPresentationPosition()) {
+        GTEST_SKIP() << "Presentation position retrieval is not possible";
+    }
+
+    ASSERT_NO_FATAL_FAILURE(createPatchIfNeeded());
+    StreamWriter writer(stream.get(), stream->getBufferSize());
+    ASSERT_TRUE(writer.start());
+    ASSERT_TRUE(writer.waitForAtLeastOneCycle());
+
+    uint64_t framesInitial;
+    ASSERT_NO_FATAL_FAILURE(waitForPresentationPositionAdvance(writer, nullptr, &framesInitial));
+    writer.pause();
+    ASSERT_OK(stream->standby());
+    writer.resume();
+
+    uint64_t frames;
+    ASSERT_NO_FATAL_FAILURE(waitForPresentationPositionAdvance(writer, &frames));
+    EXPECT_GT(frames, framesInitial);
+
+    writer.stop();
+    releasePatchIfNeeded();
+}
+
+INSTANTIATE_TEST_CASE_P(PcmOnlyConfigOutputStream, PcmOnlyConfigOutputStreamTest,
+                        ::testing::ValuesIn(getOutputDevicePcmOnlyConfigParameters()),
+                        &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PcmOnlyConfigOutputStreamTest);
+
+static const std::vector<DeviceConfigParameter>& getInputDevicePcmOnlyConfigParameters() {
+    static const std::vector<DeviceConfigParameter> parameters = [] {
+        auto allParams = getInputDeviceConfigParameters();
+        std::vector<DeviceConfigParameter> pcmParams;
+        std::copy_if(
+                allParams.begin(), allParams.end(), std::back_inserter(pcmParams), [](auto cfg) {
+                    const auto& flags = std::get<PARAM_FLAGS>(cfg);
+                    return xsd::isLinearPcm(std::get<PARAM_CONFIG>(cfg).base.format)
+                           // MMAP NOIRQ profiles use different reading protocol.
+                           &&
+                           std::find(flags.begin(), flags.end(),
+                                     toString(xsd::AudioInOutFlag::AUDIO_INPUT_FLAG_MMAP_NOIRQ)) ==
+                                   flags.end() &&
+                           !getCachedPolicyConfig()
+                                    .getAttachedSourceDeviceForMixPort(
+                                            std::get<PARAM_DEVICE_NAME>(
+                                                    std::get<PARAM_DEVICE>(cfg)),
+                                            std::get<PARAM_PORT_NAME>(cfg))
+                                    .empty();
+                });
+        return pcmParams;
+    }();
+    return parameters;
+}
+
+class PcmOnlyConfigInputStreamTest : public InputStreamTest {
+  public:
+    void TearDown() override {
+        releasePatchIfNeeded();
+        InputStreamTest::TearDown();
+    }
+
+    void createPatchIfNeeded() {
+        auto maybeSourceAddress = getCachedPolicyConfig().getSourceDeviceForMixPort(
+                getDeviceName(), getMixPortName());
+        ASSERT_TRUE(maybeSourceAddress.has_value())
+                << "No source device found for mix port " << getMixPortName() << " (module "
+                << getDeviceName() << ")";
+        if (areAudioPatchesSupported()) {
+            AudioPortConfig source;
+            source.ext.device(maybeSourceAddress.value());
+            AudioPortConfig sink;
+            sink.base.format.value(getConfig().base.format);
+            sink.base.sampleRateHz.value(getConfig().base.sampleRateHz);
+            sink.base.channelMask.value(getConfig().base.channelMask);
+            sink.ext.mix({});
+            sink.ext.mix().ioHandle = helper.getIoHandle();
+            sink.ext.mix().useCase.source(toString(xsd::AudioSource::AUDIO_SOURCE_MIC));
+            EXPECT_OK(getDevice()->createAudioPatch(hidl_vec<AudioPortConfig>{source},
+                                                    hidl_vec<AudioPortConfig>{sink},
+                                                    returnIn(res, mPatchHandle)));
+            mHasPatch = res == Result::OK;
+        } else {
+            EXPECT_OK(stream->setDevices({maybeSourceAddress.value()}));
+        }
+    }
+    void releasePatchIfNeeded() {
+        if (areAudioPatchesSupported()) {
+            if (mHasPatch) {
+                EXPECT_OK(getDevice()->releaseAudioPatch(mPatchHandle));
+                mHasPatch = false;
+            }
+        } else {
+            EXPECT_OK(stream->setDevices({address}));
+        }
+    }
+    const std::string& getMixPortName() const { return std::get<PARAM_PORT_NAME>(GetParam()); }
+
+  private:
+    AudioPatchHandle mPatchHandle = {};
+    bool mHasPatch = false;
+};
+
+TEST_P(PcmOnlyConfigInputStreamTest, Read) {
+    doc::test("Check that input streams opened for PCM input retrieve audio data");
+    StreamReader reader(stream.get(), stream->getBufferSize());
+    ASSERT_TRUE(reader.start());
+    EXPECT_TRUE(reader.waitForAtLeastOneCycle());
+}
+
+TEST_P(PcmOnlyConfigInputStreamTest, CapturePositionAdvancesWithReads) {
+    doc::test("Check that the capture position advances with reads");
+
+    ASSERT_NO_FATAL_FAILURE(createPatchIfNeeded());
+    StreamReader reader(stream.get(), stream->getBufferSize());
+    ASSERT_TRUE(reader.start());
+    EXPECT_TRUE(reader.waitForAtLeastOneCycle());
+
+    uint64_t framesInitial, ts;
+    ASSERT_OK(stream->getCapturePosition(returnIn(res, framesInitial, ts)));
+    ASSERT_RESULT(Result::OK, res);
+
+    EXPECT_TRUE(reader.waitForAtLeastOneCycle());
+
+    uint64_t frames;
+    ASSERT_OK(stream->getCapturePosition(returnIn(res, frames, ts)));
+    ASSERT_RESULT(Result::OK, res);
+    EXPECT_GT(frames, framesInitial);
+
+    reader.stop();
+    releasePatchIfNeeded();
+}
+
+TEST_P(PcmOnlyConfigInputStreamTest, CapturePositionPreservedOnStandby) {
+    doc::test("Check that the capture position does not reset on standby");
+
+    ASSERT_NO_FATAL_FAILURE(createPatchIfNeeded());
+    StreamReader reader(stream.get(), stream->getBufferSize());
+    ASSERT_TRUE(reader.start());
+    EXPECT_TRUE(reader.waitForAtLeastOneCycle());
+
+    uint64_t framesInitial, ts;
+    ASSERT_OK(stream->getCapturePosition(returnIn(res, framesInitial, ts)));
+    ASSERT_RESULT(Result::OK, res);
+
+    reader.pause();
+    ASSERT_OK(stream->standby());
+    reader.resume();
+    EXPECT_FALSE(reader.hasError());
+
+    uint64_t frames;
+    ASSERT_OK(stream->getCapturePosition(returnIn(res, frames, ts)));
+    ASSERT_RESULT(Result::OK, res);
+    EXPECT_GT(frames, framesInitial);
+
+    reader.stop();
+    releasePatchIfNeeded();
+}
+
+INSTANTIATE_TEST_CASE_P(PcmOnlyConfigInputStream, PcmOnlyConfigInputStreamTest,
+                        ::testing::ValuesIn(getInputDevicePcmOnlyConfigParameters()),
+                        &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(PcmOnlyConfigInputStreamTest);
diff --git a/audio/core/all-versions/vts/functional/7.0/Generators.cpp b/audio/core/all-versions/vts/functional/7.0/Generators.cpp
index eafc813..d2ba339 100644
--- a/audio/core/all-versions/vts/functional/7.0/Generators.cpp
+++ b/audio/core/all-versions/vts/functional/7.0/Generators.cpp
@@ -110,7 +110,7 @@
                     if (isOffload) {
                         config.offloadInfo.info(generateOffloadInfo(config.base));
                     }
-                    result.emplace_back(device, config, flags);
+                    result.emplace_back(device, mixPort.getName(), config, flags);
                     if (oneProfilePerDevice) break;
                 }
                 if (oneProfilePerDevice) break;
@@ -160,7 +160,7 @@
                         if (isOffload) {
                             config.offloadInfo.info(generateOffloadInfo(validBase));
                         }
-                        result.emplace_back(device, config, validFlags);
+                        result.emplace_back(device, mixPort.getName(), config, validFlags);
                     }
                     {
                         AudioConfig config{.base = validBase};
@@ -168,7 +168,7 @@
                         if (isOffload) {
                             config.offloadInfo.info(generateOffloadInfo(validBase));
                         }
-                        result.emplace_back(device, config, validFlags);
+                        result.emplace_back(device, mixPort.getName(), config, validFlags);
                     }
                     if (generateInvalidFlags) {
                         AudioConfig config{.base = validBase};
@@ -176,32 +176,32 @@
                             config.offloadInfo.info(generateOffloadInfo(validBase));
                         }
                         std::vector<AudioInOutFlag> flags = {"random_string", ""};
-                        result.emplace_back(device, config, flags);
+                        result.emplace_back(device, mixPort.getName(), config, flags);
                     }
                     if (isOffload) {
                         {
                             AudioConfig config{.base = validBase};
                             config.offloadInfo.info(generateOffloadInfo(validBase));
                             config.offloadInfo.info().base.channelMask = "random_string";
-                            result.emplace_back(device, config, validFlags);
+                            result.emplace_back(device, mixPort.getName(), config, validFlags);
                         }
                         {
                             AudioConfig config{.base = validBase};
                             config.offloadInfo.info(generateOffloadInfo(validBase));
                             config.offloadInfo.info().base.format = "random_string";
-                            result.emplace_back(device, config, validFlags);
+                            result.emplace_back(device, mixPort.getName(), config, validFlags);
                         }
                         {
                             AudioConfig config{.base = validBase};
                             config.offloadInfo.info(generateOffloadInfo(validBase));
                             config.offloadInfo.info().streamType = "random_string";
-                            result.emplace_back(device, config, validFlags);
+                            result.emplace_back(device, mixPort.getName(), config, validFlags);
                         }
                         {
                             AudioConfig config{.base = validBase};
                             config.offloadInfo.info(generateOffloadInfo(validBase));
                             config.offloadInfo.info().usage = "random_string";
-                            result.emplace_back(device, config, validFlags);
+                            result.emplace_back(device, mixPort.getName(), config, validFlags);
                         }
                         hasOffloadConfig = true;
                     } else {
@@ -234,7 +234,7 @@
                 auto configs = combineAudioConfig(profile.getChannelMasks(),
                                                   profile.getSamplingRates(), profile.getFormat());
                 for (const auto& config : configs) {
-                    result.emplace_back(device, config, flags);
+                    result.emplace_back(device, mixPort.getName(), config, flags);
                     if (oneProfilePerDevice) break;
                 }
                 if (oneProfilePerDevice) break;
@@ -285,17 +285,17 @@
                     {
                         AudioConfig config{.base = validBase};
                         config.base.channelMask = "random_string";
-                        result.emplace_back(device, config, validFlags);
+                        result.emplace_back(device, mixPort.getName(), config, validFlags);
                     }
                     {
                         AudioConfig config{.base = validBase};
                         config.base.format = "random_string";
-                        result.emplace_back(device, config, validFlags);
+                        result.emplace_back(device, mixPort.getName(), config, validFlags);
                     }
                     if (generateInvalidFlags) {
                         AudioConfig config{.base = validBase};
                         std::vector<AudioInOutFlag> flags = {"random_string", ""};
-                        result.emplace_back(device, config, flags);
+                        result.emplace_back(device, mixPort.getName(), config, flags);
                     }
                     hasConfig = true;
                     break;
diff --git a/audio/core/all-versions/vts/functional/7.0/PolicyConfig.cpp b/audio/core/all-versions/vts/functional/7.0/PolicyConfig.cpp
new file mode 100644
index 0000000..2988207
--- /dev/null
+++ b/audio/core/all-versions/vts/functional/7.0/PolicyConfig.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2021 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 <fcntl.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include <HidlUtils.h>
+#include <system/audio.h>
+#include <system/audio_config.h>
+
+#include "DeviceManager.h"
+#include "PolicyConfig.h"
+#include "common/all-versions/HidlSupport.h"
+
+using ::android::NO_ERROR;
+using ::android::OK;
+
+using namespace ::android::hardware::audio::common::CPP_VERSION;
+using namespace ::android::hardware::audio::CPP_VERSION;
+using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
+using ::android::hardware::audio::common::utils::splitString;
+namespace xsd {
+using namespace ::android::audio::policy::configuration::CPP_VERSION;
+using Module = Modules::Module;
+}  // namespace xsd
+
+std::string PolicyConfig::getError() const {
+    if (mFilePath.empty()) {
+        return "Could not find " + mConfigFileName +
+               " file in: " + testing::PrintToString(android::audio_get_configuration_paths());
+    } else {
+        return "Invalid config file: " + mFilePath;
+    }
+}
+
+const xsd::Module* PolicyConfig::getModuleFromName(const std::string& name) const {
+    if (mConfig && mConfig->getFirstModules()) {
+        for (const auto& module : mConfig->getFirstModules()->get_module()) {
+            if (module.getName() == name) return &module;
+        }
+    }
+    return nullptr;
+}
+
+std::optional<DeviceAddress> PolicyConfig::getSinkDeviceForMixPort(
+        const std::string& moduleName, const std::string& mixPortName) const {
+    std::string device;
+    if (auto module = getModuleFromName(moduleName); module) {
+        auto possibleDevices = getSinkDevicesForMixPort(moduleName, mixPortName);
+        if (module->hasDefaultOutputDevice() &&
+            possibleDevices.count(module->getDefaultOutputDevice())) {
+            device = module->getDefaultOutputDevice();
+        } else {
+            device = getAttachedSinkDeviceForMixPort(moduleName, mixPortName);
+        }
+    }
+    if (!device.empty()) {
+        return getDeviceAddressOfDevicePort(moduleName, device);
+    }
+    ALOGE("Could not find a route for the mix port \"%s\" in module \"%s\"", mixPortName.c_str(),
+          moduleName.c_str());
+    return std::optional<DeviceAddress>{};
+}
+
+std::optional<DeviceAddress> PolicyConfig::getSourceDeviceForMixPort(
+        const std::string& moduleName, const std::string& mixPortName) const {
+    const std::string device = getAttachedSourceDeviceForMixPort(moduleName, mixPortName);
+    if (!device.empty()) {
+        return getDeviceAddressOfDevicePort(moduleName, device);
+    }
+    ALOGE("Could not find a route for the mix port \"%s\" in module \"%s\"", mixPortName.c_str(),
+          moduleName.c_str());
+    return std::optional<DeviceAddress>{};
+}
+
+bool PolicyConfig::haveInputProfilesInModule(const std::string& name) const {
+    auto module = getModuleFromName(name);
+    if (module && module->getFirstMixPorts()) {
+        for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
+            if (mixPort.getRole() == xsd::Role::sink) return true;
+        }
+    }
+    return false;
+}
+
+// static
+std::string PolicyConfig::findExistingConfigurationFile(const std::string& fileName) {
+    for (const auto& location : android::audio_get_configuration_paths()) {
+        std::string path = location + '/' + fileName;
+        if (access(path.c_str(), F_OK) == 0) {
+            return path;
+        }
+    }
+    return {};
+}
+
+std::string PolicyConfig::findAttachedDevice(const std::vector<std::string>& attachedDevices,
+                                             const std::set<std::string>& possibleDevices) const {
+    for (const auto& device : attachedDevices) {
+        if (possibleDevices.count(device)) return device;
+    }
+    return {};
+}
+
+const std::vector<std::string>& PolicyConfig::getAttachedDevices(
+        const std::string& moduleName) const {
+    static const std::vector<std::string> empty;
+    auto module = getModuleFromName(moduleName);
+    if (module && module->getFirstAttachedDevices()) {
+        return module->getFirstAttachedDevices()->getItem();
+    }
+    return empty;
+}
+
+std::optional<DeviceAddress> PolicyConfig::getDeviceAddressOfDevicePort(
+        const std::string& moduleName, const std::string& devicePortName) const {
+    auto module = getModuleFromName(moduleName);
+    if (module->getFirstDevicePorts()) {
+        const auto& devicePorts = module->getFirstDevicePorts()->getDevicePort();
+        const auto& devicePort = std::find_if(
+                devicePorts.begin(), devicePorts.end(),
+                [&devicePortName](auto dp) { return dp.getTagName() == devicePortName; });
+        if (devicePort != devicePorts.end()) {
+            audio_devices_t halDeviceType;
+            if (HidlUtils::audioDeviceTypeToHal(devicePort->getType(), &halDeviceType) ==
+                NO_ERROR) {
+                // For AOSP device types use the standard parser for the device address.
+                const std::string address =
+                        devicePort->hasAddress() ? devicePort->getAddress() : "";
+                DeviceAddress result;
+                if (HidlUtils::deviceAddressFromHal(halDeviceType, address.c_str(), &result) ==
+                    NO_ERROR) {
+                    return result;
+                }
+            } else if (xsd::isVendorExtension(devicePort->getType())) {
+                DeviceAddress result;
+                result.deviceType = devicePort->getType();
+                if (devicePort->hasAddress()) {
+                    result.address.id(devicePort->getAddress());
+                }
+                return result;
+            }
+        } else {
+            ALOGE("Device port \"%s\" not found in module \"%s\"", devicePortName.c_str(),
+                  moduleName.c_str());
+        }
+    } else {
+        ALOGE("Module \"%s\" has no device ports", moduleName.c_str());
+    }
+    return std::optional<DeviceAddress>{};
+}
+
+std::set<std::string> PolicyConfig::getSinkDevicesForMixPort(const std::string& moduleName,
+                                                             const std::string& mixPortName) const {
+    std::set<std::string> result;
+    auto module = getModuleFromName(moduleName);
+    if (module && module->getFirstRoutes()) {
+        for (const auto& route : module->getFirstRoutes()->getRoute()) {
+            const auto sources = splitString(route.getSources(), ',');
+            if (std::find(sources.begin(), sources.end(), mixPortName) != sources.end()) {
+                result.insert(route.getSink());
+            }
+        }
+    }
+    return result;
+}
+
+std::set<std::string> PolicyConfig::getSourceDevicesForMixPort(
+        const std::string& moduleName, const std::string& mixPortName) const {
+    std::set<std::string> result;
+    auto module = getModuleFromName(moduleName);
+    if (module && module->getFirstRoutes()) {
+        const auto& routes = module->getFirstRoutes()->getRoute();
+        const auto route = std::find_if(routes.begin(), routes.end(), [&mixPortName](auto rte) {
+            return rte.getSink() == mixPortName;
+        });
+        if (route != routes.end()) {
+            const auto sources = splitString(route->getSources(), ',');
+            std::copy(sources.begin(), sources.end(), std::inserter(result, result.end()));
+        }
+    }
+    return result;
+}
+
+void PolicyConfig::init() {
+    if (mConfig) {
+        mStatus = OK;
+        mPrimaryModule = getModuleFromName(DeviceManager::kPrimaryDevice);
+        if (mConfig->getFirstModules()) {
+            for (const auto& module : mConfig->getFirstModules()->get_module()) {
+                if (module.getFirstAttachedDevices()) {
+                    auto attachedDevices = module.getFirstAttachedDevices()->getItem();
+                    if (!attachedDevices.empty()) {
+                        mModulesWithDevicesNames.insert(module.getName());
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h b/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h
index feb4d4b..f798839 100644
--- a/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h
+++ b/audio/core/all-versions/vts/functional/7.0/PolicyConfig.h
@@ -16,15 +16,12 @@
 
 #pragma once
 
-#include <fcntl.h>
-#include <unistd.h>
-
 #include <optional>
 #include <set>
 #include <string>
+#include <vector>
 
 #include <gtest/gtest.h>
-#include <system/audio_config.h>
 #include <utils/Errors.h>
 
 // clang-format off
@@ -35,12 +32,6 @@
 #include <android_audio_policy_configuration_V7_0-enums.h>
 #include <android_audio_policy_configuration_V7_0.h>
 
-#include "DeviceManager.h"
-
-using ::android::NO_INIT;
-using ::android::OK;
-using ::android::status_t;
-
 using namespace ::android::hardware::audio::common::CPP_VERSION;
 using namespace ::android::hardware::audio::CPP_VERSION;
 namespace xsd {
@@ -62,69 +53,49 @@
           mConfig{xsd::read(mFilePath.c_str())} {
         init();
     }
-    status_t getStatus() const { return mStatus; }
-    std::string getError() const {
-        if (mFilePath.empty()) {
-            return std::string{"Could not find "} + mConfigFileName +
-                   " file in: " + testing::PrintToString(android::audio_get_configuration_paths());
-        } else {
-            return "Invalid config file: " + mFilePath;
-        }
-    }
+    android::status_t getStatus() const { return mStatus; }
+    std::string getError() const;
     const std::string& getFilePath() const { return mFilePath; }
-    const xsd::Module* getModuleFromName(const std::string& name) const {
-        if (mConfig && mConfig->getFirstModules()) {
-            for (const auto& module : mConfig->getFirstModules()->get_module()) {
-                if (module.getName() == name) return &module;
-            }
-        }
-        return nullptr;
-    }
+    const xsd::Module* getModuleFromName(const std::string& name) const;
     const xsd::Module* getPrimaryModule() const { return mPrimaryModule; }
     const std::set<std::string>& getModulesWithDevicesNames() const {
         return mModulesWithDevicesNames;
     }
-    bool haveInputProfilesInModule(const std::string& name) const {
-        auto module = getModuleFromName(name);
-        if (module && module->getFirstMixPorts()) {
-            for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
-                if (mixPort.getRole() == xsd::Role::sink) return true;
-            }
-        }
-        return false;
+    std::string getAttachedSinkDeviceForMixPort(const std::string& moduleName,
+                                                const std::string& mixPortName) const {
+        return findAttachedDevice(getAttachedDevices(moduleName),
+                                  getSinkDevicesForMixPort(moduleName, mixPortName));
     }
+    std::string getAttachedSourceDeviceForMixPort(const std::string& moduleName,
+                                                  const std::string& mixPortName) const {
+        return findAttachedDevice(getAttachedDevices(moduleName),
+                                  getSourceDevicesForMixPort(moduleName, mixPortName));
+    }
+    std::optional<DeviceAddress> getSinkDeviceForMixPort(const std::string& moduleName,
+                                                         const std::string& mixPortName) const;
+    std::optional<DeviceAddress> getSourceDeviceForMixPort(const std::string& moduleName,
+                                                           const std::string& mixPortName) const;
+    bool haveInputProfilesInModule(const std::string& name) const;
 
   private:
-    static std::string findExistingConfigurationFile(const std::string& fileName) {
-        for (const auto& location : android::audio_get_configuration_paths()) {
-            std::string path = location + '/' + fileName;
-            if (access(path.c_str(), F_OK) == 0) {
-                return path;
-            }
-        }
-        return std::string{};
-    }
-    void init() {
-        if (mConfig) {
-            mStatus = OK;
-            mPrimaryModule = getModuleFromName(DeviceManager::kPrimaryDevice);
-            if (mConfig->getFirstModules()) {
-                for (const auto& module : mConfig->getFirstModules()->get_module()) {
-                    if (module.getFirstAttachedDevices()) {
-                        auto attachedDevices = module.getFirstAttachedDevices()->getItem();
-                        if (!attachedDevices.empty()) {
-                            mModulesWithDevicesNames.insert(module.getName());
-                        }
-                    }
-                }
-            }
-        }
-    }
+    static std::string findExistingConfigurationFile(const std::string& fileName);
+    std::string findAttachedDevice(const std::vector<std::string>& attachedDevices,
+                                   const std::set<std::string>& possibleDevices) const;
+    const std::vector<std::string>& getAttachedDevices(const std::string& moduleName) const;
+    std::optional<DeviceAddress> getDeviceAddressOfDevicePort(
+            const std::string& moduleName, const std::string& devicePortName) const;
+    std::string getDevicePortTagNameFromType(const std::string& moduleName,
+                                             const AudioDevice& deviceType) const;
+    std::set<std::string> getSinkDevicesForMixPort(const std::string& moduleName,
+                                                   const std::string& mixPortName) const;
+    std::set<std::string> getSourceDevicesForMixPort(const std::string& moduleName,
+                                                     const std::string& mixPortName) const;
+    void init();
 
     const std::string mConfigFileName;
     const std::string mFilePath;
     std::optional<xsd::AudioPolicyConfiguration> mConfig;
-    status_t mStatus = NO_INIT;
+    android::status_t mStatus = android::NO_INIT;
     const xsd::Module* mPrimaryModule;
     std::set<std::string> mModulesWithDevicesNames;
 };
diff --git a/audio/core/all-versions/vts/functional/Android.bp b/audio/core/all-versions/vts/functional/Android.bp
index 91c54dc..9183191 100644
--- a/audio/core/all-versions/vts/functional/Android.bp
+++ b/audio/core/all-versions/vts/functional/Android.bp
@@ -154,6 +154,7 @@
     srcs: [
         "7.0/AudioPrimaryHidlHalTest.cpp",
         "7.0/Generators.cpp",
+        "7.0/PolicyConfig.cpp",
     ],
     generated_headers: ["audio_policy_configuration_V7_0_parser"],
     generated_sources: ["audio_policy_configuration_V7_0_parser"],
@@ -161,6 +162,7 @@
         "android.hardware.audio@7.0",
         "android.hardware.audio.common@7.0",
         "android.hardware.audio.common@7.0-enums",
+        "android.hardware.audio.common@7.0-util",
     ],
     cflags: [
         "-DMAJOR_VERSION=7",
@@ -176,7 +178,15 @@
 }
 
 // Note: the following aren't VTS tests, but rather unit tests
-// to verify correctness of test parameter generator utilities.
+// to verify correctness of test utilities.
+cc_test {
+    name: "HalAudioStreamWorkerTest",
+    host_supported: true,
+    srcs: [
+        "tests/streamworker_tests.cpp",
+    ],
+}
+
 cc_test {
     name: "HalAudioV6_0GeneratorTest",
     defaults: ["VtsHalAudioTargetTest_defaults"],
@@ -208,6 +218,7 @@
     defaults: ["VtsHalAudioTargetTest_defaults"],
     srcs: [
         "7.0/Generators.cpp",
+        "7.0/PolicyConfig.cpp",
         "tests/generators_tests.cpp",
     ],
     generated_headers: ["audio_policy_configuration_V7_0_parser"],
@@ -216,6 +227,7 @@
         "android.hardware.audio@7.0",
         "android.hardware.audio.common@7.0",
         "android.hardware.audio.common@7.0-enums",
+        "android.hardware.audio.common@7.0-util",
     ],
     cflags: [
         "-DMAJOR_VERSION=7",
diff --git a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
index 56939fe..ae1467d 100644
--- a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
+++ b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
@@ -89,6 +89,10 @@
 using namespace ::android::hardware::audio::common::CPP_VERSION;
 using namespace ::android::hardware::audio::common::test::utility;
 using namespace ::android::hardware::audio::CPP_VERSION;
+using ReadParameters = ::android::hardware::audio::CPP_VERSION::IStreamIn::ReadParameters;
+using ReadStatus = ::android::hardware::audio::CPP_VERSION::IStreamIn::ReadStatus;
+using WriteCommand = ::android::hardware::audio::CPP_VERSION::IStreamOut::WriteCommand;
+using WriteStatus = ::android::hardware::audio::CPP_VERSION::IStreamOut::WriteStatus;
 #if MAJOR_VERSION >= 7
 // Make an alias for enumerations generated from the APM config XSD.
 namespace xsd {
@@ -100,6 +104,7 @@
 static auto okOrNotSupported = {Result::OK, Result::NOT_SUPPORTED};
 static auto okOrNotSupportedOrInvalidArgs = {Result::OK, Result::NOT_SUPPORTED,
                                              Result::INVALID_ARGUMENTS};
+static auto okOrInvalidState = {Result::OK, Result::INVALID_STATE};
 static auto okOrInvalidStateOrNotSupported = {Result::OK, Result::INVALID_STATE,
                                               Result::NOT_SUPPORTED};
 static auto invalidArgsOrNotSupported = {Result::INVALID_ARGUMENTS, Result::NOT_SUPPORTED};
@@ -115,6 +120,7 @@
 #include "7.0/Generators.h"
 #include "7.0/PolicyConfig.h"
 #endif
+#include "StreamWorker.h"
 
 class HidlTest : public ::testing::Test {
   public:
@@ -778,6 +784,11 @@
 ////////////////////////// open{Output,Input}Stream //////////////////////////
 //////////////////////////////////////////////////////////////////////////////
 
+static inline AudioIoHandle getNextIoHandle() {
+    static AudioIoHandle lastHandle{};
+    return ++lastHandle;
+}
+
 // This class is also used by some device tests.
 template <class Stream>
 class StreamHelper {
@@ -787,16 +798,13 @@
     template <class Open>
     void open(Open openStream, const AudioConfig& config, Result* res,
               AudioConfig* suggestedConfigPtr) {
-        // FIXME: Open a stream without an IOHandle
-        //        This is not required to be accepted by hal implementations
-        AudioIoHandle ioHandle{};
         AudioConfig suggestedConfig{};
         bool retryWithSuggestedConfig = true;
         if (suggestedConfigPtr == nullptr) {
             suggestedConfigPtr = &suggestedConfig;
             retryWithSuggestedConfig = false;
         }
-        ASSERT_OK(openStream(ioHandle, config, returnIn(*res, mStream, *suggestedConfigPtr)));
+        ASSERT_OK(openStream(mIoHandle, config, returnIn(*res, mStream, *suggestedConfigPtr)));
         switch (*res) {
             case Result::OK:
                 ASSERT_TRUE(mStream != nullptr);
@@ -806,7 +814,7 @@
                 ASSERT_TRUE(mStream == nullptr);
                 if (retryWithSuggestedConfig) {
                     AudioConfig suggestedConfigRetry;
-                    ASSERT_OK(openStream(ioHandle, *suggestedConfigPtr,
+                    ASSERT_OK(openStream(mIoHandle, *suggestedConfigPtr,
                                          returnIn(*res, mStream, suggestedConfigRetry)));
                     ASSERT_OK(*res);
                     ASSERT_TRUE(mStream != nullptr);
@@ -834,8 +842,10 @@
 #endif
         }
     }
+    AudioIoHandle getIoHandle() const { return mIoHandle; }
 
   private:
+    const AudioIoHandle mIoHandle = getNextIoHandle();
     sp<Stream>& mStream;
 };
 
@@ -861,7 +871,6 @@
         return res;
     }
 
-  private:
     void TearDown() override {
         if (open) {
             ASSERT_OK(closeStream());
@@ -879,6 +888,116 @@
 
 ////////////////////////////// openOutputStream //////////////////////////////
 
+class StreamWriter : public StreamWorker<StreamWriter> {
+  public:
+    StreamWriter(IStreamOut* stream, size_t bufferSize)
+        : mStream(stream), mBufferSize(bufferSize), mData(mBufferSize) {}
+    ~StreamWriter() {
+        stop();
+        if (mEfGroup) {
+            EventFlag::deleteEventFlag(&mEfGroup);
+        }
+    }
+
+    typedef MessageQueue<WriteCommand, ::android::hardware::kSynchronizedReadWrite> CommandMQ;
+    typedef MessageQueue<uint8_t, ::android::hardware::kSynchronizedReadWrite> DataMQ;
+    typedef MessageQueue<WriteStatus, ::android::hardware::kSynchronizedReadWrite> StatusMQ;
+
+    bool workerInit() {
+        std::unique_ptr<CommandMQ> tempCommandMQ;
+        std::unique_ptr<DataMQ> tempDataMQ;
+        std::unique_ptr<StatusMQ> tempStatusMQ;
+        Result retval;
+        Return<void> ret = mStream->prepareForWriting(
+                1, mBufferSize,
+                [&](Result r, const CommandMQ::Descriptor& commandMQ,
+                    const DataMQ::Descriptor& dataMQ, const StatusMQ::Descriptor& statusMQ,
+                    const auto& /*halThreadInfo*/) {
+                    retval = r;
+                    if (retval == Result::OK) {
+                        tempCommandMQ.reset(new CommandMQ(commandMQ));
+                        tempDataMQ.reset(new DataMQ(dataMQ));
+                        tempStatusMQ.reset(new StatusMQ(statusMQ));
+                        if (tempDataMQ->isValid() && tempDataMQ->getEventFlagWord()) {
+                            EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup);
+                        }
+                    }
+                });
+        if (!ret.isOk()) {
+            ALOGE("Transport error while calling prepareForWriting: %s", ret.description().c_str());
+            return false;
+        }
+        if (retval != Result::OK) {
+            ALOGE("Error from prepareForWriting: %d", retval);
+            return false;
+        }
+        if (!tempCommandMQ || !tempCommandMQ->isValid() || !tempDataMQ || !tempDataMQ->isValid() ||
+            !tempStatusMQ || !tempStatusMQ->isValid() || !mEfGroup) {
+            ALOGE_IF(!tempCommandMQ, "Failed to obtain command message queue for writing");
+            ALOGE_IF(tempCommandMQ && !tempCommandMQ->isValid(),
+                     "Command message queue for writing is invalid");
+            ALOGE_IF(!tempDataMQ, "Failed to obtain data message queue for writing");
+            ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(),
+                     "Data message queue for writing is invalid");
+            ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for writing");
+            ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(),
+                     "Status message queue for writing is invalid");
+            ALOGE_IF(!mEfGroup, "Event flag creation for writing failed");
+            return false;
+        }
+        mCommandMQ = std::move(tempCommandMQ);
+        mDataMQ = std::move(tempDataMQ);
+        mStatusMQ = std::move(tempStatusMQ);
+        return true;
+    }
+
+    bool workerCycle() {
+        WriteCommand cmd = WriteCommand::WRITE;
+        if (!mCommandMQ->write(&cmd)) {
+            ALOGE("command message queue write failed");
+            return false;
+        }
+        const size_t dataSize = std::min(mData.size(), mDataMQ->availableToWrite());
+        bool success = mDataMQ->write(mData.data(), dataSize);
+        ALOGE_IF(!success, "data message queue write failed");
+        mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY));
+
+        uint32_t efState = 0;
+    retry:
+        status_t ret =
+                mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL), &efState);
+        if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL)) {
+            WriteStatus writeStatus;
+            writeStatus.retval = Result::NOT_INITIALIZED;
+            if (!mStatusMQ->read(&writeStatus)) {
+                ALOGE("status message read failed");
+                success = false;
+            }
+            if (writeStatus.retval != Result::OK) {
+                ALOGE("bad write status: %d", writeStatus.retval);
+                success = false;
+            }
+        }
+        if (ret == -EAGAIN || ret == -EINTR) {
+            // Spurious wakeup. This normally retries no more than once.
+            goto retry;
+        } else if (ret) {
+            ALOGE("bad wait status: %d", ret);
+            success = false;
+        }
+        return success;
+    }
+
+  private:
+    IStreamOut* const mStream;
+    const size_t mBufferSize;
+    std::vector<uint8_t> mData;
+    std::unique_ptr<CommandMQ> mCommandMQ;
+    std::unique_ptr<DataMQ> mDataMQ;
+    std::unique_ptr<StatusMQ> mStatusMQ;
+    EventFlag* mEfGroup = nullptr;
+};
+
 class OutputStreamTest : public OpenStreamTest<IStreamOut> {
     void SetUp() override {
         ASSERT_NO_FATAL_FAILURE(OpenStreamTest::SetUp());  // setup base
@@ -954,6 +1073,121 @@
 
 ////////////////////////////// openInputStream //////////////////////////////
 
+class StreamReader : public StreamWorker<StreamReader> {
+  public:
+    StreamReader(IStreamIn* stream, size_t bufferSize)
+        : mStream(stream), mBufferSize(bufferSize), mData(mBufferSize) {}
+    ~StreamReader() {
+        stop();
+        if (mEfGroup) {
+            EventFlag::deleteEventFlag(&mEfGroup);
+        }
+    }
+
+    typedef MessageQueue<ReadParameters, ::android::hardware::kSynchronizedReadWrite> CommandMQ;
+    typedef MessageQueue<uint8_t, ::android::hardware::kSynchronizedReadWrite> DataMQ;
+    typedef MessageQueue<ReadStatus, ::android::hardware::kSynchronizedReadWrite> StatusMQ;
+
+    bool workerInit() {
+        std::unique_ptr<CommandMQ> tempCommandMQ;
+        std::unique_ptr<DataMQ> tempDataMQ;
+        std::unique_ptr<StatusMQ> tempStatusMQ;
+        Result retval;
+        Return<void> ret = mStream->prepareForReading(
+                1, mBufferSize,
+                [&](Result r, const CommandMQ::Descriptor& commandMQ,
+                    const DataMQ::Descriptor& dataMQ, const StatusMQ::Descriptor& statusMQ,
+                    const auto& /*halThreadInfo*/) {
+                    retval = r;
+                    if (retval == Result::OK) {
+                        tempCommandMQ.reset(new CommandMQ(commandMQ));
+                        tempDataMQ.reset(new DataMQ(dataMQ));
+                        tempStatusMQ.reset(new StatusMQ(statusMQ));
+                        if (tempDataMQ->isValid() && tempDataMQ->getEventFlagWord()) {
+                            EventFlag::createEventFlag(tempDataMQ->getEventFlagWord(), &mEfGroup);
+                        }
+                    }
+                });
+        if (!ret.isOk()) {
+            ALOGE("Transport error while calling prepareForReading: %s", ret.description().c_str());
+            return false;
+        }
+        if (retval != Result::OK) {
+            ALOGE("Error from prepareForReading: %d", retval);
+            return false;
+        }
+        if (!tempCommandMQ || !tempCommandMQ->isValid() || !tempDataMQ || !tempDataMQ->isValid() ||
+            !tempStatusMQ || !tempStatusMQ->isValid() || !mEfGroup) {
+            ALOGE_IF(!tempCommandMQ, "Failed to obtain command message queue for reading");
+            ALOGE_IF(tempCommandMQ && !tempCommandMQ->isValid(),
+                     "Command message queue for reading is invalid");
+            ALOGE_IF(!tempDataMQ, "Failed to obtain data message queue for reading");
+            ALOGE_IF(tempDataMQ && !tempDataMQ->isValid(),
+                     "Data message queue for reading is invalid");
+            ALOGE_IF(!tempStatusMQ, "Failed to obtain status message queue for reading");
+            ALOGE_IF(tempStatusMQ && !tempStatusMQ->isValid(),
+                     "Status message queue for reading is invalid");
+            ALOGE_IF(!mEfGroup, "Event flag creation for reading failed");
+            return false;
+        }
+        mCommandMQ = std::move(tempCommandMQ);
+        mDataMQ = std::move(tempDataMQ);
+        mStatusMQ = std::move(tempStatusMQ);
+        return true;
+    }
+
+    bool workerCycle() {
+        ReadParameters params;
+        params.command = IStreamIn::ReadCommand::READ;
+        params.params.read = mBufferSize;
+        if (!mCommandMQ->write(&params)) {
+            ALOGE("command message queue write failed");
+            return false;
+        }
+        mEfGroup->wake(static_cast<uint32_t>(MessageQueueFlagBits::NOT_FULL));
+
+        uint32_t efState = 0;
+        bool success = true;
+    retry:
+        status_t ret =
+                mEfGroup->wait(static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY), &efState);
+        if (efState & static_cast<uint32_t>(MessageQueueFlagBits::NOT_EMPTY)) {
+            ReadStatus readStatus;
+            readStatus.retval = Result::NOT_INITIALIZED;
+            if (!mStatusMQ->read(&readStatus)) {
+                ALOGE("status message read failed");
+                success = false;
+            }
+            if (readStatus.retval != Result::OK) {
+                ALOGE("bad read status: %d", readStatus.retval);
+                success = false;
+            }
+            const size_t dataSize = std::min(mData.size(), mDataMQ->availableToRead());
+            if (!mDataMQ->read(mData.data(), dataSize)) {
+                ALOGE("data message queue read failed");
+                success = false;
+            }
+        }
+        if (ret == -EAGAIN || ret == -EINTR) {
+            // Spurious wakeup. This normally retries no more than once.
+            goto retry;
+        } else if (ret) {
+            ALOGE("bad wait status: %d", ret);
+            success = false;
+        }
+        return success;
+    }
+
+  private:
+    IStreamIn* const mStream;
+    const size_t mBufferSize;
+    std::vector<uint8_t> mData;
+    std::unique_ptr<CommandMQ> mCommandMQ;
+    std::unique_ptr<DataMQ> mDataMQ;
+    std::unique_ptr<StatusMQ> mStatusMQ;
+    EventFlag* mEfGroup = nullptr;
+};
+
 class InputStreamTest : public OpenStreamTest<IStreamIn> {
     void SetUp() override {
         ASSERT_NO_FATAL_FAILURE(OpenStreamTest::SetUp());  // setup base
@@ -1377,6 +1611,12 @@
     uint64_t frames;
     uint64_t time;
     ASSERT_OK(stream->getCapturePosition(returnIn(res, frames, time)));
+    // Although 'getCapturePosition' is mandatory in V7, legacy implementations
+    // may return -ENOSYS (which is translated to NOT_SUPPORTED) in cases when
+    // the capture position can't be retrieved, e.g. when the stream isn't
+    // running. Because of this, we don't fail when getting NOT_SUPPORTED
+    // in this test. Behavior of 'getCapturePosition' for running streams is
+    // tested in 'PcmOnlyConfigInputStreamTest' for V7.
     ASSERT_RESULT(okOrInvalidStateOrNotSupported, res);
     if (res == Result::OK) {
         ASSERT_EQ(0U, frames);
@@ -1560,15 +1800,19 @@
         "If supported, a stream should always succeed to retrieve the "
         "presentation position");
     uint64_t frames;
-    TimeSpec mesureTS;
-    ASSERT_OK(stream->getPresentationPosition(returnIn(res, frames, mesureTS)));
+    TimeSpec measureTS;
+    ASSERT_OK(stream->getPresentationPosition(returnIn(res, frames, measureTS)));
+#if MAJOR_VERSION <= 6
     if (res == Result::NOT_SUPPORTED) {
-        doc::partialTest("getpresentationPosition is not supported");
+        doc::partialTest("getPresentationPosition is not supported");
         return;
     }
+#else
+    ASSERT_NE(Result::NOT_SUPPORTED, res) << "getPresentationPosition is mandatory in V7";
+#endif
     ASSERT_EQ(0U, frames);
 
-    if (mesureTS.tvNSec == 0 && mesureTS.tvSec == 0) {
+    if (measureTS.tvNSec == 0 && measureTS.tvSec == 0) {
         // As the stream has never written a frame yet,
         // the timestamp does not really have a meaning, allow to return 0
         return;
@@ -1580,8 +1824,8 @@
 
     auto toMicroSec = [](uint64_t sec, auto nsec) { return sec * 1e+6 + nsec / 1e+3; };
     auto currentTime = toMicroSec(currentTS.tv_sec, currentTS.tv_nsec);
-    auto mesureTime = toMicroSec(mesureTS.tvSec, mesureTS.tvNSec);
-    ASSERT_PRED2([](auto c, auto m) { return c - m < 1e+6; }, currentTime, mesureTime);
+    auto measureTime = toMicroSec(measureTS.tvSec, measureTS.tvNSec);
+    ASSERT_PRED2([](auto c, auto m) { return c - m < 1e+6; }, currentTime, measureTime);
 }
 
 //////////////////////////////////////////////////////////////////////////////
diff --git a/audio/core/all-versions/vts/functional/AudioTestDefinitions.h b/audio/core/all-versions/vts/functional/AudioTestDefinitions.h
index 5b14a21..aa67630 100644
--- a/audio/core/all-versions/vts/functional/AudioTestDefinitions.h
+++ b/audio/core/all-versions/vts/functional/AudioTestDefinitions.h
@@ -31,15 +31,17 @@
 
 // Nesting a tuple in another tuple allows to use GTest Combine function to generate
 // all combinations of devices and configs.
-enum { PARAM_DEVICE, PARAM_CONFIG, PARAM_FLAGS };
 #if MAJOR_VERSION <= 6
+enum { PARAM_DEVICE, PARAM_CONFIG, PARAM_FLAGS };
 enum { INDEX_INPUT, INDEX_OUTPUT };
 using DeviceConfigParameter =
         std::tuple<DeviceParameter, android::hardware::audio::common::CPP_VERSION::AudioConfig,
                    std::variant<android::hardware::audio::common::CPP_VERSION::AudioInputFlag,
                                 android::hardware::audio::common::CPP_VERSION::AudioOutputFlag>>;
 #elif MAJOR_VERSION >= 7
+enum { PARAM_DEVICE, PARAM_PORT_NAME, PARAM_CONFIG, PARAM_FLAGS };
 using DeviceConfigParameter =
-        std::tuple<DeviceParameter, android::hardware::audio::common::CPP_VERSION::AudioConfig,
+        std::tuple<DeviceParameter, std::string,
+                   android::hardware::audio::common::CPP_VERSION::AudioConfig,
                    std::vector<android::hardware::audio::CPP_VERSION::AudioInOutFlag>>;
 #endif
diff --git a/audio/core/all-versions/vts/functional/StreamWorker.h b/audio/core/all-versions/vts/functional/StreamWorker.h
new file mode 100644
index 0000000..68a8024
--- /dev/null
+++ b/audio/core/all-versions/vts/functional/StreamWorker.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2021 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 <sched.h>
+
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+
+template <typename Impl>
+class StreamWorker {
+    enum class WorkerState { STOPPED, RUNNING, PAUSE_REQUESTED, PAUSED, RESUME_REQUESTED, ERROR };
+
+  public:
+    StreamWorker() = default;
+    ~StreamWorker() { stop(); }
+    bool start() {
+        mWorker = std::thread(&StreamWorker::workerThread, this);
+        std::unique_lock<std::mutex> lock(mWorkerLock);
+        mWorkerCv.wait(lock, [&] { return mWorkerState != WorkerState::STOPPED; });
+        return mWorkerState == WorkerState::RUNNING;
+    }
+    void pause() { switchWorkerStateSync(WorkerState::RUNNING, WorkerState::PAUSE_REQUESTED); }
+    void resume() { switchWorkerStateSync(WorkerState::PAUSED, WorkerState::RESUME_REQUESTED); }
+    bool hasError() {
+        std::lock_guard<std::mutex> lock(mWorkerLock);
+        return mWorkerState == WorkerState::ERROR;
+    }
+    void stop() {
+        {
+            std::lock_guard<std::mutex> lock(mWorkerLock);
+            if (mWorkerState == WorkerState::STOPPED) return;
+            mWorkerState = WorkerState::STOPPED;
+        }
+        if (mWorker.joinable()) {
+            mWorker.join();
+        }
+    }
+    bool waitForAtLeastOneCycle() {
+        WorkerState newState;
+        switchWorkerStateSync(WorkerState::RUNNING, WorkerState::PAUSE_REQUESTED, &newState);
+        if (newState != WorkerState::PAUSED) return false;
+        switchWorkerStateSync(newState, WorkerState::RESUME_REQUESTED, &newState);
+        return newState == WorkerState::RUNNING;
+    }
+
+    // Methods that need to be provided by subclasses:
+    //
+    // Called once at the beginning of the thread loop. Must return
+    // 'true' to enter the thread loop, otherwise the thread loop
+    // exits and the worker switches into the 'error' state.
+    // bool workerInit();
+    //
+    // Called for each thread loop unless the thread is in 'paused' state.
+    // Must return 'true' to continue running, otherwise the thread loop
+    // exits and the worker switches into the 'error' state.
+    // bool workerCycle();
+
+  private:
+    void switchWorkerStateSync(WorkerState oldState, WorkerState newState,
+                               WorkerState* finalState = nullptr) {
+        std::unique_lock<std::mutex> lock(mWorkerLock);
+        if (mWorkerState != oldState) {
+            if (finalState) *finalState = mWorkerState;
+            return;
+        }
+        mWorkerState = newState;
+        mWorkerCv.wait(lock, [&] { return mWorkerState != newState; });
+        if (finalState) *finalState = mWorkerState;
+    }
+    void workerThread() {
+        bool success = static_cast<Impl*>(this)->workerInit();
+        {
+            std::lock_guard<std::mutex> lock(mWorkerLock);
+            mWorkerState = success ? WorkerState::RUNNING : WorkerState::ERROR;
+        }
+        mWorkerCv.notify_one();
+        if (!success) return;
+
+        for (WorkerState state = WorkerState::RUNNING; state != WorkerState::STOPPED;) {
+            bool needToNotify = false;
+            if (state != WorkerState::PAUSED ? static_cast<Impl*>(this)->workerCycle()
+                                             : (sched_yield(), true)) {
+                //
+                // Pause and resume are synchronous. One worker cycle must complete
+                // before the worker indicates a state change. This is how 'mWorkerState' and
+                // 'state' interact:
+                //
+                // mWorkerState == RUNNING
+                // client sets mWorkerState := PAUSE_REQUESTED
+                // last workerCycle gets executed, state := mWorkerState := PAUSED by us
+                //   (or the workers enters the 'error' state if workerCycle fails)
+                // client gets notified about state change in any case
+                // thread is doing a busy wait while 'state == PAUSED'
+                // client sets mWorkerState := RESUME_REQUESTED
+                // state := mWorkerState (RESUME_REQUESTED)
+                // mWorkerState := RUNNING, but we don't notify the client yet
+                // first workerCycle gets executed, the code below triggers a client notification
+                //   (or if workerCycle fails, worker enters 'error' state and also notifies)
+                // state := mWorkerState (RUNNING)
+                if (state == WorkerState::RESUME_REQUESTED) {
+                    needToNotify = true;
+                }
+                std::lock_guard<std::mutex> lock(mWorkerLock);
+                state = mWorkerState;
+                if (mWorkerState == WorkerState::PAUSE_REQUESTED) {
+                    state = mWorkerState = WorkerState::PAUSED;
+                    needToNotify = true;
+                } else if (mWorkerState == WorkerState::RESUME_REQUESTED) {
+                    mWorkerState = WorkerState::RUNNING;
+                }
+            } else {
+                std::lock_guard<std::mutex> lock(mWorkerLock);
+                if (state == WorkerState::RESUME_REQUESTED ||
+                    mWorkerState == WorkerState::PAUSE_REQUESTED) {
+                    needToNotify = true;
+                }
+                mWorkerState = WorkerState::ERROR;
+                state = WorkerState::STOPPED;
+            }
+            if (needToNotify) {
+                mWorkerCv.notify_one();
+            }
+        }
+    }
+
+    std::thread mWorker;
+    std::mutex mWorkerLock;
+    std::condition_variable mWorkerCv;
+    WorkerState mWorkerState = WorkerState::STOPPED;  // GUARDED_BY(mWorkerLock);
+};
diff --git a/audio/core/all-versions/vts/functional/tests/streamworker_tests.cpp b/audio/core/all-versions/vts/functional/tests/streamworker_tests.cpp
new file mode 100644
index 0000000..75116af
--- /dev/null
+++ b/audio/core/all-versions/vts/functional/tests/streamworker_tests.cpp
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2021 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 "StreamWorker.h"
+
+#include <sched.h>
+#include <unistd.h>
+#include <atomic>
+
+#include <gtest/gtest.h>
+#define LOG_TAG "StreamWorker_Test"
+#include <log/log.h>
+
+struct TestStream {
+    std::atomic<bool> error = false;
+};
+
+class TestWorker : public StreamWorker<TestWorker> {
+  public:
+    // Use nullptr to test error reporting from the worker thread.
+    explicit TestWorker(TestStream* stream) : mStream(stream) {}
+
+    void ensureWorkerCycled() {
+        const size_t cyclesBefore = mWorkerCycles;
+        while (mWorkerCycles == cyclesBefore && !hasError()) {
+            sched_yield();
+        }
+    }
+    size_t getWorkerCycles() const { return mWorkerCycles; }
+    bool hasWorkerCycleCalled() const { return mWorkerCycles != 0; }
+    bool hasNoWorkerCycleCalled(useconds_t usec) {
+        const size_t cyclesBefore = mWorkerCycles;
+        usleep(usec);
+        return mWorkerCycles == cyclesBefore;
+    }
+
+    bool workerInit() { return mStream; }
+    bool workerCycle() {
+        do {
+            mWorkerCycles++;
+        } while (mWorkerCycles == 0);
+        return !mStream->error;
+    }
+
+  private:
+    TestStream* const mStream;
+    std::atomic<size_t> mWorkerCycles = 0;
+};
+
+// The parameter specifies whether an extra call to 'stop' is made at the end.
+class StreamWorkerInvalidTest : public testing::TestWithParam<bool> {
+  public:
+    StreamWorkerInvalidTest() : StreamWorkerInvalidTest(nullptr) {}
+    void TearDown() override {
+        if (GetParam()) {
+            worker.stop();
+        }
+    }
+
+  protected:
+    StreamWorkerInvalidTest(TestStream* stream) : testing::TestWithParam<bool>(), worker(stream) {}
+    TestWorker worker;
+};
+
+TEST_P(StreamWorkerInvalidTest, Uninitialized) {
+    EXPECT_FALSE(worker.hasWorkerCycleCalled());
+    EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerInvalidTest, UninitializedPauseIgnored) {
+    EXPECT_FALSE(worker.hasError());
+    worker.pause();
+    EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerInvalidTest, UninitializedResumeIgnored) {
+    EXPECT_FALSE(worker.hasError());
+    worker.resume();
+    EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerInvalidTest, Start) {
+    EXPECT_FALSE(worker.start());
+    EXPECT_FALSE(worker.hasWorkerCycleCalled());
+    EXPECT_TRUE(worker.hasError());
+}
+
+TEST_P(StreamWorkerInvalidTest, PauseIgnored) {
+    EXPECT_FALSE(worker.start());
+    EXPECT_TRUE(worker.hasError());
+    worker.pause();
+    EXPECT_TRUE(worker.hasError());
+}
+
+TEST_P(StreamWorkerInvalidTest, ResumeIgnored) {
+    EXPECT_FALSE(worker.start());
+    EXPECT_TRUE(worker.hasError());
+    worker.resume();
+    EXPECT_TRUE(worker.hasError());
+}
+
+INSTANTIATE_TEST_SUITE_P(StreamWorkerInvalid, StreamWorkerInvalidTest, testing::Bool());
+
+class StreamWorkerTest : public StreamWorkerInvalidTest {
+  public:
+    StreamWorkerTest() : StreamWorkerInvalidTest(&stream) {}
+
+  protected:
+    TestStream stream;
+};
+
+static constexpr unsigned kWorkerIdleCheckTime = 50 * 1000;
+
+TEST_P(StreamWorkerTest, Uninitialized) {
+    EXPECT_FALSE(worker.hasWorkerCycleCalled());
+    EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, Start) {
+    ASSERT_TRUE(worker.start());
+    worker.ensureWorkerCycled();
+    EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, WorkerError) {
+    ASSERT_TRUE(worker.start());
+    stream.error = true;
+    worker.ensureWorkerCycled();
+    EXPECT_TRUE(worker.hasError());
+    EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+}
+
+TEST_P(StreamWorkerTest, PauseResume) {
+    ASSERT_TRUE(worker.start());
+    worker.ensureWorkerCycled();
+    EXPECT_FALSE(worker.hasError());
+    worker.pause();
+    EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+    EXPECT_FALSE(worker.hasError());
+    const size_t workerCyclesBefore = worker.getWorkerCycles();
+    worker.resume();
+    // 'resume' is synchronous and returns after the worker has looped at least once.
+    EXPECT_GT(worker.getWorkerCycles(), workerCyclesBefore);
+    EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, StopPaused) {
+    ASSERT_TRUE(worker.start());
+    worker.ensureWorkerCycled();
+    EXPECT_FALSE(worker.hasError());
+    worker.pause();
+    worker.stop();
+    EXPECT_FALSE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, PauseAfterErrorIgnored) {
+    ASSERT_TRUE(worker.start());
+    stream.error = true;
+    worker.ensureWorkerCycled();
+    EXPECT_TRUE(worker.hasError());
+    worker.pause();
+    EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+    EXPECT_TRUE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, ResumeAfterErrorIgnored) {
+    ASSERT_TRUE(worker.start());
+    stream.error = true;
+    worker.ensureWorkerCycled();
+    EXPECT_TRUE(worker.hasError());
+    worker.resume();
+    EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+    EXPECT_TRUE(worker.hasError());
+}
+
+TEST_P(StreamWorkerTest, WorkerErrorOnResume) {
+    ASSERT_TRUE(worker.start());
+    worker.ensureWorkerCycled();
+    EXPECT_FALSE(worker.hasError());
+    worker.pause();
+    EXPECT_FALSE(worker.hasError());
+    stream.error = true;
+    EXPECT_FALSE(worker.hasError());
+    worker.resume();
+    worker.ensureWorkerCycled();
+    EXPECT_TRUE(worker.hasError());
+    EXPECT_TRUE(worker.hasNoWorkerCycleCalled(kWorkerIdleCheckTime));
+}
+
+TEST_P(StreamWorkerTest, WaitForAtLeastOneCycle) {
+    ASSERT_TRUE(worker.start());
+    const size_t workerCyclesBefore = worker.getWorkerCycles();
+    EXPECT_TRUE(worker.waitForAtLeastOneCycle());
+    EXPECT_GT(worker.getWorkerCycles(), workerCyclesBefore);
+}
+
+TEST_P(StreamWorkerTest, WaitForAtLeastOneCycleError) {
+    ASSERT_TRUE(worker.start());
+    stream.error = true;
+    EXPECT_FALSE(worker.waitForAtLeastOneCycle());
+}
+
+INSTANTIATE_TEST_SUITE_P(StreamWorker, StreamWorkerTest, testing::Bool());
diff --git a/audio/effect/all-versions/default/OWNERS b/audio/effect/all-versions/OWNERS
similarity index 67%
copy from audio/effect/all-versions/default/OWNERS
copy to audio/effect/all-versions/OWNERS
index 6fdc97c..24071af 100644
--- a/audio/effect/all-versions/default/OWNERS
+++ b/audio/effect/all-versions/OWNERS
@@ -1,3 +1,2 @@
 elaurent@google.com
-krocard@google.com
 mnaganov@google.com
diff --git a/audio/effect/all-versions/vts/OWNERS b/audio/effect/all-versions/vts/OWNERS
deleted file mode 100644
index 0ea4666..0000000
--- a/audio/effect/all-versions/vts/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-elaurent@google.com
-krocard@google.com
-mnaganov@google.com
-yim@google.com
-zhuoyao@google.com
diff --git a/bluetooth/audio/2.1/types.hal b/bluetooth/audio/2.1/types.hal
index 5604c38..e0dcc02 100644
--- a/bluetooth/audio/2.1/types.hal
+++ b/bluetooth/audio/2.1/types.hal
@@ -16,13 +16,14 @@
 
 package android.hardware.bluetooth.audio@2.1;
 
-import @2.0::PcmParameters;
-import @2.0::SessionType;
-import @2.0::SampleRate;
-import @2.0::ChannelMode;
 import @2.0::BitsPerSample;
-import @2.0::CodecConfiguration;
+import @2.0::ChannelMode;
 import @2.0::CodecCapabilities;
+import @2.0::CodecConfiguration;
+import @2.0::CodecType;
+import @2.0::PcmParameters;
+import @2.0::SampleRate;
+import @2.0::SessionType;
 
 enum SessionType : @2.0::SessionType {
     /** Used when encoded by Bluetooth Stack and streaming to LE Audio device */
@@ -35,6 +36,10 @@
     LE_AUDIO_HARDWARE_OFFLOAD_DECODING_DATAPATH,
 };
 
+enum CodecType : @2.0::CodecType {
+    LC3 = 0x20,
+};
+
 enum SampleRate : @2.0::SampleRate {
     RATE_8000 = 0x100,
     RATE_32000 = 0x200,
@@ -49,14 +54,57 @@
     uint32_t dataIntervalUs;
 };
 
-/** Used to configure either a Hardware or Software Encoding session based on session type */
-safe_union AudioConfiguration {
-    PcmParameters pcmConfig;
-    CodecConfiguration codecConfig;
+enum Lc3FrameDuration : uint8_t {
+    DURATION_10000US = 0x00,
+    DURATION_7500US = 0x01,
+};
+
+/**
+ * Used for Hardware Encoding/Decoding LC3 codec parameters.
+ */
+struct Lc3Parameters {
+    /* PCM is Input for encoder, Output for decoder */
+    BitsPerSample pcmBitDepth;
+
+    /* codec-specific parameters */
+    SampleRate samplingFrequency;
+    Lc3FrameDuration frameDuration;
+    /* length in octets of a codec frame */
+    uint32_t octetsPerFrame;
+    /* Number of blocks of codec frames per single SDU (Service Data Unit) */
+    uint8_t blocksPerSdu;
+};
+
+/**
+ * Used to specify the capabilities of the LC3 codecs supported by Hardware Encoding.
+ */
+struct Lc3CodecCapabilities {
+    /* This is bitfield, if bit N is set, HW Offloader supports N+1 channels at the same time.
+     * Example: 0x27 = 0b00100111: One, two, three or six channels supported.*/
+    uint8_t supportedChannelCounts;
+    Lc3Parameters lc3Capabilities;
 };
 
 /** Used to specify the capabilities of the different session types */
 safe_union AudioCapabilities {
     PcmParameters pcmCapabilities;
     CodecCapabilities codecCapabilities;
+    Lc3CodecCapabilities leAudioCapabilities;
 };
+
+/**
+ * Used to configure a LC3 Hardware Encoding session.
+ */
+struct Lc3CodecConfiguration {
+    /* This is also bitfield, specifying how the channels are ordered in the outgoing media packet.
+     * Bit meaning is defined in Bluetooth Assigned Numbers. */
+    uint32_t audioChannelAllocation;
+    Lc3Parameters lc3Config;
+};
+
+/** Used to configure either a Hardware or Software Encoding session based on session type */
+safe_union AudioConfiguration {
+    PcmParameters pcmConfig;
+    CodecConfiguration codecConfig;
+    Lc3CodecConfiguration leAudioCodecConfig;
+};
\ No newline at end of file
diff --git a/broadcastradio/1.0/default/OWNERS b/broadcastradio/1.0/default/OWNERS
index b159083..57e6592 100644
--- a/broadcastradio/1.0/default/OWNERS
+++ b/broadcastradio/1.0/default/OWNERS
@@ -1,4 +1,3 @@
 elaurent@google.com
-krocard@google.com
 mnaganov@google.com
 twasilczyk@google.com
diff --git a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DataLocation.aidl b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DataLocation.aidl
index 074cc09..e836dae 100644
--- a/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DataLocation.aidl
+++ b/neuralnetworks/aidl/aidl_api/android.hardware.neuralnetworks/current/android/hardware/neuralnetworks/DataLocation.aidl
@@ -36,4 +36,5 @@
   int poolIndex;
   long offset;
   long length;
+  long padding;
 }
diff --git a/neuralnetworks/aidl/android/hardware/neuralnetworks/DataLocation.aidl b/neuralnetworks/aidl/android/hardware/neuralnetworks/DataLocation.aidl
index f6b5e0d..f656360 100644
--- a/neuralnetworks/aidl/android/hardware/neuralnetworks/DataLocation.aidl
+++ b/neuralnetworks/aidl/android/hardware/neuralnetworks/DataLocation.aidl
@@ -18,6 +18,28 @@
 
 /**
  * Describes the location of a data object.
+ *
+ * If the data object is an omitted operand, all of the fields must be 0. If the poolIndex refers to
+ * a driver-managed buffer allocated from IDevice::allocate, or an AHardwareBuffer of a format other
+ * than AHARDWAREBUFFER_FORMAT_BLOB, the offset, length, and padding must be set to 0 indicating
+ * the entire pool is used.
+ *
+ * Otherwise, the offset, length, and padding specify a sub-region of a memory pool. The sum of
+ * offset, length, and padding must not exceed the total size of the specified memory pool. If the
+ * data object is a scalar operand or a tensor operand with fully specified dimensions, the value of
+ * length must be equal to the raw size of the operand (i.e. the size of an element multiplied
+ * by the number of elements). When used in Operand, the value of padding must be 0. When used in
+ * RequestArgument, the value of padding specifies the extra bytes at the end of the memory region
+ * that may be used by the device to access memory in chunks, for efficiency. If the data object is
+ * a Request output whose dimensions are not fully specified, the value of length specifies the
+ * total size of the writable region of the output data, and padding specifies the extra bytes at
+ * the end of the memory region that may be used by the device to access memory in chunks, for
+ * efficiency, but must not be used to hold any output data.
+ *
+ * When used in RequestArgument, clients should prefer to align and pad the sub-region to
+ * 64 bytes when possible; this may allow the device to access the sub-region more efficiently.
+ * The sub-region is aligned to 64 bytes if the value of offset is a multiple of 64.
+ * The sub-region is padded to 64 bytes if the sum of length and padding is a multiple of 64.
  */
 @VintfStability
 parcelable DataLocation {
@@ -33,4 +55,8 @@
      * The length of the data in bytes.
      */
     long length;
+    /**
+     * The end padding of the specified memory region in bytes.
+     */
+    long padding;
 }
diff --git a/neuralnetworks/aidl/utils/src/Conversions.cpp b/neuralnetworks/aidl/utils/src/Conversions.cpp
index 5d9c55b..c47ba0e 100644
--- a/neuralnetworks/aidl/utils/src/Conversions.cpp
+++ b/neuralnetworks/aidl/utils/src/Conversions.cpp
@@ -250,16 +250,22 @@
     VERIFY_NON_NEGATIVE(location.poolIndex) << "DataLocation: pool index must not be negative";
     VERIFY_NON_NEGATIVE(location.offset) << "DataLocation: offset must not be negative";
     VERIFY_NON_NEGATIVE(location.length) << "DataLocation: length must not be negative";
+    VERIFY_NON_NEGATIVE(location.padding) << "DataLocation: padding must not be negative";
     if (location.offset > std::numeric_limits<uint32_t>::max()) {
         return NN_ERROR() << "DataLocation: offset must be <= std::numeric_limits<uint32_t>::max()";
     }
     if (location.length > std::numeric_limits<uint32_t>::max()) {
         return NN_ERROR() << "DataLocation: length must be <= std::numeric_limits<uint32_t>::max()";
     }
+    if (location.padding > std::numeric_limits<uint32_t>::max()) {
+        return NN_ERROR()
+               << "DataLocation: padding must be <= std::numeric_limits<uint32_t>::max()";
+    }
     return DataLocation{
             .poolIndex = static_cast<uint32_t>(location.poolIndex),
             .offset = static_cast<uint32_t>(location.offset),
             .length = static_cast<uint32_t>(location.length),
+            .padding = static_cast<uint32_t>(location.padding),
     };
 }
 
diff --git a/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp
index 4eb704b..7a042ed 100644
--- a/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp
+++ b/neuralnetworks/aidl/vts/functional/GeneratedTestHarness.cpp
@@ -299,9 +299,11 @@
 }
 
 static void makeOutputInsufficientSize(uint32_t outputIndex, Request* request) {
-    auto& length = request->outputs[outputIndex].location.length;
-    ASSERT_GT(length, 1u);
-    length -= 1u;
+    auto& loc = request->outputs[outputIndex].location;
+    ASSERT_GT(loc.length, 1u);
+    loc.length -= 1u;
+    // Test that the padding is not used for output data.
+    loc.padding += 1u;
 }
 
 static void makeOutputDimensionsUnspecified(Model* model) {
@@ -336,6 +338,12 @@
     std::vector<std::shared_ptr<IBuffer>> mBuffers;
 };
 
+// Returns the number of bytes needed to round up "size" to the nearest multiple of "multiple".
+static uint32_t roundUpBytesNeeded(uint32_t size, uint32_t multiple) {
+    CHECK(multiple != 0);
+    return ((size + multiple - 1) / multiple) * multiple - size;
+}
+
 std::optional<Request> ExecutionContext::createRequest(const TestModel& testModel,
                                                        MemoryType memoryType) {
     // Memory pools are organized as:
@@ -370,10 +378,13 @@
         }
 
         // Reserve shared memory for input.
+        inputSize += roundUpBytesNeeded(inputSize, nn::kDefaultRequestMemoryAlignment);
+        const auto padding = roundUpBytesNeeded(op.data.size(), nn::kDefaultRequestMemoryPadding);
         DataLocation loc = {.poolIndex = kInputPoolIndex,
                             .offset = static_cast<int64_t>(inputSize),
-                            .length = static_cast<int64_t>(op.data.size())};
-        inputSize += op.data.alignedSize();
+                            .length = static_cast<int64_t>(op.data.size()),
+                            .padding = static_cast<int64_t>(padding)};
+        inputSize += (op.data.size() + padding);
         inputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
     }
 
@@ -404,10 +415,13 @@
         size_t bufferSize = std::max<size_t>(op.data.size(), 1);
 
         // Reserve shared memory for output.
+        outputSize += roundUpBytesNeeded(outputSize, nn::kDefaultRequestMemoryAlignment);
+        const auto padding = roundUpBytesNeeded(bufferSize, nn::kDefaultRequestMemoryPadding);
         DataLocation loc = {.poolIndex = kOutputPoolIndex,
                             .offset = static_cast<int64_t>(outputSize),
-                            .length = static_cast<int64_t>(bufferSize)};
-        outputSize += op.data.size() == 0 ? TestBuffer::kAlignment : op.data.alignedSize();
+                            .length = static_cast<int64_t>(bufferSize),
+                            .padding = static_cast<int64_t>(padding)};
+        outputSize += (bufferSize + padding);
         outputs[i] = {.hasNoValue = false, .location = loc, .dimensions = {}};
     }
 
diff --git a/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp b/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp
index 1929750..57bc1ae 100644
--- a/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp
+++ b/neuralnetworks/aidl/vts/functional/MemoryDomainTests.cpp
@@ -1125,12 +1125,15 @@
                                        utils::toSigned(kTestOperand.dimensions).value());
     if (deviceBuffer.buffer == nullptr) return;
 
-    RequestMemoryPool sharedMemory = createSharedMemoryPool(kTestOperandDataSize);
-    RequestMemoryPool deviceMemory = createDeviceMemoryPool(deviceBuffer.token);
+    // Use an incompatible dimension and make sure the length matches with the bad dimension.
     auto badDimensions = utils::toSigned(kTestOperand.dimensions).value();
     badDimensions[0] = 2;
+    const uint32_t badTestOperandDataSize = kTestOperandDataSize * 2;
+
+    RequestMemoryPool sharedMemory = createSharedMemoryPool(badTestOperandDataSize);
+    RequestMemoryPool deviceMemory = createDeviceMemoryPool(deviceBuffer.token);
     RequestArgument sharedMemoryArg = {
-            .location = {.poolIndex = 0, .offset = 0, .length = kTestOperandDataSize},
+            .location = {.poolIndex = 0, .offset = 0, .length = badTestOperandDataSize},
             .dimensions = badDimensions};
     RequestArgument deviceMemoryArg = {.location = {.poolIndex = 1}};
     RequestArgument deviceMemoryArgWithBadDimensions = {.location = {.poolIndex = 1},
diff --git a/radio/1.6/IRadioResponse.hal b/radio/1.6/IRadioResponse.hal
index d65c6d0..883711c 100644
--- a/radio/1.6/IRadioResponse.hal
+++ b/radio/1.6/IRadioResponse.hal
@@ -342,6 +342,7 @@
      *  RadioError:RADIO_NOT_AVAILABLE
      *  RadioError:MODEM_ERR
      *  RadioError:INVALID_ARGUMENTS
+     *  RadioError:REQUEST_NOT_SUPPORTED
      */
     oneway setDataThrottlingResponse(RadioResponseInfo info);
 
diff --git a/radio/1.6/vts/functional/radio_hidl_hal_api.cpp b/radio/1.6/vts/functional/radio_hidl_hal_api.cpp
index 91d98cb..7fde18e 100644
--- a/radio/1.6/vts/functional/radio_hidl_hal_api.cpp
+++ b/radio/1.6/vts/functional/radio_hidl_hal_api.cpp
@@ -420,13 +420,18 @@
     EXPECT_EQ(std::cv_status::no_timeout, wait());
     EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_v1_6->rspInfo.type);
     EXPECT_EQ(serial, radioRsp_v1_6->rspInfo.serial);
-    ASSERT_TRUE(
-            CheckAnyOfErrors(radioRsp_v1_6->rspInfo.error,
-                             {::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE,
-                              ::android::hardware::radio::V1_6::RadioError::MODEM_ERR,
-                              ::android::hardware::radio::V1_6::RadioError::NONE,
-                              ::android::hardware::radio::V1_6::RadioError::INVALID_ARGUMENTS}));
-
+    if (getRadioHalCapabilities().modemReducedFeatureSet1) {
+        ASSERT_TRUE(CheckAnyOfErrors(
+                radioRsp_v1_6->rspInfo.error,
+                {::android::hardware::radio::V1_6::RadioError::REQUEST_NOT_SUPPORTED}));
+    } else {
+        ASSERT_TRUE(CheckAnyOfErrors(
+                radioRsp_v1_6->rspInfo.error,
+                {::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE,
+                 ::android::hardware::radio::V1_6::RadioError::MODEM_ERR,
+                 ::android::hardware::radio::V1_6::RadioError::NONE,
+                 ::android::hardware::radio::V1_6::RadioError::INVALID_ARGUMENTS}));
+    }
     serial = GetRandomSerialNumber();
 
     res = radio_v1_6->setDataThrottling(serial, DataThrottlingAction::THROTTLE_ANCHOR_CARRIER,
@@ -435,13 +440,18 @@
     EXPECT_EQ(std::cv_status::no_timeout, wait());
     EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_v1_6->rspInfo.type);
     EXPECT_EQ(serial, radioRsp_v1_6->rspInfo.serial);
-    ASSERT_TRUE(
-            CheckAnyOfErrors(radioRsp_v1_6->rspInfo.error,
-                             {::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE,
-                              ::android::hardware::radio::V1_6::RadioError::MODEM_ERR,
-                              ::android::hardware::radio::V1_6::RadioError::NONE,
-                              ::android::hardware::radio::V1_6::RadioError::INVALID_ARGUMENTS}));
-
+    if (getRadioHalCapabilities().modemReducedFeatureSet1) {
+        ASSERT_TRUE(CheckAnyOfErrors(
+                radioRsp_v1_6->rspInfo.error,
+                {::android::hardware::radio::V1_6::RadioError::REQUEST_NOT_SUPPORTED}));
+    } else {
+        ASSERT_TRUE(CheckAnyOfErrors(
+                radioRsp_v1_6->rspInfo.error,
+                {::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE,
+                 ::android::hardware::radio::V1_6::RadioError::MODEM_ERR,
+                 ::android::hardware::radio::V1_6::RadioError::NONE,
+                 ::android::hardware::radio::V1_6::RadioError::INVALID_ARGUMENTS}));
+    }
     serial = GetRandomSerialNumber();
 
     res = radio_v1_6->setDataThrottling(serial, DataThrottlingAction::HOLD, 60000);
@@ -450,13 +460,18 @@
     EXPECT_EQ(std::cv_status::no_timeout, wait());
     EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_v1_6->rspInfo.type);
     EXPECT_EQ(serial, radioRsp_v1_6->rspInfo.serial);
-    ASSERT_TRUE(
-            CheckAnyOfErrors(radioRsp_v1_6->rspInfo.error,
-                             {::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE,
-                              ::android::hardware::radio::V1_6::RadioError::MODEM_ERR,
-                              ::android::hardware::radio::V1_6::RadioError::NONE,
-                              ::android::hardware::radio::V1_6::RadioError::INVALID_ARGUMENTS}));
-
+    if (getRadioHalCapabilities().modemReducedFeatureSet1) {
+        ASSERT_TRUE(CheckAnyOfErrors(
+                radioRsp_v1_6->rspInfo.error,
+                {::android::hardware::radio::V1_6::RadioError::REQUEST_NOT_SUPPORTED}));
+    } else {
+        ASSERT_TRUE(CheckAnyOfErrors(
+                radioRsp_v1_6->rspInfo.error,
+                {::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE,
+                 ::android::hardware::radio::V1_6::RadioError::MODEM_ERR,
+                 ::android::hardware::radio::V1_6::RadioError::NONE,
+                 ::android::hardware::radio::V1_6::RadioError::INVALID_ARGUMENTS}));
+    }
     serial = GetRandomSerialNumber();
 
     res = radio_v1_6->setDataThrottling(serial, DataThrottlingAction::NO_DATA_THROTTLING, 60000);
@@ -464,12 +479,18 @@
     EXPECT_EQ(std::cv_status::no_timeout, wait());
     EXPECT_EQ(RadioResponseType::SOLICITED, radioRsp_v1_6->rspInfo.type);
     EXPECT_EQ(serial, radioRsp_v1_6->rspInfo.serial);
-    ASSERT_TRUE(
-            CheckAnyOfErrors(radioRsp_v1_6->rspInfo.error,
-                             {::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE,
-                              ::android::hardware::radio::V1_6::RadioError::MODEM_ERR,
-                              ::android::hardware::radio::V1_6::RadioError::NONE,
-                              ::android::hardware::radio::V1_6::RadioError::INVALID_ARGUMENTS}));
+    if (getRadioHalCapabilities().modemReducedFeatureSet1) {
+        ASSERT_TRUE(CheckAnyOfErrors(
+                radioRsp_v1_6->rspInfo.error,
+                {::android::hardware::radio::V1_6::RadioError::REQUEST_NOT_SUPPORTED}));
+    } else {
+        ASSERT_TRUE(CheckAnyOfErrors(
+                radioRsp_v1_6->rspInfo.error,
+                {::android::hardware::radio::V1_6::RadioError::RADIO_NOT_AVAILABLE,
+                 ::android::hardware::radio::V1_6::RadioError::MODEM_ERR,
+                 ::android::hardware::radio::V1_6::RadioError::NONE,
+                 ::android::hardware::radio::V1_6::RadioError::INVALID_ARGUMENTS}));
+    }
 }
 
 /*
diff --git a/radio/config/1.3/types.hal b/radio/config/1.3/types.hal
index 117abf3..2b6c9f0 100644
--- a/radio/config/1.3/types.hal
+++ b/radio/config/1.3/types.hal
@@ -31,6 +31,8 @@
    * <li> calling android.hardware.radio@1.6::IRadio.setNrDualConnectivityState
    * or querying android.hardware.radio@1.6::IRadio.isNrDualConnectivityEnabled
    * </li>
+   * <li>Requesting android.hardware.radio@1.6::IRadio.setDataThrottling()
+   * </li>
    * </ul>
    */
   bool modemReducedFeatureSet1;
diff --git a/scripts/list_hal_vts.py b/scripts/list_hal_vts.py
new file mode 100755
index 0000000..1fb51a5
--- /dev/null
+++ b/scripts/list_hal_vts.py
@@ -0,0 +1,143 @@
+#!/usr/bin/python3
+
+"""
+List VTS tests for each HAL by parsing module-info.json.
+
+Example usage:
+
+  # First, build modules-info.json
+  m -j "${ANDROID_PRODUCT_OUT#$ANDROID_BUILD_TOP/}/module-info.json"
+
+  # List with pretty-printed JSON. *IDL packages without a VTS module will show up
+  # as keys with empty lists.
+  ./list_hals_vts.py | python3 -m json.tool
+
+  # List with CSV. *IDL packages without a VTS module will show up as a line with
+  # empty value in the VTS module column.
+  ./list_hals_vts.py --csv
+"""
+
+import argparse
+import collections
+import csv
+import io
+import json
+import os
+import logging
+import pathlib
+import re
+import sys
+
+PATH_PACKAGE_PATTERN = re.compile(
+  r'^hardware/interfaces/(?P<path>(?:\w+/)*?)(?:aidl|(?P<version>\d+\.\d+))/.*')
+
+
+class CriticalHandler(logging.StreamHandler):
+  def emit(self, record):
+    super(CriticalHandler, self).emit(record)
+    if record.levelno >= logging.CRITICAL:
+      sys.exit(1)
+
+
+logger = logging.getLogger(__name__)
+logger.addHandler(CriticalHandler())
+
+
+def default_json():
+  out = os.environ.get('ANDROID_PRODUCT_OUT')
+  if not out: return None
+  return os.path.join(out, 'module-info.json')
+
+
+def infer_package(path):
+  """
+  Infer package from a relative path from build top where a VTS module lives.
+
+  :param path: a path like 'hardware/interfaces/vibrator/aidl/vts'
+  :return: The inferred *IDL package, e.g. 'android.hardware.vibrator'
+
+  >>> infer_package('hardware/interfaces/automotive/sv/1.0/vts/functional')
+  'android.hardware.automotive.sv@1.0'
+  >>> infer_package('hardware/interfaces/vibrator/aidl/vts')
+  'android.hardware.vibrator'
+  """
+  mo = re.match(PATH_PACKAGE_PATTERN, path)
+  if not mo: return None
+  package = 'android.hardware.' + ('.'.join(pathlib.Path(mo.group('path')).parts))
+  if mo.group('version'):
+    package += '@' + mo.group('version')
+  return package
+
+
+def load_modules_info(json_file):
+  """
+  :param json_file: The path to modules-info.json
+  :return: a dictionary, where the keys are inferred *IDL package names, and
+           values are a list of VTS modules with that inferred package name.
+  """
+  with open(json_file) as fp:
+    root = json.load(fp)
+    ret = collections.defaultdict(list)
+    for module_name, module_info in root.items():
+      if 'vts' not in module_info.get('compatibility_suites', []):
+        continue
+      for path in module_info.get('path', []):
+        inferred_package = infer_package(path)
+        if not inferred_package:
+          continue
+        ret[inferred_package].append(module_name)
+    return ret
+
+
+def add_missing_idl(vts_modules):
+  top = os.environ.get("ANDROID_BUILD_TOP")
+  interfaces = None
+  if top:
+    interfaces = os.path.join(top, "hardware", "interfaces")
+  else:
+    logger.warning("Missing ANDROID_BUILD_TOP")
+    interfaces = "hardware/interfaces"
+  if not os.path.isdir(interfaces):
+    logger.error("Not adding missing *IDL modules because missing hardware/interfaces dir")
+    return
+  assert not interfaces.endswith(os.path.sep)
+  for root, dirs, files in os.walk(interfaces):
+    for dir in dirs:
+      full_dir = os.path.join(root, dir)
+      assert full_dir.startswith(interfaces)
+      top_rel_dir = os.path.join('hardware', 'interfaces', full_dir[len(interfaces) + 1:])
+      inferred_package = infer_package(top_rel_dir)
+      if inferred_package is None:
+        continue
+      if inferred_package not in vts_modules:
+        vts_modules[inferred_package] = []
+
+
+def main():
+  parser = argparse.ArgumentParser(__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
+  parser.add_argument('json', metavar='module-info.json', default=default_json(), nargs='?')
+  parser.add_argument('--csv', action='store_true', help='Print CSV. If not specified print JSON.')
+  args = parser.parse_args()
+  if not args.json:
+    logger.critical('No module-info.json is specified or found.')
+  vts_modules = load_modules_info(args.json)
+  add_missing_idl(vts_modules)
+
+  if args.csv:
+    out = io.StringIO()
+    writer = csv.writer(out, )
+    writer.writerow(["package", "vts_module"])
+    for package, modules in vts_modules.items():
+      if not modules:
+        writer.writerow([package, ""])
+      for module in modules:
+        writer.writerow([package, module])
+    result = out.getvalue()
+  else:
+    result = json.dumps(vts_modules)
+
+  print(result)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl b/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
index 1b09e9d..327e4a1 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
@@ -165,7 +165,7 @@
      *                protected: bstr .cbor {
      *                    1 : -8,                     // Algorithm : EdDSA
      *                },
-     *                unprotected: bstr .size 0
+     *                unprotected: { },
      *                payload: bstr .cbor SignatureKey,
      *                signature: bstr PureEd25519(.cbor SignatureKeySignatureInput)
      *            ]
@@ -190,7 +190,7 @@
      *                protected: bstr .cbor {
      *                    1 : -8,                     // Algorithm : EdDSA
      *                },
-     *                unprotected: bstr .size 0
+     *                unprotected: { },
      *                payload: bstr .cbor Eek,
      *                signature: bstr PureEd25519(.cbor EekSignatureInput)
      *            ]
@@ -239,7 +239,7 @@
      *                protected : bstr .cbor {
      *                    1 : 5,                           // Algorithm : HMAC-256
      *                },
-     *                unprotected : bstr .size 0,
+     *                unprotected : { },
      *                // Payload is PublicKeys from keysToSign argument, in provided order.
      *                payload: bstr .cbor [ * PublicKey ],
      *                tag: bstr
diff --git a/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl b/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
index da85a50..cb5492d 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
@@ -29,7 +29,7 @@
      *
      *     MacedPublicKey = [                     // COSE_Mac0
      *         protected: bstr .cbor { 1 : 5},    // Algorithm : HMAC-256
-     *         unprotected: bstr .size 0,
+     *         unprotected: { },
      *         payload : bstr .cbor PublicKey,
      *         tag : bstr HMAC-256(K_mac, MAC_structure)
      *     ]
diff --git a/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl b/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl
index 1ec3bf0..438505e 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/ProtectedData.aidl
@@ -80,7 +80,7 @@
      *         bstr .cbor {                    // Protected params
      *             1 : -8,                     // Algorithm : EdDSA
      *         },
-     *         bstr .size 0,                   // Unprotected params
+     *         { },                            // Unprotected params
      *         bstr .size 32,                  // MAC key
      *         bstr PureEd25519(DK_priv, .cbor SignedMac_structure)
      *     ]
@@ -127,7 +127,7 @@
      *         protected: bstr .cbor {
      *             1 : -8,                    // Algorithm : EdDSA
      *         },
-     *         unprotected: bstr .size 0,
+     *         unprotected: { },
      *         payload: bstr .cbor BccPayload,
      *         // First entry in the chain is signed by DK_pub, the others are each signed by their
      *         // immediate predecessor.  See RFC 8032 for signature representation.
diff --git a/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp b/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp
index 2373b26..749f0bc 100644
--- a/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp
+++ b/security/keymint/aidl/default/RemotelyProvisionedComponent.cpp
@@ -156,7 +156,7 @@
         }
 
         auto protectedParms = macedKeyItem->asArray()->get(kCoseMac0ProtectedParams)->asBstr();
-        auto unprotectedParms = macedKeyItem->asArray()->get(kCoseMac0UnprotectedParams)->asBstr();
+        auto unprotectedParms = macedKeyItem->asArray()->get(kCoseMac0UnprotectedParams)->asMap();
         auto payload = macedKeyItem->asArray()->get(kCoseMac0Payload)->asBstr();
         auto tag = macedKeyItem->asArray()->get(kCoseMac0Tag)->asBstr();
         if (!protectedParms || !unprotectedParms || !payload || !tag) {
diff --git a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
index db53a8f..50e6cce 100644
--- a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
+++ b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
@@ -97,9 +97,9 @@
     ASSERT_NE(protParms, nullptr);
     ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n  1 : 5,\n}");
 
-    auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asBstr();
+    auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asMap();
     ASSERT_NE(unprotParms, nullptr);
-    ASSERT_EQ(unprotParms->value().size(), 0);
+    ASSERT_EQ(unprotParms->size(), 0);
 
     auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr();
     ASSERT_NE(payload, nullptr);
@@ -150,9 +150,9 @@
     ASSERT_NE(protParms, nullptr);
     ASSERT_EQ(cppbor::prettyPrint(protParms->value()), "{\n  1 : 5,\n}");
 
-    auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asBstr();
+    auto unprotParms = coseMac0->asArray()->get(kCoseMac0UnprotectedParams)->asMap();
     ASSERT_NE(unprotParms, nullptr);
-    ASSERT_EQ(unprotParms->value().size(), 0);
+    ASSERT_EQ(unprotParms->size(), 0);
 
     auto payload = coseMac0->asArray()->get(kCoseMac0Payload)->asBstr();
     ASSERT_NE(payload, nullptr);
@@ -279,7 +279,7 @@
                                          .add(ALGORITHM, HMAC_256)
                                          .canonicalize()
                                          .encode())
-                            .add(cppbor::Bstr())             // unprotected
+                            .add(cppbor::Map())              // unprotected
                             .add(cppbor::Array().encode())   // payload (keysToSign)
                             .add(std::move(keysToSignMac));  // tag
 
@@ -364,7 +364,7 @@
                                          .add(ALGORITHM, HMAC_256)
                                          .canonicalize()
                                          .encode())
-                            .add(cppbor::Bstr())             // unprotected
+                            .add(cppbor::Map())              // unprotected
                             .add(cborKeysToSign_.encode())   // payload
                             .add(std::move(keysToSignMac));  // tag
 
diff --git a/security/keymint/support/cppcose.cpp b/security/keymint/support/cppcose.cpp
index c626ade..bafb2b6 100644
--- a/security/keymint/support/cppcose.cpp
+++ b/security/keymint/support/cppcose.cpp
@@ -85,7 +85,7 @@
 
     return cppbor::Array()
             .add(cppbor::Map().add(ALGORITHM, HMAC_256).canonicalize().encode())
-            .add(cppbor::Bstr() /* unprotected */)
+            .add(cppbor::Map() /* unprotected */)
             .add(payload)
             .add(tag.moveValue());
 }
@@ -97,7 +97,7 @@
     }
 
     auto protectedParms = mac->get(kCoseMac0ProtectedParams)->asBstr();
-    auto unprotectedParms = mac->get(kCoseMac0UnprotectedParams)->asBstr();
+    auto unprotectedParms = mac->get(kCoseMac0UnprotectedParams)->asMap();
     auto payload = mac->get(kCoseMac0Payload)->asBstr();
     auto tag = mac->get(kCoseMac0Tag)->asBstr();
     if (!protectedParms || !unprotectedParms || !payload || !tag) {
@@ -115,7 +115,7 @@
     }
 
     auto protectedParms = mac->get(kCoseMac0ProtectedParams)->asBstr();
-    auto unprotectedParms = mac->get(kCoseMac0UnprotectedParams)->asBstr();
+    auto unprotectedParms = mac->get(kCoseMac0UnprotectedParams)->asMap();
     auto payload = mac->get(kCoseMac0Payload)->asBstr();
     auto tag = mac->get(kCoseMac0Tag)->asBstr();
     if (!protectedParms || !unprotectedParms || !payload || !tag) {
@@ -168,7 +168,7 @@
 
     return cppbor::Array()
             .add(protParms)
-            .add(bytevec{} /* unprotected parameters */)
+            .add(cppbor::Map() /* unprotected parameters */)
             .add(payload)
             .add(*signature);
 }
@@ -185,7 +185,7 @@
     }
 
     const cppbor::Bstr* protectedParams = coseSign1->get(kCoseSign1ProtectedParams)->asBstr();
-    const cppbor::Bstr* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asBstr();
+    const cppbor::Map* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asMap();
     const cppbor::Bstr* payload = coseSign1->get(kCoseSign1Payload)->asBstr();
     const cppbor::Bstr* signature = coseSign1->get(kCoseSign1Signature)->asBstr();
 
diff --git a/security/keymint/support/remote_prov_utils.cpp b/security/keymint/support/remote_prov_utils.cpp
index 111cb30..3e4f3f7 100644
--- a/security/keymint/support/remote_prov_utils.cpp
+++ b/security/keymint/support/remote_prov_utils.cpp
@@ -83,7 +83,7 @@
     }
 
     const cppbor::Bstr* protectedParams = coseSign1->get(kCoseSign1ProtectedParams)->asBstr();
-    const cppbor::Bstr* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asBstr();
+    const cppbor::Map* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asMap();
     const cppbor::Bstr* payload = coseSign1->get(kCoseSign1Payload)->asBstr();
     const cppbor::Bstr* signature = coseSign1->get(kCoseSign1Signature)->asBstr();
 
diff --git a/soundtrigger/2.0/default/OWNERS b/soundtrigger/2.0/default/OWNERS
index 6fdc97c..ed739cf 100644
--- a/soundtrigger/2.0/default/OWNERS
+++ b/soundtrigger/2.0/default/OWNERS
@@ -1,3 +1,3 @@
 elaurent@google.com
-krocard@google.com
 mnaganov@google.com
+ytai@google.com