Merge "Remove direct mapper from composer2.4 VTS" into main
diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp
index 19b2397..4e583a4 100644
--- a/audio/aidl/default/Android.bp
+++ b/audio/aidl/default/Android.bp
@@ -76,6 +76,7 @@
         "ModulePrimary.cpp",
         "SoundDose.cpp",
         "Stream.cpp",
+        "StreamSwitcher.cpp",
         "Telephony.cpp",
         "alsa/Mixer.cpp",
         "alsa/ModuleAlsa.cpp",
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index eb9cbc3..b59bd7c 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -1129,7 +1129,7 @@
     if (!mSoundDose) {
         mSoundDose = ndk::SharedRefBase::make<sounddose::SoundDose>();
     }
-    *_aidl_return = mSoundDose.getPtr();
+    *_aidl_return = mSoundDose.getInstance();
     LOG(DEBUG) << __func__ << ": returning instance of ISoundDose: " << _aidl_return->get();
     return ndk::ScopedAStatus::ok();
 }
diff --git a/audio/aidl/default/ModulePrimary.cpp b/audio/aidl/default/ModulePrimary.cpp
index d8ea9e7..29e8126 100644
--- a/audio/aidl/default/ModulePrimary.cpp
+++ b/audio/aidl/default/ModulePrimary.cpp
@@ -37,7 +37,7 @@
     if (!mTelephony) {
         mTelephony = ndk::SharedRefBase::make<Telephony>();
     }
-    *_aidl_return = mTelephony.getPtr();
+    *_aidl_return = mTelephony.getInstance();
     LOG(DEBUG) << __func__
                << ": returning instance of ITelephony: " << _aidl_return->get()->asBinder().get();
     return ndk::ScopedAStatus::ok();
diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp
index f4194d2..d2be48c 100644
--- a/audio/aidl/default/Stream.cpp
+++ b/audio/aidl/default/Stream.cpp
@@ -91,17 +91,18 @@
 }
 
 std::string StreamWorkerCommonLogic::init() {
-    if (mCommandMQ == nullptr) return "Command MQ is null";
-    if (mReplyMQ == nullptr) return "Reply MQ is null";
-    if (mDataMQ == nullptr) return "Data MQ is null";
-    if (sizeof(DataBufferElement) != mDataMQ->getQuantumSize()) {
-        return "Unexpected Data MQ quantum size: " + std::to_string(mDataMQ->getQuantumSize());
+    if (mContext->getCommandMQ() == nullptr) return "Command MQ is null";
+    if (mContext->getReplyMQ() == nullptr) return "Reply MQ is null";
+    StreamContext::DataMQ* const dataMQ = mContext->getDataMQ();
+    if (dataMQ == nullptr) return "Data MQ is null";
+    if (sizeof(DataBufferElement) != dataMQ->getQuantumSize()) {
+        return "Unexpected Data MQ quantum size: " + std::to_string(dataMQ->getQuantumSize());
     }
-    mDataBufferSize = mDataMQ->getQuantumCount() * mDataMQ->getQuantumSize();
+    mDataBufferSize = dataMQ->getQuantumCount() * dataMQ->getQuantumSize();
     mDataBuffer.reset(new (std::nothrow) DataBufferElement[mDataBufferSize]);
     if (mDataBuffer == nullptr) {
         return "Failed to allocate data buffer for element count " +
-               std::to_string(mDataMQ->getQuantumCount()) +
+               std::to_string(dataMQ->getQuantumCount()) +
                ", size in bytes: " + std::to_string(mDataBufferSize);
     }
     if (::android::status_t status = mDriver->init(); status != STATUS_OK) {
@@ -114,7 +115,7 @@
                                             bool isConnected) const {
     reply->status = STATUS_OK;
     if (isConnected) {
-        reply->observable.frames = mFrameCount;
+        reply->observable.frames = mContext->getFrameCount();
         reply->observable.timeNs = ::android::elapsedRealtimeNano();
         if (auto status = mDriver->getPosition(&reply->observable); status == ::android::OK) {
             return;
@@ -141,7 +142,7 @@
     // TODO: Add a delay for transitions of async operations when/if they added.
 
     StreamDescriptor::Command command{};
-    if (!mCommandMQ->readBlocking(&command, 1)) {
+    if (!mContext->getCommandMQ()->readBlocking(&command, 1)) {
         LOG(ERROR) << __func__ << ": reading of command from MQ failed";
         mState = StreamDescriptor::State::ERROR;
         return Status::ABORT;
@@ -159,7 +160,7 @@
     switch (command.getTag()) {
         case Tag::halReservedExit:
             if (const int32_t cookie = command.get<Tag::halReservedExit>();
-                cookie == mInternalCommandCookie) {
+                cookie == mContext->getInternalCommandCookie()) {
                 mDriver->shutdown();
                 setClosed();
                 // This is an internal command, no need to reply.
@@ -277,7 +278,7 @@
     }
     reply.state = mState;
     LOG(severity) << __func__ << ": writing reply " << reply.toString();
-    if (!mReplyMQ->writeBlocking(&reply, 1)) {
+    if (!mContext->getReplyMQ()->writeBlocking(&reply, 1)) {
         LOG(ERROR) << __func__ << ": writing of reply " << reply.toString() << " to MQ failed";
         mState = StreamDescriptor::State::ERROR;
         return Status::ABORT;
@@ -286,14 +287,16 @@
 }
 
 bool StreamInWorkerLogic::read(size_t clientSize, StreamDescriptor::Reply* reply) {
-    const size_t byteCount = std::min({clientSize, mDataMQ->availableToWrite(), mDataBufferSize});
+    StreamContext::DataMQ* const dataMQ = mContext->getDataMQ();
+    const size_t byteCount = std::min({clientSize, dataMQ->availableToWrite(), mDataBufferSize});
     const bool isConnected = mIsConnected;
+    const size_t frameSize = mContext->getFrameSize();
     size_t actualFrameCount = 0;
     bool fatal = false;
     int32_t latency = Module::kLatencyMs;
     if (isConnected) {
-        if (::android::status_t status = mDriver->transfer(
-                    mDataBuffer.get(), byteCount / mFrameSize, &actualFrameCount, &latency);
+        if (::android::status_t status = mDriver->transfer(mDataBuffer.get(), byteCount / frameSize,
+                                                           &actualFrameCount, &latency);
             status != ::android::OK) {
             fatal = true;
             LOG(ERROR) << __func__ << ": read failed: " << status;
@@ -301,17 +304,16 @@
     } else {
         usleep(3000);  // Simulate blocking transfer delay.
         for (size_t i = 0; i < byteCount; ++i) mDataBuffer[i] = 0;
-        actualFrameCount = byteCount / mFrameSize;
+        actualFrameCount = byteCount / frameSize;
     }
-    const size_t actualByteCount = actualFrameCount * mFrameSize;
-    if (bool success =
-                actualByteCount > 0 ? mDataMQ->write(&mDataBuffer[0], actualByteCount) : true;
+    const size_t actualByteCount = actualFrameCount * frameSize;
+    if (bool success = actualByteCount > 0 ? dataMQ->write(&mDataBuffer[0], actualByteCount) : true;
         success) {
         LOG(VERBOSE) << __func__ << ": writing of " << actualByteCount << " bytes into data MQ"
                      << " succeeded; connected? " << isConnected;
         // Frames are provided and counted regardless of connection status.
         reply->fmqByteCount += actualByteCount;
-        mFrameCount += actualFrameCount;
+        mContext->advanceFrameCount(actualFrameCount);
         populateReply(reply, isConnected);
     } else {
         LOG(WARNING) << __func__ << ": writing of " << actualByteCount
@@ -330,7 +332,8 @@
         if (auto stateDurationMs = std::chrono::duration_cast<std::chrono::milliseconds>(
                     std::chrono::steady_clock::now() - mTransientStateStart);
             stateDurationMs >= mTransientStateDelayMs) {
-            if (mAsyncCallback == nullptr) {
+            std::shared_ptr<IStreamCallback> asyncCallback = mContext->getAsyncCallback();
+            if (asyncCallback == nullptr) {
                 // In blocking mode, mState can only be DRAINING.
                 mState = StreamDescriptor::State::IDLE;
             } else {
@@ -338,13 +341,13 @@
                 // drain or transfer completion. In the stub, we switch unconditionally.
                 if (mState == StreamDescriptor::State::DRAINING) {
                     mState = StreamDescriptor::State::IDLE;
-                    ndk::ScopedAStatus status = mAsyncCallback->onDrainReady();
+                    ndk::ScopedAStatus status = asyncCallback->onDrainReady();
                     if (!status.isOk()) {
                         LOG(ERROR) << __func__ << ": error from onDrainReady: " << status;
                     }
                 } else {
                     mState = StreamDescriptor::State::ACTIVE;
-                    ndk::ScopedAStatus status = mAsyncCallback->onTransferReady();
+                    ndk::ScopedAStatus status = asyncCallback->onTransferReady();
                     if (!status.isOk()) {
                         LOG(ERROR) << __func__ << ": error from onTransferReady: " << status;
                     }
@@ -358,7 +361,7 @@
     }
 
     StreamDescriptor::Command command{};
-    if (!mCommandMQ->readBlocking(&command, 1)) {
+    if (!mContext->getCommandMQ()->readBlocking(&command, 1)) {
         LOG(ERROR) << __func__ << ": reading of command from MQ failed";
         mState = StreamDescriptor::State::ERROR;
         return Status::ABORT;
@@ -377,7 +380,7 @@
     switch (command.getTag()) {
         case Tag::halReservedExit:
             if (const int32_t cookie = command.get<Tag::halReservedExit>();
-                cookie == mInternalCommandCookie) {
+                cookie == mContext->getInternalCommandCookie()) {
                 mDriver->shutdown();
                 setClosed();
                 // This is an internal command, no need to reply.
@@ -432,10 +435,11 @@
                     if (!write(fmqByteCount, &reply)) {
                         mState = StreamDescriptor::State::ERROR;
                     }
+                    std::shared_ptr<IStreamCallback> asyncCallback = mContext->getAsyncCallback();
                     if (mState == StreamDescriptor::State::STANDBY ||
                         mState == StreamDescriptor::State::DRAIN_PAUSED ||
                         mState == StreamDescriptor::State::PAUSED) {
-                        if (mAsyncCallback == nullptr ||
+                        if (asyncCallback == nullptr ||
                             mState != StreamDescriptor::State::DRAIN_PAUSED) {
                             mState = StreamDescriptor::State::PAUSED;
                         } else {
@@ -444,7 +448,7 @@
                     } else if (mState == StreamDescriptor::State::IDLE ||
                                mState == StreamDescriptor::State::DRAINING ||
                                mState == StreamDescriptor::State::ACTIVE) {
-                        if (mAsyncCallback == nullptr || reply.fmqByteCount == fmqByteCount) {
+                        if (asyncCallback == nullptr || reply.fmqByteCount == fmqByteCount) {
                             mState = StreamDescriptor::State::ACTIVE;
                         } else {
                             switchToTransientState(StreamDescriptor::State::TRANSFERRING);
@@ -466,7 +470,8 @@
                     if (::android::status_t status = mDriver->drain(mode);
                         status == ::android::OK) {
                         populateReply(&reply, mIsConnected);
-                        if (mState == StreamDescriptor::State::ACTIVE && mForceSynchronousDrain) {
+                        if (mState == StreamDescriptor::State::ACTIVE &&
+                            mContext->getForceSynchronousDrain()) {
                             mState = StreamDescriptor::State::IDLE;
                         } else {
                             switchToTransientState(StreamDescriptor::State::DRAINING);
@@ -541,7 +546,7 @@
     }
     reply.state = mState;
     LOG(severity) << __func__ << ": writing reply " << reply.toString();
-    if (!mReplyMQ->writeBlocking(&reply, 1)) {
+    if (!mContext->getReplyMQ()->writeBlocking(&reply, 1)) {
         LOG(ERROR) << __func__ << ": writing of reply " << reply.toString() << " to MQ failed";
         mState = StreamDescriptor::State::ERROR;
         return Status::ABORT;
@@ -550,38 +555,40 @@
 }
 
 bool StreamOutWorkerLogic::write(size_t clientSize, StreamDescriptor::Reply* reply) {
-    const size_t readByteCount = mDataMQ->availableToRead();
+    StreamContext::DataMQ* const dataMQ = mContext->getDataMQ();
+    const size_t readByteCount = dataMQ->availableToRead();
+    const size_t frameSize = mContext->getFrameSize();
     bool fatal = false;
     int32_t latency = Module::kLatencyMs;
-    if (bool success = readByteCount > 0 ? mDataMQ->read(&mDataBuffer[0], readByteCount) : true) {
+    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"
                      << " succeeded; connected? " << isConnected;
         // Amount of data that the HAL module is going to actually use.
         size_t byteCount = std::min({clientSize, readByteCount, mDataBufferSize});
-        if (byteCount >= mFrameSize && mForceTransientBurst) {
+        if (byteCount >= frameSize && mContext->getForceTransientBurst()) {
             // In order to prevent the state machine from going to ACTIVE state,
             // simulate partial write.
-            byteCount -= mFrameSize;
+            byteCount -= frameSize;
         }
         size_t actualFrameCount = 0;
         if (isConnected) {
             if (::android::status_t status = mDriver->transfer(
-                        mDataBuffer.get(), byteCount / mFrameSize, &actualFrameCount, &latency);
+                        mDataBuffer.get(), byteCount / frameSize, &actualFrameCount, &latency);
                 status != ::android::OK) {
                 fatal = true;
                 LOG(ERROR) << __func__ << ": write failed: " << status;
             }
         } else {
-            if (mAsyncCallback == nullptr) {
+            if (mContext->getAsyncCallback() == nullptr) {
                 usleep(3000);  // Simulate blocking transfer delay.
             }
-            actualFrameCount = byteCount / mFrameSize;
+            actualFrameCount = byteCount / frameSize;
         }
-        const size_t actualByteCount = actualFrameCount * mFrameSize;
+        const size_t actualByteCount = actualFrameCount * frameSize;
         // Frames are consumed and counted regardless of the connection status.
         reply->fmqByteCount += actualByteCount;
-        mFrameCount += actualFrameCount;
+        mContext->advanceFrameCount(actualFrameCount);
         populateReply(reply, isConnected);
     } else {
         LOG(WARNING) << __func__ << ": reading of " << readByteCount
@@ -612,7 +619,7 @@
     if (!mCommon) {
         LOG(FATAL) << __func__ << ": the common interface was not created";
     }
-    *_aidl_return = mCommon.getPtr();
+    *_aidl_return = mCommon.getInstance();
     LOG(DEBUG) << __func__ << ": returning " << _aidl_return->get()->asBinder().get();
     return ndk::ScopedAStatus::ok();
 }
@@ -663,8 +670,7 @@
         LOG(DEBUG) << __func__ << ": joining the worker thread...";
         mWorker->stop();
         LOG(DEBUG) << __func__ << ": worker thread joined";
-        onClose();
-        mWorker->setClosed();
+        onClose(mWorker->setClosed());
         return ndk::ScopedAStatus::ok();
     } else {
         LOG(ERROR) << __func__ << ": stream was already closed";
diff --git a/audio/aidl/default/StreamSwitcher.cpp b/audio/aidl/default/StreamSwitcher.cpp
new file mode 100644
index 0000000..956f413
--- /dev/null
+++ b/audio/aidl/default/StreamSwitcher.cpp
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <limits>
+
+#define LOG_TAG "AHAL_StreamSwitcher"
+
+#include <Utils.h>
+#include <android-base/logging.h>
+#include <error/expected_utils.h>
+
+#include "core-impl/StreamStub.h"
+#include "core-impl/StreamSwitcher.h"
+
+using aidl::android::hardware::audio::effect::IEffect;
+using aidl::android::media::audio::common::AudioDevice;
+
+namespace aidl::android::hardware::audio::core {
+
+StreamSwitcher::StreamSwitcher(StreamContext* context, const Metadata& metadata)
+    : mMetadata(metadata), mStream(new InnerStreamWrapper<StreamStub>(context, mMetadata)) {}
+
+ndk::ScopedAStatus StreamSwitcher::closeCurrentStream(bool validateStreamState) {
+    if (!mStream) return ndk::ScopedAStatus::ok();
+    RETURN_STATUS_IF_ERROR(mStream->prepareToClose());
+    RETURN_STATUS_IF_ERROR(mStream->close());
+    if (validateStreamState && !isValidClosingStreamState(mStream->getStatePriorToClosing())) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    mStream.reset();
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus StreamSwitcher::close() {
+    if (mStream != nullptr) {
+        auto status = closeCurrentStream(false /*validateStreamState*/);
+        // The actual state is irrelevant since only StreamSwitcher cares about it.
+        onClose(StreamDescriptor::State::STANDBY);
+        return status;
+    }
+    LOG(ERROR) << __func__ << ": stream was already closed";
+    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+}
+
+ndk::ScopedAStatus StreamSwitcher::prepareToClose() {
+    if (mStream != nullptr) {
+        return mStream->prepareToClose();
+    }
+    LOG(ERROR) << __func__ << ": stream was closed";
+    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+}
+
+ndk::ScopedAStatus StreamSwitcher::updateHwAvSyncId(int32_t in_hwAvSyncId) {
+    if (mStream == nullptr) {
+        LOG(ERROR) << __func__ << ": stream was closed";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    RETURN_STATUS_IF_ERROR(mStream->updateHwAvSyncId(in_hwAvSyncId));
+    mHwAvSyncId = in_hwAvSyncId;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus StreamSwitcher::getVendorParameters(const std::vector<std::string>& in_ids,
+                                                       std::vector<VendorParameter>* _aidl_return) {
+    if (mStream == nullptr) {
+        LOG(ERROR) << __func__ << ": stream was closed";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    if (mIsStubStream) {
+        LOG(ERROR) << __func__ << ": the stream is not connected";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    return mStream->getVendorParameters(in_ids, _aidl_return);
+}
+
+ndk::ScopedAStatus StreamSwitcher::setVendorParameters(
+        const std::vector<VendorParameter>& in_parameters, bool in_async) {
+    if (mStream == nullptr) {
+        LOG(ERROR) << __func__ << ": stream was closed";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    if (mIsStubStream) {
+        mMissedParameters.emplace_back(in_parameters, in_async);
+        return ndk::ScopedAStatus::ok();
+    }
+    return mStream->setVendorParameters(in_parameters, in_async);
+}
+
+ndk::ScopedAStatus StreamSwitcher::addEffect(const std::shared_ptr<IEffect>& in_effect) {
+    if (mStream == nullptr) {
+        LOG(ERROR) << __func__ << ": stream was closed";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    if (!mIsStubStream) {
+        RETURN_STATUS_IF_ERROR(mStream->addEffect(in_effect));
+    }
+    mEffects.push_back(in_effect);
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus StreamSwitcher::removeEffect(const std::shared_ptr<IEffect>& in_effect) {
+    if (mStream == nullptr) {
+        LOG(ERROR) << __func__ << ": stream was closed";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    for (auto it = mEffects.begin(); it != mEffects.end();) {
+        if ((*it)->asBinder() == in_effect->asBinder()) {
+            it = mEffects.erase(it);
+        } else {
+            ++it;
+        }
+    }
+    return !mIsStubStream ? mStream->removeEffect(in_effect) : ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus StreamSwitcher::getStreamCommonCommon(
+        std::shared_ptr<IStreamCommon>* _aidl_return) {
+    if (!mCommon) {
+        LOG(FATAL) << __func__ << ": the common interface was not created";
+    }
+    *_aidl_return = mCommon.getInstance();
+    LOG(DEBUG) << __func__ << ": returning " << _aidl_return->get()->asBinder().get();
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus StreamSwitcher::updateMetadataCommon(const Metadata& metadata) {
+    if (mStream == nullptr) {
+        LOG(ERROR) << __func__ << ": stream was closed";
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+    }
+    mMetadata = metadata;
+    return !mIsStubStream ? mStream->updateMetadataCommon(metadata) : ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus StreamSwitcher::initInstance(
+        const std::shared_ptr<StreamCommonInterface>& delegate) {
+    mCommon = ndk::SharedRefBase::make<StreamCommonDelegator>(delegate);
+    // The delegate is null because StreamSwitcher handles IStreamCommon methods by itself.
+    return mStream->initInstance(nullptr);
+}
+
+const StreamContext& StreamSwitcher::getContext() const {
+    return *mContext;
+}
+
+bool StreamSwitcher::isClosed() const {
+    return mStream == nullptr || mStream->isClosed();
+}
+
+const StreamCommonInterface::ConnectedDevices& StreamSwitcher::getConnectedDevices() const {
+    return mStream->getConnectedDevices();
+}
+
+ndk::ScopedAStatus StreamSwitcher::setConnectedDevices(const std::vector<AudioDevice>& devices) {
+    LOG(DEBUG) << __func__ << ": " << ::android::internal::ToString(devices);
+    if (mStream->getConnectedDevices() == devices) return ndk::ScopedAStatus::ok();
+    const DeviceSwitchBehavior behavior = switchCurrentStream(devices);
+    if (behavior == DeviceSwitchBehavior::UNSUPPORTED_DEVICES) {
+        return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    } else if (behavior == DeviceSwitchBehavior::SWITCH_TO_STUB_STREAM && !devices.empty()) {
+        // This is an error in the extending class.
+        LOG(FATAL) << __func__
+                   << ": switching to stub stream with connected devices is not allowed";
+    }
+    if (behavior == USE_CURRENT_STREAM) {
+        mIsStubStream = false;
+    } else {
+        LOG(DEBUG) << __func__ << ": connected devices changed, switching stream";
+        // Two streams can't be opened for the same context, thus we always need to close
+        // the current one before creating a new one.
+        RETURN_STATUS_IF_ERROR(closeCurrentStream(true /*validateStreamState*/));
+        if (behavior == CREATE_NEW_STREAM) {
+            mStream = createNewStream(devices, mContext, mMetadata);
+            mIsStubStream = false;
+        } else {  // SWITCH_TO_STUB_STREAM
+            mStream.reset(new InnerStreamWrapper<StreamStub>(mContext, mMetadata));
+            mIsStubStream = true;
+        }
+        // The delegate is null because StreamSwitcher handles IStreamCommon methods by itself.
+        if (ndk::ScopedAStatus status = mStream->initInstance(nullptr); !status.isOk()) {
+            // Need to close the current failed stream, and report an error.
+            // Since we can't operate without a stream implementation, put a stub in.
+            RETURN_STATUS_IF_ERROR(closeCurrentStream(false /*validateStreamState*/));
+            mStream.reset(new InnerStreamWrapper<StreamStub>(mContext, mMetadata));
+            (void)mStream->initInstance(nullptr);
+            (void)mStream->setConnectedDevices(devices);
+            return status;
+        }
+    }
+    RETURN_STATUS_IF_ERROR(mStream->setConnectedDevices(devices));
+    if (behavior == CREATE_NEW_STREAM) {
+        // These updates are less critical, only log warning on failure.
+        if (mHwAvSyncId.has_value()) {
+            if (auto status = mStream->updateHwAvSyncId(*mHwAvSyncId); !status.isOk()) {
+                LOG(WARNING) << __func__ << ": could not update HW AV Sync for a new stream: "
+                             << status.getDescription();
+            }
+        }
+        for (const auto& vndParam : mMissedParameters) {
+            if (auto status = mStream->setVendorParameters(vndParam.first, vndParam.second);
+                !status.isOk()) {
+                LOG(WARNING) << __func__ << ": error while setting parameters for a new stream: "
+                             << status.getDescription();
+            }
+        }
+        mMissedParameters.clear();
+        for (const auto& effect : mEffects) {
+            if (auto status = mStream->addEffect(effect); !status.isOk()) {
+                LOG(WARNING) << __func__ << ": error while adding effect for a new stream: "
+                             << status.getDescription();
+            }
+        }
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+}  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/alsa/StreamAlsa.cpp b/audio/aidl/default/alsa/StreamAlsa.cpp
index c7fb022..00a7a84 100644
--- a/audio/aidl/default/alsa/StreamAlsa.cpp
+++ b/audio/aidl/default/alsa/StreamAlsa.cpp
@@ -27,7 +27,7 @@
 
 namespace aidl::android::hardware::audio::core {
 
-StreamAlsa::StreamAlsa(const StreamContext& context, const Metadata& metadata, int readWriteRetries)
+StreamAlsa::StreamAlsa(StreamContext* context, const Metadata& metadata, int readWriteRetries)
     : StreamCommonImpl(context, metadata),
       mFrameSizeBytes(getContext().getFrameSize()),
       mIsInput(isInput(metadata)),
diff --git a/audio/aidl/default/include/core-impl/ChildInterface.h b/audio/aidl/default/include/core-impl/ChildInterface.h
index 1b31691..2421b59 100644
--- a/audio/aidl/default/include/core-impl/ChildInterface.h
+++ b/audio/aidl/default/include/core-impl/ChildInterface.h
@@ -35,14 +35,20 @@
     }
     ChildInterface& operator=(std::shared_ptr<C>&& c) {
         this->first = std::move(c);
-        this->second = this->first->asBinder();
-        AIBinder_setMinSchedulerPolicy(this->second.get(), SCHED_NORMAL, ANDROID_PRIORITY_AUDIO);
         return *this;
     }
     explicit operator bool() const { return !!this->first; }
     C& operator*() const { return *(this->first); }
     C* operator->() const { return this->first; }
-    std::shared_ptr<C> getPtr() const { return this->first; }
+    // Use 'getInstance' when returning the interface instance.
+    std::shared_ptr<C> getInstance() {
+        if (this->second.get() == nullptr) {
+            this->second = this->first->asBinder();
+            AIBinder_setMinSchedulerPolicy(this->second.get(), SCHED_NORMAL,
+                                           ANDROID_PRIORITY_AUDIO);
+        }
+        return this->first;
+    }
 };
 
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h
index 355d3b4..fa2b760 100644
--- a/audio/aidl/default/include/core-impl/Stream.h
+++ b/audio/aidl/default/include/core-impl/Stream.h
@@ -66,7 +66,8 @@
             DataMQ;
 
     // Ensure that this value is not used by any of StreamDescriptor.State enums
-    static constexpr int32_t STATE_CLOSED = -1;
+    static constexpr StreamDescriptor::State STATE_CLOSED =
+            static_cast<StreamDescriptor::State>(-1);
 
     struct DebugParameters {
         // An extra delay for transient states, in ms.
@@ -113,7 +114,8 @@
           mDataMQ(std::move(other.mDataMQ)),
           mAsyncCallback(std::move(other.mAsyncCallback)),
           mOutEventCallback(std::move(other.mOutEventCallback)),
-          mDebugParameters(std::move(other.mDebugParameters)) {}
+          mDebugParameters(std::move(other.mDebugParameters)),
+          mFrameCount(other.mFrameCount) {}
     StreamContext& operator=(StreamContext&& other) {
         mCommandMQ = std::move(other.mCommandMQ);
         mInternalCommandCookie = other.mInternalCommandCookie;
@@ -128,6 +130,7 @@
         mAsyncCallback = std::move(other.mAsyncCallback);
         mOutEventCallback = std::move(other.mOutEventCallback);
         mDebugParameters = std::move(other.mDebugParameters);
+        mFrameCount = other.mFrameCount;
         return *this;
     }
 
@@ -156,7 +159,12 @@
     int getTransientStateDelayMs() const { return mDebugParameters.transientStateDelayMs; }
     int getSampleRate() const { return mSampleRate; }
     bool isValid() const;
+    // 'reset' is called on a Binder thread when closing the stream. Does not use
+    // locking because it only cleans MQ pointers which were also set on the Binder thread.
     void reset();
+    // 'advanceFrameCount' and 'getFrameCount' are only called on the worker thread.
+    long advanceFrameCount(size_t increase) { return mFrameCount += increase; }
+    long getFrameCount() const { return mFrameCount; }
 
   private:
     std::unique_ptr<CommandMQ> mCommandMQ;
@@ -172,6 +180,7 @@
     std::shared_ptr<IStreamCallback> mAsyncCallback;
     std::shared_ptr<IStreamOutEventCallback> mOutEventCallback;  // Only used by output streams
     DebugParameters mDebugParameters;
+    long mFrameCount = 0;
 };
 
 // This interface provides operations of the stream which are executed on the worker thread.
@@ -197,26 +206,23 @@
 
 class StreamWorkerCommonLogic : public ::android::hardware::audio::common::StreamLogic {
   public:
-    bool isClosed() const {
-        return static_cast<int32_t>(mState.load()) == StreamContext::STATE_CLOSED;
+    bool isClosed() const { return mState == StreamContext::STATE_CLOSED; }
+    StreamDescriptor::State setClosed() {
+        auto prevState = mState.exchange(StreamContext::STATE_CLOSED);
+        if (prevState != StreamContext::STATE_CLOSED) {
+            mStatePriorToClosing = prevState;
+        }
+        return mStatePriorToClosing;
     }
-    void setClosed() { mState = static_cast<StreamDescriptor::State>(StreamContext::STATE_CLOSED); }
     void setIsConnected(bool connected) { mIsConnected = connected; }
 
   protected:
     using DataBufferElement = int8_t;
 
-    StreamWorkerCommonLogic(const StreamContext& context, DriverInterface* driver)
-        : mDriver(driver),
-          mInternalCommandCookie(context.getInternalCommandCookie()),
-          mFrameSize(context.getFrameSize()),
-          mCommandMQ(context.getCommandMQ()),
-          mReplyMQ(context.getReplyMQ()),
-          mDataMQ(context.getDataMQ()),
-          mAsyncCallback(context.getAsyncCallback()),
-          mTransientStateDelayMs(context.getTransientStateDelayMs()),
-          mForceTransientBurst(context.getForceTransientBurst()),
-          mForceSynchronousDrain(context.getForceSynchronousDrain()) {}
+    StreamWorkerCommonLogic(StreamContext* context, DriverInterface* driver)
+        : mContext(context),
+          mDriver(driver),
+          mTransientStateDelayMs(context->getTransientStateDelayMs()) {}
     std::string init() override;
     void populateReply(StreamDescriptor::Reply* reply, bool isConnected) const;
     void populateReplyWrongState(StreamDescriptor::Reply* reply,
@@ -226,38 +232,35 @@
         mTransientStateStart = std::chrono::steady_clock::now();
     }
 
+    // The context is only used for reading, except for updating the frame count,
+    // which happens on the worker thread only.
+    StreamContext* const mContext;
     DriverInterface* const mDriver;
+    // This is the state the stream was in before being closed. It is retrieved by the main
+    // thread after joining the worker thread.
+    StreamDescriptor::State mStatePriorToClosing = StreamDescriptor::State::STANDBY;
     // Atomic fields are used both by the main and worker threads.
     std::atomic<bool> mIsConnected = false;
     static_assert(std::atomic<StreamDescriptor::State>::is_always_lock_free);
     std::atomic<StreamDescriptor::State> mState = StreamDescriptor::State::STANDBY;
-    // All fields are used on the worker thread only.
-    const int mInternalCommandCookie;
-    const size_t mFrameSize;
-    StreamContext::CommandMQ* const mCommandMQ;
-    StreamContext::ReplyMQ* const mReplyMQ;
-    StreamContext::DataMQ* const mDataMQ;
-    std::shared_ptr<IStreamCallback> mAsyncCallback;
+    // All fields below are used on the worker thread only.
     const std::chrono::duration<int, std::milli> mTransientStateDelayMs;
     std::chrono::time_point<std::chrono::steady_clock> mTransientStateStart;
-    const bool mForceTransientBurst;
-    const bool mForceSynchronousDrain;
     // We use an array and the "size" field instead of a vector to be able to detect
     // memory allocation issues.
     std::unique_ptr<DataBufferElement[]> mDataBuffer;
     size_t mDataBufferSize;
-    long mFrameCount = 0;
 };
 
 // This interface is used to decouple stream implementations from a concrete StreamWorker
 // implementation.
 struct StreamWorkerInterface {
-    using CreateInstance = std::function<StreamWorkerInterface*(const StreamContext& context,
-                                                                DriverInterface* driver)>;
+    using CreateInstance =
+            std::function<StreamWorkerInterface*(StreamContext* context, DriverInterface* driver)>;
     virtual ~StreamWorkerInterface() = default;
     virtual bool isClosed() const = 0;
     virtual void setIsConnected(bool isConnected) = 0;
-    virtual void setClosed() = 0;
+    virtual StreamDescriptor::State setClosed() = 0;
     virtual bool start() = 0;
     virtual void stop() = 0;
 };
@@ -268,11 +271,11 @@
     using WorkerImpl = ::android::hardware::audio::common::StreamWorker<WorkerLogic>;
 
   public:
-    StreamWorkerImpl(const StreamContext& context, DriverInterface* driver)
+    StreamWorkerImpl(StreamContext* context, DriverInterface* driver)
         : WorkerImpl(context, driver) {}
     bool isClosed() const override { return WorkerImpl::isClosed(); }
     void setIsConnected(bool isConnected) override { WorkerImpl::setIsConnected(isConnected); }
-    void setClosed() override { WorkerImpl::setClosed(); }
+    StreamDescriptor::State setClosed() override { return WorkerImpl::setClosed(); }
     bool start() override {
         return WorkerImpl::start(WorkerImpl::kThreadName, ANDROID_PRIORITY_AUDIO);
     }
@@ -282,7 +285,7 @@
 class StreamInWorkerLogic : public StreamWorkerCommonLogic {
   public:
     static const std::string kThreadName;
-    StreamInWorkerLogic(const StreamContext& context, DriverInterface* driver)
+    StreamInWorkerLogic(StreamContext* context, DriverInterface* driver)
         : StreamWorkerCommonLogic(context, driver) {}
 
   protected:
@@ -296,8 +299,9 @@
 class StreamOutWorkerLogic : public StreamWorkerCommonLogic {
   public:
     static const std::string kThreadName;
-    StreamOutWorkerLogic(const StreamContext& context, DriverInterface* driver)
-        : StreamWorkerCommonLogic(context, driver), mEventCallback(context.getOutEventCallback()) {}
+    StreamOutWorkerLogic(StreamContext* context, DriverInterface* driver)
+        : StreamWorkerCommonLogic(context, driver),
+          mEventCallback(context->getOutEventCallback()) {}
 
   protected:
     Status cycle() override;
@@ -416,10 +420,10 @@
 // who must be owner of the context.
 class StreamCommonImpl : virtual public StreamCommonInterface, virtual public DriverInterface {
   public:
-    StreamCommonImpl(const StreamContext& context, const Metadata& metadata,
+    StreamCommonImpl(StreamContext* context, const Metadata& metadata,
                      const StreamWorkerInterface::CreateInstance& createWorker)
-        : mContext(context), mMetadata(metadata), mWorker(createWorker(mContext, this)) {}
-    StreamCommonImpl(const StreamContext& context, const Metadata& metadata)
+        : mContext(*context), mMetadata(metadata), mWorker(createWorker(context, this)) {}
+    StreamCommonImpl(StreamContext* context, const Metadata& metadata)
         : StreamCommonImpl(
                   context, metadata,
                   isInput(metadata) ? getDefaultInWorkerCreator() : getDefaultOutWorkerCreator()) {}
@@ -453,17 +457,17 @@
 
   protected:
     static StreamWorkerInterface::CreateInstance getDefaultInWorkerCreator() {
-        return [](const StreamContext& ctx, DriverInterface* driver) -> StreamWorkerInterface* {
+        return [](StreamContext* ctx, DriverInterface* driver) -> StreamWorkerInterface* {
             return new StreamInWorker(ctx, driver);
         };
     }
     static StreamWorkerInterface::CreateInstance getDefaultOutWorkerCreator() {
-        return [](const StreamContext& ctx, DriverInterface* driver) -> StreamWorkerInterface* {
+        return [](StreamContext* ctx, DriverInterface* driver) -> StreamWorkerInterface* {
             return new StreamOutWorker(ctx, driver);
         };
     }
 
-    virtual void onClose() = 0;
+    virtual void onClose(StreamDescriptor::State statePriorToClosing) = 0;
     void stopWorker();
 
     const StreamContext& mContext;
diff --git a/audio/aidl/default/include/core-impl/StreamAlsa.h b/audio/aidl/default/include/core-impl/StreamAlsa.h
index 90d2e36..f98a922 100644
--- a/audio/aidl/default/include/core-impl/StreamAlsa.h
+++ b/audio/aidl/default/include/core-impl/StreamAlsa.h
@@ -31,7 +31,7 @@
 // provide necessary overrides for all interface methods omitted here.
 class StreamAlsa : public StreamCommonImpl {
   public:
-    StreamAlsa(const StreamContext& context, const Metadata& metadata, int readWriteRetries);
+    StreamAlsa(StreamContext* context, const Metadata& metadata, int readWriteRetries);
     // Methods of 'DriverInterface'.
     ::android::status_t init() override;
     ::android::status_t drain(StreamDescriptor::DrainMode) override;
diff --git a/audio/aidl/default/include/core-impl/StreamRemoteSubmix.h b/audio/aidl/default/include/core-impl/StreamRemoteSubmix.h
index 4c984af..b39583e 100644
--- a/audio/aidl/default/include/core-impl/StreamRemoteSubmix.h
+++ b/audio/aidl/default/include/core-impl/StreamRemoteSubmix.h
@@ -29,7 +29,7 @@
 
 class StreamRemoteSubmix : public StreamCommonImpl {
   public:
-    StreamRemoteSubmix(const StreamContext& context, const Metadata& metadata);
+    StreamRemoteSubmix(StreamContext* context, const Metadata& metadata);
 
     ::android::status_t init() override;
     ::android::status_t drain(StreamDescriptor::DrainMode) override;
@@ -81,7 +81,7 @@
             const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones);
 
   private:
-    void onClose() override { defaultOnClose(); }
+    void onClose(StreamDescriptor::State) override { defaultOnClose(); }
     ndk::ScopedAStatus getActiveMicrophones(
             std::vector<::aidl::android::media::audio::common::MicrophoneDynamicInfo>* _aidl_return)
             override;
@@ -97,7 +97,7 @@
                     offloadInfo);
 
   private:
-    void onClose() override { defaultOnClose(); }
+    void onClose(StreamDescriptor::State) override { defaultOnClose(); }
 };
 
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/StreamStub.h b/audio/aidl/default/include/core-impl/StreamStub.h
index 2a92deb..a8a3fc4 100644
--- a/audio/aidl/default/include/core-impl/StreamStub.h
+++ b/audio/aidl/default/include/core-impl/StreamStub.h
@@ -22,7 +22,7 @@
 
 class StreamStub : public StreamCommonImpl {
   public:
-    StreamStub(const StreamContext& context, const Metadata& metadata);
+    StreamStub(StreamContext* context, const Metadata& metadata);
     // Methods of 'DriverInterface'.
     ::android::status_t init() override;
     ::android::status_t drain(StreamDescriptor::DrainMode) override;
@@ -52,7 +52,7 @@
             const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones);
 
   private:
-    void onClose() override { defaultOnClose(); }
+    void onClose(StreamDescriptor::State) override { defaultOnClose(); }
 };
 
 class StreamOutStub final : public StreamOut, public StreamStub {
@@ -64,7 +64,7 @@
                           offloadInfo);
 
   private:
-    void onClose() override { defaultOnClose(); }
+    void onClose(StreamDescriptor::State) override { defaultOnClose(); }
 };
 
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/StreamSwitcher.h b/audio/aidl/default/include/core-impl/StreamSwitcher.h
new file mode 100644
index 0000000..e462481
--- /dev/null
+++ b/audio/aidl/default/include/core-impl/StreamSwitcher.h
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2023 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 "Stream.h"
+
+namespace aidl::android::hardware::audio::core {
+
+// 'StreamSwitcher' is implementation of 'StreamCommonInterface' which allows
+// dynamically switching the underlying stream implementation based on currently
+// connected devices. This is achieved by replacing inheritance from
+// 'StreamCommonImpl' with owning an instance of it. StreamSwitcher must be
+// extended in order to supply the logic for choosing the stream
+// implementation. When there are no connected devices, for instance, upon the
+// creation, the StreamSwitcher engages an instance of a stub stream in order to
+// keep serving requests coming via 'StreamDescriptor'.
+//
+// StreamSwitcher implements the 'IStreamCommon' interface directly, with
+// necessary delegation to the current stream implementation. While the stub
+// stream is engaged, any requests made via 'IStreamCommon' (parameters, effects
+// setting, etc) are postponed and only delivered on device connection change
+// to the "real" stream implementation provided by the extending class. This is why
+// the behavior of StreamSwitcher in the "stub" state is not identical to behavior
+// of 'StreamStub'. It can become a full substitute for 'StreamStub' once
+// device connection change event occurs and the extending class returns
+// 'LEAVE_CURRENT_STREAM' from 'switchCurrentStream' method.
+//
+// There is a natural limitation that the current stream implementation may only
+// be switched when the stream is in the 'STANDBY' state. Thus, when the event
+// to switch the stream occurs, the current stream is stopped and joined, and
+// its last state is validated. Since the change of the set of connected devices
+// normally occurs on patch updates, if the stream was not in standby, this is
+// reported to the caller of 'IModule.setAudioPatch' as the 'EX_ILLEGAL_STATE'
+// error.
+//
+// The simplest use case, when the implementor just needs to emulate the legacy HAL API
+// behavior of receiving the connected devices upon stream creation, the implementation
+// of the extending class can look as follows. We assume that 'StreamLegacy' implementation
+// is the one requiring to know connected devices on creation:
+//
+//   class StreamLegacy : public StreamCommonImpl {
+//     public:
+//       StreamLegacy(StreamContext* context, const Metadata& metadata,
+//                    const std::vector<AudioDevice>& devices);
+//   };
+//
+//   class StreamOutLegacy final : public StreamOut, public StreamSwitcher {
+//     public:
+//       StreamOutLegacy(StreamContext&& context, metatadata etc.)
+//     private:
+//       DeviceSwitchBehavior switchCurrentStream(const std::vector<AudioDevice>&) override {
+//           // This implementation effectively postpones stream creation until
+//           // receiving the first call to 'setConnectedDevices' with a non-empty list.
+//           return isStubStream() ? DeviceSwitchBehavior::CREATE_NEW_STREAM :
+//               DeviceSwitchBehavior::USE_CURRENT_STREAM;
+//       }
+//       std::unique_ptr<StreamCommonInterfaceEx> createNewStream(
+//               const std::vector<AudioDevice>& devices,
+//               StreamContext* context, const Metadata& metadata) override {
+//           return std::unique_ptr<StreamCommonInterfaceEx>(new InnerStreamWrapper<StreamLegacy>(
+//               context, metadata, devices));
+//       }
+//       void onClose(StreamDescriptor::State) override { defaultOnClose(); }
+//   }
+//
+
+class StreamCommonInterfaceEx : virtual public StreamCommonInterface {
+  public:
+    virtual StreamDescriptor::State getStatePriorToClosing() const = 0;
+};
+
+template <typename T>
+class InnerStreamWrapper : public T, public StreamCommonInterfaceEx {
+  public:
+    InnerStreamWrapper(StreamContext* context, const Metadata& metadata) : T(context, metadata) {}
+    StreamDescriptor::State getStatePriorToClosing() const override { return mStatePriorToClosing; }
+
+  private:
+    // Do not need to do anything on close notification from the inner stream
+    // because StreamSwitcher handles IStreamCommon::close by itself.
+    void onClose(StreamDescriptor::State statePriorToClosing) override {
+        mStatePriorToClosing = statePriorToClosing;
+    }
+
+    StreamDescriptor::State mStatePriorToClosing = StreamDescriptor::State::STANDBY;
+};
+
+class StreamSwitcher : virtual public StreamCommonInterface {
+  public:
+    StreamSwitcher(StreamContext* context, const Metadata& metadata);
+
+    ndk::ScopedAStatus close() override;
+    ndk::ScopedAStatus prepareToClose() override;
+    ndk::ScopedAStatus updateHwAvSyncId(int32_t in_hwAvSyncId) override;
+    ndk::ScopedAStatus getVendorParameters(const std::vector<std::string>& in_ids,
+                                           std::vector<VendorParameter>* _aidl_return) override;
+    ndk::ScopedAStatus setVendorParameters(const std::vector<VendorParameter>& in_parameters,
+                                           bool in_async) override;
+    ndk::ScopedAStatus addEffect(
+            const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>& in_effect)
+            override;
+    ndk::ScopedAStatus removeEffect(
+            const std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>& in_effect)
+            override;
+
+    ndk::ScopedAStatus getStreamCommonCommon(std::shared_ptr<IStreamCommon>* _aidl_return) override;
+    ndk::ScopedAStatus updateMetadataCommon(const Metadata& metadata) override;
+
+    ndk::ScopedAStatus initInstance(
+            const std::shared_ptr<StreamCommonInterface>& delegate) override;
+    const StreamContext& getContext() const override;
+    bool isClosed() const override;
+    const ConnectedDevices& getConnectedDevices() const override;
+    ndk::ScopedAStatus setConnectedDevices(
+            const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices)
+            override;
+
+  protected:
+    // Since switching a stream requires closing down the current stream, StreamSwitcher
+    // asks the extending class its intent on the connected devices change.
+    enum DeviceSwitchBehavior {
+        // Continue using the current stream implementation. If it's the stub implementation,
+        // StreamSwitcher starts treating the stub stream as a "real" implementation,
+        // without effectively closing it and starting again.
+        USE_CURRENT_STREAM,
+        // This is the normal case when the extending class provides a "real" implementation
+        // which is not a stub implementation.
+        CREATE_NEW_STREAM,
+        // This is the case when the extending class wants to revert back to the initial
+        // condition of using a stub stream provided by the StreamSwitcher. This behavior
+        // is only allowed when the list of connected devices is empty.
+        SWITCH_TO_STUB_STREAM,
+        // Use when the set of devices is not supported by the extending class. This returns
+        // 'EX_UNSUPPORTED_OPERATION' from 'setConnectedDevices'.
+        UNSUPPORTED_DEVICES,
+    };
+    // StreamSwitcher will call these methods from 'setConnectedDevices'. If the switch behavior
+    // is 'CREATE_NEW_STREAM', the 'createwNewStream' function will be called (with the same
+    // device vector) for obtaining a new stream implementation, assuming that closing
+    // the current stream was a success.
+    virtual DeviceSwitchBehavior switchCurrentStream(
+            const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices) = 0;
+    virtual std::unique_ptr<StreamCommonInterfaceEx> createNewStream(
+            const std::vector<::aidl::android::media::audio::common::AudioDevice>& devices,
+            StreamContext* context, const Metadata& metadata) = 0;
+    virtual void onClose(StreamDescriptor::State streamPriorToClosing) = 0;
+
+    bool isStubStream() const { return mIsStubStream; }
+    StreamCommonInterfaceEx* getCurrentStream() const { return mStream.get(); }
+
+  private:
+    using VndParam = std::pair<std::vector<VendorParameter>, bool /*isAsync*/>;
+
+    static constexpr bool isValidClosingStreamState(StreamDescriptor::State state) {
+        return state == StreamDescriptor::State::STANDBY || state == StreamDescriptor::State::ERROR;
+    }
+
+    ndk::ScopedAStatus closeCurrentStream(bool validateStreamState);
+
+    // StreamSwitcher does not own the context.
+    StreamContext* mContext;
+    Metadata mMetadata;
+    ChildInterface<StreamCommonDelegator> mCommon;
+    // The current stream.
+    std::unique_ptr<StreamCommonInterfaceEx> mStream;
+    // Indicates whether 'mCurrentStream' is a stub stream implementation
+    // maintained by StreamSwitcher until the extending class provides a "real"
+    // implementation. The invariant of this state is that there are no connected
+    // devices.
+    bool mIsStubStream = true;
+    // Storage for the data from commands received via 'IStreamCommon'.
+    std::optional<int32_t> mHwAvSyncId;
+    std::vector<VndParam> mMissedParameters;
+    std::vector<std::shared_ptr<::aidl::android::hardware::audio::effect::IEffect>> mEffects;
+};
+
+}  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/StreamUsb.h b/audio/aidl/default/include/core-impl/StreamUsb.h
index 7dc7296..74e30ff 100644
--- a/audio/aidl/default/include/core-impl/StreamUsb.h
+++ b/audio/aidl/default/include/core-impl/StreamUsb.h
@@ -28,7 +28,7 @@
 
 class StreamUsb : public StreamAlsa {
   public:
-    StreamUsb(const StreamContext& context, const Metadata& metadata);
+    StreamUsb(StreamContext* context, const Metadata& metadata);
     // Methods of 'DriverInterface'.
     ::android::status_t transfer(void* buffer, size_t frameCount, size_t* actualFrameCount,
                                  int32_t* latencyMs) override;
@@ -53,7 +53,7 @@
             const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones);
 
   private:
-    void onClose() override { defaultOnClose(); }
+    void onClose(StreamDescriptor::State) override { defaultOnClose(); }
     ndk::ScopedAStatus getActiveMicrophones(
             std::vector<::aidl::android::media::audio::common::MicrophoneDynamicInfo>* _aidl_return)
             override;
@@ -68,7 +68,7 @@
                          offloadInfo);
 
   private:
-    void onClose() override { defaultOnClose(); }
+    void onClose(StreamDescriptor::State) override { defaultOnClose(); }
     ndk::ScopedAStatus getHwVolume(std::vector<float>* _aidl_return) override;
     ndk::ScopedAStatus setHwVolume(const std::vector<float>& in_channelVolumes) override;
 
diff --git a/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp b/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
index 53d4aa6..9537ebc 100644
--- a/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
+++ b/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
@@ -29,14 +29,14 @@
 
 namespace aidl::android::hardware::audio::core {
 
-StreamRemoteSubmix::StreamRemoteSubmix(const StreamContext& context, const Metadata& metadata)
+StreamRemoteSubmix::StreamRemoteSubmix(StreamContext* context, const Metadata& metadata)
     : StreamCommonImpl(context, metadata),
-      mPortId(context.getPortId()),
+      mPortId(context->getPortId()),
       mIsInput(isInput(metadata)) {
-    mStreamConfig.frameSize = context.getFrameSize();
-    mStreamConfig.format = context.getFormat();
-    mStreamConfig.channelLayout = context.getChannelLayout();
-    mStreamConfig.sampleRate = context.getSampleRate();
+    mStreamConfig.frameSize = context->getFrameSize();
+    mStreamConfig.format = context->getFormat();
+    mStreamConfig.channelLayout = context->getChannelLayout();
+    mStreamConfig.sampleRate = context->getSampleRate();
 }
 
 std::mutex StreamRemoteSubmix::sSubmixRoutesLock;
@@ -357,7 +357,7 @@
                                            const SinkMetadata& sinkMetadata,
                                            const std::vector<MicrophoneInfo>& microphones)
     : StreamIn(std::move(context), microphones),
-      StreamRemoteSubmix(StreamIn::mContext, sinkMetadata) {}
+      StreamRemoteSubmix(&(StreamIn::mContext), sinkMetadata) {}
 
 ndk::ScopedAStatus StreamInRemoteSubmix::getActiveMicrophones(
         std::vector<MicrophoneDynamicInfo>* _aidl_return) {
@@ -370,6 +370,6 @@
                                              const SourceMetadata& sourceMetadata,
                                              const std::optional<AudioOffloadInfo>& offloadInfo)
     : StreamOut(std::move(context), offloadInfo),
-      StreamRemoteSubmix(StreamOut::mContext, sourceMetadata) {}
+      StreamRemoteSubmix(&(StreamOut::mContext), sourceMetadata) {}
 
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/stub/ModuleStub.cpp b/audio/aidl/default/stub/ModuleStub.cpp
index 5e4cd88..9f6e0b4 100644
--- a/audio/aidl/default/stub/ModuleStub.cpp
+++ b/audio/aidl/default/stub/ModuleStub.cpp
@@ -37,7 +37,7 @@
     if (!mBluetooth) {
         mBluetooth = ndk::SharedRefBase::make<Bluetooth>();
     }
-    *_aidl_return = mBluetooth.getPtr();
+    *_aidl_return = mBluetooth.getInstance();
     LOG(DEBUG) << __func__
                << ": returning instance of IBluetooth: " << _aidl_return->get()->asBinder().get();
     return ndk::ScopedAStatus::ok();
@@ -47,7 +47,7 @@
     if (!mBluetoothA2dp) {
         mBluetoothA2dp = ndk::SharedRefBase::make<BluetoothA2dp>();
     }
-    *_aidl_return = mBluetoothA2dp.getPtr();
+    *_aidl_return = mBluetoothA2dp.getInstance();
     LOG(DEBUG) << __func__ << ": returning instance of IBluetoothA2dp: "
                << _aidl_return->get()->asBinder().get();
     return ndk::ScopedAStatus::ok();
@@ -57,7 +57,7 @@
     if (!mBluetoothLe) {
         mBluetoothLe = ndk::SharedRefBase::make<BluetoothLe>();
     }
-    *_aidl_return = mBluetoothLe.getPtr();
+    *_aidl_return = mBluetoothLe.getInstance();
     LOG(DEBUG) << __func__
                << ": returning instance of IBluetoothLe: " << _aidl_return->get()->asBinder().get();
     return ndk::ScopedAStatus::ok();
diff --git a/audio/aidl/default/stub/StreamStub.cpp b/audio/aidl/default/stub/StreamStub.cpp
index e916fea..66f4605 100644
--- a/audio/aidl/default/stub/StreamStub.cpp
+++ b/audio/aidl/default/stub/StreamStub.cpp
@@ -31,7 +31,7 @@
 
 namespace aidl::android::hardware::audio::core {
 
-StreamStub::StreamStub(const StreamContext& context, const Metadata& metadata)
+StreamStub::StreamStub(StreamContext* context, const Metadata& metadata)
     : StreamCommonImpl(context, metadata),
       mFrameSizeBytes(getContext().getFrameSize()),
       mSampleRate(getContext().getSampleRate()),
@@ -120,10 +120,11 @@
 
 StreamInStub::StreamInStub(StreamContext&& context, const SinkMetadata& sinkMetadata,
                            const std::vector<MicrophoneInfo>& microphones)
-    : StreamIn(std::move(context), microphones), StreamStub(StreamIn::mContext, sinkMetadata) {}
+    : StreamIn(std::move(context), microphones), StreamStub(&(StreamIn::mContext), sinkMetadata) {}
 
 StreamOutStub::StreamOutStub(StreamContext&& context, const SourceMetadata& sourceMetadata,
                              const std::optional<AudioOffloadInfo>& offloadInfo)
-    : StreamOut(std::move(context), offloadInfo), StreamStub(StreamOut::mContext, sourceMetadata) {}
+    : StreamOut(std::move(context), offloadInfo),
+      StreamStub(&(StreamOut::mContext), sourceMetadata) {}
 
 }  // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/usb/StreamUsb.cpp b/audio/aidl/default/usb/StreamUsb.cpp
index def12e0..9684a87 100644
--- a/audio/aidl/default/usb/StreamUsb.cpp
+++ b/audio/aidl/default/usb/StreamUsb.cpp
@@ -35,7 +35,7 @@
 
 namespace aidl::android::hardware::audio::core {
 
-StreamUsb::StreamUsb(const StreamContext& context, const Metadata& metadata)
+StreamUsb::StreamUsb(StreamContext* context, const Metadata& metadata)
     : StreamAlsa(context, metadata, 1 /*readWriteRetries*/) {}
 
 ndk::ScopedAStatus StreamUsb::setConnectedDevices(
@@ -85,7 +85,7 @@
 
 StreamInUsb::StreamInUsb(StreamContext&& context, const SinkMetadata& sinkMetadata,
                          const std::vector<MicrophoneInfo>& microphones)
-    : StreamIn(std::move(context), microphones), StreamUsb(StreamIn::mContext, sinkMetadata) {}
+    : StreamIn(std::move(context), microphones), StreamUsb(&(StreamIn::mContext), sinkMetadata) {}
 
 ndk::ScopedAStatus StreamInUsb::getActiveMicrophones(
         std::vector<MicrophoneDynamicInfo>* _aidl_return __unused) {
@@ -96,7 +96,7 @@
 StreamOutUsb::StreamOutUsb(StreamContext&& context, const SourceMetadata& sourceMetadata,
                            const std::optional<AudioOffloadInfo>& offloadInfo)
     : StreamOut(std::move(context), offloadInfo),
-      StreamUsb(StreamOut::mContext, sourceMetadata),
+      StreamUsb(&(StreamOut::mContext), sourceMetadata),
       mChannelCount(getChannelCount(getContext().getChannelLayout())) {}
 
 ndk::ScopedAStatus StreamOutUsb::getHwVolume(std::vector<float>* _aidl_return) {
diff --git a/automotive/vehicle/aidl_property/android/hardware/automotive/vehicle/VehicleProperty.aidl b/automotive/vehicle/aidl_property/android/hardware/automotive/vehicle/VehicleProperty.aidl
index 0f6cac8..7d88810 100644
--- a/automotive/vehicle/aidl_property/android/hardware/automotive/vehicle/VehicleProperty.aidl
+++ b/automotive/vehicle/aidl_property/android/hardware/automotive/vehicle/VehicleProperty.aidl
@@ -169,7 +169,7 @@
      *  int32Values[4] = wheel base
      *  int32Values[5] = track width front
      *  int32Values[6] = track width rear
-     *  int32Values[7] = curb to curb turning radius
+     *  int32Values[7] = curb to curb turning diameter
      *
      * @change_mode VehiclePropertyChangeMode.STATIC
      * @access VehiclePropertyAccess.READ