Merge changes from topic "ag-wire" into main

* changes:
  AuthGraph: add per-role VTS tests
  AuthGraph: add fuzzer
  AuthGraph: move code into library
  AuthGraph: reduce dependency on authgraph_core
diff --git a/audio/aidl/Android.bp b/audio/aidl/Android.bp
index 5a009fe..0ab990a 100644
--- a/audio/aidl/Android.bp
+++ b/audio/aidl/Android.bp
@@ -34,6 +34,7 @@
     name: "android.hardware.audio.common",
     defaults: [
         "android.hardware.audio_defaults",
+        "latest_android_media_audio_common_types_import_interface",
     ],
     srcs: [
         "android/hardware/audio/common/AudioOffloadMetadata.aidl",
@@ -42,10 +43,7 @@
         "android/hardware/audio/common/SinkMetadata.aidl",
         "android/hardware/audio/common/SourceMetadata.aidl",
     ],
-    frozen: true,
-    imports: [
-        "android.media.audio.common.types-V2",
-    ],
+    frozen: false,
     backend: {
         cpp: {
             enabled: true,
@@ -83,7 +81,7 @@
 }
 
 // Note: This should always be one version ahead of the last frozen version
-latest_android_hardware_audio_common = "android.hardware.audio.common-V2"
+latest_android_hardware_audio_common = "android.hardware.audio.common-V3"
 
 // Modules that depend on android.hardware.audio.common directly can include
 // the following cc_defaults to avoid explicitly managing dependency versions
@@ -109,10 +107,21 @@
     ],
 }
 
+aidl_interface_defaults {
+    name: "latest_android_hardware_audio_common_import_interface",
+    imports: [
+        latest_android_hardware_audio_common,
+    ],
+}
+
 aidl_interface {
     name: "android.hardware.audio.core",
     defaults: [
         "android.hardware.audio_defaults",
+        "latest_android_hardware_audio_common_import_interface",
+        "latest_android_hardware_audio_core_sounddose_import_interface",
+        "latest_android_hardware_audio_effect_import_interface",
+        "latest_android_media_audio_common_types_import_interface",
     ],
     srcs: [
         "android/hardware/audio/core/AudioPatch.aidl",
@@ -137,10 +146,6 @@
     imports: [
         "android.hardware.common-V2",
         "android.hardware.common.fmq-V1",
-        "android.hardware.audio.common-V2",
-        "android.hardware.audio.core.sounddose-V1",
-        "android.hardware.audio.effect-V1",
-        "android.media.audio.common.types-V2",
     ],
     backend: {
         // The C++ backend is disabled transitively due to use of FMQ.
@@ -167,11 +172,11 @@
         // IMPORTANT: Update latest_android_hardware_audio_core every time you
         // add the latest frozen version to versions_with_info
     ],
-    frozen: true,
+    frozen: false,
 }
 
 // Note: This should always be one version ahead of the last frozen version
-latest_android_hardware_audio_core = "android.hardware.audio.core-V1"
+latest_android_hardware_audio_core = "android.hardware.audio.core-V2"
 
 // Modules that depend on android.hardware.audio.core directly can include
 // the following cc_defaults to avoid explicitly managing dependency versions
@@ -190,18 +195,23 @@
     ],
 }
 
+aidl_interface_defaults {
+    name: "latest_android_hardware_audio_core_import_interface",
+    imports: [
+        latest_android_hardware_audio_core,
+    ],
+}
+
 // Used for the standalone sounddose HAL
 aidl_interface {
     name: "android.hardware.audio.core.sounddose",
     defaults: [
         "android.hardware.audio_defaults",
+        "latest_android_media_audio_common_types_import_interface",
     ],
     srcs: [
         "android/hardware/audio/core/sounddose/ISoundDose.aidl",
     ],
-    imports: [
-        "android.media.audio.common.types-V2",
-    ],
     backend: {
         // The C++ backend is disabled transitively due to use of FMQ by the core HAL.
         cpp: {
@@ -220,11 +230,11 @@
         // IMPORTANT: Update latest_android_hardware_audio_core_sounddose every time you
         // add the latest frozen version to versions_with_info
     ],
-    frozen: true,
+    frozen: false,
 }
 
 // Note: This should always be one version ahead of the last frozen version
-latest_android_hardware_audio_core_sounddose = "android.hardware.audio.core.sounddose-V1"
+latest_android_hardware_audio_core_sounddose = "android.hardware.audio.core.sounddose-V2"
 
 // Modules that depend on android.hardware.audio.core.sounddose directly can include
 // the following cc_defaults to avoid explicitly managing dependency versions
@@ -237,16 +247,32 @@
 }
 
 cc_defaults {
+    name: "latest_android_hardware_audio_core_sounddose_ndk_export_shared_lib_header",
+    export_shared_lib_headers: [
+        latest_android_hardware_audio_core_sounddose + "-ndk",
+    ],
+}
+
+cc_defaults {
     name: "latest_android_hardware_audio_core_sounddose_ndk_static",
     static_libs: [
         latest_android_hardware_audio_core_sounddose + "-ndk",
     ],
 }
 
+aidl_interface_defaults {
+    name: "latest_android_hardware_audio_core_sounddose_import_interface",
+    imports: [
+        latest_android_hardware_audio_core_sounddose,
+    ],
+}
+
 aidl_interface {
     name: "android.hardware.audio.effect",
     defaults: [
         "android.hardware.audio_defaults",
+        "latest_android_hardware_audio_common_import_interface",
+        "latest_android_media_audio_common_types_import_interface",
     ],
     srcs: [
         "android/hardware/audio/effect/AcousticEchoCanceler.aidl",
@@ -280,8 +306,6 @@
     imports: [
         "android.hardware.common-V2",
         "android.hardware.common.fmq-V1",
-        "android.hardware.audio.common-V2",
-        "android.media.audio.common.types-V2",
     ],
     backend: {
         // The C++ backend is disabled transitively due to use of FMQ.
@@ -303,11 +327,11 @@
             ],
         },
     ],
-    frozen: true,
+    frozen: false,
 
 }
 
-latest_android_hardware_audio_effect = "android.hardware.audio.effect-V1"
+latest_android_hardware_audio_effect = "android.hardware.audio.effect-V2"
 
 cc_defaults {
     name: "latest_android_hardware_audio_effect_ndk_shared",
@@ -322,3 +346,10 @@
         latest_android_hardware_audio_effect + "-ndk",
     ],
 }
+
+aidl_interface_defaults {
+    name: "latest_android_hardware_audio_effect_import_interface",
+    imports: [
+        latest_android_hardware_audio_effect,
+    ],
+}
diff --git a/audio/aidl/common/Android.bp b/audio/aidl/common/Android.bp
index 4c6a74e..85ece3b 100644
--- a/audio/aidl/common/Android.bp
+++ b/audio/aidl/common/Android.bp
@@ -45,8 +45,8 @@
     name: "libaudioaidlranges",
     host_supported: true,
     vendor_available: true,
-    static_libs: [
-        "android.hardware.audio.effect-V1-ndk",
+    defaults: [
+        "latest_android_hardware_audio_effect_ndk_shared",
     ],
     export_include_dirs: ["include"],
     header_libs: ["libaudioaidl_headers"],
@@ -59,8 +59,10 @@
     name: "libaudioaidlcommon_test",
     host_supported: true,
     vendor_available: true,
+    defaults: [
+        "latest_android_media_audio_common_types_ndk_static",
+    ],
     static_libs: [
-        "android.media.audio.common.types-V1-ndk",
         "libaudioaidlcommon",
     ],
     shared_libs: [
diff --git a/audio/aidl/common/include/Utils.h b/audio/aidl/common/include/Utils.h
index 3b08de7..59ca92a 100644
--- a/audio/aidl/common/include/Utils.h
+++ b/audio/aidl/common/include/Utils.h
@@ -174,4 +174,12 @@
     return result;
 }
 
+constexpr int32_t frameCountFromDurationUs(long durationUs, int32_t sampleRateHz) {
+    return (durationUs * sampleRateHz) / 1000000LL;
+}
+
+constexpr int32_t frameCountFromDurationMs(int32_t durationMs, int32_t sampleRateHz) {
+    return frameCountFromDurationUs(durationMs * 1000, sampleRateHz);
+}
+
 }  // namespace aidl::android::hardware::audio::common
diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp
index 9aa86b5..7b96293 100644
--- a/audio/aidl/default/Android.bp
+++ b/audio/aidl/default/Android.bp
@@ -40,35 +40,13 @@
 }
 
 cc_library {
-    name: "libaudioservicesounddoseimpl",
-    vendor: true,
-    defaults: [
-        "latest_android_media_audio_common_types_ndk_shared",
-        "latest_android_hardware_audio_core_sounddose_ndk_shared",
-        "latest_android_hardware_audio_sounddose_ndk_shared",
-    ],
-    export_include_dirs: ["include"],
-    srcs: [
-        "SoundDose.cpp",
-    ],
-    shared_libs: [
-        "libbase",
-        "libbinder_ndk",
-        "libcutils",
-        "libutils",
-    ],
-    visibility: [
-        "//hardware/interfaces/audio/aidl/sounddose/default",
-    ],
-}
-
-cc_library {
     name: "libaudioserviceexampleimpl",
     defaults: [
         "aidlaudioservice_defaults",
         "latest_android_media_audio_common_types_ndk_shared",
         "latest_android_hardware_audio_core_ndk_shared",
         "latest_android_hardware_audio_core_sounddose_ndk_shared",
+        "latest_android_hardware_bluetooth_audio_ndk_shared",
     ],
     export_include_dirs: ["include"],
     srcs: [
@@ -116,7 +94,6 @@
         "audio_policy_engine_configuration_aidl_default",
     ],
     shared_libs: [
-        "android.hardware.bluetooth.audio-V3-ndk",
         "libaudio_aidl_conversion_common_ndk",
         "libbluetooth_audio_session_aidl",
         "libmedia_helper",
@@ -141,15 +118,15 @@
     vintf_fragments: ["android.hardware.audio.service-aidl.xml"],
     defaults: [
         "aidlaudioservice_defaults",
-        "latest_android_media_audio_common_types_ndk_shared",
         "latest_android_hardware_audio_core_sounddose_ndk_shared",
         "latest_android_hardware_audio_core_ndk_shared",
+        "latest_android_hardware_bluetooth_audio_ndk_shared",
+        "latest_android_media_audio_common_types_ndk_shared",
     ],
     static_libs: [
         "libaudioserviceexampleimpl",
     ],
     shared_libs: [
-        "android.hardware.bluetooth.audio-V3-ndk",
         "libaudio_aidl_conversion_common_ndk",
         "libbluetooth_audio_session_aidl",
         "libmedia_helper",
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index 1045009..89e3d99 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -18,7 +18,6 @@
 #include <set>
 
 #define LOG_TAG "AHAL_Module"
-#include <Utils.h>
 #include <aidl/android/media/audio/common/AudioInputFlags.h>
 #include <aidl/android/media/audio/common/AudioOutputFlags.h>
 #include <android-base/logging.h>
@@ -35,6 +34,7 @@
 #include "core-impl/SoundDose.h"
 #include "core-impl/utils.h"
 
+using aidl::android::hardware::audio::common::frameCountFromDurationMs;
 using aidl::android::hardware::audio::common::getFrameSizeInBytes;
 using aidl::android::hardware::audio::common::isBitPositionFlagSet;
 using aidl::android::hardware::audio::common::isValidAudioMode;
@@ -202,15 +202,17 @@
         LOG(ERROR) << __func__ << ": non-positive buffer size " << in_bufferSizeFrames;
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
     }
-    if (in_bufferSizeFrames < kMinimumStreamBufferSizeFrames) {
-        LOG(ERROR) << __func__ << ": insufficient buffer size " << in_bufferSizeFrames
-                   << ", must be at least " << kMinimumStreamBufferSizeFrames;
-        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
-    }
     auto& configs = getConfig().portConfigs;
     auto portConfigIt = findById<AudioPortConfig>(configs, in_portConfigId);
     // Since this is a private method, it is assumed that
     // validity of the portConfigId has already been checked.
+    const int32_t minimumStreamBufferSizeFrames = calculateBufferSizeFrames(
+            getNominalLatencyMs(*portConfigIt), portConfigIt->sampleRate.value().value);
+    if (in_bufferSizeFrames < minimumStreamBufferSizeFrames) {
+        LOG(ERROR) << __func__ << ": insufficient buffer size " << in_bufferSizeFrames
+                   << ", must be at least " << minimumStreamBufferSizeFrames;
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
     const size_t frameSize =
             getFrameSizeInBytes(portConfigIt->format.value(), portConfigIt->channelMask.value());
     if (frameSize == 0) {
@@ -238,11 +240,12 @@
         StreamContext temp(
                 std::make_unique<StreamContext::CommandMQ>(1, true /*configureEventFlagWord*/),
                 std::make_unique<StreamContext::ReplyMQ>(1, true /*configureEventFlagWord*/),
-                portConfigIt->portId, portConfigIt->format.value(),
-                portConfigIt->channelMask.value(), portConfigIt->sampleRate.value().value, flags,
+                portConfigIt->format.value(), portConfigIt->channelMask.value(),
+                portConfigIt->sampleRate.value().value, flags, getNominalLatencyMs(*portConfigIt),
                 portConfigIt->ext.get<AudioPortExt::mix>().handle,
                 std::make_unique<StreamContext::DataMQ>(frameSize * in_bufferSizeFrames),
-                asyncCallback, outEventCallback, params);
+                asyncCallback, outEventCallback,
+                std::weak_ptr<sounddose::StreamDataProcessorInterface>{}, params);
         if (temp.isValid()) {
             *out_context = std::move(temp);
         } else {
@@ -358,6 +361,12 @@
     return internal::getConfiguration(getType());
 }
 
+int32_t Module::getNominalLatencyMs(const AudioPortConfig&) {
+    // Arbitrary value. Implementations must override this method to provide their actual latency.
+    static constexpr int32_t kLatencyMs = 5;
+    return kLatencyMs;
+}
+
 std::vector<AudioRoute*> Module::getAudioRoutesForAudioPortImpl(int32_t portId) {
     std::vector<AudioRoute*> result;
     auto& routes = getConfig().routes;
@@ -608,32 +617,30 @@
 
     std::vector<AudioRoute*> routesToMixPorts = getAudioRoutesForAudioPortImpl(templateId);
     std::set<int32_t> routableMixPortIds = getRoutableAudioPortIds(templateId, &routesToMixPorts);
-    if (hasDynamicProfilesOnly(connectedPort.profiles)) {
-        if (!mDebug.simulateDeviceConnections) {
-            RETURN_STATUS_IF_ERROR(populateConnectedDevicePort(&connectedPort));
-        } else {
-            auto& connectedProfiles = getConfig().connectedProfiles;
-            if (auto connectedProfilesIt = connectedProfiles.find(templateId);
-                connectedProfilesIt != connectedProfiles.end()) {
-                connectedPort.profiles = connectedProfilesIt->second;
-            }
+    if (!mDebug.simulateDeviceConnections) {
+        // Even if the device port has static profiles, the HAL module might need to update
+        // them, or abort the connection process.
+        RETURN_STATUS_IF_ERROR(populateConnectedDevicePort(&connectedPort));
+    } else if (hasDynamicProfilesOnly(connectedPort.profiles)) {
+        auto& connectedProfiles = getConfig().connectedProfiles;
+        if (auto connectedProfilesIt = connectedProfiles.find(templateId);
+            connectedProfilesIt != connectedProfiles.end()) {
+            connectedPort.profiles = connectedProfilesIt->second;
         }
-        if (hasDynamicProfilesOnly(connectedPort.profiles)) {
-            // Possible case 2. Check if all routable mix ports have static profiles.
-            if (auto dynamicMixPortIt = std::find_if(ports.begin(), ports.end(),
-                                                     [&routableMixPortIds](const auto& p) {
-                                                         return routableMixPortIds.count(p.id) >
-                                                                        0 &&
-                                                                hasDynamicProfilesOnly(p.profiles);
-                                                     });
-                dynamicMixPortIt != ports.end()) {
-                LOG(ERROR) << __func__
-                           << ": connected port only has dynamic profiles after connecting "
-                           << "external device " << connectedPort.toString() << ", and there exist "
-                           << "a routable mix port with dynamic profiles: "
-                           << dynamicMixPortIt->toString();
-                return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
-            }
+    }
+    if (hasDynamicProfilesOnly(connectedPort.profiles)) {
+        // Possible case 2. Check if all routable mix ports have static profiles.
+        if (auto dynamicMixPortIt = std::find_if(ports.begin(), ports.end(),
+                                                 [&routableMixPortIds](const auto& p) {
+                                                     return routableMixPortIds.count(p.id) > 0 &&
+                                                            hasDynamicProfilesOnly(p.profiles);
+                                                 });
+            dynamicMixPortIt != ports.end()) {
+            LOG(ERROR) << __func__ << ": connected port only has dynamic profiles after connecting "
+                       << "external device " << connectedPort.toString() << ", and there exist "
+                       << "a routable mix port with dynamic profiles: "
+                       << dynamicMixPortIt->toString();
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
         }
     }
 
@@ -964,11 +971,21 @@
             return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
         }
     }
+    // Find the highest sample rate among mix port configs.
+    std::map<int32_t, AudioPortConfig*> sampleRates;
+    std::vector<AudioPortConfig*>& mixPortConfigs =
+            sources[0]->ext.getTag() == AudioPortExt::mix ? sources : sinks;
+    for (auto mix : mixPortConfigs) {
+        sampleRates.emplace(mix->sampleRate.value().value, mix);
+    }
     *_aidl_return = in_requested;
-    _aidl_return->minimumStreamBufferSizeFrames = kMinimumStreamBufferSizeFrames;
+    auto maxSampleRateIt = std::max_element(sampleRates.begin(), sampleRates.end());
+    const int32_t latencyMs = getNominalLatencyMs(*(maxSampleRateIt->second));
+    _aidl_return->minimumStreamBufferSizeFrames =
+            calculateBufferSizeFrames(latencyMs, maxSampleRateIt->first);
     _aidl_return->latenciesMs.clear();
     _aidl_return->latenciesMs.insert(_aidl_return->latenciesMs.end(),
-                                     _aidl_return->sinkPortConfigIds.size(), kLatencyMs);
+                                     _aidl_return->sinkPortConfigIds.size(), latencyMs);
     AudioPatch oldPatch{};
     if (existing == patches.end()) {
         _aidl_return->id = getConfig().nextPatchId++;
@@ -1210,7 +1227,7 @@
         // Reset master mute if it failed.
         onMasterMuteChanged(mMasterMute);
     }
-    return std::move(result);
+    return result;
 }
 
 ndk::ScopedAStatus Module::getMasterVolume(float* _aidl_return) {
@@ -1232,7 +1249,7 @@
                        << "), error=" << result;
             onMasterVolumeChanged(mMasterVolume);
         }
-        return std::move(result);
+        return result;
     }
     LOG(ERROR) << __func__ << ": invalid master volume value: " << in_volume;
     return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
@@ -1553,11 +1570,6 @@
     return result;
 }
 
-Module::BtProfileHandles Module::getBtProfileManagerHandles() {
-    return std::make_tuple(std::weak_ptr<IBluetooth>(), std::weak_ptr<IBluetoothA2dp>(),
-                           std::weak_ptr<IBluetoothLe>());
-}
-
 ndk::ScopedAStatus Module::bluetoothParametersUpdated() {
     return mStreams.bluetoothParametersUpdated();
 }
diff --git a/audio/aidl/default/ModulePrimary.cpp b/audio/aidl/default/ModulePrimary.cpp
index 9919c7f..3da6d48 100644
--- a/audio/aidl/default/ModulePrimary.cpp
+++ b/audio/aidl/default/ModulePrimary.cpp
@@ -58,4 +58,12 @@
                                                   offloadInfo);
 }
 
+int32_t ModulePrimary::getNominalLatencyMs(const AudioPortConfig&) {
+    // 85 ms is chosen considering 4096 frames @ 48 kHz. This is the value which allows
+    // the virtual Android device implementation to pass CTS. Hardware implementations
+    // should have significantly lower latency.
+    static constexpr int32_t kLatencyMs = 85;
+    return kLatencyMs;
+}
+
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp
index f7298c0..e8c1693 100644
--- a/audio/aidl/default/Stream.cpp
+++ b/audio/aidl/default/Stream.cpp
@@ -90,6 +90,14 @@
     return true;
 }
 
+void StreamContext::startStreamDataProcessor() {
+    auto streamDataProcessor = mStreamDataProcessor.lock();
+    if (streamDataProcessor != nullptr) {
+        streamDataProcessor->startDataProcessor(mSampleRate, getChannelCount(mChannelLayout),
+                                                mFormat);
+    }
+}
+
 void StreamContext::reset() {
     mCommandMQ.reset();
     mReplyMQ.reset();
@@ -130,7 +138,7 @@
     reply->status = STATUS_OK;
     if (isConnected) {
         reply->observable.frames = mContext->getFrameCount();
-        reply->observable.timeNs = ::android::elapsedRealtimeNano();
+        reply->observable.timeNs = ::android::uptimeNanos();
         if (auto status = mDriver->refinePosition(&reply->observable); status == ::android::OK) {
             return;
         }
@@ -307,7 +315,7 @@
     const size_t frameSize = mContext->getFrameSize();
     size_t actualFrameCount = 0;
     bool fatal = false;
-    int32_t latency = Module::kLatencyMs;
+    int32_t latency = mContext->getNominalLatencyMs();
     if (isConnected) {
         if (::android::status_t status = mDriver->transfer(mDataBuffer.get(), byteCount / frameSize,
                                                            &actualFrameCount, &latency);
@@ -573,7 +581,7 @@
     const size_t readByteCount = dataMQ->availableToRead();
     const size_t frameSize = mContext->getFrameSize();
     bool fatal = false;
-    int32_t latency = Module::kLatencyMs;
+    int32_t latency = mContext->getNominalLatencyMs();
     if (bool success = readByteCount > 0 ? dataMQ->read(&mDataBuffer[0], readByteCount) : true) {
         const bool isConnected = mIsConnected;
         LOG(VERBOSE) << __func__ << ": reading of " << readByteCount << " bytes from data MQ"
@@ -593,6 +601,10 @@
                 fatal = true;
                 LOG(ERROR) << __func__ << ": write failed: " << status;
             }
+            auto streamDataProcessor = mContext->getStreamDataProcessor().lock();
+            if (streamDataProcessor != nullptr) {
+                streamDataProcessor->process(mDataBuffer.get(), actualFrameCount * frameSize);
+            }
         } else {
             if (mContext->getAsyncCallback() == nullptr) {
                 usleep(3000);  // Simulate blocking transfer delay.
@@ -836,7 +848,7 @@
 }
 
 StreamInHwGainHelper::StreamInHwGainHelper(const StreamContext* context)
-    : mChannelCount(getChannelCount(context->getChannelLayout())) {}
+    : mChannelCount(getChannelCount(context->getChannelLayout())), mHwGains(mChannelCount, 0.0f) {}
 
 ndk::ScopedAStatus StreamInHwGainHelper::getHwGainImpl(std::vector<float>* _aidl_return) {
     *_aidl_return = mHwGains;
@@ -967,7 +979,8 @@
 }
 
 StreamOutHwVolumeHelper::StreamOutHwVolumeHelper(const StreamContext* context)
-    : mChannelCount(getChannelCount(context->getChannelLayout())) {}
+    : mChannelCount(getChannelCount(context->getChannelLayout())),
+      mHwVolumes(mChannelCount, 0.0f) {}
 
 ndk::ScopedAStatus StreamOutHwVolumeHelper::getHwVolumeImpl(std::vector<float>* _aidl_return) {
     *_aidl_return = mHwVolumes;
diff --git a/audio/aidl/default/alsa/Mixer.cpp b/audio/aidl/default/alsa/Mixer.cpp
index 126c033..e72502b 100644
--- a/audio/aidl/default/alsa/Mixer.cpp
+++ b/audio/aidl/default/alsa/Mixer.cpp
@@ -20,9 +20,24 @@
 #define LOG_TAG "AHAL_AlsaMixer"
 #include <android-base/logging.h>
 #include <android/binder_status.h>
+#include <error/expected_utils.h>
 
 #include "Mixer.h"
 
+namespace ndk {
+
+// This enables use of 'error/expected_utils' for ScopedAStatus.
+
+inline bool errorIsOk(const ScopedAStatus& s) {
+    return s.isOk();
+}
+
+inline std::string errorToString(const ScopedAStatus& s) {
+    return s.getDescription();
+}
+
+}  // namespace ndk
+
 namespace aidl::android::hardware::audio::core::alsa {
 
 // static
@@ -93,6 +108,36 @@
     }
 }
 
+ndk::ScopedAStatus Mixer::getMasterMute(bool* muted) {
+    return getMixerControlMute(MASTER_SWITCH, muted);
+}
+
+ndk::ScopedAStatus Mixer::getMasterVolume(float* volume) {
+    return getMixerControlVolume(MASTER_VOLUME, volume);
+}
+
+ndk::ScopedAStatus Mixer::getMicGain(float* gain) {
+    return getMixerControlVolume(MIC_GAIN, gain);
+}
+
+ndk::ScopedAStatus Mixer::getMicMute(bool* muted) {
+    return getMixerControlMute(MIC_SWITCH, muted);
+}
+
+ndk::ScopedAStatus Mixer::getVolumes(std::vector<float>* volumes) {
+    struct mixer_ctl* mctl;
+    RETURN_STATUS_IF_ERROR(findControl(Mixer::HW_VOLUME, &mctl));
+    std::vector<int> percents;
+    std::lock_guard l(mMixerAccess);
+    if (int err = getMixerControlPercent(mctl, &percents); err != 0) {
+        LOG(ERROR) << __func__ << ": failed to get volume, err=" << err;
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    std::transform(percents.begin(), percents.end(), std::back_inserter(*volumes),
+                   [](int percent) -> float { return std::clamp(percent / 100.0f, 0.0f, 1.0f); });
+    return ndk::ScopedAStatus::ok();
+}
+
 ndk::ScopedAStatus Mixer::setMasterMute(bool muted) {
     return setMixerControlMute(MASTER_SWITCH, muted);
 }
@@ -110,35 +155,70 @@
 }
 
 ndk::ScopedAStatus Mixer::setVolumes(const std::vector<float>& volumes) {
-    if (!isValid()) {
-        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
-    }
-    auto it = mMixerControls.find(Mixer::HW_VOLUME);
-    if (it == mMixerControls.end()) {
-        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
-    }
+    struct mixer_ctl* mctl;
+    RETURN_STATUS_IF_ERROR(findControl(Mixer::HW_VOLUME, &mctl));
     std::vector<int> percents;
     std::transform(
             volumes.begin(), volumes.end(), std::back_inserter(percents),
             [](float volume) -> int { return std::floor(std::clamp(volume, 0.0f, 1.0f) * 100); });
     std::lock_guard l(mMixerAccess);
-    if (int err = setMixerControlPercent(it->second, percents); err != 0) {
+    if (int err = setMixerControlPercent(mctl, percents); err != 0) {
         LOG(ERROR) << __func__ << ": failed to set volume, err=" << err;
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     }
     return ndk::ScopedAStatus::ok();
 }
 
-ndk::ScopedAStatus Mixer::setMixerControlMute(Mixer::Control ctl, bool muted) {
+ndk::ScopedAStatus Mixer::findControl(Control ctl, struct mixer_ctl** result) {
     if (!isValid()) {
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     }
-    auto it = mMixerControls.find(ctl);
-    if (it == mMixerControls.end()) {
-        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    if (auto it = mMixerControls.find(ctl); it != mMixerControls.end()) {
+        *result = it->second;
+        return ndk::ScopedAStatus::ok();
     }
+    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus Mixer::getMixerControlMute(Control ctl, bool* muted) {
+    struct mixer_ctl* mctl;
+    RETURN_STATUS_IF_ERROR(findControl(ctl, &mctl));
     std::lock_guard l(mMixerAccess);
-    if (int err = setMixerControlValue(it->second, muted ? 0 : 1); err != 0) {
+    std::vector<int> mutedValues;
+    if (int err = getMixerControlValues(mctl, &mutedValues); err != 0) {
+        LOG(ERROR) << __func__ << ": failed to get " << ctl << ", err=" << err;
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    if (mutedValues.empty()) {
+        LOG(ERROR) << __func__ << ": got no values for " << ctl;
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    *muted = mutedValues[0] != 0;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Mixer::getMixerControlVolume(Control ctl, float* volume) {
+    struct mixer_ctl* mctl;
+    RETURN_STATUS_IF_ERROR(findControl(ctl, &mctl));
+    std::lock_guard l(mMixerAccess);
+    std::vector<int> percents;
+    if (int err = getMixerControlPercent(mctl, &percents); err != 0) {
+        LOG(ERROR) << __func__ << ": failed to get " << ctl << ", err=" << err;
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    if (percents.empty()) {
+        LOG(ERROR) << __func__ << ": got no values for " << ctl;
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    *volume = std::clamp(percents[0] / 100.0f, 0.0f, 1.0f);
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Mixer::setMixerControlMute(Control ctl, bool muted) {
+    struct mixer_ctl* mctl;
+    RETURN_STATUS_IF_ERROR(findControl(ctl, &mctl));
+    std::lock_guard l(mMixerAccess);
+    if (int err = setMixerControlValue(mctl, muted ? 0 : 1); err != 0) {
         LOG(ERROR) << __func__ << ": failed to set " << ctl << " to " << muted << ", err=" << err;
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     }
@@ -146,54 +226,72 @@
 }
 
 ndk::ScopedAStatus Mixer::setMixerControlVolume(Control ctl, float volume) {
-    if (!isValid()) {
-        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
-    }
-    auto it = mMixerControls.find(ctl);
-    if (it == mMixerControls.end()) {
-        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
-    }
+    struct mixer_ctl* mctl;
+    RETURN_STATUS_IF_ERROR(findControl(ctl, &mctl));
     volume = std::clamp(volume, 0.0f, 1.0f);
     std::lock_guard l(mMixerAccess);
-    if (int err = setMixerControlPercent(it->second, std::floor(volume * 100)); err != 0) {
+    if (int err = setMixerControlPercent(mctl, std::floor(volume * 100)); err != 0) {
         LOG(ERROR) << __func__ << ": failed to set " << ctl << " to " << volume << ", err=" << err;
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     }
     return ndk::ScopedAStatus::ok();
 }
 
+int Mixer::getMixerControlPercent(struct mixer_ctl* ctl, std::vector<int>* percents) {
+    const unsigned int n = mixer_ctl_get_num_values(ctl);
+    percents->resize(n);
+    for (unsigned int id = 0; id < n; id++) {
+        if (int valueOrError = mixer_ctl_get_percent(ctl, id); valueOrError >= 0) {
+            (*percents)[id] = valueOrError;
+        } else {
+            return valueOrError;
+        }
+    }
+    return 0;
+}
+
+int Mixer::getMixerControlValues(struct mixer_ctl* ctl, std::vector<int>* values) {
+    const unsigned int n = mixer_ctl_get_num_values(ctl);
+    values->resize(n);
+    for (unsigned int id = 0; id < n; id++) {
+        if (int valueOrError = mixer_ctl_get_value(ctl, id); valueOrError >= 0) {
+            (*values)[id] = valueOrError;
+        } else {
+            return valueOrError;
+        }
+    }
+    return 0;
+}
+
 int Mixer::setMixerControlPercent(struct mixer_ctl* ctl, int percent) {
-    int ret = 0;
     const unsigned int n = mixer_ctl_get_num_values(ctl);
     for (unsigned int id = 0; id < n; id++) {
         if (int error = mixer_ctl_set_percent(ctl, id, percent); error != 0) {
-            ret = error;
+            return error;
         }
     }
-    return ret;
+    return 0;
 }
 
 int Mixer::setMixerControlPercent(struct mixer_ctl* ctl, const std::vector<int>& percents) {
-    int ret = 0;
     const unsigned int n = mixer_ctl_get_num_values(ctl);
     for (unsigned int id = 0; id < n; id++) {
         if (int error = mixer_ctl_set_percent(ctl, id, id < percents.size() ? percents[id] : 0);
             error != 0) {
-            ret = error;
+            return error;
         }
     }
-    return ret;
+    return 0;
 }
 
 int Mixer::setMixerControlValue(struct mixer_ctl* ctl, int value) {
-    int ret = 0;
     const unsigned int n = mixer_ctl_get_num_values(ctl);
     for (unsigned int id = 0; id < n; id++) {
         if (int error = mixer_ctl_set_value(ctl, id, value); error != 0) {
-            ret = error;
+            return error;
         }
     }
-    return ret;
+    return 0;
 }
 
 }  // namespace aidl::android::hardware::audio::core::alsa
diff --git a/audio/aidl/default/alsa/Mixer.h b/audio/aidl/default/alsa/Mixer.h
index 8fba1e0..41f19a8 100644
--- a/audio/aidl/default/alsa/Mixer.h
+++ b/audio/aidl/default/alsa/Mixer.h
@@ -39,6 +39,11 @@
 
     bool isValid() const { return mMixer != nullptr; }
 
+    ndk::ScopedAStatus getMasterMute(bool* muted);
+    ndk::ScopedAStatus getMasterVolume(float* volume);
+    ndk::ScopedAStatus getMicGain(float* gain);
+    ndk::ScopedAStatus getMicMute(bool* muted);
+    ndk::ScopedAStatus getVolumes(std::vector<float>* volumes);
     ndk::ScopedAStatus setMasterMute(bool muted);
     ndk::ScopedAStatus setMasterVolume(float volume);
     ndk::ScopedAStatus setMicGain(float gain);
@@ -60,9 +65,16 @@
     static const std::map<Control, std::vector<ControlNamesAndExpectedCtlType>> kPossibleControls;
     static Controls initializeMixerControls(struct mixer* mixer);
 
+    ndk::ScopedAStatus findControl(Control ctl, struct mixer_ctl** result);
+    ndk::ScopedAStatus getMixerControlMute(Control ctl, bool* muted);
+    ndk::ScopedAStatus getMixerControlVolume(Control ctl, float* volume);
     ndk::ScopedAStatus setMixerControlMute(Control ctl, bool muted);
     ndk::ScopedAStatus setMixerControlVolume(Control ctl, float volume);
 
+    int getMixerControlPercent(struct mixer_ctl* ctl, std::vector<int>* percents)
+            REQUIRES(mMixerAccess);
+    int getMixerControlValues(struct mixer_ctl* ctl, std::vector<int>* values)
+            REQUIRES(mMixerAccess);
     int setMixerControlPercent(struct mixer_ctl* ctl, int percent) REQUIRES(mMixerAccess);
     int setMixerControlPercent(struct mixer_ctl* ctl, const std::vector<int>& percents)
             REQUIRES(mMixerAccess);
diff --git a/audio/aidl/default/alsa/StreamAlsa.cpp b/audio/aidl/default/alsa/StreamAlsa.cpp
index 403b94b..e57d538 100644
--- a/audio/aidl/default/alsa/StreamAlsa.cpp
+++ b/audio/aidl/default/alsa/StreamAlsa.cpp
@@ -119,7 +119,7 @@
 
 ::android::status_t StreamAlsa::refinePosition(StreamDescriptor::Position* position) {
     if (mAlsaDeviceProxies.empty()) {
-        LOG(FATAL) << __func__ << ": no opened devices";
+        LOG(WARNING) << __func__ << ": no opened devices";
         return ::android::NO_INIT;
     }
     // Since the proxy can only count transferred frames since its creation,
diff --git a/audio/aidl/default/android.hardware.audio.effect.service-aidl.xml b/audio/aidl/default/android.hardware.audio.effect.service-aidl.xml
index fdc53a3..05a825d 100644
--- a/audio/aidl/default/android.hardware.audio.effect.service-aidl.xml
+++ b/audio/aidl/default/android.hardware.audio.effect.service-aidl.xml
@@ -1,7 +1,7 @@
 <manifest version="1.0" type="device">
   <hal format="aidl">
     <name>android.hardware.audio.effect</name>
-    <version>1</version>
+    <version>2</version>
     <fqname>IFactory/default</fqname>
   </hal>
 </manifest>
diff --git a/audio/aidl/default/android.hardware.audio.service-aidl.xml b/audio/aidl/default/android.hardware.audio.service-aidl.xml
index 57f61c9..2a51876 100644
--- a/audio/aidl/default/android.hardware.audio.service-aidl.xml
+++ b/audio/aidl/default/android.hardware.audio.service-aidl.xml
@@ -1,22 +1,22 @@
 <manifest version="1.0" type="device">
   <hal format="aidl">
     <name>android.hardware.audio.core</name>
-    <version>1</version>
+    <version>2</version>
     <fqname>IModule/default</fqname>
   </hal>
   <hal format="aidl">
     <name>android.hardware.audio.core</name>
-    <version>1</version>
+    <version>2</version>
     <fqname>IModule/r_submix</fqname>
   </hal>
   <hal format="aidl">
     <name>android.hardware.audio.core</name>
-    <version>1</version>
+    <version>2</version>
     <fqname>IModule/bluetooth</fqname>
   </hal>
   <hal format="aidl">
     <name>android.hardware.audio.core</name>
-    <version>1</version>
+    <version>2</version>
     <fqname>IConfig/default</fqname>
   </hal>
   <!-- Uncomment when these modules present in the configuration
diff --git a/audio/aidl/default/bluetooth/DevicePortProxy.cpp b/audio/aidl/default/bluetooth/DevicePortProxy.cpp
index 12e204a..1be0875 100644
--- a/audio/aidl/default/bluetooth/DevicePortProxy.cpp
+++ b/audio/aidl/default/bluetooth/DevicePortProxy.cpp
@@ -19,11 +19,25 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <audio_utils/primitives.h>
-#include <inttypes.h>
 #include <log/log.h>
 
+#include "BluetoothAudioSessionControl.h"
 #include "core-impl/DevicePortProxy.h"
 
+using aidl::android::hardware::audio::common::SinkMetadata;
+using aidl::android::hardware::audio::common::SourceMetadata;
+using aidl::android::hardware::bluetooth::audio::AudioConfiguration;
+using aidl::android::hardware::bluetooth::audio::BluetoothAudioSessionControl;
+using aidl::android::hardware::bluetooth::audio::BluetoothAudioStatus;
+using aidl::android::hardware::bluetooth::audio::ChannelMode;
+using aidl::android::hardware::bluetooth::audio::PcmConfiguration;
+using aidl::android::hardware::bluetooth::audio::PortStatusCallbacks;
+using aidl::android::hardware::bluetooth::audio::PresentationPosition;
+using aidl::android::hardware::bluetooth::audio::SessionType;
+using aidl::android::media::audio::common::AudioDeviceDescription;
+using aidl::android::media::audio::common::AudioDeviceType;
+using android::base::StringPrintf;
+
 namespace android::bluetooth::audio::aidl {
 
 namespace {
@@ -33,20 +47,6 @@
 
 }  // namespace
 
-using ::aidl::android::hardware::audio::common::SinkMetadata;
-using ::aidl::android::hardware::audio::common::SourceMetadata;
-using ::aidl::android::hardware::bluetooth::audio::AudioConfiguration;
-using ::aidl::android::hardware::bluetooth::audio::BluetoothAudioSessionControl;
-using ::aidl::android::hardware::bluetooth::audio::BluetoothAudioStatus;
-using ::aidl::android::hardware::bluetooth::audio::ChannelMode;
-using ::aidl::android::hardware::bluetooth::audio::PcmConfiguration;
-using ::aidl::android::hardware::bluetooth::audio::PortStatusCallbacks;
-using ::aidl::android::hardware::bluetooth::audio::PresentationPosition;
-using ::aidl::android::hardware::bluetooth::audio::SessionType;
-using ::aidl::android::media::audio::common::AudioDeviceDescription;
-using ::aidl::android::media::audio::common::AudioDeviceType;
-using ::android::base::StringPrintf;
-
 std::ostream& operator<<(std::ostream& os, const BluetoothStreamState& state) {
     switch (state) {
         case BluetoothStreamState::DISABLED:
diff --git a/audio/aidl/default/bluetooth/ModuleBluetooth.cpp b/audio/aidl/default/bluetooth/ModuleBluetooth.cpp
index 3c33207..502b153 100644
--- a/audio/aidl/default/bluetooth/ModuleBluetooth.cpp
+++ b/audio/aidl/default/bluetooth/ModuleBluetooth.cpp
@@ -18,50 +18,56 @@
 
 #include <android-base/logging.h>
 
-#include "BluetoothAudioSessionControl.h"
+#include "BluetoothAudioSession.h"
 #include "core-impl/ModuleBluetooth.h"
 #include "core-impl/StreamBluetooth.h"
 
-namespace aidl::android::hardware::audio::core {
+using aidl::android::hardware::audio::common::SinkMetadata;
+using aidl::android::hardware::audio::common::SourceMetadata;
+using aidl::android::media::audio::common::AudioDeviceDescription;
+using aidl::android::media::audio::common::AudioDeviceType;
+using aidl::android::media::audio::common::AudioOffloadInfo;
+using aidl::android::media::audio::common::AudioPort;
+using aidl::android::media::audio::common::AudioPortExt;
+using aidl::android::media::audio::common::MicrophoneInfo;
+using android::bluetooth::audio::aidl::BluetoothAudioPortAidl;
+using android::bluetooth::audio::aidl::BluetoothAudioPortAidlOut;
 
-using ::aidl::android::hardware::audio::common::SinkMetadata;
-using ::aidl::android::hardware::audio::common::SourceMetadata;
-using ::aidl::android::hardware::bluetooth::audio::BluetoothAudioSession;
-using ::aidl::android::media::audio::common::AudioDeviceDescription;
-using ::aidl::android::media::audio::common::AudioDeviceType;
-using ::aidl::android::media::audio::common::AudioOffloadInfo;
-using ::aidl::android::media::audio::common::AudioPort;
-using ::aidl::android::media::audio::common::AudioPortExt;
-using ::aidl::android::media::audio::common::MicrophoneInfo;
-using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidl;
-using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidlOut;
+namespace aidl::android::hardware::audio::core {
 
 ndk::ScopedAStatus ModuleBluetooth::getBluetoothA2dp(
         std::shared_ptr<IBluetoothA2dp>* _aidl_return) {
-    if (!mBluetoothA2dp) {
-        auto handle = ndk::SharedRefBase::make<BluetoothA2dp>();
-        handle->registerHandler(std::bind(&ModuleBluetooth::bluetoothParametersUpdated, this));
-        mBluetoothA2dp = handle;
-    }
-    *_aidl_return = mBluetoothA2dp.getInstance();
+    *_aidl_return = getBtA2dp().getInstance();
     LOG(DEBUG) << __func__ << ": returning instance of IBluetoothA2dp: " << _aidl_return->get();
     return ndk::ScopedAStatus::ok();
 }
 
 ndk::ScopedAStatus ModuleBluetooth::getBluetoothLe(std::shared_ptr<IBluetoothLe>* _aidl_return) {
+    *_aidl_return = getBtLe().getInstance();
+    LOG(DEBUG) << __func__ << ": returning instance of IBluetoothLe: " << _aidl_return->get();
+    return ndk::ScopedAStatus::ok();
+}
+
+ChildInterface<BluetoothA2dp>& ModuleBluetooth::getBtA2dp() {
+    if (!mBluetoothA2dp) {
+        auto handle = ndk::SharedRefBase::make<BluetoothA2dp>();
+        handle->registerHandler(std::bind(&ModuleBluetooth::bluetoothParametersUpdated, this));
+        mBluetoothA2dp = handle;
+    }
+    return mBluetoothA2dp;
+}
+
+ChildInterface<BluetoothLe>& ModuleBluetooth::getBtLe() {
     if (!mBluetoothLe) {
         auto handle = ndk::SharedRefBase::make<BluetoothLe>();
         handle->registerHandler(std::bind(&ModuleBluetooth::bluetoothParametersUpdated, this));
         mBluetoothLe = handle;
     }
-    *_aidl_return = mBluetoothLe.getInstance();
-    LOG(DEBUG) << __func__ << ": returning instance of IBluetoothLe: " << _aidl_return->get();
-    return ndk::ScopedAStatus::ok();
+    return mBluetoothLe;
 }
 
-Module::BtProfileHandles ModuleBluetooth::getBtProfileManagerHandles() {
-    return std::make_tuple(std::weak_ptr<IBluetooth>(), mBluetoothA2dp.getInstance(),
-                           mBluetoothLe.getInstance());
+ModuleBluetooth::BtProfileHandles ModuleBluetooth::getBtProfileManagerHandles() {
+    return std::make_tuple(std::weak_ptr<IBluetooth>(), getBtA2dp().getPtr(), getBtLe().getPtr());
 }
 
 ndk::ScopedAStatus ModuleBluetooth::getMicMute(bool* _aidl_return __unused) {
@@ -101,30 +107,35 @@
     if (description.connection == AudioDeviceDescription::CONNECTION_BT_A2DP) {
         bool isA2dpEnabled = false;
         if (!!mBluetoothA2dp) {
-            RETURN_STATUS_IF_ERROR(mBluetoothA2dp.getInstance()->isEnabled(&isA2dpEnabled));
+            RETURN_STATUS_IF_ERROR((*mBluetoothA2dp).isEnabled(&isA2dpEnabled));
         }
+        LOG(DEBUG) << __func__ << ": isA2dpEnabled: " << isA2dpEnabled;
         return isA2dpEnabled ? ndk::ScopedAStatus::ok()
                              : ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     } else if (description.connection == AudioDeviceDescription::CONNECTION_BT_LE) {
         bool isLeEnabled = false;
         if (!!mBluetoothLe) {
-            RETURN_STATUS_IF_ERROR(mBluetoothLe.getInstance()->isEnabled(&isLeEnabled));
+            RETURN_STATUS_IF_ERROR((*mBluetoothLe).isEnabled(&isLeEnabled));
         }
+        LOG(DEBUG) << __func__ << ": isLeEnabled: " << isLeEnabled;
         return isLeEnabled ? ndk::ScopedAStatus::ok()
                            : ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     } else if (description.connection == AudioDeviceDescription::CONNECTION_WIRELESS &&
                description.type == AudioDeviceType::OUT_HEARING_AID) {
         // Hearing aids can use a number of profiles, thus the only way to check
         // connectivity is to try to talk to the BT HAL.
-        if (!BluetoothAudioSession::IsAidlAvailable()) {
+        if (!::aidl::android::hardware::bluetooth::audio::BluetoothAudioSession::
+                    IsAidlAvailable()) {
             return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
         }
         std::shared_ptr<BluetoothAudioPortAidl> proxy = std::shared_ptr<BluetoothAudioPortAidl>(
                 std::make_shared<BluetoothAudioPortAidlOut>());
         if (proxy->registerPort(description)) {
+            LOG(DEBUG) << __func__ << ": registered hearing aid port";
             proxy->unregisterPort();
             return ndk::ScopedAStatus::ok();
         }
+        LOG(DEBUG) << __func__ << ": failed to register hearing aid port";
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     }
     LOG(ERROR) << __func__ << ": unsupported device type: " << audioPort->toString();
diff --git a/audio/aidl/default/bluetooth/StreamBluetooth.cpp b/audio/aidl/default/bluetooth/StreamBluetooth.cpp
index 91a33c2..0cee7f4 100644
--- a/audio/aidl/default/bluetooth/StreamBluetooth.cpp
+++ b/audio/aidl/default/bluetooth/StreamBluetooth.cpp
@@ -20,52 +20,49 @@
 #include <android-base/logging.h>
 #include <audio_utils/clock.h>
 
-#include "BluetoothAudioSessionControl.h"
+#include "BluetoothAudioSession.h"
 #include "core-impl/StreamBluetooth.h"
 
-namespace aidl::android::hardware::audio::core {
+using aidl::android::hardware::audio::common::frameCountFromDurationUs;
+using aidl::android::hardware::audio::common::SinkMetadata;
+using aidl::android::hardware::audio::common::SourceMetadata;
+using aidl::android::hardware::audio::core::VendorParameter;
+using aidl::android::hardware::bluetooth::audio::ChannelMode;
+using aidl::android::hardware::bluetooth::audio::PcmConfiguration;
+using aidl::android::hardware::bluetooth::audio::PresentationPosition;
+using aidl::android::media::audio::common::AudioChannelLayout;
+using aidl::android::media::audio::common::AudioDevice;
+using aidl::android::media::audio::common::AudioDeviceAddress;
+using aidl::android::media::audio::common::AudioFormatDescription;
+using aidl::android::media::audio::common::AudioFormatType;
+using aidl::android::media::audio::common::AudioOffloadInfo;
+using aidl::android::media::audio::common::MicrophoneDynamicInfo;
+using aidl::android::media::audio::common::MicrophoneInfo;
+using android::bluetooth::audio::aidl::BluetoothAudioPortAidl;
+using android::bluetooth::audio::aidl::BluetoothAudioPortAidlIn;
+using android::bluetooth::audio::aidl::BluetoothAudioPortAidlOut;
+using android::bluetooth::audio::aidl::BluetoothStreamState;
 
-using ::aidl::android::hardware::audio::common::SinkMetadata;
-using ::aidl::android::hardware::audio::common::SourceMetadata;
-using ::aidl::android::hardware::audio::core::VendorParameter;
-using ::aidl::android::hardware::bluetooth::audio::ChannelMode;
-using ::aidl::android::hardware::bluetooth::audio::PcmConfiguration;
-using ::aidl::android::hardware::bluetooth::audio::PresentationPosition;
-using ::aidl::android::media::audio::common::AudioChannelLayout;
-using ::aidl::android::media::audio::common::AudioDevice;
-using ::aidl::android::media::audio::common::AudioDeviceAddress;
-using ::aidl::android::media::audio::common::AudioFormatDescription;
-using ::aidl::android::media::audio::common::AudioFormatType;
-using ::aidl::android::media::audio::common::AudioOffloadInfo;
-using ::aidl::android::media::audio::common::MicrophoneDynamicInfo;
-using ::aidl::android::media::audio::common::MicrophoneInfo;
-using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidl;
-using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidlIn;
-using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidlOut;
-using ::android::bluetooth::audio::aidl::BluetoothStreamState;
+namespace aidl::android::hardware::audio::core {
 
 constexpr int kBluetoothDefaultInputBufferMs = 20;
 constexpr int kBluetoothDefaultOutputBufferMs = 10;
 // constexpr int kBluetoothSpatializerOutputBufferMs = 10;
 
-size_t getFrameCount(uint64_t durationUs, uint32_t sampleRate) {
-    return (durationUs * sampleRate) / 1000000;
-}
-
 // pcm configuration params are not really used by the module
 StreamBluetooth::StreamBluetooth(StreamContext* context, const Metadata& metadata,
-                                 Module::BtProfileHandles&& btHandles)
+                                 ModuleBluetooth::BtProfileHandles&& btHandles)
     : StreamCommonImpl(context, metadata),
       mSampleRate(getContext().getSampleRate()),
       mChannelLayout(getContext().getChannelLayout()),
       mFormat(getContext().getFormat()),
       mFrameSizeBytes(getContext().getFrameSize()),
       mIsInput(isInput(metadata)),
-      mBluetoothA2dp(std::move(std::get<Module::BtInterface::BTA2DP>(btHandles))),
-      mBluetoothLe(std::move(std::get<Module::BtInterface::BTLE>(btHandles))) {
+      mBluetoothA2dp(std::move(std::get<ModuleBluetooth::BtInterface::BTA2DP>(btHandles))),
+      mBluetoothLe(std::move(std::get<ModuleBluetooth::BtInterface::BTLE>(btHandles))) {
     mPreferredDataIntervalUs =
-            mIsInput ? kBluetoothDefaultInputBufferMs : kBluetoothDefaultOutputBufferMs;
-    mPreferredFrameCount = getFrameCount(mPreferredDataIntervalUs, mSampleRate);
+            (mIsInput ? kBluetoothDefaultInputBufferMs : kBluetoothDefaultOutputBufferMs) * 1000;
+    mPreferredFrameCount = frameCountFromDurationUs(mPreferredDataIntervalUs, mSampleRate);
     mIsInitialized = false;
     mIsReadyToClose = false;
 }
@@ -226,7 +223,7 @@
     if (config.dataIntervalUs > 0) {
         mPreferredDataIntervalUs =
                 std::min((int32_t)mPreferredDataIntervalUs, config.dataIntervalUs);
-        mPreferredFrameCount = getFrameCount(mPreferredDataIntervalUs, mSampleRate);
+        mPreferredFrameCount = frameCountFromDurationUs(mPreferredDataIntervalUs, mSampleRate);
     }
     return true;
 }
@@ -318,7 +315,7 @@
 
 StreamInBluetooth::StreamInBluetooth(StreamContext&& context, const SinkMetadata& sinkMetadata,
                                      const std::vector<MicrophoneInfo>& microphones,
-                                     Module::BtProfileHandles&& btProfileHandles)
+                                     ModuleBluetooth::BtProfileHandles&& btProfileHandles)
     : StreamIn(std::move(context), microphones),
       StreamBluetooth(&mContextInstance, sinkMetadata, std::move(btProfileHandles)) {}
 
@@ -331,7 +328,7 @@
 StreamOutBluetooth::StreamOutBluetooth(StreamContext&& context,
                                        const SourceMetadata& sourceMetadata,
                                        const std::optional<AudioOffloadInfo>& offloadInfo,
-                                       Module::BtProfileHandles&& btProfileHandles)
+                                       ModuleBluetooth::BtProfileHandles&& btProfileHandles)
     : StreamOut(std::move(context), offloadInfo),
       StreamBluetooth(&mContextInstance, sourceMetadata, std::move(btProfileHandles)) {}
 
diff --git a/audio/aidl/default/include/core-impl/Bluetooth.h b/audio/aidl/default/include/core-impl/Bluetooth.h
index 44899bc..002cb19 100644
--- a/audio/aidl/default/include/core-impl/Bluetooth.h
+++ b/audio/aidl/default/include/core-impl/Bluetooth.h
@@ -46,9 +46,9 @@
 class BluetoothA2dp : public BnBluetoothA2dp, public ParamChangeHandler {
   public:
     BluetoothA2dp() = default;
+    ndk::ScopedAStatus isEnabled(bool* _aidl_return) override;
 
   private:
-    ndk::ScopedAStatus isEnabled(bool* _aidl_return) override;
     ndk::ScopedAStatus setEnabled(bool in_enabled) override;
     ndk::ScopedAStatus supportsOffloadReconfiguration(bool* _aidl_return) override;
     ndk::ScopedAStatus reconfigureOffload(
@@ -61,9 +61,9 @@
 class BluetoothLe : public BnBluetoothLe, public ParamChangeHandler {
   public:
     BluetoothLe() = default;
+    ndk::ScopedAStatus isEnabled(bool* _aidl_return) override;
 
   private:
-    ndk::ScopedAStatus isEnabled(bool* _aidl_return) override;
     ndk::ScopedAStatus setEnabled(bool in_enabled) override;
     ndk::ScopedAStatus supportsOffloadReconfiguration(bool* _aidl_return) override;
     ndk::ScopedAStatus reconfigureOffload(
diff --git a/audio/aidl/default/include/core-impl/ChildInterface.h b/audio/aidl/default/include/core-impl/ChildInterface.h
index 3b74c5e..f5f1855 100644
--- a/audio/aidl/default/include/core-impl/ChildInterface.h
+++ b/audio/aidl/default/include/core-impl/ChildInterface.h
@@ -40,6 +40,7 @@
     explicit operator bool() const { return !!this->first; }
     C& operator*() const { return *(this->first); }
     C* operator->() const { return this->first; }
+    std::shared_ptr<C> getPtr() { return this->first; }
     // Use 'getInstance' when returning the interface instance.
     std::shared_ptr<C> getInstance() {
         (void)getBinder();
diff --git a/audio/aidl/default/include/core-impl/DevicePortProxy.h b/audio/aidl/default/include/core-impl/DevicePortProxy.h
index 13b8c91..17a8cf3 100644
--- a/audio/aidl/default/include/core-impl/DevicePortProxy.h
+++ b/audio/aidl/default/include/core-impl/DevicePortProxy.h
@@ -25,11 +25,10 @@
 #include <aidl/android/hardware/audio/common/SourceMetadata.h>
 #include <aidl/android/hardware/bluetooth/audio/BluetoothAudioStatus.h>
 #include <aidl/android/hardware/bluetooth/audio/PcmConfiguration.h>
+#include <aidl/android/hardware/bluetooth/audio/PresentationPosition.h>
 #include <aidl/android/hardware/bluetooth/audio/SessionType.h>
 #include <aidl/android/media/audio/common/AudioDeviceDescription.h>
 
-#include "BluetoothAudioSessionControl.h"
-
 namespace android::bluetooth::audio::aidl {
 
 enum class BluetoothStreamState : uint8_t {
@@ -239,4 +238,4 @@
     size_t readData(void* buffer, size_t bytes) const override;
 };
 
-}  // namespace android::bluetooth::audio::aidl
\ No newline at end of file
+}  // namespace android::bluetooth::audio::aidl
diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h
index 718c07d..caf43f1 100644
--- a/audio/aidl/default/include/core-impl/Module.h
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -22,6 +22,7 @@
 #include <optional>
 #include <set>
 
+#include <Utils.h>
 #include <aidl/android/hardware/audio/core/BnModule.h>
 
 #include "core-impl/ChildInterface.h"
@@ -45,13 +46,6 @@
         int32_t nextPatchId = 1;
     };
     enum Type : int { DEFAULT, R_SUBMIX, STUB, USB, BLUETOOTH };
-    enum BtInterface : int { BTCONF, BTA2DP, BTLE };
-    typedef std::tuple<std::weak_ptr<IBluetooth>, std::weak_ptr<IBluetoothA2dp>,
-                       std::weak_ptr<IBluetoothLe>>
-            BtProfileHandles;
-
-    // This value is used by default for all AudioPatches and reported by all streams.
-    static constexpr int32_t kLatencyMs = 10;
 
     static std::shared_ptr<Module> createInstance(Type type) {
         return createInstance(type, std::make_unique<Configuration>());
@@ -145,8 +139,6 @@
     ndk::ScopedAStatus getAAudioMixerBurstCount(int32_t* _aidl_return) override;
     ndk::ScopedAStatus getAAudioHardwareBurstMinUsec(int32_t* _aidl_return) override;
 
-    // This value is used for all AudioPatches.
-    static constexpr int32_t kMinimumStreamBufferSizeFrames = 256;
     // The maximum stream buffer size is 1 GiB = 2 ** 30 bytes;
     static constexpr int32_t kMaximumStreamBufferSizeBytes = 1 << 30;
 
@@ -175,7 +167,7 @@
     bool mMicMute = false;
     bool mMasterMute = false;
     float mMasterVolume = 1.0f;
-    ChildInterface<sounddose::ISoundDose> mSoundDose;
+    ChildInterface<sounddose::SoundDose> mSoundDose;
     std::optional<bool> mIsMmapSupported;
 
   protected:
@@ -207,8 +199,19 @@
     virtual ndk::ScopedAStatus onMasterVolumeChanged(float volume);
     virtual std::vector<::aidl::android::media::audio::common::MicrophoneInfo> getMicrophoneInfos();
     virtual std::unique_ptr<Configuration> initializeConfig();
+    virtual int32_t getNominalLatencyMs(
+            const ::aidl::android::media::audio::common::AudioPortConfig& portConfig);
 
     // Utility and helper functions accessible to subclasses.
+    static int32_t calculateBufferSizeFrames(int32_t latencyMs, int32_t sampleRateHz) {
+        const int32_t rawSizeFrames =
+                aidl::android::hardware::audio::common::frameCountFromDurationMs(latencyMs,
+                                                                                 sampleRateHz);
+        int32_t powerOf2 = 1;
+        while (powerOf2 < rawSizeFrames) powerOf2 <<= 1;
+        return powerOf2;
+    }
+
     ndk::ScopedAStatus bluetoothParametersUpdated();
     void cleanUpPatch(int32_t patchId);
     ndk::ScopedAStatus createStreamContext(
@@ -222,7 +225,6 @@
     ndk::ScopedAStatus findPortIdForNewStream(
             int32_t in_portConfigId, ::aidl::android::media::audio::common::AudioPort** port);
     std::vector<AudioRoute*> getAudioRoutesForAudioPortImpl(int32_t portId);
-    virtual BtProfileHandles getBtProfileManagerHandles();
     Configuration& getConfig();
     const ConnectedDevicePorts& getConnectedDevicePorts() const { return mConnectedDevicePorts; }
     bool getMasterMute() const { return mMasterMute; }
diff --git a/audio/aidl/default/include/core-impl/ModuleBluetooth.h b/audio/aidl/default/include/core-impl/ModuleBluetooth.h
index 7ac2d34..a58798b 100644
--- a/audio/aidl/default/include/core-impl/ModuleBluetooth.h
+++ b/audio/aidl/default/include/core-impl/ModuleBluetooth.h
@@ -23,11 +23,18 @@
 
 class ModuleBluetooth final : public Module {
   public:
+    enum BtInterface : int { BTSCO, BTA2DP, BTLE };
+    typedef std::tuple<std::weak_ptr<IBluetooth>, std::weak_ptr<IBluetoothA2dp>,
+                       std::weak_ptr<IBluetoothLe>>
+            BtProfileHandles;
+
     ModuleBluetooth(std::unique_ptr<Configuration>&& config)
         : Module(Type::BLUETOOTH, std::move(config)) {}
 
   private:
-    BtProfileHandles getBtProfileManagerHandles() override;
+    ChildInterface<BluetoothA2dp>& getBtA2dp();
+    ChildInterface<BluetoothLe>& getBtLe();
+    BtProfileHandles getBtProfileManagerHandles();
 
     ndk::ScopedAStatus getBluetoothA2dp(std::shared_ptr<IBluetoothA2dp>* _aidl_return) override;
     ndk::ScopedAStatus getBluetoothLe(std::shared_ptr<IBluetoothLe>* _aidl_return) override;
@@ -50,8 +57,8 @@
     ndk::ScopedAStatus onMasterMuteChanged(bool mute) override;
     ndk::ScopedAStatus onMasterVolumeChanged(float volume) override;
 
-    ChildInterface<IBluetoothA2dp> mBluetoothA2dp;
-    ChildInterface<IBluetoothLe> mBluetoothLe;
+    ChildInterface<BluetoothA2dp> mBluetoothA2dp;
+    ChildInterface<BluetoothLe> mBluetoothLe;
 };
 
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/ModulePrimary.h b/audio/aidl/default/include/core-impl/ModulePrimary.h
index ee86d64..82c8a03 100644
--- a/audio/aidl/default/include/core-impl/ModulePrimary.h
+++ b/audio/aidl/default/include/core-impl/ModulePrimary.h
@@ -39,6 +39,8 @@
             const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
                     offloadInfo,
             std::shared_ptr<StreamOut>* result) override;
+    int32_t getNominalLatencyMs(
+            const ::aidl::android::media::audio::common::AudioPortConfig& portConfig) override;
 
   private:
     ChildInterface<ITelephony> mTelephony;
diff --git a/audio/aidl/default/include/core-impl/ModuleRemoteSubmix.h b/audio/aidl/default/include/core-impl/ModuleRemoteSubmix.h
index ebf4558..9f8acc9 100644
--- a/audio/aidl/default/include/core-impl/ModuleRemoteSubmix.h
+++ b/audio/aidl/default/include/core-impl/ModuleRemoteSubmix.h
@@ -50,6 +50,9 @@
             override;
     ndk::ScopedAStatus onMasterMuteChanged(bool mute) override;
     ndk::ScopedAStatus onMasterVolumeChanged(float volume) override;
+    int32_t getNominalLatencyMs(
+            const ::aidl::android::media::audio::common::AudioPortConfig& portConfig) override;
+    // TODO(b/307586684): Report proper minimum stream buffer size by overriding 'setAudioPatch'.
 };
 
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/SoundDose.h b/audio/aidl/default/include/core-impl/SoundDose.h
index 2a069d9..c0edc9f 100644
--- a/audio/aidl/default/include/core-impl/SoundDose.h
+++ b/audio/aidl/default/include/core-impl/SoundDose.h
@@ -20,11 +20,23 @@
 
 #include <aidl/android/hardware/audio/core/sounddose/BnSoundDose.h>
 #include <aidl/android/media/audio/common/AudioDevice.h>
-
-using aidl::android::media::audio::common::AudioDevice;
+#include <aidl/android/media/audio/common/AudioFormatDescription.h>
 
 namespace aidl::android::hardware::audio::core::sounddose {
 
+// Interface used for processing the data received by a stream.
+class StreamDataProcessorInterface {
+  public:
+    virtual ~StreamDataProcessorInterface() = default;
+
+    virtual void startDataProcessor(
+            uint32_t samplerate, uint32_t channelCount,
+            const ::aidl::android::media::audio::common::AudioFormatDescription& format) = 0;
+    virtual void setAudioDevice(
+            const ::aidl::android::media::audio::common::AudioDevice& audioDevice) = 0;
+    virtual void process(const void* buffer, size_t size) = 0;
+};
+
 class SoundDose : public BnSoundDose {
   public:
     SoundDose() : mRs2Value(DEFAULT_MAX_RS2){};
diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h
index 88fddec..aa9fb19 100644
--- a/audio/aidl/default/include/core-impl/Stream.h
+++ b/audio/aidl/default/include/core-impl/Stream.h
@@ -44,6 +44,7 @@
 #include <utils/Errors.h>
 
 #include "core-impl/ChildInterface.h"
+#include "core-impl/SoundDose.h"
 #include "core-impl/utils.h"
 
 namespace aidl::android::hardware::audio::core {
@@ -80,59 +81,28 @@
 
     StreamContext() = default;
     StreamContext(std::unique_ptr<CommandMQ> commandMQ, std::unique_ptr<ReplyMQ> replyMQ,
-                  int portId,
                   const ::aidl::android::media::audio::common::AudioFormatDescription& format,
                   const ::aidl::android::media::audio::common::AudioChannelLayout& channelLayout,
                   int sampleRate, const ::aidl::android::media::audio::common::AudioIoFlags& flags,
-                  int32_t mixPortHandle, std::unique_ptr<DataMQ> dataMQ,
+                  int32_t nominalLatencyMs, int32_t mixPortHandle, std::unique_ptr<DataMQ> dataMQ,
                   std::shared_ptr<IStreamCallback> asyncCallback,
                   std::shared_ptr<IStreamOutEventCallback> outEventCallback,
+                  std::weak_ptr<sounddose::StreamDataProcessorInterface> streamDataProcessor,
                   DebugParameters debugParameters)
         : mCommandMQ(std::move(commandMQ)),
           mInternalCommandCookie(std::rand()),
           mReplyMQ(std::move(replyMQ)),
-          mPortId(portId),
           mFormat(format),
           mChannelLayout(channelLayout),
           mSampleRate(sampleRate),
           mFlags(flags),
+          mNominalLatencyMs(nominalLatencyMs),
           mMixPortHandle(mixPortHandle),
           mDataMQ(std::move(dataMQ)),
           mAsyncCallback(asyncCallback),
           mOutEventCallback(outEventCallback),
+          mStreamDataProcessor(streamDataProcessor),
           mDebugParameters(debugParameters) {}
-    StreamContext(StreamContext&& other)
-        : mCommandMQ(std::move(other.mCommandMQ)),
-          mInternalCommandCookie(other.mInternalCommandCookie),
-          mReplyMQ(std::move(other.mReplyMQ)),
-          mPortId(other.mPortId),
-          mFormat(other.mFormat),
-          mChannelLayout(other.mChannelLayout),
-          mSampleRate(other.mSampleRate),
-          mFlags(std::move(other.mFlags)),
-          mMixPortHandle(other.mMixPortHandle),
-          mDataMQ(std::move(other.mDataMQ)),
-          mAsyncCallback(std::move(other.mAsyncCallback)),
-          mOutEventCallback(std::move(other.mOutEventCallback)),
-          mDebugParameters(std::move(other.mDebugParameters)),
-          mFrameCount(other.mFrameCount) {}
-    StreamContext& operator=(StreamContext&& other) {
-        mCommandMQ = std::move(other.mCommandMQ);
-        mInternalCommandCookie = other.mInternalCommandCookie;
-        mReplyMQ = std::move(other.mReplyMQ);
-        mPortId = std::move(other.mPortId);
-        mFormat = std::move(other.mFormat);
-        mChannelLayout = std::move(other.mChannelLayout);
-        mSampleRate = other.mSampleRate;
-        mFlags = std::move(other.mFlags);
-        mMixPortHandle = other.mMixPortHandle;
-        mDataMQ = std::move(other.mDataMQ);
-        mAsyncCallback = std::move(other.mAsyncCallback);
-        mOutEventCallback = std::move(other.mOutEventCallback);
-        mDebugParameters = std::move(other.mDebugParameters);
-        mFrameCount = other.mFrameCount;
-        return *this;
-    }
 
     void fillDescriptor(StreamDescriptor* desc);
     std::shared_ptr<IStreamCallback> getAsyncCallback() const { return mAsyncCallback; }
@@ -151,10 +121,14 @@
     size_t getFrameSize() const;
     int getInternalCommandCookie() const { return mInternalCommandCookie; }
     int32_t getMixPortHandle() const { return mMixPortHandle; }
+    int32_t getNominalLatencyMs() const { return mNominalLatencyMs; }
     std::shared_ptr<IStreamOutEventCallback> getOutEventCallback() const {
         return mOutEventCallback;
     }
-    int getPortId() const { return mPortId; }
+    std::weak_ptr<sounddose::StreamDataProcessorInterface> getStreamDataProcessor() const {
+        return mStreamDataProcessor;
+    }
+    void startStreamDataProcessor();
     ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
     int getTransientStateDelayMs() const { return mDebugParameters.transientStateDelayMs; }
     int getSampleRate() const { return mSampleRate; }
@@ -167,18 +141,20 @@
     long getFrameCount() const { return mFrameCount; }
 
   private:
+    // Fields are non const to allow move assignment.
     std::unique_ptr<CommandMQ> mCommandMQ;
     int mInternalCommandCookie;  // The value used to confirm that the command was posted internally
     std::unique_ptr<ReplyMQ> mReplyMQ;
-    int mPortId;
     ::aidl::android::media::audio::common::AudioFormatDescription mFormat;
     ::aidl::android::media::audio::common::AudioChannelLayout mChannelLayout;
     int mSampleRate;
     ::aidl::android::media::audio::common::AudioIoFlags mFlags;
+    int32_t mNominalLatencyMs;
     int32_t mMixPortHandle;
     std::unique_ptr<DataMQ> mDataMQ;
     std::shared_ptr<IStreamCallback> mAsyncCallback;
     std::shared_ptr<IStreamOutEventCallback> mOutEventCallback;  // Only used by output streams
+    std::weak_ptr<sounddose::StreamDataProcessorInterface> mStreamDataProcessor;
     DebugParameters mDebugParameters;
     long mFrameCount = 0;
 };
diff --git a/audio/aidl/default/include/core-impl/StreamBluetooth.h b/audio/aidl/default/include/core-impl/StreamBluetooth.h
index c2f8c1d..1258d38 100644
--- a/audio/aidl/default/include/core-impl/StreamBluetooth.h
+++ b/audio/aidl/default/include/core-impl/StreamBluetooth.h
@@ -24,7 +24,7 @@
 #include <aidl/android/hardware/audio/core/IBluetoothLe.h>
 
 #include "core-impl/DevicePortProxy.h"
-#include "core-impl/Module.h"
+#include "core-impl/ModuleBluetooth.h"
 #include "core-impl/Stream.h"
 
 namespace aidl::android::hardware::audio::core {
@@ -32,7 +32,7 @@
 class StreamBluetooth : public StreamCommonImpl {
   public:
     StreamBluetooth(StreamContext* context, const Metadata& metadata,
-                    Module::BtProfileHandles&& btHandles);
+                    ModuleBluetooth::BtProfileHandles&& btHandles);
     // Methods of 'DriverInterface'.
     ::android::status_t init() override;
     ::android::status_t drain(StreamDescriptor::DrainMode) override;
@@ -80,7 +80,7 @@
             StreamContext&& context,
             const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata,
             const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones,
-            Module::BtProfileHandles&& btHandles);
+            ModuleBluetooth::BtProfileHandles&& btHandles);
 
   private:
     void onClose(StreamDescriptor::State) override { defaultOnClose(); }
@@ -97,7 +97,7 @@
             const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata,
             const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
                     offloadInfo,
-            Module::BtProfileHandles&& btHandles);
+            ModuleBluetooth::BtProfileHandles&& btHandles);
 
   private:
     void onClose(StreamDescriptor::State) override { defaultOnClose(); }
diff --git a/audio/aidl/default/include/core-impl/StreamPrimary.h b/audio/aidl/default/include/core-impl/StreamPrimary.h
index b3ddd0b..abc119c 100644
--- a/audio/aidl/default/include/core-impl/StreamPrimary.h
+++ b/audio/aidl/default/include/core-impl/StreamPrimary.h
@@ -27,10 +27,13 @@
   public:
     StreamPrimary(StreamContext* context, const Metadata& metadata);
 
+    ::android::status_t transfer(void* buffer, size_t frameCount, size_t* actualFrameCount,
+                                 int32_t* latencyMs) override;
+
   protected:
     std::vector<alsa::DeviceProfile> getDeviceProfiles() override;
 
-    const bool mIsInput;
+    const bool mIsAsynchronous;
 };
 
 class StreamInPrimary final : public StreamIn, public StreamSwitcher, public StreamInHwGainHelper {
@@ -79,6 +82,10 @@
 
     ndk::ScopedAStatus getHwVolume(std::vector<float>* _aidl_return) override;
     ndk::ScopedAStatus setHwVolume(const std::vector<float>& in_channelVolumes) override;
+
+    ndk::ScopedAStatus setConnectedDevices(
+            const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices)
+            override;
 };
 
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/StreamRemoteSubmix.h b/audio/aidl/default/include/core-impl/StreamRemoteSubmix.h
index 94404a1..21592b3 100644
--- a/audio/aidl/default/include/core-impl/StreamRemoteSubmix.h
+++ b/audio/aidl/default/include/core-impl/StreamRemoteSubmix.h
@@ -46,7 +46,7 @@
     ndk::ScopedAStatus prepareToClose() override;
 
   private:
-    size_t getPipeSizeInFrames();
+    long getDelayInUsForFrameCount(size_t frameCount);
     size_t getStreamPipeSizeInFrames();
     ::android::status_t outWrite(void* buffer, size_t frameCount, size_t* actualFrameCount);
     ::android::status_t inRead(void* buffer, size_t frameCount, size_t* actualFrameCount);
diff --git a/audio/aidl/default/primary/StreamPrimary.cpp b/audio/aidl/default/primary/StreamPrimary.cpp
index e01be8a..7e3bdd4 100644
--- a/audio/aidl/default/primary/StreamPrimary.cpp
+++ b/audio/aidl/default/primary/StreamPrimary.cpp
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-#include <limits>
+#include <chrono>
 
 #define LOG_TAG "AHAL_StreamPrimary"
 #include <android-base/logging.h>
 #include <android-base/properties.h>
+#include <audio_utils/clock.h>
 #include <error/expected_utils.h>
 
 #include "PrimaryMixer.h"
@@ -37,7 +38,33 @@
 namespace aidl::android::hardware::audio::core {
 
 StreamPrimary::StreamPrimary(StreamContext* context, const Metadata& metadata)
-    : StreamAlsa(context, metadata, 3 /*readWriteRetries*/), mIsInput(isInput(metadata)) {}
+    : StreamAlsa(context, metadata, 3 /*readWriteRetries*/),
+      mIsAsynchronous(!!getContext().getAsyncCallback()) {
+    context->startStreamDataProcessor();
+}
+
+::android::status_t StreamPrimary::transfer(void* buffer, size_t frameCount,
+                                            size_t* actualFrameCount, int32_t* latencyMs) {
+    auto start = std::chrono::steady_clock::now();
+    if (auto status = StreamAlsa::transfer(buffer, frameCount, actualFrameCount, latencyMs);
+        status != ::android::OK) {
+        return status;
+    }
+    // This is a workaround for the emulator implementation which has a host-side buffer
+    // and this can result in reading faster than real time.
+    if (mIsInput && !mIsAsynchronous) {
+        auto recordDurationUs = std::chrono::duration_cast<std::chrono::microseconds>(
+                std::chrono::steady_clock::now() - start);
+        const long projectedVsObservedOffsetUs =
+                *actualFrameCount * MICROS_PER_SECOND / mContext.getSampleRate() -
+                recordDurationUs.count();
+        if (projectedVsObservedOffsetUs > 0) {
+            LOG(VERBOSE) << __func__ << ": sleeping for " << projectedVsObservedOffsetUs << " us";
+            usleep(projectedVsObservedOffsetUs);
+        }
+    }
+    return ::android::OK;
+}
 
 std::vector<alsa::DeviceProfile> StreamPrimary::getDeviceProfiles() {
     static const std::vector<alsa::DeviceProfile> kBuiltInSource{
@@ -64,7 +91,8 @@
             GetBoolProperty("ro.boot.audio.tinyalsa.simulate_input", false);
     return kSimulateInput || device.type.type == AudioDeviceType::IN_TELEPHONY_RX ||
            device.type.type == AudioDeviceType::IN_FM_TUNER ||
-           device.type.connection == AudioDeviceDescription::CONNECTION_BUS;
+           device.type.connection == AudioDeviceDescription::CONNECTION_BUS ||
+           (device.type.type == AudioDeviceType::IN_DEVICE && device.type.connection.empty());
 }
 
 StreamSwitcher::DeviceSwitchBehavior StreamInPrimary::switchCurrentStream(
@@ -99,6 +127,11 @@
     if (isStubStream()) {
         return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
     }
+    float gain;
+    RETURN_STATUS_IF_ERROR(primary::PrimaryMixer::getInstance().getMicGain(&gain));
+    _aidl_return->resize(0);
+    _aidl_return->resize(mChannelCount, gain);
+    RETURN_STATUS_IF_ERROR(setHwGainImpl(*_aidl_return));
     return getHwGainImpl(_aidl_return);
 }
 
@@ -130,7 +163,8 @@
     static const bool kSimulateOutput =
             GetBoolProperty("ro.boot.audio.tinyalsa.ignore_output", false);
     return kSimulateOutput || device.type.type == AudioDeviceType::OUT_TELEPHONY_TX ||
-           device.type.connection == AudioDeviceDescription::CONNECTION_BUS;
+           device.type.connection == AudioDeviceDescription::CONNECTION_BUS ||
+           (device.type.type == AudioDeviceType::OUT_DEVICE && device.type.connection.empty());
 }
 
 StreamSwitcher::DeviceSwitchBehavior StreamOutPrimary::switchCurrentStream(
@@ -165,6 +199,9 @@
     if (isStubStream()) {
         return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
     }
+    RETURN_STATUS_IF_ERROR(primary::PrimaryMixer::getInstance().getVolumes(_aidl_return));
+    _aidl_return->resize(mChannelCount);
+    RETURN_STATUS_IF_ERROR(setHwVolumeImpl(*_aidl_return));
     return getHwVolumeImpl(_aidl_return);
 }
 
@@ -183,4 +220,15 @@
     return ndk::ScopedAStatus::ok();
 }
 
+ndk::ScopedAStatus StreamOutPrimary::setConnectedDevices(
+        const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) {
+    if (!devices.empty()) {
+        auto streamDataProcessor = mContextInstance.getStreamDataProcessor().lock();
+        if (streamDataProcessor != nullptr) {
+            streamDataProcessor->setAudioDevice(devices[0]);
+        }
+    }
+    return StreamSwitcher::setConnectedDevices(devices);
+}
+
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/r_submix/ModuleRemoteSubmix.cpp b/audio/aidl/default/r_submix/ModuleRemoteSubmix.cpp
index f8c775f..3e8dd7c 100644
--- a/audio/aidl/default/r_submix/ModuleRemoteSubmix.cpp
+++ b/audio/aidl/default/r_submix/ModuleRemoteSubmix.cpp
@@ -21,6 +21,7 @@
 #include <android-base/logging.h>
 #include <error/expected_utils.h>
 
+#include "SubmixRoute.h"
 #include "core-impl/ModuleRemoteSubmix.h"
 #include "core-impl/StreamRemoteSubmix.h"
 
@@ -106,4 +107,12 @@
     return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
 }
 
+int32_t ModuleRemoteSubmix::getNominalLatencyMs(const AudioPortConfig&) {
+    // See the note on kDefaultPipePeriodCount.
+    static constexpr int32_t kMaxLatencyMs =
+            (r_submix::kDefaultPipeSizeInFrames * 1000) / r_submix::kDefaultSampleRateHz;
+    static constexpr int32_t kMinLatencyMs = kMaxLatencyMs / r_submix::kDefaultPipePeriodCount;
+    return (kMaxLatencyMs + kMinLatencyMs) / 2;
+}
+
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp b/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
index 9c9c08b..38281b9 100644
--- a/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
+++ b/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
@@ -17,8 +17,6 @@
 #define LOG_TAG "AHAL_StreamRemoteSubmix"
 #include <android-base/logging.h>
 
-#include <cmath>
-
 #include "core-impl/StreamRemoteSubmix.h"
 
 using aidl::android::hardware::audio::common::SinkMetadata;
@@ -158,27 +156,8 @@
 
 ::android::status_t StreamRemoteSubmix::transfer(void* buffer, size_t frameCount,
                                                  size_t* actualFrameCount, int32_t* latencyMs) {
-    *latencyMs = (getStreamPipeSizeInFrames() * MILLIS_PER_SECOND) / mStreamConfig.sampleRate;
+    *latencyMs = getDelayInUsForFrameCount(getStreamPipeSizeInFrames()) / 1000;
     LOG(VERBOSE) << __func__ << ": Latency " << *latencyMs << "ms";
-
-    sp<MonoPipe> sink = mCurrentRoute->getSink();
-    if (sink != nullptr) {
-        if (sink->isShutdown()) {
-            sink.clear();
-            LOG(VERBOSE) << __func__ << ": pipe shutdown, ignoring the transfer.";
-            // the pipe has already been shutdown, this buffer will be lost but we must simulate
-            // timing so we don't drain the output faster than realtime
-            const size_t delayUs = static_cast<size_t>(
-                    std::roundf(frameCount * MICROS_PER_SECOND / mStreamConfig.sampleRate));
-            usleep(delayUs);
-
-            *actualFrameCount = frameCount;
-            return ::android::OK;
-        }
-    } else {
-        LOG(ERROR) << __func__ << ": transfer without a pipe!";
-        return ::android::UNEXPECTED_NULL;
-    }
     mCurrentRoute->exitStandby(mIsInput);
     return (mIsInput ? inRead(buffer, frameCount, actualFrameCount)
                      : outWrite(buffer, frameCount, actualFrameCount));
@@ -202,6 +181,10 @@
     return ::android::OK;
 }
 
+long StreamRemoteSubmix::getDelayInUsForFrameCount(size_t frameCount) {
+    return frameCount * MICROS_PER_SECOND / mStreamConfig.sampleRate;
+}
+
 // Calculate the maximum size of the pipe buffer in frames for the specified stream.
 size_t StreamRemoteSubmix::getStreamPipeSizeInFrames() {
     auto pipeConfig = mCurrentRoute->mPipeConfig;
@@ -215,11 +198,11 @@
     if (sink != nullptr) {
         if (sink->isShutdown()) {
             sink.clear();
-            LOG(VERBOSE) << __func__ << ": pipe shutdown, ignoring the write.";
+            const auto delayUs = getDelayInUsForFrameCount(frameCount);
+            LOG(DEBUG) << __func__ << ": pipe shutdown, ignoring the write, sleeping for "
+                       << delayUs << " us";
             // the pipe has already been shutdown, this buffer will be lost but we must
             // simulate timing so we don't drain the output faster than realtime
-            const size_t delayUs = static_cast<size_t>(
-                    std::roundf(frameCount * MICROS_PER_SECOND / mStreamConfig.sampleRate));
             usleep(delayUs);
             *actualFrameCount = frameCount;
             return ::android::OK;
@@ -229,17 +212,18 @@
         return ::android::UNKNOWN_ERROR;
     }
 
-    const size_t availableToWrite = sink->availableToWrite();
+    const bool shouldBlockWrite = mCurrentRoute->shouldBlockWrite();
+    size_t availableToWrite = sink->availableToWrite();
     // NOTE: sink has been checked above and sink and source life cycles are synchronized
     sp<MonoPipeReader> source = mCurrentRoute->getSource();
     // If the write to the sink should be blocked, flush enough frames from the pipe to make space
     // to write the most recent data.
-    if (!mCurrentRoute->shouldBlockWrite() && availableToWrite < frameCount) {
+    if (!shouldBlockWrite && availableToWrite < frameCount) {
         static uint8_t flushBuffer[64];
         const size_t flushBufferSizeFrames = sizeof(flushBuffer) / mStreamConfig.frameSize;
         size_t framesToFlushFromSource = frameCount - availableToWrite;
-        LOG(VERBOSE) << __func__ << ": flushing " << framesToFlushFromSource
-                     << " frames from the pipe to avoid blocking";
+        LOG(DEBUG) << __func__ << ": flushing " << framesToFlushFromSource
+                   << " frames from the pipe to avoid blocking";
         while (framesToFlushFromSource) {
             const size_t flushSize = std::min(framesToFlushFromSource, flushBufferSizeFrames);
             framesToFlushFromSource -= flushSize;
@@ -247,7 +231,12 @@
             source->read(flushBuffer, flushSize);
         }
     }
+    availableToWrite = sink->availableToWrite();
 
+    if (!shouldBlockWrite && frameCount > availableToWrite) {
+        // Truncate the request to avoid blocking.
+        frameCount = availableToWrite;
+    }
     ssize_t writtenFrames = sink->write(buffer, frameCount);
     if (writtenFrames < 0) {
         if (writtenFrames == (ssize_t)::android::NEGOTIATE) {
@@ -261,7 +250,6 @@
             writtenFrames = sink->write(buffer, frameCount);
         }
     }
-    sink.clear();
 
     if (writtenFrames < 0) {
         LOG(ERROR) << __func__ << ": failed writing to pipe with " << writtenFrames;
@@ -286,8 +274,9 @@
         } else {
             LOG(ERROR) << __func__ << ": Read errors " << readErrorCount;
         }
-        const size_t delayUs = static_cast<size_t>(
-                std::roundf(frameCount * MICROS_PER_SECOND / mStreamConfig.sampleRate));
+        const auto delayUs = getDelayInUsForFrameCount(frameCount);
+        LOG(DEBUG) << __func__ << ": no source, ignoring the read, sleeping for " << delayUs
+                   << " us";
         usleep(delayUs);
         memset(buffer, 0, mStreamConfig.frameSize * frameCount);
         *actualFrameCount = frameCount;
@@ -296,7 +285,7 @@
 
     // read the data from the pipe
     int attempts = 0;
-    const size_t delayUs = static_cast<size_t>(std::roundf(kReadAttemptSleepUs));
+    const long delayUs = kReadAttemptSleepUs;
     char* buff = (char*)buffer;
     size_t remainingFrames = frameCount;
     int availableToRead = source->availableToRead();
@@ -313,11 +302,12 @@
             buff += framesRead * mStreamConfig.frameSize;
             availableToRead -= framesRead;
             LOG(VERBOSE) << __func__ << ": (attempts = " << attempts << ") got " << framesRead
-                         << " frames, remaining=" << remainingFrames;
+                         << " frames, remaining =" << remainingFrames;
         } else {
             attempts++;
             LOG(WARNING) << __func__ << ": read returned " << framesRead
-                         << " , read failure attempts = " << attempts;
+                         << " , read failure attempts = " << attempts << ", sleeping for "
+                         << delayUs << " us";
             usleep(delayUs);
         }
     }
@@ -337,18 +327,18 @@
     // compute how much we need to sleep after reading the data by comparing the wall clock with
     //   the projected time at which we should return.
     // wall clock after reading from the pipe
-    auto recordDurationUs = std::chrono::steady_clock::now() - mCurrentRoute->getRecordStartTime();
+    auto recordDurationUs = std::chrono::duration_cast<std::chrono::microseconds>(
+            std::chrono::steady_clock::now() - mCurrentRoute->getRecordStartTime());
 
     // readCounterFrames contains the number of frames that have been read since the beginning of
     // recording (including this call): it's converted to usec and compared to how long we've been
     // recording for, which gives us how long we must wait to sync the projected recording time, and
     // the observed recording time.
-    const int projectedVsObservedOffsetUs =
-            std::roundf((readCounterFrames * MICROS_PER_SECOND / mStreamConfig.sampleRate) -
-                        recordDurationUs.count());
+    const long projectedVsObservedOffsetUs =
+            getDelayInUsForFrameCount(readCounterFrames) - recordDurationUs.count();
 
     LOG(VERBOSE) << __func__ << ": record duration " << recordDurationUs.count()
-                 << " microseconds, will wait: " << projectedVsObservedOffsetUs << " microseconds";
+                 << " us, will wait: " << projectedVsObservedOffsetUs << " us";
     if (projectedVsObservedOffsetUs > 0) {
         usleep(projectedVsObservedOffsetUs);
     }
diff --git a/audio/aidl/default/r_submix/SubmixRoute.h b/audio/aidl/default/r_submix/SubmixRoute.h
index 1a98df2..92b95e9 100644
--- a/audio/aidl/default/r_submix/SubmixRoute.h
+++ b/audio/aidl/default/r_submix/SubmixRoute.h
@@ -14,16 +14,19 @@
  * limitations under the License.
  */
 
+#pragma once
+
+#include <chrono>
 #include <mutex>
 
+#include <android-base/thread_annotations.h>
 #include <audio_utils/clock.h>
 
 #include <media/nbaio/MonoPipe.h>
 #include <media/nbaio/MonoPipeReader.h>
 
 #include <aidl/android/media/audio/common/AudioChannelLayout.h>
-
-#include "core-impl/Stream.h"
+#include <aidl/android/media/audio/common/AudioFormatDescription.h>
 
 using aidl::android::media::audio::common::AudioChannelLayout;
 using aidl::android::media::audio::common::AudioFormatDescription;
@@ -36,9 +39,13 @@
 namespace aidl::android::hardware::audio::core::r_submix {
 
 static constexpr int kDefaultSampleRateHz = 48000;
-// Size at default sample rate
-// NOTE: This value will be rounded up to the nearest power of 2 by MonoPipe().
-static constexpr int kDefaultPipeSizeInFrames = (1024 * 4);
+// Value used to divide the MonoPipe buffer into segments that are written to the source and
+// read from the sink. The maximum latency of the device is the size of the MonoPipe's buffer
+// the minimum latency is the MonoPipe buffer size divided by this value.
+static constexpr int kDefaultPipePeriodCount = 4;
+// Size at the default sample rate
+// NOTE: This value will be rounded up to the nearest power of 2 by MonoPipe.
+static constexpr int kDefaultPipeSizeInFrames = 1024 * kDefaultPipePeriodCount;
 
 // Configuration of the audio stream.
 struct AudioConfig {
diff --git a/audio/aidl/default/stub/StreamStub.cpp b/audio/aidl/default/stub/StreamStub.cpp
index 660a51e..2422fe4 100644
--- a/audio/aidl/default/stub/StreamStub.cpp
+++ b/audio/aidl/default/stub/StreamStub.cpp
@@ -94,7 +94,7 @@
 }
 
 ::android::status_t StreamStub::transfer(void* buffer, size_t frameCount, size_t* actualFrameCount,
-                                         int32_t* latencyMs) {
+                                         int32_t*) {
     if (!mIsInitialized) {
         LOG(FATAL) << __func__ << ": must not happen for an uninitialized driver";
     }
@@ -117,7 +117,6 @@
         }
     }
     *actualFrameCount = frameCount;
-    *latencyMs = Module::kLatencyMs;
     return ::android::OK;
 }
 
diff --git a/audio/aidl/sounddose/Android.bp b/audio/aidl/sounddose/Android.bp
index 6f2f790..c65e4ff 100644
--- a/audio/aidl/sounddose/Android.bp
+++ b/audio/aidl/sounddose/Android.bp
@@ -52,11 +52,11 @@
         // IMPORTANT: Update latest_android_hardware_audio_sounddose every time you
         // add the latest frozen version to versions_with_info
     ],
-    frozen: true,
+    frozen: false,
 }
 
 // Note: This should always be one version ahead of the last frozen version
-latest_android_hardware_audio_sounddose = "android.hardware.audio.sounddose-V1"
+latest_android_hardware_audio_sounddose = "android.hardware.audio.sounddose-V2"
 
 // Modules that depend on android.hardware.audio.sounddose directly can include
 // the following cc_defaults to avoid explicitly managing dependency versions
diff --git a/audio/aidl/vts/Android.bp b/audio/aidl/vts/Android.bp
index 0de8574..ad816c7 100644
--- a/audio/aidl/vts/Android.bp
+++ b/audio/aidl/vts/Android.bp
@@ -11,6 +11,7 @@
     name: "VtsHalAudioTargetTestDefaults",
     defaults: [
         "latest_android_hardware_audio_common_ndk_static",
+        "latest_android_hardware_audio_effect_ndk_static",
         "latest_android_media_audio_common_types_ndk_static",
         "use_libaidlvintf_gtest_helper_static",
         "VtsHalTargetTestDefaults",
@@ -20,7 +21,6 @@
         "libfmq",
     ],
     static_libs: [
-        "android.hardware.audio.effect-V1-ndk",
         "android.hardware.common-V2-ndk",
         "android.hardware.common.fmq-V1-ndk",
         "libaudioaidlcommon",
diff --git a/audio/aidl/vts/AudioHalBinderServiceUtil.h b/audio/aidl/vts/AudioHalBinderServiceUtil.h
index b4b4632..4ebc1b1 100644
--- a/audio/aidl/vts/AudioHalBinderServiceUtil.h
+++ b/audio/aidl/vts/AudioHalBinderServiceUtil.h
@@ -42,20 +42,9 @@
 
     ndk::SpAIBinder restartService(
             std::chrono::milliseconds timeoutMs = std::chrono::milliseconds(3000)) {
-        mDeathHandler.reset(new AidlDeathRecipient(mBinder));
-        if (STATUS_OK != mDeathHandler->linkToDeath()) {
-            LOG(ERROR) << "linkToDeath failed";
-            return nullptr;
+        if (!stopService(timeoutMs)) {
+            return {};
         }
-        if (!android::base::SetProperty("sys.audio.restart.hal", "1")) {
-            LOG(ERROR) << "SetProperty failed";
-            return nullptr;
-        }
-        if (!mDeathHandler->waitForFired(timeoutMs)) {
-            LOG(ERROR) << "Timeout wait for death";
-            return nullptr;
-        }
-        mDeathHandler.reset();
         return connectToService(mServiceName);
     }
 
@@ -71,8 +60,7 @@
 
         bool waitForFired(std::chrono::milliseconds timeoutMs) {
             std::unique_lock<std::mutex> lock(mutex);
-            condition.wait_for(lock, timeoutMs, [this]() { return fired; });
-            return fired;
+            return condition.wait_for(lock, timeoutMs, [this]() { return fired; });
         }
 
       private:
@@ -94,7 +82,23 @@
         }
     };
 
+    bool stopService(std::chrono::milliseconds timeoutMs) {
+        AidlDeathRecipient deathHandler(mBinder);
+        if (STATUS_OK != deathHandler.linkToDeath()) {
+            LOG(ERROR) << "linkToDeath failed";
+            return false;
+        }
+        if (!android::base::SetProperty("sys.audio.restart.hal", "1")) {
+            LOG(ERROR) << "SetProperty failed";
+            return false;
+        }
+        if (!deathHandler.waitForFired(timeoutMs)) {
+            LOG(ERROR) << "Timeout wait for death of " << mServiceName;
+            return false;
+        }
+        return true;
+    }
+
     std::string mServiceName;
     ndk::SpAIBinder mBinder;
-    std::unique_ptr<AidlDeathRecipient> mDeathHandler;
 };
diff --git a/audio/aidl/vts/ModuleConfig.cpp b/audio/aidl/vts/ModuleConfig.cpp
index 8f19547..a633d83 100644
--- a/audio/aidl/vts/ModuleConfig.cpp
+++ b/audio/aidl/vts/ModuleConfig.cpp
@@ -67,20 +67,7 @@
     return {};
 }
 
-std::vector<aidl::android::media::audio::common::AudioPort>
-ModuleConfig::getAudioPortsForDeviceTypes(const std::vector<AudioDeviceType>& deviceTypes,
-                                          const std::string& connection) {
-    return getAudioPortsForDeviceTypes(mPorts, deviceTypes, connection);
-}
-
 // static
-std::vector<aidl::android::media::audio::common::AudioPort> ModuleConfig::getBuiltInMicPorts(
-        const std::vector<aidl::android::media::audio::common::AudioPort>& ports) {
-    return getAudioPortsForDeviceTypes(
-            ports, std::vector<AudioDeviceType>{AudioDeviceType::IN_MICROPHONE,
-                                                AudioDeviceType::IN_MICROPHONE_BACK});
-}
-
 std::vector<aidl::android::media::audio::common::AudioPort>
 ModuleConfig::getAudioPortsForDeviceTypes(
         const std::vector<aidl::android::media::audio::common::AudioPort>& ports,
@@ -100,6 +87,14 @@
     return result;
 }
 
+// static
+std::vector<aidl::android::media::audio::common::AudioPort> ModuleConfig::getBuiltInMicPorts(
+        const std::vector<aidl::android::media::audio::common::AudioPort>& ports) {
+    return getAudioPortsForDeviceTypes(
+            ports, std::vector<AudioDeviceType>{AudioDeviceType::IN_MICROPHONE,
+                                                AudioDeviceType::IN_MICROPHONE_BACK});
+}
+
 template <typename T>
 auto findById(const std::vector<T>& v, int32_t id) {
     return std::find_if(v.begin(), v.end(), [&](const auto& p) { return p.id == id; });
@@ -119,10 +114,7 @@
             } else {
                 mAttachedSinkDevicePorts.insert(port.id);
             }
-        } else if (devicePort.device.type.connection != AudioDeviceDescription::CONNECTION_VIRTUAL
-                   // The "virtual" connection is used for remote submix which is a dynamic
-                   // device but it can be connected and used w/o external hardware.
-                   && port.profiles.empty()) {
+        } else {
             mExternalDevicePorts.insert(port.id);
         }
     }
@@ -141,6 +133,12 @@
     return result;
 }
 
+std::vector<aidl::android::media::audio::common::AudioPort>
+ModuleConfig::getAudioPortsForDeviceTypes(const std::vector<AudioDeviceType>& deviceTypes,
+                                          const std::string& connection) const {
+    return getAudioPortsForDeviceTypes(mPorts, deviceTypes, connection);
+}
+
 std::vector<AudioPort> ModuleConfig::getConnectedExternalDevicePorts() const {
     std::vector<AudioPort> result;
     std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [&](const auto& port) {
@@ -229,6 +227,16 @@
     });
 }
 
+std::vector<AudioPort> ModuleConfig::getRemoteSubmixPorts(bool isInput, bool singlePort) const {
+    AudioDeviceType deviceType = isInput ? AudioDeviceType::IN_SUBMIX : AudioDeviceType::OUT_SUBMIX;
+    auto ports = getAudioPortsForDeviceTypes(std::vector<AudioDeviceType>{deviceType},
+                                             AudioDeviceDescription::CONNECTION_VIRTUAL);
+    if (singlePort) {
+        if (!ports.empty()) ports.resize(1);
+    }
+    return ports;
+}
+
 std::vector<AudioPort> ModuleConfig::getConnectedDevicesPortsForMixPort(
         bool isInput, const AudioPortConfig& mixPortConfig) const {
     const auto mixPortIt = findById<AudioPort>(mPorts, mixPortConfig.portId);
@@ -281,19 +289,29 @@
     return {};
 }
 
-std::vector<AudioPort> ModuleConfig::getRoutableMixPortsForDevicePort(const AudioPort& port) const {
-    std::set<int32_t> portIds;
-    for (const auto& route : mRoutes) {
-        if (port.id == route.sinkPortId) {
-            portIds.insert(route.sourcePortIds.begin(), route.sourcePortIds.end());
-        } else if (auto it = std::find(route.sourcePortIds.begin(), route.sourcePortIds.end(),
-                                       port.id);
-                   it != route.sourcePortIds.end()) {
-            portIds.insert(route.sinkPortId);
-        }
-    }
+std::vector<AudioPort> ModuleConfig::getRoutableDevicePortsForMixPort(const AudioPort& port,
+                                                                      bool connectedOnly) const {
+    std::set<int32_t> portIds = findRoutablePortIds(port.id);
     const bool isInput = port.flags.getTag() == AudioIoFlags::input;
-    return findMixPorts(isInput, false /*connectedOnly*/, false /*singlePort*/,
+    std::set<int32_t> devicePortIds;
+    if (connectedOnly) {
+        devicePortIds = isInput ? getConnectedSourceDevicePorts() : getConnectedSinkDevicePorts();
+    } else {
+        devicePortIds = portIds;
+    }
+    std::vector<AudioPort> result;
+    std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [&](const auto& port) {
+        return port.ext.getTag() == AudioPortExt::Tag::device && portIds.count(port.id) > 0 &&
+               devicePortIds.count(port.id) > 0;
+    });
+    return result;
+}
+
+std::vector<AudioPort> ModuleConfig::getRoutableMixPortsForDevicePort(const AudioPort& port,
+                                                                      bool connectedOnly) const {
+    std::set<int32_t> portIds = findRoutablePortIds(port.id);
+    const bool isInput = port.flags.getTag() == AudioIoFlags::input;
+    return findMixPorts(isInput, connectedOnly, false /*singlePort*/,
                         [&portIds](const AudioPort& p) { return portIds.count(p.id) > 0; });
 }
 
@@ -470,6 +488,20 @@
     return result;
 }
 
+std::set<int32_t> ModuleConfig::findRoutablePortIds(int32_t portId) const {
+    std::set<int32_t> portIds;
+    for (const auto& route : mRoutes) {
+        if (portId == route.sinkPortId) {
+            portIds.insert(route.sourcePortIds.begin(), route.sourcePortIds.end());
+        } else if (auto it = std::find(route.sourcePortIds.begin(), route.sourcePortIds.end(),
+                                       portId);
+                   it != route.sourcePortIds.end()) {
+            portIds.insert(route.sinkPortId);
+        }
+    }
+    return portIds;
+}
+
 std::vector<AudioPortConfig> ModuleConfig::generateAudioMixPortConfigs(
         const std::vector<AudioPort>& ports, bool isInput, bool singleProfile) const {
     std::vector<AudioPortConfig> result;
diff --git a/audio/aidl/vts/ModuleConfig.h b/audio/aidl/vts/ModuleConfig.h
index b89adc0..4a87f8c 100644
--- a/audio/aidl/vts/ModuleConfig.h
+++ b/audio/aidl/vts/ModuleConfig.h
@@ -38,9 +38,6 @@
     generateOffloadInfoIfNeeded(
             const aidl::android::media::audio::common::AudioPortConfig& portConfig);
 
-    std::vector<aidl::android::media::audio::common::AudioPort> getAudioPortsForDeviceTypes(
-            const std::vector<aidl::android::media::audio::common::AudioDeviceType>& deviceTypes,
-            const std::string& connection = "");
     static std::vector<aidl::android::media::audio::common::AudioPort> getAudioPortsForDeviceTypes(
             const std::vector<aidl::android::media::audio::common::AudioPort>& ports,
             const std::vector<aidl::android::media::audio::common::AudioDeviceType>& deviceTypes,
@@ -53,6 +50,9 @@
     std::string getError() const { return mStatus.getMessage(); }
 
     std::vector<aidl::android::media::audio::common::AudioPort> getAttachedDevicePorts() const;
+    std::vector<aidl::android::media::audio::common::AudioPort> getAudioPortsForDeviceTypes(
+            const std::vector<aidl::android::media::audio::common::AudioDeviceType>& deviceTypes,
+            const std::string& connection = "") const;
     std::vector<aidl::android::media::audio::common::AudioPort> getConnectedExternalDevicePorts()
             const;
     std::set<int32_t> getConnectedSinkDevicePorts() const;
@@ -85,6 +85,8 @@
     std::vector<aidl::android::media::audio::common::AudioPort> getMmapInMixPorts(
             bool connectedOnly /*Permanently attached and connected external devices*/,
             bool singlePort) const;
+    std::vector<aidl::android::media::audio::common::AudioPort> getRemoteSubmixPorts(
+            bool isInput, bool singlePort) const;
 
     std::vector<aidl::android::media::audio::common::AudioPort> getConnectedDevicesPortsForMixPort(
             bool isInput, const aidl::android::media::audio::common::AudioPort& mixPort) const {
@@ -103,8 +105,12 @@
     std::optional<aidl::android::media::audio::common::AudioPort>
     getSourceMixPortForConnectedDevice() const;
 
+    std::vector<aidl::android::media::audio::common::AudioPort> getRoutableDevicePortsForMixPort(
+            const aidl::android::media::audio::common::AudioPort& port,
+            bool connectedOnly /*Permanently attached and connected external devices*/) const;
     std::vector<aidl::android::media::audio::common::AudioPort> getRoutableMixPortsForDevicePort(
-            const aidl::android::media::audio::common::AudioPort& port) const;
+            const aidl::android::media::audio::common::AudioPort& port,
+            bool connectedOnly /*Permanently attached and connected external devices*/) const;
 
     std::optional<SrcSinkPair> getNonRoutableSrcSinkPair(bool isInput) const;
     std::optional<SrcSinkPair> getRoutableSrcSinkPair(bool isInput) const;
@@ -176,6 +182,7 @@
             bool isInput, bool connectedOnly, bool singlePort,
             const std::function<bool(const aidl::android::media::audio::common::AudioPort&)>& pred)
             const;
+    std::set<int32_t> findRoutablePortIds(int32_t portId) const;
     std::vector<aidl::android::media::audio::common::AudioPortConfig> generateAudioMixPortConfigs(
             const std::vector<aidl::android::media::audio::common::AudioPort>& ports, bool isInput,
             bool singleProfile) const;
diff --git a/audio/aidl/vts/TestUtils.h b/audio/aidl/vts/TestUtils.h
index 191e980..e9f3251 100644
--- a/audio/aidl/vts/TestUtils.h
+++ b/audio/aidl/vts/TestUtils.h
@@ -18,7 +18,6 @@
 
 #include <algorithm>
 #include <initializer_list>
-#include <iostream>
 
 #include <android/binder_auto_utils.h>
 #include <gtest/gtest.h>
@@ -93,4 +92,4 @@
         if ((flags).hwAcceleratorMode == Flags::HardwareAccelerator::TUNNEL || (flags).bypass) { \
             GTEST_SKIP() << "Skip data path for offload";                                        \
         }                                                                                        \
-    })
\ No newline at end of file
+    })
diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
index 53e51f4..697aae9 100644
--- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
@@ -113,10 +113,23 @@
 using ndk::ScopedAStatus;
 
 template <typename T>
-auto findById(std::vector<T>& v, int32_t id) {
+std::set<int32_t> extractIds(const std::vector<T>& v) {
+    std::set<int32_t> ids;
+    std::transform(v.begin(), v.end(), std::inserter(ids, ids.begin()),
+                   [](const auto& entity) { return entity.id; });
+    return ids;
+}
+
+template <typename T>
+auto findById(const std::vector<T>& v, int32_t id) {
     return std::find_if(v.begin(), v.end(), [&](const auto& e) { return e.id == id; });
 }
 
+template <typename T>
+auto findAny(const std::vector<T>& v, const std::set<int32_t>& ids) {
+    return std::find_if(v.begin(), v.end(), [&](const auto& e) { return ids.count(e.id) > 0; });
+}
+
 template <typename C>
 std::vector<int32_t> GetNonExistentIds(const C& allIds) {
     if (allIds.empty()) {
@@ -155,8 +168,10 @@
     static int nextId = 0;
     using Tag = AudioDeviceAddress::Tag;
     const auto& deviceDescription = port.ext.get<AudioPortExt::Tag::device>().device.type;
-    AudioDeviceAddress address;
-    if (kPointToPointConnections.count(deviceDescription.connection) == 0) {
+    AudioDeviceAddress address = port.ext.get<AudioPortExt::Tag::device>().device.address;
+    // If the address is already set, do not re-generate.
+    if (address == AudioDeviceAddress() &&
+        kPointToPointConnections.count(deviceDescription.connection) == 0) {
         switch (suggestDeviceAddressTag(deviceDescription)) {
             case Tag::id:
                 address = AudioDeviceAddress::make<Tag::id>(std::to_string(++nextId));
@@ -417,18 +432,21 @@
 // Can be used as a base for any test here, does not depend on the fixture GTest parameters.
 class AudioCoreModuleBase {
   public:
-    // Default buffer sizes are used mostly for negative tests.
-    static constexpr int kDefaultBufferSizeFrames = 256;
+    // Fixed buffer size are used for negative tests only. For any tests involving stream
+    // opening that must success, the minimum buffer size must be obtained from a patch.
+    // This is implemented by the 'StreamFixture' utility class.
+    static constexpr int kNegativeTestBufferSizeFrames = 256;
     static constexpr int kDefaultLargeBufferSizeFrames = 48000;
 
-    void SetUpImpl(const std::string& moduleName) {
-        ASSERT_NO_FATAL_FAILURE(ConnectToService(moduleName));
+    void SetUpImpl(const std::string& moduleName, bool setUpDebug = true) {
+        ASSERT_NO_FATAL_FAILURE(ConnectToService(moduleName, setUpDebug));
         ASSERT_IS_OK(module->getAudioPorts(&initialPorts));
         ASSERT_IS_OK(module->getAudioRoutes(&initialRoutes));
     }
 
     void TearDownImpl() {
         debug.reset();
+        ASSERT_NE(module, nullptr);
         std::vector<AudioPort> finalPorts;
         ASSERT_IS_OK(module->getAudioPorts(&finalPorts));
         EXPECT_NO_FATAL_FAILURE(VerifyVectorsAreEqual<AudioPort>(initialPorts, finalPorts))
@@ -439,21 +457,26 @@
                 << "The list of audio routes was not restored to the initial state";
     }
 
-    void ConnectToService(const std::string& moduleName) {
+    void ConnectToService(const std::string& moduleName, bool setUpDebug) {
         ASSERT_EQ(module, nullptr);
         ASSERT_EQ(debug, nullptr);
         module = IModule::fromBinder(binderUtil.connectToService(moduleName));
         ASSERT_NE(module, nullptr);
-        ASSERT_NO_FATAL_FAILURE(SetUpDebug());
+        if (setUpDebug) {
+            ASSERT_NO_FATAL_FAILURE(SetUpDebug());
+        }
     }
 
     void RestartService() {
         ASSERT_NE(module, nullptr);
         moduleConfig.reset();
+        const bool setUpDebug = !!debug;
         debug.reset();
         module = IModule::fromBinder(binderUtil.restartService());
         ASSERT_NE(module, nullptr);
-        ASSERT_NO_FATAL_FAILURE(SetUpDebug());
+        if (setUpDebug) {
+            ASSERT_NO_FATAL_FAILURE(SetUpDebug());
+        }
     }
 
     void SetUpDebug() {
@@ -493,9 +516,7 @@
                          const std::string& errorMessage) {
         std::vector<Entity> entities;
         { ASSERT_IS_OK((module.get()->*getter)(&entities)); }
-        std::transform(entities.begin(), entities.end(),
-                       std::inserter(*entityIds, entityIds->begin()),
-                       [](const auto& entity) { return entity.id; });
+        *entityIds = extractIds<Entity>(entities);
         EXPECT_EQ(entities.size(), entityIds->size()) << errorMessage;
     }
 
@@ -1112,6 +1133,7 @@
 template <typename T>
 struct IOTraits {
     static constexpr bool is_input = std::is_same_v<T, IStreamIn>;
+    static constexpr const char* directionStr = is_input ? "input" : "output";
     using Worker = std::conditional_t<is_input, StreamReader, StreamWriter>;
 };
 
@@ -1143,8 +1165,7 @@
     }
     ScopedAStatus SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig,
                                 long bufferSizeFrames);
-    void SetUp(IModule* module, long bufferSizeFrames) {
-        ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module));
+    void SetUpStream(IModule* module, long bufferSizeFrames) {
         ASSERT_IS_OK(SetUpNoChecks(module, bufferSizeFrames)) << "port config id " << getPortId();
         ASSERT_NE(nullptr, mStream) << "port config id " << getPortId();
         EXPECT_GE(mDescriptor.bufferSizeFrames, bufferSizeFrames)
@@ -1152,6 +1173,10 @@
         mContext.emplace(mDescriptor);
         ASSERT_NO_FATAL_FAILURE(mContext.value().checkIsValid());
     }
+    void SetUp(IModule* module, long bufferSizeFrames) {
+        ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module));
+        ASSERT_NO_FATAL_FAILURE(SetUpStream(module, bufferSizeFrames));
+    }
     Stream* get() const { return mStream.get(); }
     const StreamContext* getContext() const { return mContext ? &(mContext.value()) : nullptr; }
     StreamEventReceiver* getEventReceiver() { return mStreamCallback->getEventReceiver(); }
@@ -1291,6 +1316,9 @@
     }
     int32_t getId() const { return mPatch.id; }
     const AudioPatch& get() const { return mPatch; }
+    int32_t getMinimumStreamBufferSizeFrames() const {
+        return mPatch.minimumStreamBufferSizeFrames;
+    }
     const AudioPortConfig& getSinkPortConfig() const { return mSinkPortConfig.get(); }
     const AudioPortConfig& getSrcPortConfig() const { return mSrcPortConfig.get(); }
     const AudioPortConfig& getPortConfig(bool getSink) const {
@@ -1503,8 +1531,8 @@
         EXPECT_EQ(portConnected.get(), connectedPort);
         const auto& portProfiles = connectedPort.profiles;
         if (portProfiles.empty()) {
-            const auto routableMixPorts =
-                    moduleConfig->getRoutableMixPortsForDevicePort(connectedPort);
+            const auto routableMixPorts = moduleConfig->getRoutableMixPortsForDevicePort(
+                    connectedPort, true /*connectedOnly*/);
             bool hasMixPortWithStaticProfile = false;
             for (const auto& mixPort : routableMixPorts) {
                 const auto& mixPortProfiles = mixPort.profiles;
@@ -1545,7 +1573,7 @@
         {
             aidl::android::hardware::audio::core::IModule::OpenInputStreamArguments args;
             args.portConfigId = portConfigId;
-            args.bufferSizeFrames = kDefaultBufferSizeFrames;
+            args.bufferSizeFrames = kNegativeTestBufferSizeFrames;
             aidl::android::hardware::audio::core::IModule::OpenInputStreamReturn ret;
             EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openInputStream(args, &ret))
                     << "port config ID " << portConfigId;
@@ -1554,7 +1582,7 @@
         {
             aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
             args.portConfigId = portConfigId;
-            args.bufferSizeFrames = kDefaultBufferSizeFrames;
+            args.bufferSizeFrames = kNegativeTestBufferSizeFrames;
             aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
             EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret))
                     << "port config ID " << portConfigId;
@@ -1717,6 +1745,10 @@
     doNotSimulateConnections.flags().simulateDeviceConnections = false;
     ASSERT_NO_FATAL_FAILURE(doNotSimulateConnections.SetUp(module.get()));
     for (const auto& port : ports) {
+        // Virtual devices may not require external hardware and thus can always be connected.
+        if (port.ext.get<AudioPortExt::device>().device.type.connection ==
+            AudioDeviceDescription::CONNECTION_VIRTUAL)
+            continue;
         AudioPort portWithData = GenerateUniqueDeviceAddress(port), connectedPort;
         ScopedAStatus status = module->connectExternalDevice(portWithData, &connectedPort);
         EXPECT_STATUS(EX_ILLEGAL_STATE, status) << "static port " << portWithData.toString();
@@ -2563,6 +2595,260 @@
     std::vector<std::string> mStatuses;
 };
 
+// A helper which sets up necessary HAL structures for a proper stream initialization.
+//
+// The full sequence of actions to set up a stream is as follows:
+//
+//  device port -> connect if necessary -> set up port config   | -> set up patch
+//  mix port -> set up port config, unless it has been provided |
+//
+//  then, from the patch, figure out the minimum HAL buffer size -> set up stream
+//
+// This sequence is reflected in the order of fields declaration.
+// Various tests need to be able to start and stop at various point in this sequence,
+// this is why there are methods that do just part of the work.
+//
+// Note: To maximize test coverage, this class relies on simulation of external device
+// connections by the HAL module.
+template <typename Stream>
+class StreamFixture {
+  public:
+    // Tests might need to override the direction.
+    StreamFixture(bool isInput = IOTraits<Stream>::is_input) : mIsInput(isInput) {}
+
+    void SetUpPortConfigAnyMixPort(IModule* module, ModuleConfig* moduleConfig,
+                                   bool connectedOnly) {
+        const auto mixPorts = moduleConfig->getMixPorts(mIsInput, connectedOnly);
+        mSkipTestReason = "No mix ports";
+        for (const auto& mixPort : mixPorts) {
+            mSkipTestReason = "";
+            ASSERT_NO_FATAL_FAILURE(SetUpPortConfigForMixPortOrConfig(module, moduleConfig, mixPort,
+                                                                      connectedOnly));
+            if (mSkipTestReason.empty()) break;
+        }
+    }
+
+    void SetUpPortConfigForMixPortOrConfig(
+            IModule* module, ModuleConfig* moduleConfig, const AudioPort& initialMixPort,
+            bool connectedOnly, const std::optional<AudioPortConfig>& mixPortConfig = {}) {
+        if (mixPortConfig.has_value() && !connectedOnly) {
+            // Connecting an external device may cause change in mix port profiles and the provided
+            // config may become invalid.
+            LOG(FATAL) << __func__ << ": when specifying a mix port config, it is not allowed "
+                       << "to change connected devices, thus `connectedOnly` must be `true`";
+        }
+        std::optional<AudioPort> connectedDevicePort;
+        ASSERT_NO_FATAL_FAILURE(SetUpDevicePortForMixPort(module, moduleConfig, initialMixPort,
+                                                          connectedOnly, &connectedDevicePort));
+        if (!mSkipTestReason.empty()) return;
+        if (mixPortConfig.has_value()) {
+            ASSERT_NO_FATAL_FAILURE(
+                    SetUpPortConfig(module, moduleConfig, *mixPortConfig, *connectedDevicePort));
+        } else {
+            // If an external device was connected, the profiles of the mix port might have changed.
+            AudioPort mixPort;
+            ASSERT_NO_FATAL_FAILURE(module->getAudioPort(initialMixPort.id, &mixPort));
+            ASSERT_NO_FATAL_FAILURE(
+                    SetUpPortConfig(module, moduleConfig, mixPort, *connectedDevicePort));
+        }
+    }
+
+    void SetUpPortConfig(IModule* module, ModuleConfig* moduleConfig, const AudioPort& mixPort,
+                         const AudioPort& devicePort) {
+        auto mixPortConfig = moduleConfig->getSingleConfigForMixPort(mIsInput, mixPort);
+        ASSERT_TRUE(mixPortConfig.has_value())
+                << "Unable to generate port config for mix port " << mixPort.toString();
+        ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module, moduleConfig, *mixPortConfig, devicePort));
+    }
+    void SetUpPortConfig(IModule* module, ModuleConfig* moduleConfig,
+                         const AudioPortConfig& mixPortConfig, const AudioPort& devicePort) {
+        ASSERT_NO_FATAL_FAILURE(SetUpPatch(module, moduleConfig, mixPortConfig, devicePort));
+        mStream = std::make_unique<WithStream<Stream>>(mMixPortConfig->get());
+        ASSERT_NO_FATAL_FAILURE(mStream->SetUpPortConfig(module));
+    }
+
+    ScopedAStatus SetUpStreamNoChecks(IModule* module) {
+        return mStream->SetUpNoChecks(module, getMinimumStreamBufferSizeFrames());
+    }
+    void SetUpStream(IModule* module) {
+        ASSERT_NO_FATAL_FAILURE(mStream->SetUpStream(module, getMinimumStreamBufferSizeFrames()));
+    }
+
+    void SetUpStreamForDevicePort(IModule* module, ModuleConfig* moduleConfig,
+                                  const AudioPort& devicePort, bool connectedOnly = false) {
+        ASSERT_NO_FATAL_FAILURE(
+                SetUpPortConfigForDevicePort(module, moduleConfig, devicePort, connectedOnly));
+        if (!mSkipTestReason.empty()) return;
+        ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
+    }
+    void SetUpStreamForAnyMixPort(IModule* module, ModuleConfig* moduleConfig,
+                                  bool connectedOnly = false) {
+        ASSERT_NO_FATAL_FAILURE(SetUpPortConfigAnyMixPort(module, moduleConfig, connectedOnly));
+        if (!mSkipTestReason.empty()) return;
+        ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
+    }
+    void SetUpStreamForMixPort(IModule* module, ModuleConfig* moduleConfig,
+                               const AudioPort& mixPort, bool connectedOnly = false) {
+        ASSERT_NO_FATAL_FAILURE(
+                SetUpPortConfigForMixPortOrConfig(module, moduleConfig, mixPort, connectedOnly));
+        if (!mSkipTestReason.empty()) return;
+        ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
+    }
+    void SetUpStreamForPortsPair(IModule* module, ModuleConfig* moduleConfig,
+                                 const AudioPort& mixPort, const AudioPort& devicePort) {
+        ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module, moduleConfig, mixPort, devicePort));
+        if (!mSkipTestReason.empty()) return;
+        ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
+    }
+    void SetUpStreamForMixPortConfig(IModule* module, ModuleConfig* moduleConfig,
+                                     const AudioPortConfig& mixPortConfig) {
+        // Since mix port configs may change after connecting an external device,
+        // only connected device ports are considered.
+        constexpr bool connectedOnly = true;
+        const auto& ports = moduleConfig->getMixPorts(mIsInput, connectedOnly);
+        const auto mixPortIt = findById<AudioPort>(ports, mixPortConfig.portId);
+        ASSERT_NE(mixPortIt, ports.end()) << "Port id " << mixPortConfig.portId << " not found";
+        ASSERT_NO_FATAL_FAILURE(SetUpPortConfigForMixPortOrConfig(module, moduleConfig, *mixPortIt,
+                                                                  connectedOnly, mixPortConfig));
+        if (!mSkipTestReason.empty()) return;
+        ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
+    }
+    void SetUpPatchForMixPortConfig(IModule* module, ModuleConfig* moduleConfig,
+                                    const AudioPortConfig& mixPortConfig) {
+        constexpr bool connectedOnly = true;
+        const auto& ports = moduleConfig->getMixPorts(mIsInput, connectedOnly);
+        const auto mixPortIt = findById<AudioPort>(ports, mixPortConfig.portId);
+        ASSERT_NE(mixPortIt, ports.end()) << "Port id " << mixPortConfig.portId << " not found";
+        std::optional<AudioPort> connectedDevicePort;
+        ASSERT_NO_FATAL_FAILURE(SetUpDevicePortForMixPort(module, moduleConfig, *mixPortIt,
+                                                          connectedOnly, &connectedDevicePort));
+        if (!mSkipTestReason.empty()) return;
+        ASSERT_NO_FATAL_FAILURE(
+                SetUpPatch(module, moduleConfig, mixPortConfig, *connectedDevicePort));
+    }
+
+    void ReconnectPatch(IModule* module) {
+        mPatch = std::make_unique<WithAudioPatch>(mIsInput, mMixPortConfig->get(),
+                                                  mDevicePortConfig->get());
+        ASSERT_NO_FATAL_FAILURE(mPatch->SetUp(module));
+    }
+    void TeardownPatch() { mPatch.reset(); }
+    // Assuming that the patch is set up, while the stream isn't yet,
+    // tear the patch down and set up stream.
+    void TeardownPatchSetUpStream(IModule* module) {
+        const int32_t bufferSize = getMinimumStreamBufferSizeFrames();
+        ASSERT_NO_FATAL_FAILURE(TeardownPatch());
+        mStream = std::make_unique<WithStream<Stream>>(mMixPortConfig->get());
+        ASSERT_NO_FATAL_FAILURE(mStream->SetUpPortConfig(module));
+        ASSERT_NO_FATAL_FAILURE(mStream->SetUpStream(module, bufferSize));
+    }
+
+    const AudioDevice& getDevice() const { return mDevice; }
+    int32_t getMinimumStreamBufferSizeFrames() const {
+        return mPatch->getMinimumStreamBufferSizeFrames();
+    }
+    const AudioPatch& getPatch() const { return mPatch->get(); }
+    const AudioPortConfig& getPortConfig() const { return mMixPortConfig->get(); }
+    int32_t getPortId() const { return mMixPortConfig->getId(); }
+    Stream* getStream() const { return mStream->get(); }
+    const StreamContext* getStreamContext() const { return mStream->getContext(); }
+    StreamEventReceiver* getStreamEventReceiver() { return mStream->getEventReceiver(); }
+    std::shared_ptr<Stream> getStreamSharedPointer() const { return mStream->getSharedPointer(); }
+    const std::string& skipTestReason() const { return mSkipTestReason; }
+
+  private:
+    void SetUpDevicePort(IModule* module, ModuleConfig* moduleConfig,
+                         const std::set<int32_t>& devicePortIds, bool connectedOnly,
+                         std::optional<AudioPort>* connectedDevicePort) {
+        const auto attachedDevicePorts = moduleConfig->getAttachedDevicePorts();
+        if (auto it = findAny<AudioPort>(attachedDevicePorts, devicePortIds);
+            it != attachedDevicePorts.end()) {
+            *connectedDevicePort = *it;
+            LOG(DEBUG) << __func__ << ": found attached port " << it->toString();
+        }
+        const auto connectedDevicePorts = moduleConfig->getConnectedExternalDevicePorts();
+        if (auto it = findAny<AudioPort>(connectedDevicePorts, devicePortIds);
+            it != connectedDevicePorts.end()) {
+            *connectedDevicePort = *it;
+            LOG(DEBUG) << __func__ << ": found connected port " << it->toString();
+        }
+        if (!connectedOnly && !connectedDevicePort->has_value()) {
+            const auto externalDevicePorts = moduleConfig->getExternalDevicePorts();
+            if (auto it = findAny<AudioPort>(externalDevicePorts, devicePortIds);
+                it != externalDevicePorts.end()) {
+                AudioPort portWithData = GenerateUniqueDeviceAddress(*it);
+                mPortConnected = std::make_unique<WithDevicePortConnectedState>(portWithData);
+                ASSERT_NO_FATAL_FAILURE(mPortConnected->SetUp(module, moduleConfig));
+                *connectedDevicePort = mPortConnected->get();
+                LOG(DEBUG) << __func__ << ": connected port " << mPortConnected->get().toString();
+            }
+        }
+    }
+    void SetUpDevicePortForMixPort(IModule* module, ModuleConfig* moduleConfig,
+                                   const AudioPort& mixPort, bool connectedOnly,
+                                   std::optional<AudioPort>* connectedDevicePort) {
+        const auto devicePorts =
+                moduleConfig->getRoutableDevicePortsForMixPort(mixPort, connectedOnly);
+        if (devicePorts.empty()) {
+            mSkipTestReason = std::string("No routable device ports found for mix port id ")
+                                      .append(std::to_string(mixPort.id));
+            LOG(DEBUG) << __func__ << ": " << mSkipTestReason;
+            return;
+        };
+        ASSERT_NO_FATAL_FAILURE(SetUpDevicePort(module, moduleConfig,
+                                                extractIds<AudioPort>(devicePorts), connectedOnly,
+                                                connectedDevicePort));
+        if (!connectedDevicePort->has_value()) {
+            mSkipTestReason = std::string("Unable to find a device port pair for mix port id ")
+                                      .append(std::to_string(mixPort.id));
+            LOG(DEBUG) << __func__ << ": " << mSkipTestReason;
+            return;
+        }
+    }
+    void SetUpPortConfigForDevicePort(IModule* module, ModuleConfig* moduleConfig,
+                                      const AudioPort& devicePort, bool connectedOnly) {
+        std::optional<AudioPort> connectedDevicePort;
+        ASSERT_NO_FATAL_FAILURE(SetUpDevicePort(module, moduleConfig, {devicePort.id},
+                                                connectedOnly, &connectedDevicePort));
+        if (!connectedDevicePort.has_value()) {
+            mSkipTestReason = std::string("Device port id ")
+                                      .append(std::to_string(devicePort.id))
+                                      .append(" is not attached and can not be connected");
+            return;
+        }
+        const auto mixPorts = moduleConfig->getRoutableMixPortsForDevicePort(
+                *connectedDevicePort, true /*connectedOnly*/);
+        if (mixPorts.empty()) {
+            mSkipTestReason = std::string("No routable mix ports found for device port id ")
+                                      .append(std::to_string(devicePort.id));
+            return;
+        }
+        ASSERT_NO_FATAL_FAILURE(
+                SetUpPortConfig(module, moduleConfig, *mixPorts.begin(), *connectedDevicePort));
+    }
+    void SetUpPatch(IModule* module, ModuleConfig* moduleConfig,
+                    const AudioPortConfig& mixPortConfig, const AudioPort& devicePort) {
+        mMixPortConfig = std::make_unique<WithAudioPortConfig>(mixPortConfig);
+        ASSERT_NO_FATAL_FAILURE(mMixPortConfig->SetUp(module));
+        mDevicePortConfig = std::make_unique<WithAudioPortConfig>(
+                moduleConfig->getSingleConfigForDevicePort(devicePort));
+        ASSERT_NO_FATAL_FAILURE(mDevicePortConfig->SetUp(module));
+        mDevice = devicePort.ext.get<AudioPortExt::device>().device;
+        mPatch = std::make_unique<WithAudioPatch>(mIsInput, mMixPortConfig->get(),
+                                                  mDevicePortConfig->get());
+        ASSERT_NO_FATAL_FAILURE(mPatch->SetUp(module));
+    }
+
+    const bool mIsInput;
+    std::string mSkipTestReason;
+    std::unique_ptr<WithDevicePortConnectedState> mPortConnected;
+    AudioDevice mDevice;
+    std::unique_ptr<WithAudioPortConfig> mMixPortConfig;
+    std::unique_ptr<WithAudioPortConfig> mDevicePortConfig;
+    std::unique_ptr<WithAudioPatch> mPatch;
+    std::unique_ptr<WithStream<Stream>> mStream;
+};
+
 template <typename Stream>
 class AudioStream : public AudioCoreModule {
   public:
@@ -2572,16 +2858,15 @@
     }
 
     void GetStreamCommon() {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            GTEST_SKIP() << "No mix port for attached devices";
+        StreamFixture<Stream> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+        if (auto reason = stream.skipTestReason(); !reason.empty()) {
+            GTEST_SKIP() << reason;
         }
-        WithStream<Stream> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
         std::shared_ptr<IStreamCommon> streamCommon1;
-        EXPECT_IS_OK(stream.get()->getStreamCommon(&streamCommon1));
+        EXPECT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon1));
         std::shared_ptr<IStreamCommon> streamCommon2;
-        EXPECT_IS_OK(stream.get()->getStreamCommon(&streamCommon2));
+        EXPECT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon2));
         ASSERT_NE(nullptr, streamCommon1);
         ASSERT_NE(nullptr, streamCommon2);
         EXPECT_EQ(streamCommon1->asBinder(), streamCommon2->asBinder())
@@ -2589,31 +2874,31 @@
     }
 
     void CloseTwice() {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            GTEST_SKIP() << "No mix port for attached devices";
-        }
         std::shared_ptr<Stream> heldStream;
         {
-            WithStream<Stream> stream(portConfig.value());
-            ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
-            heldStream = stream.getSharedPointer();
+            StreamFixture<Stream> stream;
+            ASSERT_NO_FATAL_FAILURE(
+                    stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+            if (auto reason = stream.skipTestReason(); !reason.empty()) {
+                GTEST_SKIP() << reason;
+            }
+            heldStream = stream.getStreamSharedPointer();
         }
         EXPECT_STATUS(EX_ILLEGAL_STATE, WithStream<Stream>::callClose(heldStream))
                 << "when closing the stream twice";
     }
 
     void PrepareToCloseTwice() {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            GTEST_SKIP() << "No mix port for attached devices";
-        }
         std::shared_ptr<IStreamCommon> heldStreamCommon;
         {
-            WithStream<Stream> stream(portConfig.value());
-            ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+            StreamFixture<Stream> stream;
+            ASSERT_NO_FATAL_FAILURE(
+                    stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+            if (auto reason = stream.skipTestReason(); !reason.empty()) {
+                GTEST_SKIP() << reason;
+            }
             std::shared_ptr<IStreamCommon> streamCommon;
-            ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon));
+            ASSERT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon));
             heldStreamCommon = streamCommon;
             EXPECT_IS_OK(streamCommon->prepareToClose());
             EXPECT_IS_OK(streamCommon->prepareToClose())
@@ -2626,9 +2911,13 @@
     void OpenAllConfigs() {
         const auto allPortConfigs =
                 moduleConfig->getPortConfigsForMixPorts(IOTraits<Stream>::is_input);
+        if (allPortConfigs.empty()) {
+            GTEST_SKIP() << "No mix ports for attached devices";
+        }
         for (const auto& portConfig : allPortConfigs) {
-            WithStream<Stream> stream(portConfig);
-            ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+            StreamFixture<Stream> stream;
+            ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPortConfig(
+                    module.get(), moduleConfig.get(), portConfig));
         }
     }
 
@@ -2648,22 +2937,21 @@
 
     void OpenInvalidDirection() {
         // Important! The direction of the port config must be reversed.
-        const auto portConfig =
-                moduleConfig->getSingleConfigForMixPort(!IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            GTEST_SKIP() << "No mix port for attached devices";
+        StreamFixture<Stream> stream(!IOTraits<Stream>::is_input);
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfigAnyMixPort(module.get(), moduleConfig.get(),
+                                                                 false /*connectedOnly*/));
+        if (auto reason = stream.skipTestReason(); !reason.empty()) {
+            GTEST_SKIP() << reason;
         }
-        WithStream<Stream> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get()));
-        EXPECT_STATUS(EX_ILLEGAL_ARGUMENT,
-                      stream.SetUpNoChecks(module.get(), kDefaultBufferSizeFrames))
+        EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, stream.SetUpStreamNoChecks(module.get()))
                 << "port config ID " << stream.getPortId();
-        EXPECT_EQ(nullptr, stream.get());
+        EXPECT_EQ(nullptr, stream.getStream());
     }
 
     void OpenOverMaxCount() {
+        constexpr bool connectedOnly = true;
         constexpr bool isInput = IOTraits<Stream>::is_input;
-        auto ports = moduleConfig->getMixPorts(isInput, true /*connectedOnly*/);
+        auto ports = moduleConfig->getMixPorts(isInput, connectedOnly);
         bool hasSingleRun = false;
         for (const auto& port : ports) {
             const size_t maxStreamCount = port.ext.get<AudioPortExt::Tag::mix>().maxOpenStreamCount;
@@ -2676,16 +2964,16 @@
                 continue;
             }
             hasSingleRun = true;
-            std::optional<WithStream<Stream>> streamWraps[maxStreamCount + 1];
+            StreamFixture<Stream> streams[maxStreamCount + 1];
             for (size_t i = 0; i <= maxStreamCount; ++i) {
-                streamWraps[i].emplace(portConfigs[i]);
-                WithStream<Stream>& stream = streamWraps[i].value();
+                ASSERT_NO_FATAL_FAILURE(streams[i].SetUpPortConfigForMixPortOrConfig(
+                        module.get(), moduleConfig.get(), port, connectedOnly, portConfigs[i]));
+                ASSERT_EQ("", streams[i].skipTestReason());
+                auto& stream = streams[i];
                 if (i < maxStreamCount) {
-                    ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+                    ASSERT_NO_FATAL_FAILURE(stream.SetUpStream(module.get()));
                 } else {
-                    ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get()));
-                    EXPECT_STATUS(EX_ILLEGAL_STATE,
-                                  stream.SetUpNoChecks(module.get(), kDefaultBufferSizeFrames))
+                    EXPECT_STATUS(EX_ILLEGAL_STATE, stream.SetUpStreamNoChecks(module.get()))
                             << "port config ID " << stream.getPortId() << ", maxOpenStreamCount is "
                             << maxStreamCount;
                 }
@@ -2705,12 +2993,11 @@
     }
 
     void ResetPortConfigWithOpenStream() {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            GTEST_SKIP() << "No mix port for attached devices";
+        StreamFixture<Stream> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+        if (auto reason = stream.skipTestReason(); !reason.empty()) {
+            GTEST_SKIP() << reason;
         }
-        WithStream<Stream> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
         EXPECT_STATUS(EX_ILLEGAL_STATE, module->resetAudioPortConfig(stream.getPortId()))
                 << "port config ID " << stream.getPortId();
     }
@@ -2724,14 +3011,13 @@
     }
 
     void UpdateHwAvSyncId() {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            GTEST_SKIP() << "No mix port for attached devices";
+        StreamFixture<Stream> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+        if (auto reason = stream.skipTestReason(); !reason.empty()) {
+            GTEST_SKIP() << reason;
         }
-        WithStream<Stream> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
         std::shared_ptr<IStreamCommon> streamCommon;
-        ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon));
+        ASSERT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon));
         ASSERT_NE(nullptr, streamCommon);
         const auto kStatuses = {EX_NONE, EX_ILLEGAL_ARGUMENT, EX_ILLEGAL_STATE};
         for (const auto id : {-100, -1, 0, 1, 100}) {
@@ -2744,14 +3030,13 @@
     }
 
     void GetVendorParameters() {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            GTEST_SKIP() << "No mix port for attached devices";
+        StreamFixture<Stream> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+        if (auto reason = stream.skipTestReason(); !reason.empty()) {
+            GTEST_SKIP() << reason;
         }
-        WithStream<Stream> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
         std::shared_ptr<IStreamCommon> streamCommon;
-        ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon));
+        ASSERT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon));
         ASSERT_NE(nullptr, streamCommon);
 
         bool isGetterSupported = false;
@@ -2765,14 +3050,13 @@
     }
 
     void SetVendorParameters() {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            GTEST_SKIP() << "No mix port for attached devices";
+        StreamFixture<Stream> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+        if (auto reason = stream.skipTestReason(); !reason.empty()) {
+            GTEST_SKIP() << reason;
         }
-        WithStream<Stream> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
         std::shared_ptr<IStreamCommon> streamCommon;
-        ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon));
+        ASSERT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon));
         ASSERT_NE(nullptr, streamCommon);
 
         bool isSupported = false;
@@ -2783,32 +3067,37 @@
     }
 
     void HwGainHwVolume() {
-        const auto ports =
-                moduleConfig->getMixPorts(IOTraits<Stream>::is_input, true /*connectedOnly*/);
+        // Since device connection emulation does not cover complete functionality,
+        // only use this test with connected devices.
+        constexpr bool connectedOnly = true;
+        const auto ports = moduleConfig->getMixPorts(IOTraits<Stream>::is_input, connectedOnly);
         if (ports.empty()) {
             GTEST_SKIP() << "No mix ports";
         }
         bool atLeastOneSupports = false;
         for (const auto& port : ports) {
-            const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port);
-            if (!portConfig.has_value()) continue;
-            WithStream<Stream> stream(portConfig.value());
-            ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+            SCOPED_TRACE(port.toString());
+            StreamFixture<Stream> stream;
+            ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(),
+                                                                 port, connectedOnly));
+            if (!stream.skipTestReason().empty()) continue;
+            const auto portConfig = stream.getPortConfig();
+            SCOPED_TRACE(portConfig.toString());
             std::vector<std::vector<float>> validValues, invalidValues;
             bool isSupported = false;
             if constexpr (IOTraits<Stream>::is_input) {
-                GenerateTestArrays<float>(getChannelCount(portConfig.value().channelMask.value()),
+                GenerateTestArrays<float>(getChannelCount(portConfig.channelMask.value()),
                                           IStreamIn::HW_GAIN_MIN, IStreamIn::HW_GAIN_MAX,
                                           &validValues, &invalidValues);
                 EXPECT_NO_FATAL_FAILURE(TestAccessors<std::vector<float>>(
-                        stream.get(), &IStreamIn::getHwGain, &IStreamIn::setHwGain, validValues,
-                        invalidValues, &isSupported));
+                        stream.getStream(), &IStreamIn::getHwGain, &IStreamIn::setHwGain,
+                        validValues, invalidValues, &isSupported));
             } else {
-                GenerateTestArrays<float>(getChannelCount(portConfig.value().channelMask.value()),
+                GenerateTestArrays<float>(getChannelCount(portConfig.channelMask.value()),
                                           IStreamOut::HW_VOLUME_MIN, IStreamOut::HW_VOLUME_MAX,
                                           &validValues, &invalidValues);
                 EXPECT_NO_FATAL_FAILURE(TestAccessors<std::vector<float>>(
-                        stream.get(), &IStreamOut::getHwVolume, &IStreamOut::setHwVolume,
+                        stream.getStream(), &IStreamOut::getHwVolume, &IStreamOut::setHwVolume,
                         validValues, invalidValues, &isSupported));
             }
             if (isSupported) atLeastOneSupports = true;
@@ -2822,19 +3111,22 @@
     // currently we can only pass a nullptr, and the HAL module must either reject
     // it as an invalid argument, or say that offloaded effects are not supported.
     void AddRemoveEffectInvalidArguments() {
-        const auto ports =
-                moduleConfig->getMixPorts(IOTraits<Stream>::is_input, true /*connectedOnly*/);
+        constexpr bool connectedOnly = true;
+        const auto ports = moduleConfig->getMixPorts(IOTraits<Stream>::is_input, connectedOnly);
         if (ports.empty()) {
             GTEST_SKIP() << "No mix ports";
         }
         bool atLeastOneSupports = false;
         for (const auto& port : ports) {
-            const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port);
-            if (!portConfig.has_value()) continue;
-            WithStream<Stream> stream(portConfig.value());
-            ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+            SCOPED_TRACE(port.toString());
+            StreamFixture<Stream> stream;
+            ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(),
+                                                                 port, connectedOnly));
+            if (!stream.skipTestReason().empty()) continue;
+            const auto portConfig = stream.getPortConfig();
+            SCOPED_TRACE(portConfig.toString());
             std::shared_ptr<IStreamCommon> streamCommon;
-            ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon));
+            ASSERT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon));
             ASSERT_NE(nullptr, streamCommon);
             ndk::ScopedAStatus addEffectStatus = streamCommon->addEffect(nullptr);
             ndk::ScopedAStatus removeEffectStatus = streamCommon->removeEffect(nullptr);
@@ -2854,11 +3146,14 @@
     }
 
     void OpenTwiceSamePortConfigImpl(const AudioPortConfig& portConfig) {
-        WithStream<Stream> stream1(portConfig);
-        ASSERT_NO_FATAL_FAILURE(stream1.SetUp(module.get(), kDefaultBufferSizeFrames));
+        StreamFixture<Stream> stream1;
+        ASSERT_NO_FATAL_FAILURE(
+                stream1.SetUpStreamForMixPortConfig(module.get(), moduleConfig.get(), portConfig));
+        ASSERT_EQ("", stream1.skipTestReason());
         WithStream<Stream> stream2;
-        EXPECT_STATUS(EX_ILLEGAL_STATE, stream2.SetUpNoChecks(module.get(), stream1.getPortConfig(),
-                                                              kDefaultBufferSizeFrames))
+        EXPECT_STATUS(EX_ILLEGAL_STATE,
+                      stream2.SetUpNoChecks(module.get(), stream1.getPortConfig(),
+                                            stream1.getMinimumStreamBufferSizeFrames()))
                 << "when opening a stream twice for the same port config ID "
                 << stream1.getPortId();
     }
@@ -2893,11 +3188,13 @@
         for (const auto& seq : sequences) {
             SCOPED_TRACE(std::string("Sequence ").append(seq.first));
             LOG(DEBUG) << __func__ << ": Sequence " << seq.first;
-            WithStream<Stream> stream(portConfig);
-            ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+            StreamFixture<Stream> stream;
+            ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPortConfig(
+                    module.get(), moduleConfig.get(), portConfig));
+            ASSERT_EQ("", stream.skipTestReason());
             StreamLogicDriverInvalidCommand driver(seq.second);
-            typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
-                                                     stream.getEventReceiver());
+            typename IOTraits<Stream>::Worker worker(*stream.getStreamContext(), &driver,
+                                                     stream.getStreamEventReceiver());
             LOG(DEBUG) << __func__ << ": starting worker...";
             ASSERT_TRUE(worker.start());
             LOG(DEBUG) << __func__ << ": joining worker...";
@@ -2950,63 +3247,59 @@
     if (ports.empty()) {
         GTEST_SKIP() << "No input mix ports for attached devices";
     }
+    bool atLeastOnePort = false;
     for (const auto& port : ports) {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port);
-        ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for input mix port";
-        WithStream<IStreamIn> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
-        {
-            // The port of the stream is not connected, thus the list of active mics must be empty.
-            std::vector<MicrophoneDynamicInfo> activeMics;
-            EXPECT_IS_OK(stream.get()->getActiveMicrophones(&activeMics));
-            EXPECT_TRUE(activeMics.empty()) << "a stream on an unconnected port returns a "
-                                               "non-empty list of active microphones";
+        auto micDevicePorts = ModuleConfig::getBuiltInMicPorts(
+                moduleConfig->getConnectedSourceDevicesPortsForMixPort(port));
+        if (micDevicePorts.empty()) continue;
+        atLeastOnePort = true;
+        SCOPED_TRACE(port.toString());
+        StreamFixture<IStreamIn> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForPortsPair(module.get(), moduleConfig.get(),
+                                                               port, micDevicePorts[0]));
+        if (!stream.skipTestReason().empty()) continue;
+        std::vector<MicrophoneDynamicInfo> activeMics;
+        EXPECT_IS_OK(stream.getStream()->getActiveMicrophones(&activeMics));
+        EXPECT_FALSE(activeMics.empty());
+        for (const auto& mic : activeMics) {
+            EXPECT_NE(micInfos.end(),
+                      std::find_if(micInfos.begin(), micInfos.end(),
+                                   [&](const auto& micInfo) { return micInfo.id == mic.id; }))
+                    << "active microphone \"" << mic.id << "\" is not listed in "
+                    << "microphone infos returned by the module: "
+                    << ::android::internal::ToString(micInfos);
+            EXPECT_NE(0UL, mic.channelMapping.size())
+                    << "No channels specified for the microphone \"" << mic.id << "\"";
         }
-        if (auto micDevicePorts = ModuleConfig::getBuiltInMicPorts(
-                    moduleConfig->getConnectedSourceDevicesPortsForMixPort(port));
-            !micDevicePorts.empty()) {
-            auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(micDevicePorts[0]);
-            WithAudioPatch patch(true /*isInput*/, stream.getPortConfig(), devicePortConfig);
-            ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
-            std::vector<MicrophoneDynamicInfo> activeMics;
-            EXPECT_IS_OK(stream.get()->getActiveMicrophones(&activeMics));
-            EXPECT_FALSE(activeMics.empty());
-            for (const auto& mic : activeMics) {
-                EXPECT_NE(micInfos.end(),
-                          std::find_if(micInfos.begin(), micInfos.end(),
-                                       [&](const auto& micInfo) { return micInfo.id == mic.id; }))
-                        << "active microphone \"" << mic.id << "\" is not listed in "
-                        << "microphone infos returned by the module: "
-                        << ::android::internal::ToString(micInfos);
-                EXPECT_NE(0UL, mic.channelMapping.size())
-                        << "No channels specified for the microphone \"" << mic.id << "\"";
-            }
-        }
-        {
-            // Now the port of the stream is not connected again, re-check that there are no
-            // active microphones.
-            std::vector<MicrophoneDynamicInfo> activeMics;
-            EXPECT_IS_OK(stream.get()->getActiveMicrophones(&activeMics));
-            EXPECT_TRUE(activeMics.empty()) << "a stream on an unconnected port returns a "
-                                               "non-empty list of active microphones";
-        }
+        stream.TeardownPatch();
+        // Now the port of the stream is not connected, check that there are no active microphones.
+        std::vector<MicrophoneDynamicInfo> emptyMics;
+        EXPECT_IS_OK(stream.getStream()->getActiveMicrophones(&emptyMics));
+        EXPECT_TRUE(emptyMics.empty()) << "a stream on an unconnected port returns a "
+                                          "non-empty list of active microphones";
+    }
+    if (!atLeastOnePort) {
+        GTEST_SKIP() << "No input mix ports could be routed to built-in microphone devices";
     }
 }
 
 TEST_P(AudioStreamIn, MicrophoneDirection) {
     using MD = IStreamIn::MicrophoneDirection;
-    const auto ports = moduleConfig->getInputMixPorts(true /*connectedOnly*/);
+    constexpr bool connectedOnly = true;
+    const auto ports = moduleConfig->getInputMixPorts(connectedOnly);
     if (ports.empty()) {
         GTEST_SKIP() << "No input mix ports for attached devices";
     }
-    bool isSupported = false;
+    bool isSupported = false, atLeastOnePort = false;
     for (const auto& port : ports) {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port);
-        ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for input mix port";
-        WithStream<IStreamIn> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+        SCOPED_TRACE(port.toString());
+        StreamFixture<IStreamIn> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(), port,
+                                                             connectedOnly));
+        if (!stream.skipTestReason().empty()) continue;
+        atLeastOnePort = true;
         EXPECT_NO_FATAL_FAILURE(
-                TestAccessors<MD>(stream.get(), &IStreamIn::getMicrophoneDirection,
+                TestAccessors<MD>(stream.getStream(), &IStreamIn::getMicrophoneDirection,
                                   &IStreamIn::setMicrophoneDirection,
                                   std::vector<MD>(enum_range<MD>().begin(), enum_range<MD>().end()),
                                   {}, &isSupported));
@@ -3015,21 +3308,27 @@
     if (!isSupported) {
         GTEST_SKIP() << "Microphone direction is not supported";
     }
+    if (!atLeastOnePort) {
+        GTEST_SKIP() << "No input mix ports could be routed to built-in microphone devices";
+    }
 }
 
 TEST_P(AudioStreamIn, MicrophoneFieldDimension) {
-    const auto ports = moduleConfig->getInputMixPorts(true /*connectedOnly*/);
+    constexpr bool connectedOnly = true;
+    const auto ports = moduleConfig->getInputMixPorts(connectedOnly);
     if (ports.empty()) {
         GTEST_SKIP() << "No input mix ports for attached devices";
     }
-    bool isSupported = false;
+    bool isSupported = false, atLeastOnePort = false;
     for (const auto& port : ports) {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port);
-        ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for input mix port";
-        WithStream<IStreamIn> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+        SCOPED_TRACE(port.toString());
+        StreamFixture<IStreamIn> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(), port,
+                                                             connectedOnly));
+        if (!stream.skipTestReason().empty()) continue;
+        atLeastOnePort = true;
         EXPECT_NO_FATAL_FAILURE(TestAccessors<float>(
-                stream.get(), &IStreamIn::getMicrophoneFieldDimension,
+                stream.getStream(), &IStreamIn::getMicrophoneFieldDimension,
                 &IStreamIn::setMicrophoneFieldDimension,
                 {IStreamIn::MIC_FIELD_DIMENSION_WIDE_ANGLE,
                  IStreamIn::MIC_FIELD_DIMENSION_WIDE_ANGLE / 2.0f,
@@ -3046,6 +3345,9 @@
     if (!isSupported) {
         GTEST_SKIP() << "Microphone direction is not supported";
     }
+    if (!atLeastOnePort) {
+        GTEST_SKIP() << "No input mix ports could be routed to built-in microphone devices";
+    }
 }
 
 TEST_P(AudioStreamOut, OpenTwicePrimary) {
@@ -3060,65 +3362,79 @@
 }
 
 TEST_P(AudioStreamOut, RequireOffloadInfo) {
+    constexpr bool connectedOnly = true;
     const auto offloadMixPorts =
-            moduleConfig->getOffloadMixPorts(true /*connectedOnly*/, true /*singlePort*/);
+            moduleConfig->getOffloadMixPorts(connectedOnly, true /*singlePort*/);
     if (offloadMixPorts.empty()) {
         GTEST_SKIP()
                 << "No mix port for compressed offload that could be routed to attached devices";
     }
-    const auto config = moduleConfig->getSingleConfigForMixPort(false, *offloadMixPorts.begin());
-    ASSERT_TRUE(config.has_value()) << "No profiles specified for the compressed offload mix port";
-    WithAudioPortConfig portConfig(config.value());
-    ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get()));
+    StreamFixture<IStreamOut> stream;
+    ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfigForMixPortOrConfig(
+            module.get(), moduleConfig.get(), *offloadMixPorts.begin(), connectedOnly));
+    if (auto reason = stream.skipTestReason(); !reason.empty()) {
+        GTEST_SKIP() << reason;
+    }
+    const auto portConfig = stream.getPortConfig();
     StreamDescriptor descriptor;
-    std::shared_ptr<IStreamOut> ignored;
     aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
-    args.portConfigId = portConfig.getId();
-    args.sourceMetadata = GenerateSourceMetadata(portConfig.get());
+    args.portConfigId = portConfig.id;
+    args.sourceMetadata = GenerateSourceMetadata(portConfig);
     args.bufferSizeFrames = kDefaultLargeBufferSizeFrames;
     aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
     EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret))
             << "when no offload info is provided for a compressed offload mix port";
+    if (ret.stream != nullptr) {
+        (void)WithStream<IStreamOut>::callClose(ret.stream);
+    }
 }
 
 TEST_P(AudioStreamOut, RequireAsyncCallback) {
+    constexpr bool connectedOnly = true;
     const auto nonBlockingMixPorts =
-            moduleConfig->getNonBlockingMixPorts(true /*connectedOnly*/, true /*singlePort*/);
+            moduleConfig->getNonBlockingMixPorts(connectedOnly, true /*singlePort*/);
     if (nonBlockingMixPorts.empty()) {
         GTEST_SKIP()
                 << "No mix port for non-blocking output that could be routed to attached devices";
     }
-    const auto config =
-            moduleConfig->getSingleConfigForMixPort(false, *nonBlockingMixPorts.begin());
-    ASSERT_TRUE(config.has_value()) << "No profiles specified for the non-blocking mix port";
-    WithAudioPortConfig portConfig(config.value());
-    ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get()));
+    StreamFixture<IStreamOut> stream;
+    ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfigForMixPortOrConfig(
+            module.get(), moduleConfig.get(), *nonBlockingMixPorts.begin(), connectedOnly));
+    if (auto reason = stream.skipTestReason(); !reason.empty()) {
+        GTEST_SKIP() << reason;
+    }
+    const auto portConfig = stream.getPortConfig();
     StreamDescriptor descriptor;
-    std::shared_ptr<IStreamOut> ignored;
     aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
-    args.portConfigId = portConfig.getId();
-    args.sourceMetadata = GenerateSourceMetadata(portConfig.get());
-    args.offloadInfo = ModuleConfig::generateOffloadInfoIfNeeded(portConfig.get());
-    args.bufferSizeFrames = kDefaultBufferSizeFrames;
+    args.portConfigId = portConfig.id;
+    args.sourceMetadata = GenerateSourceMetadata(portConfig);
+    args.offloadInfo = ModuleConfig::generateOffloadInfoIfNeeded(portConfig);
+    args.bufferSizeFrames = stream.getPatch().minimumStreamBufferSizeFrames;
     aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
     EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret))
             << "when no async callback is provided for a non-blocking mix port";
+    if (ret.stream != nullptr) {
+        (void)WithStream<IStreamOut>::callClose(ret.stream);
+    }
 }
 
 TEST_P(AudioStreamOut, AudioDescriptionMixLevel) {
-    const auto ports = moduleConfig->getOutputMixPorts(true /*connectedOnly*/);
+    constexpr bool connectedOnly = true;
+    const auto ports = moduleConfig->getOutputMixPorts(connectedOnly);
     if (ports.empty()) {
-        GTEST_SKIP() << "No output mix ports";
+        GTEST_SKIP() << "No output mix ports for attached devices";
     }
-    bool atLeastOneSupports = false;
+    bool atLeastOneSupports = false, atLeastOnePort = false;
     for (const auto& port : ports) {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(false, port);
-        ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for output mix port";
-        WithStream<IStreamOut> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+        SCOPED_TRACE(port.toString());
+        StreamFixture<IStreamOut> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(), port,
+                                                             connectedOnly));
+        if (!stream.skipTestReason().empty()) continue;
+        atLeastOnePort = true;
         bool isSupported = false;
         EXPECT_NO_FATAL_FAILURE(
-                TestAccessors<float>(stream.get(), &IStreamOut::getAudioDescriptionMixLevel,
+                TestAccessors<float>(stream.getStream(), &IStreamOut::getAudioDescriptionMixLevel,
                                      &IStreamOut::setAudioDescriptionMixLevel,
                                      {IStreamOut::AUDIO_DESCRIPTION_MIX_LEVEL_MAX,
                                       IStreamOut::AUDIO_DESCRIPTION_MIX_LEVEL_MAX - 1, 0,
@@ -3128,48 +3444,60 @@
                                      &isSupported));
         if (isSupported) atLeastOneSupports = true;
     }
+    if (!atLeastOnePort) {
+        GTEST_SKIP() << "No output mix ports could be routed to devices";
+    }
     if (!atLeastOneSupports) {
         GTEST_SKIP() << "Audio description mix level is not supported";
     }
 }
 
 TEST_P(AudioStreamOut, DualMonoMode) {
-    const auto ports = moduleConfig->getOutputMixPorts(true /*connectedOnly*/);
+    constexpr bool connectedOnly = true;
+    const auto ports = moduleConfig->getOutputMixPorts(connectedOnly);
     if (ports.empty()) {
-        GTEST_SKIP() << "No output mix ports";
+        GTEST_SKIP() << "No output mix ports for attached devices";
     }
-    bool atLeastOneSupports = false;
+    bool atLeastOneSupports = false, atLeastOnePort = false;
     for (const auto& port : ports) {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(false, port);
-        ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for output mix port";
-        WithStream<IStreamOut> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+        SCOPED_TRACE(port.toString());
+        StreamFixture<IStreamOut> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(), port,
+                                                             connectedOnly));
+        if (!stream.skipTestReason().empty()) continue;
+        atLeastOnePort = true;
         bool isSupported = false;
         EXPECT_NO_FATAL_FAILURE(TestAccessors<AudioDualMonoMode>(
-                stream.get(), &IStreamOut::getDualMonoMode, &IStreamOut::setDualMonoMode,
+                stream.getStream(), &IStreamOut::getDualMonoMode, &IStreamOut::setDualMonoMode,
                 std::vector<AudioDualMonoMode>(enum_range<AudioDualMonoMode>().begin(),
                                                enum_range<AudioDualMonoMode>().end()),
                 {}, &isSupported));
         if (isSupported) atLeastOneSupports = true;
     }
+    if (!atLeastOnePort) {
+        GTEST_SKIP() << "No output mix ports could be routed to devices";
+    }
     if (!atLeastOneSupports) {
         GTEST_SKIP() << "Audio dual mono mode is not supported";
     }
 }
 
 TEST_P(AudioStreamOut, LatencyMode) {
-    const auto ports = moduleConfig->getOutputMixPorts(true /*connectedOnly*/);
+    constexpr bool connectedOnly = true;
+    const auto ports = moduleConfig->getOutputMixPorts(connectedOnly);
     if (ports.empty()) {
-        GTEST_SKIP() << "No output mix ports";
+        GTEST_SKIP() << "No output mix ports for attached devices";
     }
-    bool atLeastOneSupports = false;
+    bool atLeastOneSupports = false, atLeastOnePort = false;
     for (const auto& port : ports) {
-        const auto portConfig = moduleConfig->getSingleConfigForMixPort(false, port);
-        ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for output mix port";
-        WithStream<IStreamOut> stream(portConfig.value());
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+        SCOPED_TRACE(port.toString());
+        StreamFixture<IStreamOut> stream;
+        ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(), port,
+                                                             connectedOnly));
+        if (!stream.skipTestReason().empty()) continue;
+        atLeastOnePort = true;
         std::vector<AudioLatencyMode> supportedModes;
-        ndk::ScopedAStatus status = stream.get()->getRecommendedLatencyModes(&supportedModes);
+        ndk::ScopedAStatus status = stream.getStream()->getRecommendedLatencyModes(&supportedModes);
         if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) continue;
         atLeastOneSupports = true;
         if (!status.isOk()) {
@@ -3181,7 +3509,7 @@
                                                     enum_range<AudioLatencyMode>().end());
         for (const auto mode : supportedModes) {
             unsupportedModes.erase(mode);
-            ndk::ScopedAStatus status = stream.get()->setLatencyMode(mode);
+            ndk::ScopedAStatus status = stream.getStream()->setLatencyMode(mode);
             if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
                 ADD_FAILURE() << "When latency modes are supported, both getRecommendedLatencyModes"
                               << " and setLatencyMode must be supported";
@@ -3189,12 +3517,15 @@
             EXPECT_IS_OK(status) << "Setting of supported latency mode must succeed";
         }
         for (const auto mode : unsupportedModes) {
-            EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, stream.get()->setLatencyMode(mode));
+            EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, stream.getStream()->setLatencyMode(mode));
         }
     }
     if (!atLeastOneSupports) {
         GTEST_SKIP() << "Audio latency modes are not supported";
     }
+    if (!atLeastOnePort) {
+        GTEST_SKIP() << "No output mix ports could be routed to devices";
+    }
 }
 
 TEST_P(AudioStreamOut, PlaybackRate) {
@@ -3496,29 +3827,22 @@
         }
     }
 
-    bool ValidateObservablePosition(const AudioPortConfig& devicePortConfig) {
-        return !isTelephonyDeviceType(
-                devicePortConfig.ext.get<AudioPortExt::Tag::device>().device.type.type);
+    bool ValidateObservablePosition(const AudioDevice& device) {
+        return !isTelephonyDeviceType(device.type.type);
     }
 
     // Set up a patch first, then open a stream.
     void RunStreamIoCommandsImplSeq1(const AudioPortConfig& portConfig,
                                      std::shared_ptr<StateSequence> commandsAndStates,
                                      bool validatePositionIncrease) {
-        auto devicePorts = moduleConfig->getConnectedDevicesPortsForMixPort(
-                IOTraits<Stream>::is_input, portConfig);
-        ASSERT_FALSE(devicePorts.empty());
-        auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(devicePorts[0]);
-        SCOPED_TRACE(devicePortConfig.toString());
-        WithAudioPatch patch(IOTraits<Stream>::is_input, portConfig, devicePortConfig);
-        ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
-
-        WithStream<Stream> stream(patch.getPortConfig(IOTraits<Stream>::is_input));
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+        StreamFixture<Stream> stream;
+        ASSERT_NO_FATAL_FAILURE(
+                stream.SetUpStreamForMixPortConfig(module.get(), moduleConfig.get(), portConfig));
+        ASSERT_EQ("", stream.skipTestReason());
         StreamLogicDefaultDriver driver(commandsAndStates,
-                                        stream.getContext()->getFrameSizeBytes());
-        typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
-                                                 stream.getEventReceiver());
+                                        stream.getStreamContext()->getFrameSizeBytes());
+        typename IOTraits<Stream>::Worker worker(*stream.getStreamContext(), &driver,
+                                                 stream.getStreamEventReceiver());
 
         LOG(DEBUG) << __func__ << ": starting worker...";
         ASSERT_TRUE(worker.start());
@@ -3526,7 +3850,7 @@
         worker.join();
         EXPECT_FALSE(worker.hasError()) << worker.getError();
         EXPECT_EQ("", driver.getUnexpectedStateTransition());
-        if (ValidateObservablePosition(devicePortConfig)) {
+        if (ValidateObservablePosition(stream.getDevice())) {
             if (validatePositionIncrease) {
                 EXPECT_TRUE(driver.hasObservablePositionIncrease());
             }
@@ -3534,24 +3858,21 @@
         }
     }
 
-    // Open a stream, then set up a patch for it.
+    // Open a stream, then set up a patch for it. Since first it is needed to get
+    // the minimum buffer size, a preliminary patch is set up, then removed.
     void RunStreamIoCommandsImplSeq2(const AudioPortConfig& portConfig,
                                      std::shared_ptr<StateSequence> commandsAndStates,
                                      bool validatePositionIncrease) {
-        WithStream<Stream> stream(portConfig);
-        ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+        StreamFixture<Stream> stream;
+        ASSERT_NO_FATAL_FAILURE(
+                stream.SetUpPatchForMixPortConfig(module.get(), moduleConfig.get(), portConfig));
+        ASSERT_EQ("", stream.skipTestReason());
+        ASSERT_NO_FATAL_FAILURE(stream.TeardownPatchSetUpStream(module.get()));
         StreamLogicDefaultDriver driver(commandsAndStates,
-                                        stream.getContext()->getFrameSizeBytes());
-        typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
-                                                 stream.getEventReceiver());
-
-        auto devicePorts = moduleConfig->getConnectedDevicesPortsForMixPort(
-                IOTraits<Stream>::is_input, portConfig);
-        ASSERT_FALSE(devicePorts.empty());
-        auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(devicePorts[0]);
-        SCOPED_TRACE(devicePortConfig.toString());
-        WithAudioPatch patch(IOTraits<Stream>::is_input, stream.getPortConfig(), devicePortConfig);
-        ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
+                                        stream.getStreamContext()->getFrameSizeBytes());
+        typename IOTraits<Stream>::Worker worker(*stream.getStreamContext(), &driver,
+                                                 stream.getStreamEventReceiver());
+        ASSERT_NO_FATAL_FAILURE(stream.ReconnectPatch(module.get()));
 
         LOG(DEBUG) << __func__ << ": starting worker...";
         ASSERT_TRUE(worker.start());
@@ -3559,7 +3880,7 @@
         worker.join();
         EXPECT_FALSE(worker.hasError()) << worker.getError();
         EXPECT_EQ("", driver.getUnexpectedStateTransition());
-        if (ValidateObservablePosition(devicePortConfig)) {
+        if (ValidateObservablePosition(stream.getDevice())) {
             if (validatePositionIncrease) {
                 EXPECT_TRUE(driver.hasObservablePositionIncrease());
             }
@@ -4253,191 +4574,154 @@
     explicit WithRemoteSubmix(AudioDeviceAddress address) : mAddress(address) {}
     WithRemoteSubmix(const WithRemoteSubmix&) = delete;
     WithRemoteSubmix& operator=(const WithRemoteSubmix&) = delete;
+
     static std::optional<AudioPort> getRemoteSubmixAudioPort(
             ModuleConfig* moduleConfig,
             const std::optional<AudioDeviceAddress>& address = std::nullopt) {
-        AudioDeviceType deviceType = IOTraits<Stream>::is_input ? AudioDeviceType::IN_SUBMIX
-                                                                : AudioDeviceType::OUT_SUBMIX;
-        auto ports = moduleConfig->getAudioPortsForDeviceTypes(
-                std::vector<AudioDeviceType>{deviceType},
-                AudioDeviceDescription::CONNECTION_VIRTUAL);
+        auto ports =
+                moduleConfig->getRemoteSubmixPorts(IOTraits<Stream>::is_input, true /*singlePort*/);
         if (ports.empty()) return {};
         AudioPort port = ports.front();
         if (address) {
             port.ext.template get<AudioPortExt::Tag::device>().device.address = address.value();
-        } else {
-            port = GenerateUniqueDeviceAddress(port);
         }
         return port;
     }
-    std::optional<AudioDeviceAddress> getAudioDeviceAddress() const { return mAddress; }
-    void SetUp(IModule* module, ModuleConfig* moduleConfig, const AudioPort& connectedPort) {
-        mModule = module;
-        mModuleConfig = moduleConfig;
 
-        ASSERT_NO_FATAL_FAILURE(SetupPatch(connectedPort));
-        if (!mSkipTest) {
-            // open stream
-            mStream = std::make_unique<WithStream<Stream>>(
-                    mPatch->getPortConfig(IOTraits<Stream>::is_input));
-            ASSERT_NO_FATAL_FAILURE(
-                    mStream->SetUp(mModule, AudioCoreModuleBase::kDefaultBufferSizeFrames));
-        }
-        mAddress = connectedPort.ext.template get<AudioPortExt::Tag::device>().device.address;
-    }
     void SetUp(IModule* module, ModuleConfig* moduleConfig) {
-        ASSERT_NO_FATAL_FAILURE(SetUpPortConnection(module, moduleConfig));
-        SetUp(module, moduleConfig, mConnectedPort->get());
+        auto devicePort = getRemoteSubmixAudioPort(moduleConfig, mAddress);
+        ASSERT_TRUE(devicePort.has_value()) << "Device port for remote submix device not found";
+        ASSERT_NO_FATAL_FAILURE(SetUp(module, moduleConfig, *devicePort));
     }
-    void sendBurstCommands() {
-        const StreamContext* context = mStream->getContext();
-        StreamLogicDefaultDriver driver(makeBurstCommands(true), context->getFrameSizeBytes());
-        typename IOTraits<Stream>::Worker worker(*context, &driver, mStream->getEventReceiver());
 
-        LOG(DEBUG) << __func__ << ": starting worker...";
-        ASSERT_TRUE(worker.start());
-        LOG(DEBUG) << __func__ << ": joining worker...";
-        worker.join();
-        EXPECT_FALSE(worker.hasError()) << worker.getError();
-        EXPECT_EQ("", driver.getUnexpectedStateTransition());
-        if (IOTraits<Stream>::is_input) {
-            EXPECT_TRUE(driver.hasObservablePositionIncrease());
-        }
-        EXPECT_FALSE(driver.hasRetrogradeObservablePosition());
+    void SendBurstCommandsStartWorker() {
+        const StreamContext* context = mStream->getStreamContext();
+        mWorkerDriver = std::make_unique<StreamLogicDefaultDriver>(makeBurstCommands(true),
+                                                                   context->getFrameSizeBytes());
+        mWorker = std::make_unique<typename IOTraits<Stream>::Worker>(
+                *context, mWorkerDriver.get(), mStream->getStreamEventReceiver());
+        LOG(DEBUG) << __func__ << ": starting " << IOTraits<Stream>::directionStr << " worker...";
+        ASSERT_TRUE(mWorker->start());
     }
-    bool skipTest() const { return mSkipTest; }
+
+    void SendBurstCommandsJoinWorker() {
+        // Must call 'prepareToClose' before attempting to join because the stream may be
+        // stuck due to absence of activity from the other side of the remote submix pipe.
+        std::shared_ptr<IStreamCommon> common;
+        ASSERT_IS_OK(mStream->getStream()->getStreamCommon(&common));
+        ASSERT_IS_OK(common->prepareToClose());
+        LOG(DEBUG) << __func__ << ": joining " << IOTraits<Stream>::directionStr << " worker...";
+        mWorker->join();
+        EXPECT_FALSE(mWorker->hasError()) << mWorker->getError();
+        EXPECT_EQ("", mWorkerDriver->getUnexpectedStateTransition());
+        if (IOTraits<Stream>::is_input) {
+            EXPECT_TRUE(mWorkerDriver->hasObservablePositionIncrease());
+        }
+        EXPECT_FALSE(mWorkerDriver->hasRetrogradeObservablePosition());
+        mWorker.reset();
+        mWorkerDriver.reset();
+    }
+
+    void SendBurstCommands() {
+        ASSERT_NO_FATAL_FAILURE(SendBurstCommandsStartWorker());
+        ASSERT_NO_FATAL_FAILURE(SendBurstCommandsJoinWorker());
+    }
+
+    std::optional<AudioDeviceAddress> getAudioDeviceAddress() const { return mAddress; }
+    std::string skipTestReason() const { return mStream->skipTestReason(); }
 
   private:
-    /* Connect remote submix external device */
-    void SetUpPortConnection(IModule* module, ModuleConfig* moduleConfig) {
-        auto port = getRemoteSubmixAudioPort(moduleConfig, mAddress);
-        ASSERT_TRUE(port.has_value()) << "Device AudioPort for remote submix not found";
-        mConnectedPort = std::make_unique<WithDevicePortConnectedState>(port.value());
-        ASSERT_NO_FATAL_FAILURE(mConnectedPort->SetUp(module, moduleConfig));
-    }
-    /* Get mix port config for stream and setup patch for it. */
-    void SetupPatch(const AudioPort& connectedPort) {
-        const auto portConfig =
-                mModuleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
-        if (!portConfig.has_value()) {
-            LOG(DEBUG) << __func__ << ": portConfig not found";
-            mSkipTest = true;
-            return;
-        }
-        auto devicePortConfig = mModuleConfig->getSingleConfigForDevicePort(connectedPort);
-        mPatch = std::make_unique<WithAudioPatch>(IOTraits<Stream>::is_input, portConfig.value(),
-                                                  devicePortConfig);
-        ASSERT_NO_FATAL_FAILURE(mPatch->SetUp(mModule));
+    void SetUp(IModule* module, ModuleConfig* moduleConfig, const AudioPort& devicePort) {
+        mStream = std::make_unique<StreamFixture<Stream>>();
+        ASSERT_NO_FATAL_FAILURE(
+                mStream->SetUpStreamForDevicePort(module, moduleConfig, devicePort));
+        mAddress = mStream->getDevice().address;
     }
 
-    bool mSkipTest = false;
-    IModule* mModule = nullptr;
-    ModuleConfig* mModuleConfig = nullptr;
     std::optional<AudioDeviceAddress> mAddress;
-    std::unique_ptr<WithDevicePortConnectedState> mConnectedPort;
-    std::unique_ptr<WithAudioPatch> mPatch;
-    std::unique_ptr<WithStream<Stream>> mStream;
+    std::unique_ptr<StreamFixture<Stream>> mStream;
+    std::unique_ptr<StreamLogicDefaultDriver> mWorkerDriver;
+    std::unique_ptr<typename IOTraits<Stream>::Worker> mWorker;
 };
 
 class AudioModuleRemoteSubmix : public AudioCoreModule {
   public:
     void SetUp() override {
-        ASSERT_NO_FATAL_FAILURE(AudioCoreModule::SetUp());
+        // Turn off "debug" which enables connections simulation. Since devices of the remote
+        // submix module are virtual, there is no need for simulation.
+        ASSERT_NO_FATAL_FAILURE(SetUpImpl(GetParam(), false /*setUpDebug*/));
         ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
     }
-
-    void TearDown() override { ASSERT_NO_FATAL_FAILURE(TearDownImpl()); }
 };
 
 TEST_P(AudioModuleRemoteSubmix, OutputDoesNotBlockWhenNoInput) {
-    // open output stream
     WithRemoteSubmix<IStreamOut> streamOut;
     ASSERT_NO_FATAL_FAILURE(streamOut.SetUp(module.get(), moduleConfig.get()));
-    if (streamOut.skipTest()) {
-        GTEST_SKIP() << "No mix port for attached devices";
-    }
-    // write something to stream
-    ASSERT_NO_FATAL_FAILURE(streamOut.sendBurstCommands());
+    // Note: here and in other tests any issue with connection attempts is considered as a problem.
+    ASSERT_EQ("", streamOut.skipTestReason());
+    ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommands());
 }
 
 TEST_P(AudioModuleRemoteSubmix, OutputDoesNotBlockWhenInputStuck) {
-    // open output stream
     WithRemoteSubmix<IStreamOut> streamOut;
     ASSERT_NO_FATAL_FAILURE(streamOut.SetUp(module.get(), moduleConfig.get()));
-    if (streamOut.skipTest()) {
-        GTEST_SKIP() << "No mix port for attached devices";
-    }
+    ASSERT_EQ("", streamOut.skipTestReason());
     auto address = streamOut.getAudioDeviceAddress();
     ASSERT_TRUE(address.has_value());
 
-    // open input stream
     WithRemoteSubmix<IStreamIn> streamIn(address.value());
     ASSERT_NO_FATAL_FAILURE(streamIn.SetUp(module.get(), moduleConfig.get()));
-    if (streamIn.skipTest()) {
-        GTEST_SKIP() << "No mix port for attached devices";
-    }
+    ASSERT_EQ("", streamIn.skipTestReason());
 
-    // write something to stream
-    ASSERT_NO_FATAL_FAILURE(streamOut.sendBurstCommands());
+    ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommands());
 }
 
 TEST_P(AudioModuleRemoteSubmix, OutputAndInput) {
-    // open output stream
     WithRemoteSubmix<IStreamOut> streamOut;
     ASSERT_NO_FATAL_FAILURE(streamOut.SetUp(module.get(), moduleConfig.get()));
-    if (streamOut.skipTest()) {
-        GTEST_SKIP() << "No mix port for attached devices";
-    }
+    ASSERT_EQ("", streamOut.skipTestReason());
     auto address = streamOut.getAudioDeviceAddress();
     ASSERT_TRUE(address.has_value());
 
-    // open input stream
     WithRemoteSubmix<IStreamIn> streamIn(address.value());
     ASSERT_NO_FATAL_FAILURE(streamIn.SetUp(module.get(), moduleConfig.get()));
-    if (streamIn.skipTest()) {
-        GTEST_SKIP() << "No mix port for attached devices";
-    }
+    ASSERT_EQ("", streamIn.skipTestReason());
 
-    // write something to stream
-    ASSERT_NO_FATAL_FAILURE(streamOut.sendBurstCommands());
-    // read from input stream
-    ASSERT_NO_FATAL_FAILURE(streamIn.sendBurstCommands());
+    // Start writing into the output stream.
+    ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommandsStartWorker());
+    // Simultaneously, read from the input stream.
+    ASSERT_NO_FATAL_FAILURE(streamIn.SendBurstCommands());
+    ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommandsJoinWorker());
 }
 
 TEST_P(AudioModuleRemoteSubmix, OpenInputMultipleTimes) {
-    // open output stream
     WithRemoteSubmix<IStreamOut> streamOut;
     ASSERT_NO_FATAL_FAILURE(streamOut.SetUp(module.get(), moduleConfig.get()));
-    if (streamOut.skipTest()) {
-        GTEST_SKIP() << "No mix port for attached devices";
-    }
+    ASSERT_EQ("", streamOut.skipTestReason());
     auto address = streamOut.getAudioDeviceAddress();
     ASSERT_TRUE(address.has_value());
 
-    // connect remote submix input device port
-    auto port = WithRemoteSubmix<IStreamIn>::getRemoteSubmixAudioPort(moduleConfig.get(),
-                                                                      address.value());
-    ASSERT_TRUE(port.has_value()) << "Device AudioPort for remote submix not found";
-    WithDevicePortConnectedState connectedInputPort(port.value());
-    ASSERT_NO_FATAL_FAILURE(connectedInputPort.SetUp(module.get(), moduleConfig.get()));
-
-    // open input streams
-    const int streamInCount = 3;
+    const size_t streamInCount = 3;
     std::vector<std::unique_ptr<WithRemoteSubmix<IStreamIn>>> streamIns(streamInCount);
-    for (int i = 0; i < streamInCount; i++) {
-        streamIns[i] = std::make_unique<WithRemoteSubmix<IStreamIn>>();
-        ASSERT_NO_FATAL_FAILURE(
-                streamIns[i]->SetUp(module.get(), moduleConfig.get(), connectedInputPort.get()));
-        if (streamIns[i]->skipTest()) {
-            GTEST_SKIP() << "No mix port for attached devices";
-        }
+    for (size_t i = 0; i < streamInCount; i++) {
+        streamIns[i] = std::make_unique<WithRemoteSubmix<IStreamIn>>(address.value());
+        ASSERT_NO_FATAL_FAILURE(streamIns[i]->SetUp(module.get(), moduleConfig.get()));
+        ASSERT_EQ("", streamIns[i]->skipTestReason());
     }
-    // write something to output stream
-    ASSERT_NO_FATAL_FAILURE(streamOut.sendBurstCommands());
-
-    // read from input streams
-    for (int i = 0; i < streamInCount; i++) {
-        ASSERT_NO_FATAL_FAILURE(streamIns[i]->sendBurstCommands());
+    // Start writing into the output stream.
+    ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommandsStartWorker());
+    // Simultaneously, read from input streams.
+    for (size_t i = 0; i < streamInCount; i++) {
+        ASSERT_NO_FATAL_FAILURE(streamIns[i]->SendBurstCommandsStartWorker());
+    }
+    for (size_t i = 0; i < streamInCount; i++) {
+        ASSERT_NO_FATAL_FAILURE(streamIns[i]->SendBurstCommandsJoinWorker());
+    }
+    ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommandsJoinWorker());
+    // Clean up input streams in the reverse order because the device connection is owned
+    // by the first one.
+    for (size_t i = streamInCount; i != 0; --i) {
+        streamIns[i - 1].reset();
     }
 }
 
diff --git a/automotive/audiocontrol/aidl/Android.bp b/automotive/audiocontrol/aidl/Android.bp
index 9ae77cd..86b63a6 100644
--- a/automotive/audiocontrol/aidl/Android.bp
+++ b/automotive/audiocontrol/aidl/Android.bp
@@ -13,9 +13,9 @@
     name: "android.hardware.automotive.audiocontrol",
     vendor_available: true,
     srcs: ["android/hardware/automotive/audiocontrol/*.aidl"],
-    imports: [
-        "android.hardware.audio.common-V1",
-        "android.media.audio.common.types-V2",
+    defaults: [
+        "latest_android_hardware_audio_common_import_interface",
+        "latest_android_media_audio_common_types_import_interface",
     ],
     stability: "vintf",
     backend: {
@@ -52,6 +52,37 @@
         },
 
     ],
-    frozen: true,
+    frozen: false,
 
 }
+
+// Note: This should always be one version ahead of the last frozen version
+latest_android_hardware_automotive_audiocontrol = "android.hardware.automotive.audiocontrol-V4"
+
+cc_defaults {
+    name: "latest_android_hardware_automotive_audiocontrol_cpp_static",
+    static_libs: [
+        latest_android_hardware_automotive_audiocontrol + "-cpp",
+    ],
+}
+
+cc_defaults {
+    name: "latest_android_hardware_automotive_audiocontrol_cpp_shared",
+    shared_libs: [
+        latest_android_hardware_automotive_audiocontrol + "-cpp",
+    ],
+}
+
+cc_defaults {
+    name: "latest_android_hardware_automotive_audiocontrol_ndk_static",
+    static_libs: [
+        latest_android_hardware_automotive_audiocontrol + "-ndk",
+    ],
+}
+
+cc_defaults {
+    name: "latest_android_hardware_automotive_audiocontrol_ndk_shared",
+    shared_libs: [
+        latest_android_hardware_automotive_audiocontrol + "-ndk",
+    ],
+}
diff --git a/automotive/audiocontrol/aidl/default/audiocontrol-default.xml b/automotive/audiocontrol/aidl/default/audiocontrol-default.xml
index 95cd7f0..bcb5669 100644
--- a/automotive/audiocontrol/aidl/default/audiocontrol-default.xml
+++ b/automotive/audiocontrol/aidl/default/audiocontrol-default.xml
@@ -1,7 +1,7 @@
 <manifest version="2.0" type="device">
     <hal format="aidl">
         <name>android.hardware.automotive.audiocontrol</name>
-        <version>3</version>
+        <version>4</version>
         <fqname>IAudioControl/default</fqname>
     </hal>
 </manifest>
diff --git a/automotive/audiocontrol/aidl/vts/Android.bp b/automotive/audiocontrol/aidl/vts/Android.bp
index cfc2a3e..c73ad79 100644
--- a/automotive/audiocontrol/aidl/vts/Android.bp
+++ b/automotive/audiocontrol/aidl/vts/Android.bp
@@ -24,6 +24,8 @@
 cc_test {
     name: "VtsAidlHalAudioControlTest",
     defaults: [
+        "latest_android_hardware_audio_common_cpp_static",
+        "latest_android_hardware_automotive_audiocontrol_cpp_static",
         "latest_android_media_audio_common_types_cpp_static",
         "VtsHalTargetTestDefaults",
         "use_libaidlvintf_gtest_helper_static",
@@ -38,8 +40,6 @@
         "libxml2",
     ],
     static_libs: [
-        "android.hardware.automotive.audiocontrol-V3-cpp",
-        "android.hardware.audio.common-V1-cpp",
         "libgmock",
     ],
     test_suites: [
diff --git a/bluetooth/audio/aidl/Android.bp b/bluetooth/audio/aidl/Android.bp
index 1028fae..feed6f5 100644
--- a/bluetooth/audio/aidl/Android.bp
+++ b/bluetooth/audio/aidl/Android.bp
@@ -27,10 +27,12 @@
     host_supported: true,
     srcs: ["android/hardware/bluetooth/audio/*.aidl"],
     stability: "vintf",
+    defaults: [
+        "latest_android_hardware_audio_common_import_interface",
+    ],
     imports: [
         "android.hardware.common-V2",
         "android.hardware.common.fmq-V1",
-        "android.hardware.audio.common-V2",
     ],
     backend: {
         cpp: {
@@ -75,6 +77,23 @@
         },
 
     ],
-    frozen: true,
+    frozen: false,
 
 }
+
+// Note: This should always be one version ahead of the last frozen version
+latest_android_hardware_bluetooth_audio = "android.hardware.bluetooth.audio-V4"
+
+cc_defaults {
+    name: "latest_android_hardware_bluetooth_audio_ndk_shared",
+    shared_libs: [
+        latest_android_hardware_bluetooth_audio + "-ndk",
+    ],
+}
+
+cc_defaults {
+    name: "latest_android_hardware_bluetooth_audio_ndk_static",
+    static_libs: [
+        latest_android_hardware_bluetooth_audio + "-ndk",
+    ],
+}
diff --git a/bluetooth/audio/aidl/default/Android.bp b/bluetooth/audio/aidl/default/Android.bp
index e4c2844..40aea32 100644
--- a/bluetooth/audio/aidl/default/Android.bp
+++ b/bluetooth/audio/aidl/default/Android.bp
@@ -11,6 +11,9 @@
     name: "android.hardware.bluetooth.audio-impl",
     vendor: true,
     vintf_fragments: ["bluetooth_audio.xml"],
+    defaults: [
+        "latest_android_hardware_bluetooth_audio_ndk_shared",
+    ],
     srcs: [
         "BluetoothAudioProvider.cpp",
         "BluetoothAudioProviderFactory.cpp",
@@ -29,7 +32,6 @@
         "libcutils",
         "libfmq",
         "liblog",
-        "android.hardware.bluetooth.audio-V3-ndk",
         "libbluetooth_audio_session_aidl",
     ],
 }
diff --git a/bluetooth/audio/aidl/default/bluetooth_audio.xml b/bluetooth/audio/aidl/default/bluetooth_audio.xml
index c0bc55e..3561dd1 100644
--- a/bluetooth/audio/aidl/default/bluetooth_audio.xml
+++ b/bluetooth/audio/aidl/default/bluetooth_audio.xml
@@ -1,7 +1,7 @@
 <manifest version="1.0" type="device">
     <hal format="aidl">
         <name>android.hardware.bluetooth.audio</name>
-        <version>3</version>
+        <version>4</version>
         <fqname>IBluetoothAudioProviderFactory/default</fqname>
     </hal>
 </manifest>
diff --git a/bluetooth/audio/aidl/vts/Android.bp b/bluetooth/audio/aidl/vts/Android.bp
index fa85fa8..884062a 100644
--- a/bluetooth/audio/aidl/vts/Android.bp
+++ b/bluetooth/audio/aidl/vts/Android.bp
@@ -10,17 +10,17 @@
 cc_test {
     name: "VtsHalBluetoothAudioTargetTest",
     defaults: [
+        "latest_android_hardware_audio_common_ndk_static",
+        "latest_android_hardware_bluetooth_audio_ndk_static",
+        "latest_android_media_audio_common_types_ndk_static",
         "VtsHalTargetTestDefaults",
         "use_libaidlvintf_gtest_helper_static",
     ],
     tidy_timeout_srcs: ["VtsHalBluetoothAudioTargetTest.cpp"],
     srcs: ["VtsHalBluetoothAudioTargetTest.cpp"],
     static_libs: [
-        "android.hardware.audio.common-V2-ndk",
-        "android.hardware.bluetooth.audio-V3-ndk",
         "android.hardware.common-V2-ndk",
         "android.hardware.common.fmq-V1-ndk",
-        "android.media.audio.common.types-V2-ndk",
     ],
     shared_libs: [
         "libbase",
diff --git a/bluetooth/audio/utils/Android.bp b/bluetooth/audio/utils/Android.bp
index a09e7fe..75081d6 100644
--- a/bluetooth/audio/utils/Android.bp
+++ b/bluetooth/audio/utils/Android.bp
@@ -48,6 +48,9 @@
         "libhardware_headers",
         "libxsdc-utils",
     ],
+    defaults: [
+        "latest_android_hardware_bluetooth_audio_ndk_shared",
+    ],
     shared_libs: [
         "android.hardware.bluetooth.audio@2.0",
         "android.hardware.bluetooth.audio@2.1",
@@ -56,7 +59,6 @@
         "libbinder_ndk",
         "libfmq",
         "liblog",
-        "android.hardware.bluetooth.audio-V3-ndk",
         "libhidlbase",
         "libxml2",
     ],
diff --git a/camera/device/default/ExternalCameraUtils.cpp b/camera/device/default/ExternalCameraUtils.cpp
index cfb95f2..30c216f 100644
--- a/camera/device/default/ExternalCameraUtils.cpp
+++ b/camera/device/default/ExternalCameraUtils.cpp
@@ -402,7 +402,10 @@
         buffer_handle_t buf,
         /*out*/ buffer_handle_t** outBufPtr) {
     using ::aidl::android::hardware::camera::common::Status;
-    if (buf == nullptr && bufId == BUFFER_ID_NO_BUFFER) {
+    // AIDL does not have null NativeHandles. It sends empty handles instead.
+    // We check for when the buf is empty instead of when buf is null.
+    bool isBufEmpty = buf == nullptr || (buf->numFds == 0 && buf->numInts == 0);
+    if (isBufEmpty && bufId == BUFFER_ID_NO_BUFFER) {
         ALOGE("%s: bufferId %" PRIu64 " has null buffer handle!", __FUNCTION__, bufId);
         return Status::ILLEGAL_ARGUMENT;
     }
@@ -857,4 +860,4 @@
 }  // namespace device
 }  // namespace camera
 }  // namespace hardware
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/common/aidl/Android.bp b/common/aidl/Android.bp
index f3ea8e8..1457b8a 100644
--- a/common/aidl/Android.bp
+++ b/common/aidl/Android.bp
@@ -38,7 +38,7 @@
         },
         rust: {
             enabled: true,
-        }
+        },
     },
     frozen: true,
     versions: [
diff --git a/compatibility_matrices/compatibility_matrix.9.xml b/compatibility_matrices/compatibility_matrix.9.xml
index 6ed8e8f..83d2665 100644
--- a/compatibility_matrices/compatibility_matrix.9.xml
+++ b/compatibility_matrices/compatibility_matrix.9.xml
@@ -19,7 +19,7 @@
     </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.audio.core</name>
-        <version>1</version>
+        <version>1-2</version>
         <interface>
             <name>IModule</name>
             <instance>default</instance>
@@ -38,7 +38,7 @@
     </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.audio.effect</name>
-        <version>1</version>
+        <version>1-2</version>
         <interface>
             <name>IFactory</name>
             <instance>default</instance>
@@ -46,7 +46,7 @@
     </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.audio.sounddose</name>
-        <version>1</version>
+        <version>1-2</version>
         <interface>
             <name>ISoundDoseFactory</name>
             <instance>default</instance>
@@ -62,7 +62,7 @@
     </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.automotive.audiocontrol</name>
-        <version>2-3</version>
+        <version>2-4</version>
         <interface>
             <name>IAudioControl</name>
             <instance>default</instance>
@@ -140,7 +140,7 @@
     </hal>
     <hal format="aidl" optional="true">
         <name>android.hardware.bluetooth.audio</name>
-        <version>3</version>
+        <version>3-4</version>
         <interface>
             <name>IBluetoothAudioProviderFactory</name>
             <instance>default</instance>
@@ -543,7 +543,7 @@
     </hal>
     <hal format="aidl" optional="true">
          <name>android.hardware.soundtrigger3</name>
-         <version>1</version>
+         <version>1-2</version>
          <interface>
              <name>ISoundTriggerHw</name>
              <instance>default</instance>
diff --git a/compatibility_matrices/exclude/fcm_exclude.cpp b/compatibility_matrices/exclude/fcm_exclude.cpp
index d92c0b9..2cb4ffa 100644
--- a/compatibility_matrices/exclude/fcm_exclude.cpp
+++ b/compatibility_matrices/exclude/fcm_exclude.cpp
@@ -142,6 +142,7 @@
 
             // AIDL
             "android.hardware.audio.core.sounddose@1",
+            "android.hardware.audio.core.sounddose@2",
 
             // Deprecated HALs.
             "android.hardware.bluetooth.audio@1",
diff --git a/gnss/aidl/default/Android.bp b/gnss/aidl/default/Android.bp
index ca5a41f..7310922 100644
--- a/gnss/aidl/default/Android.bp
+++ b/gnss/aidl/default/Android.bp
@@ -26,12 +26,7 @@
 cc_binary {
     name: "android.hardware.gnss-service.example",
     relative_install_path: "hw",
-    init_rc: [
-        "gnss-default.rc",
-    ],
-    vintf_fragments: [
-        "gnss-default.xml",
-    ],
+    installable: false, // install APEX instead
     vendor: true,
     cflags: [
         "-Wall",
@@ -73,3 +68,35 @@
         "android.hardware.gnss@common-default-lib",
     ],
 }
+
+prebuilt_etc {
+    name: "gnss-default.rc",
+    src: "gnss-default.rc",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "gnss-default.xml",
+    src: "gnss-default.xml",
+    sub_dir: "vintf",
+    installable: false,
+}
+
+apex {
+    name: "com.android.hardware.gnss",
+    manifest: "apex_manifest.json",
+    file_contexts: "apex_file_contexts",
+    key: "com.android.hardware.key",
+    certificate: ":com.android.hardware.certificate",
+    updatable: false,
+    vendor: true,
+
+    binaries: [
+        "android.hardware.gnss-service.example",
+    ],
+    prebuilts: [
+        "gnss-default.rc",
+        "gnss-default.xml",
+        "android.hardware.location.gps.prebuilt.xml", // permission
+    ],
+}
diff --git a/gnss/aidl/default/apex_file_contexts b/gnss/aidl/default/apex_file_contexts
new file mode 100644
index 0000000..83b01ed
--- /dev/null
+++ b/gnss/aidl/default/apex_file_contexts
@@ -0,0 +1,3 @@
+(/.*)?                                                          u:object_r:vendor_file:s0
+/etc(/.*)?                                                      u:object_r:vendor_configs_file:s0
+/bin/hw/android\.hardware\.gnss-service\.example                u:object_r:hal_gnss_default_exec:s0
diff --git a/gnss/aidl/default/apex_manifest.json b/gnss/aidl/default/apex_manifest.json
new file mode 100644
index 0000000..9b2db23
--- /dev/null
+++ b/gnss/aidl/default/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+    "name": "com.android.hardware.gnss",
+    "version": 1
+}
diff --git a/gnss/aidl/default/gnss-default.rc b/gnss/aidl/default/gnss-default.rc
index fe179c3..d47b3a5 100644
--- a/gnss/aidl/default/gnss-default.rc
+++ b/gnss/aidl/default/gnss-default.rc
@@ -1,4 +1,4 @@
-service vendor.gnss-default /vendor/bin/hw/android.hardware.gnss-service.example
+service vendor.gnss-default /apex/com.android.hardware.gnss/bin/hw/android.hardware.gnss-service.example
     class hal
     user nobody
     group nobody
diff --git a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
index 36f0106..aa7bf28 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
@@ -643,6 +643,8 @@
      * Tag::ATTESTATION_CHALLENGE is used to deliver a "challenge" value to the attested key
      * generation/import methods, which must place the value in the KeyDescription SEQUENCE of the
      * attestation extension.
+     * The challenge value may be up to 128 bytes. If the caller provides a bigger challenge,
+     * INVALID_INPUT_LENGTH error should be returned.
      *
      * Must never appear in KeyCharacteristics.
      */
diff --git a/sensors/aidl/vts/VtsAidlHalSensorsTargetTest.cpp b/sensors/aidl/vts/VtsAidlHalSensorsTargetTest.cpp
index 618acbb..be11b87 100644
--- a/sensors/aidl/vts/VtsAidlHalSensorsTargetTest.cpp
+++ b/sensors/aidl/vts/VtsAidlHalSensorsTargetTest.cpp
@@ -41,6 +41,7 @@
 using aidl::android::hardware::sensors::SensorInfo;
 using aidl::android::hardware::sensors::SensorStatus;
 using aidl::android::hardware::sensors::SensorType;
+using aidl::android::hardware::sensors::AdditionalInfo;
 using android::ProcessState;
 using std::chrono::duration_cast;
 
@@ -629,6 +630,15 @@
     Event additionalInfoEvent;
     additionalInfoEvent.sensorType = SensorType::ADDITIONAL_INFO;
     additionalInfoEvent.timestamp = android::elapsedRealtimeNano();
+    AdditionalInfo info;
+    info.type = AdditionalInfo::AdditionalInfoType::AINFO_BEGIN;
+    info.serial = 1;
+    AdditionalInfo::AdditionalInfoPayload::Int32Values infoData;
+    for (int32_t i = 0; i < 14; i++) {
+        infoData.values[i] = i;
+    }
+    info.payload.set<AdditionalInfo::AdditionalInfoPayload::Tag::dataInt32>(infoData);
+    additionalInfoEvent.payload.set<Event::EventPayload::Tag::additional>(info);
 
     Event injectedEvent;
     injectedEvent.timestamp = android::elapsedRealtimeNano();
diff --git a/soundtrigger/aidl/Android.bp b/soundtrigger/aidl/Android.bp
index 27d43d3..aa400c1 100644
--- a/soundtrigger/aidl/Android.bp
+++ b/soundtrigger/aidl/Android.bp
@@ -22,8 +22,8 @@
         "android/hardware/soundtrigger3/ISoundTriggerHwGlobalCallback.aidl",
     ],
     stability: "vintf",
-    imports: [
-        "android.media.soundtrigger.types-V1",
+    defaults: [
+        "latest_android_media_soundtrigger_types_import_interface",
     ],
     backend: {
         cpp: {
@@ -34,6 +34,7 @@
             sdk_version: "module_current",
         },
     },
+    frozen: false,
     versions_with_info: [
         {
             version: "1",
@@ -45,7 +46,7 @@
 }
 
 // Note: This should always be one version ahead of the last frozen version
-latest_android_hardware_soundtrigger3 = "android.hardware.soundtrigger3-V1"
+latest_android_hardware_soundtrigger3 = "android.hardware.soundtrigger3-V2"
 
 // Modules that depend on android.hardware.soundtrigger3 directly can include
 // the following java_defaults to avoid explicitly managing dependency versions
@@ -56,3 +57,10 @@
         latest_android_hardware_soundtrigger3 + "-java",
     ],
 }
+
+cc_defaults {
+    name: "latest_android_hardware_soundtrigger3_ndk_shared",
+    shared_libs: [
+        latest_android_hardware_soundtrigger3 + "-ndk",
+    ],
+}
diff --git a/wifi/netlinkinterceptor/aidl/default/Android.bp b/wifi/netlinkinterceptor/aidl/default/Android.bp
index 5227e51..c3a0c03 100644
--- a/wifi/netlinkinterceptor/aidl/default/Android.bp
+++ b/wifi/netlinkinterceptor/aidl/default/Android.bp
@@ -25,8 +25,6 @@
 
 cc_binary {
     name: "android.hardware.net.nlinterceptor-service.default",
-    init_rc: ["nlinterceptor-default.rc"],
-    vintf_fragments: ["nlinterceptor-default.xml"],
     vendor: true,
     relative_install_path: "hw",
     defaults: ["nlinterceptor@defaults"],
@@ -45,4 +43,35 @@
         "service.cpp",
         "util.cpp",
     ],
+    installable: false, // installed in APEX
+}
+
+apex {
+    name: "com.android.hardware.net.nlinterceptor",
+    vendor: true,
+    manifest: "apex_manifest.json",
+    file_contexts: "apex_file_contexts",
+    key: "com.android.hardware.key",
+    certificate: ":com.android.hardware.certificate",
+    updatable: false,
+    binaries: [
+        "android.hardware.net.nlinterceptor-service.default",
+    ],
+    prebuilts: [
+        "nlinterceptor.rc",
+        "nlinterceptor.xml",
+    ],
+}
+
+prebuilt_etc {
+    name: "nlinterceptor.rc",
+    src: "nlinterceptor.rc",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "nlinterceptor.xml",
+    src: "nlinterceptor.xml",
+    sub_dir: "vintf",
+    installable: false,
 }
diff --git a/wifi/netlinkinterceptor/aidl/default/apex_file_contexts b/wifi/netlinkinterceptor/aidl/default/apex_file_contexts
new file mode 100644
index 0000000..6ee544c
--- /dev/null
+++ b/wifi/netlinkinterceptor/aidl/default/apex_file_contexts
@@ -0,0 +1,3 @@
+(/.*)?                                                          u:object_r:vendor_file:s0
+/etc(/.*)?                                                      u:object_r:vendor_configs_file:s0
+/bin/hw/android\.hardware\.net\.nlinterceptor-service\.default	u:object_r:hal_nlinterceptor_default_exec:s0
diff --git a/wifi/netlinkinterceptor/aidl/default/apex_manifest.json b/wifi/netlinkinterceptor/aidl/default/apex_manifest.json
new file mode 100644
index 0000000..4ffeac5
--- /dev/null
+++ b/wifi/netlinkinterceptor/aidl/default/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+    "name": "com.android.hardware.net.nlinterceptor",
+    "version": 1
+}
diff --git a/wifi/netlinkinterceptor/aidl/default/nlinterceptor-default.rc b/wifi/netlinkinterceptor/aidl/default/nlinterceptor-default.rc
deleted file mode 100644
index 353cb27..0000000
--- a/wifi/netlinkinterceptor/aidl/default/nlinterceptor-default.rc
+++ /dev/null
@@ -1,4 +0,0 @@
-service nlinterceptor /vendor/bin/hw/android.hardware.net.nlinterceptor-service.default
-    class hal
-    user root
-    group system inet
diff --git a/wifi/netlinkinterceptor/aidl/default/nlinterceptor.rc b/wifi/netlinkinterceptor/aidl/default/nlinterceptor.rc
new file mode 100644
index 0000000..ec9baa9
--- /dev/null
+++ b/wifi/netlinkinterceptor/aidl/default/nlinterceptor.rc
@@ -0,0 +1,4 @@
+service nlinterceptor /apex/com.android.hardware.net.nlinterceptor/bin/hw/android.hardware.net.nlinterceptor-service.default
+    class hal
+    user root
+    group system inet
diff --git a/wifi/netlinkinterceptor/aidl/default/nlinterceptor-default.xml b/wifi/netlinkinterceptor/aidl/default/nlinterceptor.xml
similarity index 100%
rename from wifi/netlinkinterceptor/aidl/default/nlinterceptor-default.xml
rename to wifi/netlinkinterceptor/aidl/default/nlinterceptor.xml