Merge changes from topic "cherrypicker-L47600000963081744:N22400001405878786" into udc-qpr-dev

* changes:
  audio: Improve testing of point-to-point connections
  audio: Mitigate double receiving of the "exit" command
  Audio : Add 3 VTS test cases for remote submix module
  r_submix : Fix minor issues in AIDL implementation
  Avoid sub_overflow_minimal in AIDL effects VTS
  audio: Properly support external device port with static configs
  Remove unused file EffectWorker.h
  Remove the limitation of max open streams
  audio: Make IConfig.getSurroundSound default implementation more robust
diff --git a/audio/aidl/default/Config.cpp b/audio/aidl/default/Config.cpp
index d1023da..308200a 100644
--- a/audio/aidl/default/Config.cpp
+++ b/audio/aidl/default/Config.cpp
@@ -27,12 +27,11 @@
 
 namespace aidl::android::hardware::audio::core {
 ndk::ScopedAStatus Config::getSurroundSoundConfig(SurroundSoundConfig* _aidl_return) {
+    static const auto& func = __func__;
     static const SurroundSoundConfig surroundSoundConfig = [this]() {
-        SurroundSoundConfig surroundCfg;
-        if (mAudioPolicyConverter.getStatus() == ::android::OK) {
-            surroundCfg = mAudioPolicyConverter.getSurroundSoundConfig();
-        } else {
-            LOG(WARNING) << __func__ << mAudioPolicyConverter.getError();
+        SurroundSoundConfig surroundCfg = mAudioPolicyConverter.getSurroundSoundConfig();
+        if (mAudioPolicyConverter.getStatus() != ::android::OK) {
+            LOG(WARNING) << func << ": " << mAudioPolicyConverter.getError();
         }
         return surroundCfg;
     }();
@@ -42,21 +41,22 @@
 }
 
 ndk::ScopedAStatus Config::getEngineConfig(AudioHalEngineConfig* _aidl_return) {
+    static const auto& func = __func__;
     static const AudioHalEngineConfig returnEngCfg = [this]() {
         AudioHalEngineConfig engConfig;
         if (mEngConfigConverter.getStatus() == ::android::OK) {
             engConfig = mEngConfigConverter.getAidlEngineConfig();
         } else {
-            LOG(INFO) << __func__ << mEngConfigConverter.getError();
+            LOG(INFO) << func << ": " << mEngConfigConverter.getError();
             if (mAudioPolicyConverter.getStatus() == ::android::OK) {
                 engConfig = mAudioPolicyConverter.getAidlEngineConfig();
             } else {
-                LOG(WARNING) << __func__ << mAudioPolicyConverter.getError();
+                LOG(WARNING) << func << ": " << mAudioPolicyConverter.getError();
             }
         }
         // Logging full contents of the config is an overkill, just provide statistics.
-        LOG(DEBUG) << "getEngineConfig: number of strategies parsed: "
-                   << engConfig.productStrategies.size()
+        LOG(DEBUG) << func
+                   << ": number of strategies parsed: " << engConfig.productStrategies.size()
                    << ", default strategy: " << engConfig.defaultProductStrategyId
                    << ", number of volume groups parsed: " << engConfig.volumeGroups.size();
         return engConfig;
diff --git a/audio/aidl/default/Configuration.cpp b/audio/aidl/default/Configuration.cpp
index 8e02e7d..b9f1131 100644
--- a/audio/aidl/default/Configuration.cpp
+++ b/audio/aidl/default/Configuration.cpp
@@ -230,14 +230,14 @@
 
         AudioPort primaryOutMix = createPort(c.nextPortId++, "primary output",
                                              makeBitPositionFlagMask(AudioOutputFlags::PRIMARY),
-                                             false, createPortMixExt(1, 1));
+                                             false, createPortMixExt(0, 0));
         primaryOutMix.profiles.insert(primaryOutMix.profiles.begin(),
                                       standardPcmAudioProfiles.begin(),
                                       standardPcmAudioProfiles.end());
         c.ports.push_back(primaryOutMix);
 
         AudioPort primaryInMix =
-                createPort(c.nextPortId++, "primary input", 0, true, createPortMixExt(1, 1));
+                createPort(c.nextPortId++, "primary input", 0, true, createPortMixExt(0, 0));
         primaryInMix.profiles.push_back(
                 createProfile(PcmType::INT_16_BIT,
                               {AudioChannelLayout::LAYOUT_MONO, AudioChannelLayout::LAYOUT_STEREO},
@@ -461,6 +461,10 @@
 //    - no profiles specified
 //  * "Test In", IN_AFE_PROXY
 //    - no profiles specified
+//  * "Wired Headset", OUT_HEADSET
+//    - profile PCM 24-bit; STEREO; 48000
+//  * "Wired Headset Mic", IN_HEADSET
+//    - profile PCM 24-bit; MONO; 48000
 //
 // Mix ports:
 //  * "test output", 1 max open, 1 max active stream
@@ -476,7 +480,8 @@
 //
 // Routes:
 //  "test output", "test fast output", "test compressed offload" -> "Test Out"
-//  "Test In" -> "test input"
+//  "test output" -> "Wired Headset"
+//  "Test In", "Wired Headset Mic" -> "test input"
 //
 // Initial port configs:
 //  * "Test Out" device port: PCM 24-bit; STEREO; 48000
@@ -496,6 +501,14 @@
                                  AudioChannelLayout::LAYOUT_STEREO, 48000, 0, false,
                                  createDeviceExt(AudioDeviceType::OUT_AFE_PROXY, 0)));
 
+        AudioPort headsetOutDevice =
+                createPort(c.nextPortId++, "Wired Headset", 0, false,
+                           createDeviceExt(AudioDeviceType::OUT_HEADSET, 0,
+                                           AudioDeviceDescription::CONNECTION_ANALOG));
+        headsetOutDevice.profiles.push_back(
+                createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_STEREO}, {48000}));
+        c.ports.push_back(headsetOutDevice);
+
         AudioPort testInDevice = createPort(c.nextPortId++, "Test In", 0, true,
                                             createDeviceExt(AudioDeviceType::IN_AFE_PROXY, 0));
         c.ports.push_back(testInDevice);
@@ -504,6 +517,14 @@
                                  AudioChannelLayout::LAYOUT_MONO, 48000, 0, true,
                                  createDeviceExt(AudioDeviceType::IN_AFE_PROXY, 0)));
 
+        AudioPort headsetInDevice =
+                createPort(c.nextPortId++, "Wired Headset Mic", 0, true,
+                           createDeviceExt(AudioDeviceType::IN_HEADSET, 0,
+                                           AudioDeviceDescription::CONNECTION_ANALOG));
+        headsetInDevice.profiles.push_back(
+                createProfile(PcmType::INT_24_BIT, {AudioChannelLayout::LAYOUT_MONO}, {48000}));
+        c.ports.push_back(headsetInDevice);
+
         // Mix ports
 
         AudioPort testOutMix =
@@ -549,7 +570,8 @@
 
         c.routes.push_back(
                 createRoute({testOutMix, testFastOutMix, compressedOffloadOutMix}, testOutDevice));
-        c.routes.push_back(createRoute({testInDevice}, testInMIx));
+        c.routes.push_back(createRoute({testOutMix}, headsetOutDevice));
+        c.routes.push_back(createRoute({testInDevice, headsetInDevice}, testInMIx));
 
         c.portConfigs.insert(c.portConfigs.end(), c.initialConfigs.begin(), c.initialConfigs.end());
 
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index f81095e..b7761bf 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -454,16 +454,15 @@
             LOG(ERROR) << __func__ << ": port id " << templateId << " is not a device port";
             return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
         }
-        if (!templateIt->profiles.empty()) {
-            LOG(ERROR) << __func__ << ": port id " << templateId
-                       << " does not have dynamic profiles";
-            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
-        }
         auto& templateDevicePort = templateIt->ext.get<AudioPortExt::Tag::device>();
         if (templateDevicePort.device.type.connection.empty()) {
             LOG(ERROR) << __func__ << ": port id " << templateId << " is permanently attached";
             return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
         }
+        if (mConnectedDevicePorts.find(templateId) != mConnectedDevicePorts.end()) {
+            LOG(ERROR) << __func__ << ": port id " << templateId << " is a connected device port";
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        }
         // Postpone id allocation until we ensure that there are no client errors.
         connectedPort = *templateIt;
         connectedPort.extraAudioDescriptors = in_templateIdAndAdditionalData.extraAudioDescriptors;
@@ -486,19 +485,23 @@
         }
     }
 
-    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 (connectedPort.profiles.empty()) {
-        LOG(ERROR) << "Profiles of a connected port still empty after connecting external device "
-                   << connectedPort.toString();
-        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+        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 (connectedPort.profiles.empty()) {
+            LOG(ERROR) << __func__
+                       << ": profiles of a connected port still empty after connecting external "
+                          "device "
+                       << connectedPort.toString();
+            return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+        }
     }
 
     for (auto profile : connectedPort.profiles) {
diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp
index af89f5f..f7298c0 100644
--- a/audio/aidl/default/Stream.cpp
+++ b/audio/aidl/default/Stream.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <pthread.h>
+
 #define LOG_TAG "AHAL_Stream"
 #include <android-base/logging.h>
 #include <android/binder_ibinder_platform.h>
@@ -94,6 +96,14 @@
     mDataMQ.reset();
 }
 
+pid_t StreamWorkerCommonLogic::getTid() const {
+#if defined(__ANDROID__)
+    return pthread_gettid_np(pthread_self());
+#else
+    return 0;
+#endif
+}
+
 std::string StreamWorkerCommonLogic::init() {
     if (mContext->getCommandMQ() == nullptr) return "Command MQ is null";
     if (mContext->getReplyMQ() == nullptr) return "Reply MQ is null";
@@ -164,7 +174,7 @@
     switch (command.getTag()) {
         case Tag::halReservedExit:
             if (const int32_t cookie = command.get<Tag::halReservedExit>();
-                cookie == mContext->getInternalCommandCookie()) {
+                cookie == (mContext->getInternalCommandCookie() ^ getTid())) {
                 mDriver->shutdown();
                 setClosed();
                 // This is an internal command, no need to reply.
@@ -384,7 +394,7 @@
     switch (command.getTag()) {
         case Tag::halReservedExit:
             if (const int32_t cookie = command.get<Tag::halReservedExit>();
-                cookie == mContext->getInternalCommandCookie()) {
+                cookie == (mContext->getInternalCommandCookie() ^ getTid())) {
                 mDriver->shutdown();
                 setClosed();
                 // This is an internal command, no need to reply.
@@ -717,7 +727,7 @@
     if (auto commandMQ = mContext.getCommandMQ(); commandMQ != nullptr) {
         LOG(DEBUG) << __func__ << ": asking the worker to exit...";
         auto cmd = StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::halReservedExit>(
-                mContext.getInternalCommandCookie());
+                mContext.getInternalCommandCookie() ^ mWorker->getTid());
         // Note: never call 'pause' and 'resume' methods of StreamWorker
         // in the HAL implementation. These methods are to be used by
         // the client side only. Preventing the worker loop from running
diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h
index a02655f..88fddec 100644
--- a/audio/aidl/default/include/core-impl/Stream.h
+++ b/audio/aidl/default/include/core-impl/Stream.h
@@ -223,6 +223,7 @@
         : mContext(context),
           mDriver(driver),
           mTransientStateDelayMs(context->getTransientStateDelayMs()) {}
+    pid_t getTid() const;
     std::string init() override;
     void populateReply(StreamDescriptor::Reply* reply, bool isConnected) const;
     void populateReplyWrongState(StreamDescriptor::Reply* reply,
diff --git a/audio/aidl/default/include/core-impl/XmlConverter.h b/audio/aidl/default/include/core-impl/XmlConverter.h
index a68a6fd..383ea24 100644
--- a/audio/aidl/default/include/core-impl/XmlConverter.h
+++ b/audio/aidl/default/include/core-impl/XmlConverter.h
@@ -53,10 +53,16 @@
                                      const ::android::status_t& status) {
         std::string errorMessage;
         if (status != ::android::OK) {
-            if (!isReadableConfigFile) {
-                errorMessage = "Could not read requested config file:" + configFilePath;
+            if (configFilePath.empty()) {
+                errorMessage = "No audio configuration files found";
+            } else if (!isReadableConfigFile) {
+                errorMessage = std::string("Could not read requested XML config file: \"")
+                                       .append(configFilePath)
+                                       .append("\"");
             } else {
-                errorMessage = "Invalid config file: " + configFilePath;
+                errorMessage = std::string("Invalid XML config file: \"")
+                                       .append(configFilePath)
+                                       .append("\"");
             }
         }
         return errorMessage;
diff --git a/audio/aidl/default/include/effect-impl/EffectWorker.h b/audio/aidl/default/include/effect-impl/EffectWorker.h
deleted file mode 100644
index 421429a..0000000
--- a/audio/aidl/default/include/effect-impl/EffectWorker.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-#include <algorithm>
-#include <memory>
-#include <mutex>
-#include <string>
-
-#include "EffectContext.h"
-#include "EffectThread.h"
-
-namespace aidl::android::hardware::audio::effect {
-
-std::string toString(RetCode& code);
-
-class EffectWorker : public EffectThread {
-  public:
-    // set effect context for worker, suppose to only happen once here
-    void setContext(std::shared_ptr<EffectContext> context) {
-        std::call_once(mOnceFlag, [&]() { mContext = context; });
-    };
-
-    // handle FMQ and call effect implemented virtual function
-    void process() override {
-        RETURN_VALUE_IF(!mContext, void(), "nullContext");
-        std::shared_ptr<EffectContext::StatusMQ> statusMQ = mContext->getStatusFmq();
-        std::shared_ptr<EffectContext::DataMQ> inputMQ = mContext->getInputDataFmq();
-        std::shared_ptr<EffectContext::DataMQ> outputMQ = mContext->getOutputDataFmq();
-
-        // Only this worker will read from input data MQ and write to output data MQ.
-        auto readSamples = inputMQ->availableToRead(), writeSamples = outputMQ->availableToWrite();
-        if (readSamples && writeSamples) {
-            auto processSamples = std::min(readSamples, writeSamples);
-            LOG(VERBOSE) << __func__ << " available to read " << readSamples
-                         << " available to write " << writeSamples << " process " << processSamples;
-
-            auto buffer = mContext->getWorkBuffer();
-            inputMQ->read(buffer, processSamples);
-
-            IEffect::Status status = effectProcessImpl(buffer, buffer, processSamples);
-            outputMQ->write(buffer, status.fmqProduced);
-            statusMQ->writeBlocking(&status, 1);
-            LOG(VERBOSE) << __func__ << " done processing, effect consumed " << status.fmqConsumed
-                         << " produced " << status.fmqProduced;
-        } else {
-            // TODO: maybe add some sleep here to avoid busy waiting
-        }
-    }
-
-    // must implement by each effect implementation
-    // TODO: consider if this interface need adjustment to handle in-place processing
-    virtual IEffect::Status effectProcessImpl(float* in, float* out, int samples) = 0;
-
-  private:
-    // make sure the context only set once.
-    std::once_flag mOnceFlag;
-    std::shared_ptr<EffectContext> mContext;
-};
-
-}  // namespace aidl::android::hardware::audio::effect
diff --git a/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp b/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
index 3134b86..9c9c08b 100644
--- a/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
+++ b/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
@@ -179,7 +179,7 @@
         LOG(ERROR) << __func__ << ": transfer without a pipe!";
         return ::android::UNEXPECTED_NULL;
     }
-
+    mCurrentRoute->exitStandby(mIsInput);
     return (mIsInput ? inRead(buffer, frameCount, actualFrameCount)
                      : outWrite(buffer, frameCount, actualFrameCount));
 }
@@ -190,17 +190,14 @@
         return ::android::NO_INIT;
     }
     const ssize_t framesInPipe = source->availableToRead();
-    if (framesInPipe < 0) {
-        return ::android::INVALID_OPERATION;
+    if (framesInPipe <= 0) {
+        // No need to update the position frames
+        return ::android::OK;
     }
     if (mIsInput) {
         position->frames += framesInPipe;
-    } else {
-        if (position->frames > framesInPipe) {
-            position->frames -= framesInPipe;
-        } else {
-            position->frames = 0;
-        }
+    } else if (position->frames >= framesInPipe) {
+        position->frames -= framesInPipe;
     }
     return ::android::OK;
 }
@@ -280,18 +277,14 @@
                                                size_t* actualFrameCount) {
     // about to read from audio source
     sp<MonoPipeReader> source = mCurrentRoute->getSource();
-    if (source == nullptr || source->availableToRead() == 0) {
-        if (source == nullptr) {
-            int readErrorCount = mCurrentRoute->notifyReadError();
-            if (readErrorCount < kMaxReadErrorLogs) {
-                LOG(ERROR) << __func__
-                           << ": no audio pipe yet we're trying to read! (not all errors will be "
-                              "logged)";
-            } else {
-                LOG(ERROR) << __func__ << ": Read errors " << readErrorCount;
-            }
+    if (source == nullptr) {
+        int readErrorCount = mCurrentRoute->notifyReadError();
+        if (readErrorCount < kMaxReadErrorLogs) {
+            LOG(ERROR) << __func__
+                       << ": no audio pipe yet we're trying to read! (not all errors will be "
+                          "logged)";
         } else {
-            LOG(INFO) << __func__ << ": no data to read yet, providing empty data";
+            LOG(ERROR) << __func__ << ": Read errors " << readErrorCount;
         }
         const size_t delayUs = static_cast<size_t>(
                 std::roundf(frameCount * MICROS_PER_SECOND / mStreamConfig.sampleRate));
@@ -306,9 +299,10 @@
     const size_t delayUs = static_cast<size_t>(std::roundf(kReadAttemptSleepUs));
     char* buff = (char*)buffer;
     size_t remainingFrames = frameCount;
+    int availableToRead = source->availableToRead();
 
-    while ((remainingFrames > 0) && (attempts < kMaxReadFailureAttempts)) {
-        LOG(VERBOSE) << __func__ << ": frames available to read " << source->availableToRead();
+    while ((remainingFrames > 0) && (availableToRead > 0) && (attempts < kMaxReadFailureAttempts)) {
+        LOG(VERBOSE) << __func__ << ": frames available to read " << availableToRead;
 
         ssize_t framesRead = source->read(buff, remainingFrames);
 
@@ -317,6 +311,7 @@
         if (framesRead > 0) {
             remainingFrames -= framesRead;
             buff += framesRead * mStreamConfig.frameSize;
+            availableToRead -= framesRead;
             LOG(VERBOSE) << __func__ << ": (attempts = " << attempts << ") got " << framesRead
                          << " frames, remaining=" << remainingFrames;
         } else {
diff --git a/audio/aidl/vts/EffectHelper.h b/audio/aidl/vts/EffectHelper.h
index 685d07d..2c8edf2 100644
--- a/audio/aidl/vts/EffectHelper.h
+++ b/audio/aidl/vts/EffectHelper.h
@@ -250,11 +250,11 @@
                    maxLimit = std::numeric_limits<S>::max();
         if (s.size()) {
             const auto min = *s.begin(), max = *s.rbegin();
-            s.insert(min + (max - min) / 2);
-            if (min != minLimit) {
+            s.insert((min & max) + ((min ^ max) >> 1));
+            if (min > minLimit + 1) {
                 s.insert(min - 1);
             }
-            if (max != maxLimit) {
+            if (max < maxLimit - 1) {
                 s.insert(max + 1);
             }
         }
diff --git a/audio/aidl/vts/ModuleConfig.cpp b/audio/aidl/vts/ModuleConfig.cpp
index 7213034..8fdb155 100644
--- a/audio/aidl/vts/ModuleConfig.cpp
+++ b/audio/aidl/vts/ModuleConfig.cpp
@@ -66,15 +66,36 @@
     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,
+        const std::vector<AudioDeviceType>& deviceTypes, const std::string& connection) {
     std::vector<AudioPort> result;
-    std::copy_if(ports.begin(), ports.end(), std::back_inserter(result), [](const auto& port) {
-        const auto type = port.ext.template get<AudioPortExt::Tag::device>().device.type;
-        return type.connection.empty() && (type.type == AudioDeviceType::IN_MICROPHONE ||
-                                           type.type == AudioDeviceType::IN_MICROPHONE_BACK);
-    });
+    for (const auto& port : ports) {
+        if (port.ext.getTag() != AudioPortExt::Tag::device) continue;
+        const auto type = port.ext.get<AudioPortExt::Tag::device>().device.type;
+        if (type.connection == connection) {
+            for (auto deviceType : deviceTypes) {
+                if (type.type == deviceType) {
+                    result.push_back(port);
+                }
+            }
+        }
+    }
     return result;
 }
 
@@ -119,6 +140,31 @@
     return result;
 }
 
+std::vector<AudioPort> ModuleConfig::getConnectedExternalDevicePorts() const {
+    std::vector<AudioPort> result;
+    std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [&](const auto& port) {
+        return mConnectedExternalSinkDevicePorts.count(port.id) != 0 ||
+               mConnectedExternalSourceDevicePorts.count(port.id) != 0;
+    });
+    return result;
+}
+
+std::set<int32_t> ModuleConfig::getConnectedSinkDevicePorts() const {
+    std::set<int32_t> result;
+    result.insert(mAttachedSinkDevicePorts.begin(), mAttachedSinkDevicePorts.end());
+    result.insert(mConnectedExternalSinkDevicePorts.begin(),
+                  mConnectedExternalSinkDevicePorts.end());
+    return result;
+}
+
+std::set<int32_t> ModuleConfig::getConnectedSourceDevicePorts() const {
+    std::set<int32_t> result;
+    result.insert(mAttachedSourceDevicePorts.begin(), mAttachedSourceDevicePorts.end());
+    result.insert(mConnectedExternalSourceDevicePorts.begin(),
+                  mConnectedExternalSourceDevicePorts.end());
+    return result;
+}
+
 std::vector<AudioPort> ModuleConfig::getExternalDevicePorts() const {
     std::vector<AudioPort> result;
     std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result),
@@ -126,76 +172,77 @@
     return result;
 }
 
-std::vector<AudioPort> ModuleConfig::getInputMixPorts(bool attachedOnly) const {
+std::vector<AudioPort> ModuleConfig::getInputMixPorts(bool connectedOnly) const {
     std::vector<AudioPort> result;
     std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [&](const auto& port) {
         return port.ext.getTag() == AudioPortExt::Tag::mix &&
                port.flags.getTag() == AudioIoFlags::Tag::input &&
-               (!attachedOnly || !getAttachedSourceDevicesPortsForMixPort(port).empty());
+               (!connectedOnly || !getConnectedSourceDevicesPortsForMixPort(port).empty());
     });
     return result;
 }
 
-std::vector<AudioPort> ModuleConfig::getOutputMixPorts(bool attachedOnly) const {
+std::vector<AudioPort> ModuleConfig::getOutputMixPorts(bool connectedOnly) const {
     std::vector<AudioPort> result;
     std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [&](const auto& port) {
         return port.ext.getTag() == AudioPortExt::Tag::mix &&
                port.flags.getTag() == AudioIoFlags::Tag::output &&
-               (!attachedOnly || !getAttachedSinkDevicesPortsForMixPort(port).empty());
+               (!connectedOnly || !getConnectedSinkDevicesPortsForMixPort(port).empty());
     });
     return result;
 }
 
-std::vector<AudioPort> ModuleConfig::getNonBlockingMixPorts(bool attachedOnly,
+std::vector<AudioPort> ModuleConfig::getNonBlockingMixPorts(bool connectedOnly,
                                                             bool singlePort) const {
-    return findMixPorts(false /*isInput*/, attachedOnly, singlePort, [&](const AudioPort& port) {
+    return findMixPorts(false /*isInput*/, connectedOnly, singlePort, [&](const AudioPort& port) {
         return isBitPositionFlagSet(port.flags.get<AudioIoFlags::Tag::output>(),
                                     AudioOutputFlags::NON_BLOCKING);
     });
 }
 
-std::vector<AudioPort> ModuleConfig::getOffloadMixPorts(bool attachedOnly, bool singlePort) const {
-    return findMixPorts(false /*isInput*/, attachedOnly, singlePort, [&](const AudioPort& port) {
+std::vector<AudioPort> ModuleConfig::getOffloadMixPorts(bool connectedOnly, bool singlePort) const {
+    return findMixPorts(false /*isInput*/, connectedOnly, singlePort, [&](const AudioPort& port) {
         return isBitPositionFlagSet(port.flags.get<AudioIoFlags::Tag::output>(),
                                     AudioOutputFlags::COMPRESS_OFFLOAD);
     });
 }
 
-std::vector<AudioPort> ModuleConfig::getPrimaryMixPorts(bool attachedOnly, bool singlePort) const {
-    return findMixPorts(false /*isInput*/, attachedOnly, singlePort, [&](const AudioPort& port) {
+std::vector<AudioPort> ModuleConfig::getPrimaryMixPorts(bool connectedOnly, bool singlePort) const {
+    return findMixPorts(false /*isInput*/, connectedOnly, singlePort, [&](const AudioPort& port) {
         return isBitPositionFlagSet(port.flags.get<AudioIoFlags::Tag::output>(),
                                     AudioOutputFlags::PRIMARY);
     });
 }
 
-std::vector<AudioPort> ModuleConfig::getMmapOutMixPorts(bool attachedOnly, bool singlePort) const {
-    return findMixPorts(false /*isInput*/, attachedOnly, singlePort, [&](const AudioPort& port) {
+std::vector<AudioPort> ModuleConfig::getMmapOutMixPorts(bool connectedOnly, bool singlePort) const {
+    return findMixPorts(false /*isInput*/, connectedOnly, singlePort, [&](const AudioPort& port) {
         return isBitPositionFlagSet(port.flags.get<AudioIoFlags::Tag::output>(),
                                     AudioOutputFlags::MMAP_NOIRQ);
     });
 }
 
-std::vector<AudioPort> ModuleConfig::getMmapInMixPorts(bool attachedOnly, bool singlePort) const {
-    return findMixPorts(true /*isInput*/, attachedOnly, singlePort, [&](const AudioPort& port) {
+std::vector<AudioPort> ModuleConfig::getMmapInMixPorts(bool connectedOnly, bool singlePort) const {
+    return findMixPorts(true /*isInput*/, connectedOnly, singlePort, [&](const AudioPort& port) {
         return isBitPositionFlagSet(port.flags.get<AudioIoFlags::Tag::input>(),
                                     AudioInputFlags::MMAP_NOIRQ);
     });
 }
 
-std::vector<AudioPort> ModuleConfig::getAttachedDevicesPortsForMixPort(
+std::vector<AudioPort> ModuleConfig::getConnectedDevicesPortsForMixPort(
         bool isInput, const AudioPortConfig& mixPortConfig) const {
     const auto mixPortIt = findById<AudioPort>(mPorts, mixPortConfig.portId);
     if (mixPortIt != mPorts.end()) {
-        return getAttachedDevicesPortsForMixPort(isInput, *mixPortIt);
+        return getConnectedDevicesPortsForMixPort(isInput, *mixPortIt);
     }
     return {};
 }
 
-std::vector<AudioPort> ModuleConfig::getAttachedSinkDevicesPortsForMixPort(
+std::vector<AudioPort> ModuleConfig::getConnectedSinkDevicesPortsForMixPort(
         const AudioPort& mixPort) const {
     std::vector<AudioPort> result;
+    std::set<int32_t> connectedSinkDevicePorts = getConnectedSinkDevicePorts();
     for (const auto& route : mRoutes) {
-        if (mAttachedSinkDevicePorts.count(route.sinkPortId) != 0 &&
+        if ((connectedSinkDevicePorts.count(route.sinkPortId) != 0) &&
             std::find(route.sourcePortIds.begin(), route.sourcePortIds.end(), mixPort.id) !=
                     route.sourcePortIds.end()) {
             const auto devicePortIt = findById<AudioPort>(mPorts, route.sinkPortId);
@@ -205,13 +252,14 @@
     return result;
 }
 
-std::vector<AudioPort> ModuleConfig::getAttachedSourceDevicesPortsForMixPort(
+std::vector<AudioPort> ModuleConfig::getConnectedSourceDevicesPortsForMixPort(
         const AudioPort& mixPort) const {
     std::vector<AudioPort> result;
+    std::set<int32_t> connectedSourceDevicePorts = getConnectedSourceDevicePorts();
     for (const auto& route : mRoutes) {
         if (route.sinkPortId == mixPort.id) {
             for (const auto srcId : route.sourcePortIds) {
-                if (mAttachedSourceDevicePorts.count(srcId) != 0) {
+                if (connectedSourceDevicePorts.count(srcId) != 0) {
                     const auto devicePortIt = findById<AudioPort>(mPorts, srcId);
                     if (devicePortIt != mPorts.end()) result.push_back(*devicePortIt);
                 }
@@ -221,9 +269,10 @@
     return result;
 }
 
-std::optional<AudioPort> ModuleConfig::getSourceMixPortForAttachedDevice() const {
+std::optional<AudioPort> ModuleConfig::getSourceMixPortForConnectedDevice() const {
+    std::set<int32_t> connectedSinkDevicePorts = getConnectedSinkDevicePorts();
     for (const auto& route : mRoutes) {
-        if (mAttachedSinkDevicePorts.count(route.sinkPortId) != 0) {
+        if (connectedSinkDevicePorts.count(route.sinkPortId) != 0) {
             const auto mixPortIt = findById<AudioPort>(mPorts, route.sourcePortIds[0]);
             if (mixPortIt != mPorts.end()) return *mixPortIt;
         }
@@ -233,7 +282,7 @@
 
 std::optional<ModuleConfig::SrcSinkPair> ModuleConfig::getNonRoutableSrcSinkPair(
         bool isInput) const {
-    const auto mixPorts = getMixPorts(isInput, false /*attachedOnly*/);
+    const auto mixPorts = getMixPorts(isInput, false /*connectedOnly*/);
     std::set<std::pair<int32_t, int32_t>> allowedRoutes;
     for (const auto& route : mRoutes) {
         for (const auto srcPortId : route.sourcePortIds) {
@@ -243,7 +292,8 @@
     auto make_pair = [isInput](auto& device, auto& mix) {
         return isInput ? std::make_pair(device, mix) : std::make_pair(mix, device);
     };
-    for (const auto portId : isInput ? mAttachedSourceDevicePorts : mAttachedSinkDevicePorts) {
+    for (const auto portId :
+         isInput ? getConnectedSourceDevicePorts() : getConnectedSinkDevicePorts()) {
         const auto devicePortIt = findById<AudioPort>(mPorts, portId);
         if (devicePortIt == mPorts.end()) continue;
         auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt);
@@ -262,10 +312,11 @@
 
 std::optional<ModuleConfig::SrcSinkPair> ModuleConfig::getRoutableSrcSinkPair(bool isInput) const {
     if (isInput) {
+        std::set<int32_t> connectedSourceDevicePorts = getConnectedSourceDevicePorts();
         for (const auto& route : mRoutes) {
             auto srcPortIdIt = std::find_if(
                     route.sourcePortIds.begin(), route.sourcePortIds.end(),
-                    [&](const auto& portId) { return mAttachedSourceDevicePorts.count(portId); });
+                    [&](const auto& portId) { return connectedSourceDevicePorts.count(portId); });
             if (srcPortIdIt == route.sourcePortIds.end()) continue;
             const auto devicePortIt = findById<AudioPort>(mPorts, *srcPortIdIt);
             const auto mixPortIt = findById<AudioPort>(mPorts, route.sinkPortId);
@@ -276,8 +327,9 @@
             return std::make_pair(devicePortConfig, mixPortConfig.value());
         }
     } else {
+        std::set<int32_t> connectedSinkDevicePorts = getConnectedSinkDevicePorts();
         for (const auto& route : mRoutes) {
-            if (mAttachedSinkDevicePorts.count(route.sinkPortId) == 0) continue;
+            if (connectedSinkDevicePorts.count(route.sinkPortId) == 0) continue;
             const auto mixPortIt = findById<AudioPort>(mPorts, route.sourcePortIds[0]);
             const auto devicePortIt = findById<AudioPort>(mPorts, route.sinkPortId);
             if (devicePortIt == mPorts.end() || mixPortIt == mPorts.end()) continue;
@@ -293,11 +345,12 @@
 std::vector<ModuleConfig::SrcSinkGroup> ModuleConfig::getRoutableSrcSinkGroups(bool isInput) const {
     std::vector<SrcSinkGroup> result;
     if (isInput) {
+        std::set<int32_t> connectedSourceDevicePorts = getConnectedSourceDevicePorts();
         for (const auto& route : mRoutes) {
             std::vector<int32_t> srcPortIds;
             std::copy_if(route.sourcePortIds.begin(), route.sourcePortIds.end(),
                          std::back_inserter(srcPortIds), [&](const auto& portId) {
-                             return mAttachedSourceDevicePorts.count(portId);
+                             return connectedSourceDevicePorts.count(portId);
                          });
             if (srcPortIds.empty()) continue;
             const auto mixPortIt = findById<AudioPort>(mPorts, route.sinkPortId);
@@ -317,8 +370,9 @@
             }
         }
     } else {
+        std::set<int32_t> connectedSinkDevicePorts = getConnectedSinkDevicePorts();
         for (const auto& route : mRoutes) {
-            if (mAttachedSinkDevicePorts.count(route.sinkPortId) == 0) continue;
+            if (connectedSinkDevicePorts.count(route.sinkPortId) == 0) continue;
             const auto devicePortIt = findById<AudioPort>(mPorts, route.sinkPortId);
             if (devicePortIt == mPorts.end()) continue;
             auto devicePortConfig = getSingleConfigForDevicePort(*devicePortIt);
@@ -352,6 +406,8 @@
     result.append(android::internal::ToString(mAttachedSourceDevicePorts));
     result.append("\nExternal device ports: ");
     result.append(android::internal::ToString(mExternalDevicePorts));
+    result.append("\nConnected external device ports: ");
+    result.append(android::internal::ToString(getConnectedExternalDevicePorts()));
     result.append("\nRoutes: ");
     result.append(android::internal::ToString(mRoutes));
     return result;
@@ -384,10 +440,10 @@
 }
 
 std::vector<AudioPort> ModuleConfig::findMixPorts(
-        bool isInput, bool attachedOnly, bool singlePort,
+        bool isInput, bool connectedOnly, bool singlePort,
         const std::function<bool(const AudioPort&)>& pred) const {
     std::vector<AudioPort> result;
-    const auto mixPorts = getMixPorts(isInput, attachedOnly);
+    const auto mixPorts = getMixPorts(isInput, connectedOnly);
     for (auto mixPortIt = mixPorts.begin(); mixPortIt != mixPorts.end();) {
         mixPortIt = std::find_if(mixPortIt, mixPorts.end(), pred);
         if (mixPortIt == mixPorts.end()) break;
@@ -401,7 +457,7 @@
         const std::vector<AudioPort>& ports, bool isInput, bool singleProfile) const {
     std::vector<AudioPortConfig> result;
     for (const auto& mixPort : ports) {
-        if (getAttachedDevicesPortsForMixPort(isInput, mixPort).empty()) {
+        if (getConnectedDevicesPortsForMixPort(isInput, mixPort).empty()) {
             continue;
         }
         for (const auto& profile : mixPort.profiles) {
@@ -443,10 +499,48 @@
     return result;
 }
 
+const ndk::ScopedAStatus& ModuleConfig::onExternalDeviceConnected(IModule* module,
+                                                                  const AudioPort& port) {
+    // Update ports and routes
+    mStatus = module->getAudioPorts(&mPorts);
+    if (!mStatus.isOk()) return mStatus;
+    mStatus = module->getAudioRoutes(&mRoutes);
+    if (!mStatus.isOk()) return mStatus;
+
+    // Validate port is present in module
+    if (std::find(mPorts.begin(), mPorts.end(), port) == mPorts.end()) {
+        mStatus = ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+        return mStatus;
+    }
+
+    if (port.flags.getTag() == aidl::android::media::audio::common::AudioIoFlags::Tag::input) {
+        mConnectedExternalSourceDevicePorts.insert(port.id);
+    } else {
+        mConnectedExternalSinkDevicePorts.insert(port.id);
+    }
+    return mStatus;
+}
+
+const ndk::ScopedAStatus& ModuleConfig::onExternalDeviceDisconnected(IModule* module,
+                                                                     const AudioPort& port) {
+    // Update ports and routes
+    mStatus = module->getAudioPorts(&mPorts);
+    if (!mStatus.isOk()) return mStatus;
+    mStatus = module->getAudioRoutes(&mRoutes);
+    if (!mStatus.isOk()) return mStatus;
+
+    if (port.flags.getTag() == aidl::android::media::audio::common::AudioIoFlags::Tag::input) {
+        mConnectedExternalSourceDevicePorts.erase(port.id);
+    } else {
+        mConnectedExternalSinkDevicePorts.erase(port.id);
+    }
+    return mStatus;
+}
+
 bool ModuleConfig::isMmapSupported() const {
     const std::vector<AudioPort> mmapOutMixPorts =
-            getMmapOutMixPorts(false /*attachedOnly*/, false /*singlePort*/);
+            getMmapOutMixPorts(false /*connectedOnly*/, false /*singlePort*/);
     const std::vector<AudioPort> mmapInMixPorts =
-            getMmapInMixPorts(false /*attachedOnly*/, false /*singlePort*/);
+            getMmapInMixPorts(false /*connectedOnly*/, false /*singlePort*/);
     return !mmapOutMixPorts.empty() || !mmapInMixPorts.empty();
 }
diff --git a/audio/aidl/vts/ModuleConfig.h b/audio/aidl/vts/ModuleConfig.h
index 6a22075..bce1de1 100644
--- a/audio/aidl/vts/ModuleConfig.h
+++ b/audio/aidl/vts/ModuleConfig.h
@@ -37,6 +37,14 @@
     static std::optional<aidl::android::media::audio::common::AudioOffloadInfo>
     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,
+            const std::string& connection = "");
     static std::vector<aidl::android::media::audio::common::AudioPort> getBuiltInMicPorts(
             const std::vector<aidl::android::media::audio::common::AudioPort>& ports);
 
@@ -45,45 +53,55 @@
     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> getConnectedExternalDevicePorts()
+            const;
+    std::set<int32_t> getConnectedSinkDevicePorts() const;
+    std::set<int32_t> getConnectedSourceDevicePorts() const;
     std::vector<aidl::android::media::audio::common::AudioPort> getAttachedMicrophonePorts() const {
         return getBuiltInMicPorts(getAttachedDevicePorts());
     }
     std::vector<aidl::android::media::audio::common::AudioPort> getExternalDevicePorts() const;
     std::vector<aidl::android::media::audio::common::AudioPort> getInputMixPorts(
-            bool attachedOnly) const;
+            bool connectedOnly /*Permanently attached and connected external devices*/) const;
     std::vector<aidl::android::media::audio::common::AudioPort> getOutputMixPorts(
-            bool attachedOnly) const;
+            bool connectedOnly /*Permanently attached and connected external devices*/) const;
     std::vector<aidl::android::media::audio::common::AudioPort> getMixPorts(
-            bool isInput, bool attachedOnly) const {
-        return isInput ? getInputMixPorts(attachedOnly) : getOutputMixPorts(attachedOnly);
+            bool isInput,
+            bool connectedOnly /*Permanently attached and connected external devices*/) const {
+        return isInput ? getInputMixPorts(connectedOnly) : getOutputMixPorts(connectedOnly);
     }
     std::vector<aidl::android::media::audio::common::AudioPort> getNonBlockingMixPorts(
-            bool attachedOnly, bool singlePort) const;
+            bool connectedOnly /*Permanently attached and connected external devices*/,
+            bool singlePort) const;
     std::vector<aidl::android::media::audio::common::AudioPort> getOffloadMixPorts(
-            bool attachedOnly, bool singlePort) const;
+            bool connectedOnly /*Permanently attached and connected external devices*/,
+            bool singlePort) const;
     std::vector<aidl::android::media::audio::common::AudioPort> getPrimaryMixPorts(
-            bool attachedOnly, bool singlePort) const;
+            bool connectedOnly /*Permanently attached and connected external devices*/,
+            bool singlePort) const;
     std::vector<aidl::android::media::audio::common::AudioPort> getMmapOutMixPorts(
-            bool attachedOnly, bool singlePort) const;
+            bool connectedOnly /*Permanently attached and connected external devices*/,
+            bool singlePort) const;
     std::vector<aidl::android::media::audio::common::AudioPort> getMmapInMixPorts(
-            bool attachedOnly, bool singlePort) const;
+            bool connectedOnly /*Permanently attached and connected external devices*/,
+            bool singlePort) const;
 
-    std::vector<aidl::android::media::audio::common::AudioPort> getAttachedDevicesPortsForMixPort(
+    std::vector<aidl::android::media::audio::common::AudioPort> getConnectedDevicesPortsForMixPort(
             bool isInput, const aidl::android::media::audio::common::AudioPort& mixPort) const {
-        return isInput ? getAttachedSourceDevicesPortsForMixPort(mixPort)
-                       : getAttachedSinkDevicesPortsForMixPort(mixPort);
+        return isInput ? getConnectedSourceDevicesPortsForMixPort(mixPort)
+                       : getConnectedSinkDevicesPortsForMixPort(mixPort);
     }
-    std::vector<aidl::android::media::audio::common::AudioPort> getAttachedDevicesPortsForMixPort(
+    std::vector<aidl::android::media::audio::common::AudioPort> getConnectedDevicesPortsForMixPort(
             bool isInput,
             const aidl::android::media::audio::common::AudioPortConfig& mixPortConfig) const;
     std::vector<aidl::android::media::audio::common::AudioPort>
-    getAttachedSinkDevicesPortsForMixPort(
+    getConnectedSinkDevicesPortsForMixPort(
             const aidl::android::media::audio::common::AudioPort& mixPort) const;
     std::vector<aidl::android::media::audio::common::AudioPort>
-    getAttachedSourceDevicesPortsForMixPort(
+    getConnectedSourceDevicesPortsForMixPort(
             const aidl::android::media::audio::common::AudioPort& mixPort) const;
     std::optional<aidl::android::media::audio::common::AudioPort>
-    getSourceMixPortForAttachedDevice() const;
+    getSourceMixPortForConnectedDevice() const;
 
     std::optional<SrcSinkPair> getNonRoutableSrcSinkPair(bool isInput) const;
     std::optional<SrcSinkPair> getRoutableSrcSinkPair(bool isInput) const;
@@ -96,15 +114,15 @@
     std::vector<aidl::android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts()
             const {
         auto inputs =
-                generateAudioMixPortConfigs(getInputMixPorts(false /*attachedOnly*/), true, false);
-        auto outputs = generateAudioMixPortConfigs(getOutputMixPorts(false /*attachedOnly*/), false,
-                                                   false);
+                generateAudioMixPortConfigs(getInputMixPorts(false /*connectedOnly*/), true, false);
+        auto outputs = generateAudioMixPortConfigs(getOutputMixPorts(false /*connectedOnly*/),
+                                                   false, false);
         inputs.insert(inputs.end(), outputs.begin(), outputs.end());
         return inputs;
     }
     std::vector<aidl::android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts(
             bool isInput) const {
-        return generateAudioMixPortConfigs(getMixPorts(isInput, false /*attachedOnly*/), isInput,
+        return generateAudioMixPortConfigs(getMixPorts(isInput, false /*connectedOnly*/), isInput,
                                            false);
     }
     std::vector<aidl::android::media::audio::common::AudioPortConfig> getPortConfigsForMixPorts(
@@ -114,7 +132,7 @@
     std::optional<aidl::android::media::audio::common::AudioPortConfig> getSingleConfigForMixPort(
             bool isInput) const {
         const auto config = generateAudioMixPortConfigs(
-                getMixPorts(isInput, false /*attachedOnly*/), isInput, true);
+                getMixPorts(isInput, false /*connectedOnly*/), isInput, true);
         if (!config.empty()) {
             return *config.begin();
         }
@@ -139,13 +157,20 @@
         return *config.begin();
     }
 
+    const ndk::ScopedAStatus& onExternalDeviceConnected(
+            aidl::android::hardware::audio::core::IModule* module,
+            const aidl::android::media::audio::common::AudioPort& port);
+    const ndk::ScopedAStatus& onExternalDeviceDisconnected(
+            aidl::android::hardware::audio::core::IModule* module,
+            const aidl::android::media::audio::common::AudioPort& port);
+
     bool isMmapSupported() const;
 
     std::string toString() const;
 
   private:
     std::vector<aidl::android::media::audio::common::AudioPort> findMixPorts(
-            bool isInput, bool attachedOnly, bool singlePort,
+            bool isInput, bool connectedOnly, bool singlePort,
             const std::function<bool(const aidl::android::media::audio::common::AudioPort&)>& pred)
             const;
     std::vector<aidl::android::media::audio::common::AudioPortConfig> generateAudioMixPortConfigs(
@@ -167,5 +192,7 @@
     std::set<int32_t> mAttachedSinkDevicePorts;
     std::set<int32_t> mAttachedSourceDevicePorts;
     std::set<int32_t> mExternalDevicePorts;
+    std::set<int32_t> mConnectedExternalSinkDevicePorts;
+    std::set<int32_t> mConnectedExternalSourceDevicePorts;
     std::vector<aidl::android::hardware::audio::core::AudioRoute> mRoutes;
 };
diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
index 68e7151..b1eecd2 100644
--- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
@@ -144,28 +144,36 @@
 }
 
 AudioPort GenerateUniqueDeviceAddress(const AudioPort& port) {
+    // Point-to-point connections do not use addresses.
+    static const std::set<std::string> kPointToPointConnections = {
+            AudioDeviceDescription::CONNECTION_ANALOG, AudioDeviceDescription::CONNECTION_HDMI,
+            AudioDeviceDescription::CONNECTION_HDMI_ARC,
+            AudioDeviceDescription::CONNECTION_HDMI_EARC, AudioDeviceDescription::CONNECTION_SPDIF};
     static int nextId = 0;
     using Tag = AudioDeviceAddress::Tag;
+    const auto& deviceDescription = port.ext.get<AudioPortExt::Tag::device>().device.type;
     AudioDeviceAddress address;
-    switch (suggestDeviceAddressTag(port.ext.get<AudioPortExt::Tag::device>().device.type)) {
-        case Tag::id:
-            address = AudioDeviceAddress::make<Tag::id>(std::to_string(++nextId));
-            break;
-        case Tag::mac:
-            address = AudioDeviceAddress::make<Tag::mac>(
-                    std::vector<uint8_t>{1, 2, 3, 4, 5, static_cast<uint8_t>(++nextId & 0xff)});
-            break;
-        case Tag::ipv4:
-            address = AudioDeviceAddress::make<Tag::ipv4>(
-                    std::vector<uint8_t>{192, 168, 0, static_cast<uint8_t>(++nextId & 0xff)});
-            break;
-        case Tag::ipv6:
-            address = AudioDeviceAddress::make<Tag::ipv6>(std::vector<int32_t>{
-                    0xfc00, 0x0123, 0x4567, 0x89ab, 0xcdef, 0, 0, ++nextId & 0xffff});
-            break;
-        case Tag::alsa:
-            address = AudioDeviceAddress::make<Tag::alsa>(std::vector<int32_t>{1, ++nextId});
-            break;
+    if (kPointToPointConnections.count(deviceDescription.connection) == 0) {
+        switch (suggestDeviceAddressTag(deviceDescription)) {
+            case Tag::id:
+                address = AudioDeviceAddress::make<Tag::id>(std::to_string(++nextId));
+                break;
+            case Tag::mac:
+                address = AudioDeviceAddress::make<Tag::mac>(
+                        std::vector<uint8_t>{1, 2, 3, 4, 5, static_cast<uint8_t>(++nextId & 0xff)});
+                break;
+            case Tag::ipv4:
+                address = AudioDeviceAddress::make<Tag::ipv4>(
+                        std::vector<uint8_t>{192, 168, 0, static_cast<uint8_t>(++nextId & 0xff)});
+                break;
+            case Tag::ipv6:
+                address = AudioDeviceAddress::make<Tag::ipv6>(std::vector<int32_t>{
+                        0xfc00, 0x0123, 0x4567, 0x89ab, 0xcdef, 0, 0, ++nextId & 0xffff});
+                break;
+            case Tag::alsa:
+                address = AudioDeviceAddress::make<Tag::alsa>(std::vector<int32_t>{1, ++nextId});
+                break;
+        }
     }
     AudioPort result = port;
     result.ext.get<AudioPortExt::Tag::device>().device.address = std::move(address);
@@ -525,13 +533,21 @@
             EXPECT_IS_OK(mModule->disconnectExternalDevice(getId()))
                     << "when disconnecting device port ID " << getId();
         }
+        if (mModuleConfig != nullptr) {
+            EXPECT_IS_OK(mModuleConfig->onExternalDeviceDisconnected(mModule, mConnectedPort))
+                    << "when external device disconnected";
+        }
     }
-    void SetUp(IModule* module) {
+    void SetUp(IModule* module, ModuleConfig* moduleConfig) {
         ASSERT_IS_OK(module->connectExternalDevice(mIdAndData, &mConnectedPort))
                 << "when connecting device port ID & data " << mIdAndData.toString();
         ASSERT_NE(mIdAndData.id, getId())
                 << "ID of the connected port must not be the same as the ID of the template port";
+        ASSERT_NE(moduleConfig, nullptr);
+        ASSERT_IS_OK(moduleConfig->onExternalDeviceConnected(module, mConnectedPort))
+                << "when external device connected";
         mModule = module;
+        mModuleConfig = moduleConfig;
     }
     int32_t getId() const { return mConnectedPort.id; }
     const AudioPort& get() { return mConnectedPort; }
@@ -539,6 +555,7 @@
   private:
     const AudioPort mIdAndData;
     IModule* mModule = nullptr;
+    ModuleConfig* mModuleConfig = nullptr;
     AudioPort mConnectedPort;
 };
 
@@ -1379,7 +1396,7 @@
                     << "At least two mix ports have PRIMARY flag set: " << primaryMixPort.value()
                     << " and " << port.id;
             primaryMixPort = port.id;
-            EXPECT_EQ(1, mixPort.maxOpenStreamCount)
+            EXPECT_GE(mixPort.maxOpenStreamCount, 0)
                     << "Primary mix port " << port.id << " can not have maxOpenStreamCount "
                     << mixPort.maxOpenStreamCount;
         }
@@ -1422,7 +1439,7 @@
     for (const auto& port : ports) {
         AudioPort portWithData = GenerateUniqueDeviceAddress(port);
         WithDevicePortConnectedState portConnected(portWithData);
-        ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+        ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get(), moduleConfig.get()));
         const int32_t connectedPortId = portConnected.getId();
         ASSERT_NE(portWithData.id, connectedPortId);
         ASSERT_EQ(portWithData.ext.getTag(), portConnected.get().ext.getTag());
@@ -1526,7 +1543,7 @@
 
 TEST_P(AudioCoreModule, SetAudioPortConfigSuggestedConfig) {
     ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
-    auto srcMixPort = moduleConfig->getSourceMixPortForAttachedDevice();
+    auto srcMixPort = moduleConfig->getSourceMixPortForConnectedDevice();
     if (!srcMixPort.has_value()) {
         GTEST_SKIP() << "No mix port for attached output devices";
     }
@@ -1578,7 +1595,7 @@
     }
     for (const auto& port : ports) {
         WithDevicePortConnectedState portConnected(GenerateUniqueDeviceAddress(port));
-        ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+        ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get(), moduleConfig.get()));
         ASSERT_NO_FATAL_FAILURE(
                 ApplyEveryConfig(moduleConfig->getPortConfigsForDevicePort(portConnected.get())));
     }
@@ -1648,7 +1665,7 @@
         GTEST_SKIP() << "No external devices in the module.";
     }
     WithDevicePortConnectedState portConnected(GenerateUniqueDeviceAddress(*ports.begin()));
-    ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+    ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get(), moduleConfig.get()));
     ModuleDebug midwayDebugChange = debug->flags();
     midwayDebugChange.simulateDeviceConnections = false;
     EXPECT_STATUS(EX_ILLEGAL_STATE, module->setModuleDebug(midwayDebugChange))
@@ -1703,7 +1720,7 @@
                 << "when disconnecting already disconnected device port ID " << port.id;
         AudioPort portWithData = GenerateUniqueDeviceAddress(port);
         WithDevicePortConnectedState portConnected(portWithData);
-        ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+        ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get(), moduleConfig.get()));
         EXPECT_STATUS(EX_ILLEGAL_ARGUMENT,
                       module->connectExternalDevice(portConnected.get(), &ignored))
                 << "when trying to connect a connected device port "
@@ -1725,7 +1742,7 @@
     }
     for (const auto& port : ports) {
         WithDevicePortConnectedState portConnected(GenerateUniqueDeviceAddress(port));
-        ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+        ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get(), moduleConfig.get()));
         const auto portConfig = moduleConfig->getSingleConfigForDevicePort(portConnected.get());
         {
             WithAudioPortConfig config(portConfig);
@@ -1753,7 +1770,7 @@
         int32_t connectedPortId;
         {
             WithDevicePortConnectedState portConnected(GenerateUniqueDeviceAddress(port));
-            ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+            ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get(), moduleConfig.get()));
             connectedPortId = portConnected.getId();
             std::vector<AudioRoute> connectedPortRoutes;
             ASSERT_IS_OK(module->getAudioRoutesForAudioPort(connectedPortId, &connectedPortRoutes))
@@ -1794,7 +1811,7 @@
     }
     for (const auto& port : externalDevicePorts) {
         WithDevicePortConnectedState portConnected(GenerateUniqueDeviceAddress(port));
-        ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
+        ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get(), moduleConfig.get()));
         std::vector<AudioRoute> routes;
         ASSERT_IS_OK(module->getAudioRoutesForAudioPort(portConnected.getId(), &routes));
         std::vector<AudioPort> allPorts;
@@ -2459,7 +2476,7 @@
 
     void OpenOverMaxCount() {
         constexpr bool isInput = IOTraits<Stream>::is_input;
-        auto ports = moduleConfig->getMixPorts(isInput, true /*attachedOnly*/);
+        auto ports = moduleConfig->getMixPorts(isInput, true /*connectedOnly*/);
         bool hasSingleRun = false;
         for (const auto& port : ports) {
             const size_t maxStreamCount = port.ext.get<AudioPortExt::Tag::mix>().maxOpenStreamCount;
@@ -2580,7 +2597,7 @@
 
     void HwGainHwVolume() {
         const auto ports =
-                moduleConfig->getMixPorts(IOTraits<Stream>::is_input, true /*attachedOnly*/);
+                moduleConfig->getMixPorts(IOTraits<Stream>::is_input, true /*connectedOnly*/);
         if (ports.empty()) {
             GTEST_SKIP() << "No mix ports";
         }
@@ -2619,7 +2636,7 @@
     // 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 /*attachedOnly*/);
+                moduleConfig->getMixPorts(IOTraits<Stream>::is_input, true /*connectedOnly*/);
         if (ports.empty()) {
             GTEST_SKIP() << "No mix ports";
         }
@@ -2742,7 +2759,7 @@
     if (!status.isOk()) {
         GTEST_SKIP() << "Microphone info is not supported";
     }
-    const auto ports = moduleConfig->getInputMixPorts(true /*attachedOnly*/);
+    const auto ports = moduleConfig->getInputMixPorts(true /*connectedOnly*/);
     if (ports.empty()) {
         GTEST_SKIP() << "No input mix ports for attached devices";
     }
@@ -2759,7 +2776,7 @@
                                                "non-empty list of active microphones";
         }
         if (auto micDevicePorts = ModuleConfig::getBuiltInMicPorts(
-                    moduleConfig->getAttachedSourceDevicesPortsForMixPort(port));
+                    moduleConfig->getConnectedSourceDevicesPortsForMixPort(port));
             !micDevicePorts.empty()) {
             auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(micDevicePorts[0]);
             WithAudioPatch patch(true /*isInput*/, stream.getPortConfig(), devicePortConfig);
@@ -2791,7 +2808,7 @@
 
 TEST_P(AudioStreamIn, MicrophoneDirection) {
     using MD = IStreamIn::MicrophoneDirection;
-    const auto ports = moduleConfig->getInputMixPorts(true /*attachedOnly*/);
+    const auto ports = moduleConfig->getInputMixPorts(true /*connectedOnly*/);
     if (ports.empty()) {
         GTEST_SKIP() << "No input mix ports for attached devices";
     }
@@ -2814,7 +2831,7 @@
 }
 
 TEST_P(AudioStreamIn, MicrophoneFieldDimension) {
-    const auto ports = moduleConfig->getInputMixPorts(true /*attachedOnly*/);
+    const auto ports = moduleConfig->getInputMixPorts(true /*connectedOnly*/);
     if (ports.empty()) {
         GTEST_SKIP() << "No input mix ports for attached devices";
     }
@@ -2846,7 +2863,7 @@
 
 TEST_P(AudioStreamOut, OpenTwicePrimary) {
     const auto mixPorts =
-            moduleConfig->getPrimaryMixPorts(true /*attachedOnly*/, true /*singlePort*/);
+            moduleConfig->getPrimaryMixPorts(true /*connectedOnly*/, true /*singlePort*/);
     if (mixPorts.empty()) {
         GTEST_SKIP() << "No primary mix port which could be routed to attached devices";
     }
@@ -2857,7 +2874,7 @@
 
 TEST_P(AudioStreamOut, RequireOffloadInfo) {
     const auto offloadMixPorts =
-            moduleConfig->getOffloadMixPorts(true /*attachedOnly*/, true /*singlePort*/);
+            moduleConfig->getOffloadMixPorts(true /*connectedOnly*/, true /*singlePort*/);
     if (offloadMixPorts.empty()) {
         GTEST_SKIP()
                 << "No mix port for compressed offload that could be routed to attached devices";
@@ -2879,7 +2896,7 @@
 
 TEST_P(AudioStreamOut, RequireAsyncCallback) {
     const auto nonBlockingMixPorts =
-            moduleConfig->getNonBlockingMixPorts(true /*attachedOnly*/, true /*singlePort*/);
+            moduleConfig->getNonBlockingMixPorts(true /*connectedOnly*/, true /*singlePort*/);
     if (nonBlockingMixPorts.empty()) {
         GTEST_SKIP()
                 << "No mix port for non-blocking output that could be routed to attached devices";
@@ -2902,7 +2919,7 @@
 }
 
 TEST_P(AudioStreamOut, AudioDescriptionMixLevel) {
-    const auto ports = moduleConfig->getOutputMixPorts(true /*attachedOnly*/);
+    const auto ports = moduleConfig->getOutputMixPorts(true /*connectedOnly*/);
     if (ports.empty()) {
         GTEST_SKIP() << "No output mix ports";
     }
@@ -2930,7 +2947,7 @@
 }
 
 TEST_P(AudioStreamOut, DualMonoMode) {
-    const auto ports = moduleConfig->getOutputMixPorts(true /*attachedOnly*/);
+    const auto ports = moduleConfig->getOutputMixPorts(true /*connectedOnly*/);
     if (ports.empty()) {
         GTEST_SKIP() << "No output mix ports";
     }
@@ -2954,7 +2971,7 @@
 }
 
 TEST_P(AudioStreamOut, LatencyMode) {
-    const auto ports = moduleConfig->getOutputMixPorts(true /*attachedOnly*/);
+    const auto ports = moduleConfig->getOutputMixPorts(true /*connectedOnly*/);
     if (ports.empty()) {
         GTEST_SKIP() << "No output mix ports";
     }
@@ -2996,7 +3013,7 @@
 TEST_P(AudioStreamOut, PlaybackRate) {
     static const auto kStatuses = {EX_NONE, EX_UNSUPPORTED_OPERATION};
     const auto offloadMixPorts =
-            moduleConfig->getOffloadMixPorts(true /*attachedOnly*/, false /*singlePort*/);
+            moduleConfig->getOffloadMixPorts(true /*connectedOnly*/, false /*singlePort*/);
     if (offloadMixPorts.empty()) {
         GTEST_SKIP()
                 << "No mix port for compressed offload that could be routed to attached devices";
@@ -3066,7 +3083,7 @@
 TEST_P(AudioStreamOut, SelectPresentation) {
     static const auto kStatuses = {EX_ILLEGAL_ARGUMENT, EX_UNSUPPORTED_OPERATION};
     const auto offloadMixPorts =
-            moduleConfig->getOffloadMixPorts(true /*attachedOnly*/, false /*singlePort*/);
+            moduleConfig->getOffloadMixPorts(true /*connectedOnly*/, false /*singlePort*/);
     if (offloadMixPorts.empty()) {
         GTEST_SKIP()
                 << "No mix port for compressed offload that could be routed to attached devices";
@@ -3088,7 +3105,7 @@
 
 TEST_P(AudioStreamOut, UpdateOffloadMetadata) {
     const auto offloadMixPorts =
-            moduleConfig->getOffloadMixPorts(true /*attachedOnly*/, false /*singlePort*/);
+            moduleConfig->getOffloadMixPorts(true /*connectedOnly*/, false /*singlePort*/);
     if (offloadMixPorts.empty()) {
         GTEST_SKIP()
                 << "No mix port for compressed offload that could be routed to attached devices";
@@ -3301,7 +3318,7 @@
     void RunStreamIoCommandsImplSeq1(const AudioPortConfig& portConfig,
                                      std::shared_ptr<StateSequence> commandsAndStates,
                                      bool validatePositionIncrease) {
-        auto devicePorts = moduleConfig->getAttachedDevicesPortsForMixPort(
+        auto devicePorts = moduleConfig->getConnectedDevicesPortsForMixPort(
                 IOTraits<Stream>::is_input, portConfig);
         ASSERT_FALSE(devicePorts.empty());
         auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(devicePorts[0]);
@@ -3341,7 +3358,7 @@
         typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
                                                  stream.getEventReceiver());
 
-        auto devicePorts = moduleConfig->getAttachedDevicesPortsForMixPort(
+        auto devicePorts = moduleConfig->getConnectedDevicesPortsForMixPort(
                 IOTraits<Stream>::is_input, portConfig);
         ASSERT_FALSE(devicePorts.empty());
         auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(devicePorts[0]);
@@ -4006,6 +4023,172 @@
                          android::PrintInstanceNameToString);
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioModulePatch);
 
+static std::vector<std::string> getRemoteSubmixModuleInstance() {
+    auto instances = android::getAidlHalInstanceNames(IModule::descriptor);
+    for (auto instance : instances) {
+        if (instance.find("r_submix") != std::string::npos)
+            return (std::vector<std::string>{instance});
+    }
+    return {};
+}
+
+template <typename Stream>
+class WithRemoteSubmix {
+  public:
+    WithRemoteSubmix() = default;
+    WithRemoteSubmix(AudioDeviceAddress address) : mAddress(address) {}
+    WithRemoteSubmix(const WithRemoteSubmix&) = delete;
+    WithRemoteSubmix& operator=(const WithRemoteSubmix&) = delete;
+    std::optional<AudioPort> getAudioPort() {
+        AudioDeviceType deviceType = IOTraits<Stream>::is_input ? AudioDeviceType::IN_SUBMIX
+                                                                : AudioDeviceType::OUT_SUBMIX;
+        auto ports = mModuleConfig->getAudioPortsForDeviceTypes(
+                std::vector<AudioDeviceType>{deviceType},
+                AudioDeviceDescription::CONNECTION_VIRTUAL);
+        if (!ports.empty()) return ports.front();
+        return {};
+    }
+    /* Connect remote submix external device */
+    void SetUpPortConnection() {
+        auto port = getAudioPort();
+        ASSERT_TRUE(port.has_value()) << "Device AudioPort for remote submix not found";
+        if (mAddress.has_value()) {
+            port.value().ext.template get<AudioPortExt::Tag::device>().device.address =
+                    mAddress.value();
+        } else {
+            port = GenerateUniqueDeviceAddress(port.value());
+        }
+        mConnectedPort = std::make_unique<WithDevicePortConnectedState>(port.value());
+        ASSERT_NO_FATAL_FAILURE(mConnectedPort->SetUp(mModule, mModuleConfig));
+    }
+    AudioDeviceAddress getAudioDeviceAddress() {
+        if (!mAddress.has_value()) {
+            mAddress = mConnectedPort->get()
+                               .ext.template get<AudioPortExt::Tag::device>()
+                               .device.address;
+        }
+        return mAddress.value();
+    }
+    /* Get mix port config for stream and setup patch for it. */
+    void SetupPatch() {
+        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(mConnectedPort->get());
+        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) {
+        mModule = module;
+        mModuleConfig = moduleConfig;
+        ASSERT_NO_FATAL_FAILURE(SetUpPortConnection());
+        ASSERT_NO_FATAL_FAILURE(SetupPatch());
+        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));
+        }
+    }
+    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());
+    }
+    bool skipTest() { return mSkipTest; }
+
+  private:
+    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;
+};
+
+class AudioModuleRemoteSubmix : public AudioCoreModule {
+  public:
+    void SetUp() override {
+        ASSERT_NO_FATAL_FAILURE(AudioCoreModule::SetUp());
+        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());
+}
+
+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";
+    }
+
+    // open input stream
+    WithRemoteSubmix<IStreamIn> streamIn(streamOut.getAudioDeviceAddress());
+    ASSERT_NO_FATAL_FAILURE(streamIn.SetUp(module.get(), moduleConfig.get()));
+    if (streamIn.skipTest()) {
+        GTEST_SKIP() << "No mix port for attached devices";
+    }
+
+    // write something to stream
+    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";
+    }
+
+    // open input stream
+    WithRemoteSubmix<IStreamIn> streamIn(streamOut.getAudioDeviceAddress());
+    ASSERT_NO_FATAL_FAILURE(streamIn.SetUp(module.get(), moduleConfig.get()));
+    if (streamIn.skipTest()) {
+        GTEST_SKIP() << "No mix port for attached devices";
+    }
+
+    // write something to stream
+    ASSERT_NO_FATAL_FAILURE(streamOut.sendBurstCommands());
+    // read from input stream
+    ASSERT_NO_FATAL_FAILURE(streamIn.sendBurstCommands());
+}
+
+INSTANTIATE_TEST_SUITE_P(AudioModuleRemoteSubmixTest, AudioModuleRemoteSubmix,
+                         ::testing::ValuesIn(getRemoteSubmixModuleInstance()));
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioModuleRemoteSubmix);
+
 class TestExecutionTracer : public ::testing::EmptyTestEventListener {
   public:
     void OnTestStart(const ::testing::TestInfo& test_info) override {
diff --git a/audio/aidl/vts/VtsHalHapticGeneratorTargetTest.cpp b/audio/aidl/vts/VtsHalHapticGeneratorTargetTest.cpp
index 54caed9..b33234b 100644
--- a/audio/aidl/vts/VtsHalHapticGeneratorTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalHapticGeneratorTargetTest.cpp
@@ -208,7 +208,7 @@
         HapticGeneratorInvalidTest, HapticGeneratorParamTest,
         ::testing::Combine(testing::ValuesIn(EffectFactoryHelper::getAllEffectDescriptors(
                                    IFactory::descriptor, getEffectTypeUuidHapticGenerator())),
-                           testing::Values(MIN_ID - 1),
+                           testing::Values(MIN_ID),
                            testing::Values(HapticGenerator::VibratorScale::NONE),
                            testing::Values(MIN_FLOAT), testing::Values(MIN_FLOAT),
                            testing::Values(MIN_FLOAT)),