audio: Implement transient state testing

Add ModuleDebug.streamTransientStateDelayMs parameter to ensure
that streams stay in transient states for the specified amount of
time. This enabled sending commands from VTS while the stream is
still in a transient state.

Add 'getStatus' stream command to retrieve current positions,
counters, and stream state. Previously we were planning to use a
zero-sized burst command for that, however, after the
introduction of stream state machines, the 'burst' command is
not handled in every stream state, and may even affect the
current state, thus it's no more usable for this purpose.

Bug: 205884982
Test: atest VtsHalAudioCoreTargetTest
Change-Id: I8717acace8d95d76bef2ec9fd6561796d7544992
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl
index 80ee185..467d37b 100644
--- a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/ModuleDebug.aidl
@@ -35,4 +35,5 @@
 @JavaDerive(equals=true, toString=true) @VintfStability
 parcelable ModuleDebug {
   boolean simulateDeviceConnections;
+  int streamTransientStateDelayMs;
 }
diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/StreamDescriptor.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/StreamDescriptor.aidl
index 3a77ad1..84a1fe7 100644
--- a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/StreamDescriptor.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/StreamDescriptor.aidl
@@ -57,7 +57,8 @@
   }
   @FixedSize @VintfStability
   union Command {
-    int hal_reserved_exit;
+    int halReservedExit;
+    android.media.audio.common.Void getStatus;
     android.media.audio.common.Void start;
     int burst;
     android.media.audio.common.Void drain;
diff --git a/audio/aidl/android/hardware/audio/core/IModule.aidl b/audio/aidl/android/hardware/audio/core/IModule.aidl
index be40051..1bc2ccc 100644
--- a/audio/aidl/android/hardware/audio/core/IModule.aidl
+++ b/audio/aidl/android/hardware/audio/core/IModule.aidl
@@ -53,9 +53,13 @@
      * the HAL module behavior that would otherwise require human intervention.
      *
      * The HAL module must throw an error if there is an attempt to change
-     * the debug behavior for the aspect which is currently in use.
+     * the debug behavior for the aspect which is currently in use, or when
+     * the value of any of the debug flags is invalid. See 'ModuleDebug' for
+     * the full list of constraints.
      *
      * @param debug The debug options.
+     * @throws EX_ILLEGAL_ARGUMENT If some of the configuration parameters are
+     *                             invalid.
      * @throws EX_ILLEGAL_STATE If the flag(s) being changed affect functionality
      *                          which is currently in use.
      */
diff --git a/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl b/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl
index 858a9bd..86a9b22 100644
--- a/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl
+++ b/audio/aidl/android/hardware/audio/core/ModuleDebug.aidl
@@ -35,4 +35,13 @@
      *    profiles.
      */
     boolean simulateDeviceConnections;
+    /**
+     * Must be non-negative. When set to non-zero, HAL module must delay
+     * transition from "transient" stream states (see StreamDescriptor.aidl)
+     * by the specified amount of milliseconds. The purpose of this delay
+     * is to allow VTS to test sending of stream commands while the stream is
+     * in a transient state. The delay must apply to newly created streams,
+     * it is not required to apply the delay to already opened streams.
+     */
+    int streamTransientStateDelayMs;
 }
diff --git a/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl b/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl
index 2b1fc99..92d131a 100644
--- a/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl
+++ b/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl
@@ -84,13 +84,13 @@
  *     are different.
  *
  * State machines of both input and output streams start from the 'STANDBY'
- * state.  Transitions between states happen naturally with changes in the
+ * state. Transitions between states happen naturally with changes in the
  * states of the model elements. For simplicity, we restrict the change to one
  * element only, for example, in the 'STANDBY' state, either the producer or the
  * consumer can become active, but not both at the same time. States 'STANDBY',
  * 'IDLE', 'READY', and '*PAUSED' are "stable"—they require an external event,
  * whereas a change from the 'DRAINING' state can happen with time as the buffer
- * gets empty.
+ * gets empty, thus it's a "transient" state.
  *
  * The state machine for input streams is defined in the `stream-in-sm.gv` file,
  * for output streams—in the `stream-out-sm.gv` file. State machines define how
@@ -198,7 +198,14 @@
          * implementation must pass a random cookie as the command argument,
          * which is only known to the implementation.
          */
-        int hal_reserved_exit;
+        int halReservedExit;
+        /**
+         * Retrieve the current state of the stream. This command must be
+         * processed by the stream in any state. The stream must provide current
+         * positions, counters, and its state in the reply. This command must be
+         * handled by the HAL module without any observable side effects.
+         */
+        Void getStatus;
         /**
          * See the state machines on the applicability of this command to
          * different states.
@@ -215,15 +222,14 @@
          *    read from the hardware into the 'audio.fmq' queue.
          *
          * In both cases it is allowed for this field to contain any
-         * non-negative number. The value 0 can be used if the client only needs
-         * to retrieve current positions and latency. Any sufficiently big value
-         * which exceeds the size of the queue's area which is currently
-         * available for reading or writing by the HAL module must be trimmed by
-         * the HAL module to the available size. Note that the HAL module is
-         * allowed to consume or provide less data than requested, and it must
-         * return the amount of actually read or written data via the
-         * 'Reply.fmqByteCount' field. Thus, only attempts to pass a negative
-         * number must be constituted as a client's error.
+         * non-negative number. Any sufficiently big value which exceeds the
+         * size of the queue's area which is currently available for reading or
+         * writing by the HAL module must be trimmed by the HAL module to the
+         * available size. Note that the HAL module is allowed to consume or
+         * provide less data than requested, and it must return the amount of
+         * actually read or written data via the 'Reply.fmqByteCount'
+         * field. Thus, only attempts to pass a negative number must be
+         * constituted as a client's error.
          *
          * Differences for the MMap No IRQ mode:
          *
@@ -233,6 +239,9 @@
          *    with sending of this command.
          *
          *  - the value must always be set to 0.
+         *
+         * See the state machines on the applicability of this command to
+         * different states.
          */
         int burst;
         /**
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index 6863fe3..a8f3b9b 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -135,8 +135,8 @@
         StreamContext temp(
                 std::make_unique<StreamContext::CommandMQ>(1, true /*configureEventFlagWord*/),
                 std::make_unique<StreamContext::ReplyMQ>(1, true /*configureEventFlagWord*/),
-                frameSize,
-                std::make_unique<StreamContext::DataMQ>(frameSize * in_bufferSizeFrames));
+                frameSize, std::make_unique<StreamContext::DataMQ>(frameSize * in_bufferSizeFrames),
+                mDebug.streamTransientStateDelayMs);
         if (temp.isValid()) {
             *out_context = std::move(temp);
         } else {
@@ -242,6 +242,11 @@
                    << "while having external devices connected";
         return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
     }
+    if (in_debug.streamTransientStateDelayMs < 0) {
+        LOG(ERROR) << __func__ << ": streamTransientStateDelayMs is negative: "
+                   << in_debug.streamTransientStateDelayMs;
+        return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+    }
     mDebug = in_debug;
     return ndk::ScopedAStatus::ok();
 }
diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp
index 21dc4b6..c5d00a2 100644
--- a/audio/aidl/default/Stream.cpp
+++ b/audio/aidl/default/Stream.cpp
@@ -96,23 +96,36 @@
     }
 }
 
+void StreamWorkerCommonLogic::populateReplyWrongState(
+        StreamDescriptor::Reply* reply, const StreamDescriptor::Command& command) const {
+    LOG(WARNING) << "command '" << toString(command.getTag())
+                 << "' can not be handled in the state " << toString(mState);
+    reply->status = STATUS_INVALID_OPERATION;
+}
+
 const std::string StreamInWorkerLogic::kThreadName = "reader";
 
 StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() {
+    // Note: for input streams, draining is driven by the client, thus
+    // "empty buffer" condition can only happen while handling the 'burst'
+    // command. Thus, unlike for output streams, it does not make sense to
+    // delay the 'DRAINING' state here by 'mTransientStateDelayMs'.
+    // TODO: Add a delay for transitions of async operations when/if they added.
+
     StreamDescriptor::Command command{};
     if (!mCommandMQ->readBlocking(&command, 1)) {
         LOG(ERROR) << __func__ << ": reading of command from MQ failed";
         mState = StreamDescriptor::State::ERROR;
         return Status::ABORT;
     }
+    LOG(DEBUG) << __func__ << ": received command " << command.toString() << " in " << kThreadName;
     StreamDescriptor::Reply reply{};
     reply.status = STATUS_BAD_VALUE;
     using Tag = StreamDescriptor::Command::Tag;
     switch (command.getTag()) {
-        case Tag::hal_reserved_exit:
-            if (const int32_t cookie = command.get<Tag::hal_reserved_exit>();
+        case Tag::halReservedExit:
+            if (const int32_t cookie = command.get<Tag::halReservedExit>();
                 cookie == mInternalCommandCookie) {
-                LOG(DEBUG) << __func__ << ": received EXIT command";
                 setClosed();
                 // This is an internal command, no need to reply.
                 return Status::EXIT;
@@ -120,8 +133,10 @@
                 LOG(WARNING) << __func__ << ": EXIT command has a bad cookie: " << cookie;
             }
             break;
+        case Tag::getStatus:
+            populateReply(&reply, mIsConnected);
+            break;
         case Tag::start:
-            LOG(DEBUG) << __func__ << ": received START read command";
             if (mState == StreamDescriptor::State::STANDBY ||
                 mState == StreamDescriptor::State::DRAINING) {
                 populateReply(&reply, mIsConnected);
@@ -129,15 +144,13 @@
                                  ? StreamDescriptor::State::IDLE
                                  : StreamDescriptor::State::ACTIVE;
             } else {
-                LOG(WARNING) << __func__ << ": START command can not be handled in the state "
-                             << toString(mState);
-                reply.status = STATUS_INVALID_OPERATION;
+                populateReplyWrongState(&reply, command);
             }
             break;
         case Tag::burst:
             if (const int32_t fmqByteCount = command.get<Tag::burst>(); fmqByteCount >= 0) {
-                LOG(DEBUG) << __func__ << ": received BURST read command for " << fmqByteCount
-                           << " bytes";
+                LOG(DEBUG) << __func__ << ": '" << toString(command.getTag()) << "' command for "
+                           << fmqByteCount << " bytes";
                 if (mState == StreamDescriptor::State::IDLE ||
                     mState == StreamDescriptor::State::ACTIVE ||
                     mState == StreamDescriptor::State::PAUSED ||
@@ -151,69 +164,56 @@
                     } else if (mState == StreamDescriptor::State::DRAINING) {
                         // To simplify the reference code, we assume that the read operation
                         // has consumed all the data remaining in the hardware buffer.
-                        // TODO: Provide parametrization on the duration of draining to test
-                        //       handling of commands during the 'DRAINING' state.
+                        // In a real implementation, here we would either remain in
+                        // the 'DRAINING' state, or transfer to 'STANDBY' depending on the
+                        // buffer state.
                         mState = StreamDescriptor::State::STANDBY;
                     }
                 } else {
-                    LOG(WARNING) << __func__ << ": BURST command can not be handled in the state "
-                                 << toString(mState);
-                    reply.status = STATUS_INVALID_OPERATION;
+                    populateReplyWrongState(&reply, command);
                 }
             } else {
                 LOG(WARNING) << __func__ << ": invalid burst byte count: " << fmqByteCount;
             }
             break;
         case Tag::drain:
-            LOG(DEBUG) << __func__ << ": received DRAIN read command";
             if (mState == StreamDescriptor::State::ACTIVE) {
                 usleep(1000);  // Simulate a blocking call into the driver.
                 populateReply(&reply, mIsConnected);
                 // Can switch the state to ERROR if a driver error occurs.
                 mState = StreamDescriptor::State::DRAINING;
             } else {
-                LOG(WARNING) << __func__ << ": DRAIN command can not be handled in the state "
-                             << toString(mState);
-                reply.status = STATUS_INVALID_OPERATION;
+                populateReplyWrongState(&reply, command);
             }
             break;
         case Tag::standby:
-            LOG(DEBUG) << __func__ << ": received STANDBY read command";
             if (mState == StreamDescriptor::State::IDLE) {
                 usleep(1000);  // Simulate a blocking call into the driver.
                 populateReply(&reply, mIsConnected);
                 // Can switch the state to ERROR if a driver error occurs.
                 mState = StreamDescriptor::State::STANDBY;
             } else {
-                LOG(WARNING) << __func__ << ": FLUSH command can not be handled in the state "
-                             << toString(mState);
-                reply.status = STATUS_INVALID_OPERATION;
+                populateReplyWrongState(&reply, command);
             }
             break;
         case Tag::pause:
-            LOG(DEBUG) << __func__ << ": received PAUSE read command";
             if (mState == StreamDescriptor::State::ACTIVE) {
                 usleep(1000);  // Simulate a blocking call into the driver.
                 populateReply(&reply, mIsConnected);
                 // Can switch the state to ERROR if a driver error occurs.
                 mState = StreamDescriptor::State::PAUSED;
             } else {
-                LOG(WARNING) << __func__ << ": PAUSE command can not be handled in the state "
-                             << toString(mState);
-                reply.status = STATUS_INVALID_OPERATION;
+                populateReplyWrongState(&reply, command);
             }
             break;
         case Tag::flush:
-            LOG(DEBUG) << __func__ << ": received FLUSH read command";
             if (mState == StreamDescriptor::State::PAUSED) {
                 usleep(1000);  // Simulate a blocking call into the driver.
                 populateReply(&reply, mIsConnected);
                 // Can switch the state to ERROR if a driver error occurs.
                 mState = StreamDescriptor::State::STANDBY;
             } else {
-                LOG(WARNING) << __func__ << ": FLUSH command can not be handled in the state "
-                             << toString(mState);
-                reply.status = STATUS_INVALID_OPERATION;
+                populateReplyWrongState(&reply, command);
             }
             break;
     }
@@ -261,20 +261,32 @@
 const std::string StreamOutWorkerLogic::kThreadName = "writer";
 
 StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
+    if (mState == StreamDescriptor::State::DRAINING) {
+        if (auto stateDurationMs = std::chrono::duration_cast<std::chrono::milliseconds>(
+                    std::chrono::steady_clock::now() - mTransientStateStart);
+            stateDurationMs >= mTransientStateDelayMs) {
+            mState = StreamDescriptor::State::IDLE;
+            if (mTransientStateDelayMs.count() != 0) {
+                LOG(DEBUG) << __func__ << ": switched to state " << toString(mState)
+                           << " after a timeout";
+            }
+        }
+    }
+
     StreamDescriptor::Command command{};
     if (!mCommandMQ->readBlocking(&command, 1)) {
         LOG(ERROR) << __func__ << ": reading of command from MQ failed";
         mState = StreamDescriptor::State::ERROR;
         return Status::ABORT;
     }
+    LOG(DEBUG) << __func__ << ": received command " << command.toString() << " in " << kThreadName;
     StreamDescriptor::Reply reply{};
     reply.status = STATUS_BAD_VALUE;
     using Tag = StreamDescriptor::Command::Tag;
     switch (command.getTag()) {
-        case Tag::hal_reserved_exit:
-            if (const int32_t cookie = command.get<Tag::hal_reserved_exit>();
+        case Tag::halReservedExit:
+            if (const int32_t cookie = command.get<Tag::halReservedExit>();
                 cookie == mInternalCommandCookie) {
-                LOG(DEBUG) << __func__ << ": received EXIT command";
                 setClosed();
                 // This is an internal command, no need to reply.
                 return Status::EXIT;
@@ -282,31 +294,31 @@
                 LOG(WARNING) << __func__ << ": EXIT command has a bad cookie: " << cookie;
             }
             break;
+        case Tag::getStatus:
+            populateReply(&reply, mIsConnected);
+            break;
         case Tag::start:
-            LOG(DEBUG) << __func__ << ": received START write command";
             switch (mState) {
                 case StreamDescriptor::State::STANDBY:
                     mState = StreamDescriptor::State::IDLE;
+                    populateReply(&reply, mIsConnected);
                     break;
                 case StreamDescriptor::State::PAUSED:
                     mState = StreamDescriptor::State::ACTIVE;
+                    populateReply(&reply, mIsConnected);
                     break;
                 case StreamDescriptor::State::DRAIN_PAUSED:
-                    mState = StreamDescriptor::State::PAUSED;
+                    switchToTransientState(StreamDescriptor::State::DRAINING);
+                    populateReply(&reply, mIsConnected);
                     break;
                 default:
-                    LOG(WARNING) << __func__ << ": START command can not be handled in the state "
-                                 << toString(mState);
-                    reply.status = STATUS_INVALID_OPERATION;
-            }
-            if (reply.status != STATUS_INVALID_OPERATION) {
-                populateReply(&reply, mIsConnected);
+                    populateReplyWrongState(&reply, command);
             }
             break;
         case Tag::burst:
             if (const int32_t fmqByteCount = command.get<Tag::burst>(); fmqByteCount >= 0) {
-                LOG(DEBUG) << __func__ << ": received BURST write command for " << fmqByteCount
-                           << " bytes";
+                LOG(DEBUG) << __func__ << ": '" << toString(command.getTag()) << "' command for "
+                           << fmqByteCount << " bytes";
                 if (mState !=
                     StreamDescriptor::State::ERROR) {  // BURST can be handled in all valid states
                     if (!write(fmqByteCount, &reply)) {
@@ -320,47 +332,33 @@
                         mState = StreamDescriptor::State::ACTIVE;
                     }  // When in 'ACTIVE' and 'PAUSED' do not need to change the state.
                 } else {
-                    LOG(WARNING) << __func__ << ": BURST command can not be handled in the state "
-                                 << toString(mState);
-                    reply.status = STATUS_INVALID_OPERATION;
+                    populateReplyWrongState(&reply, command);
                 }
             } else {
                 LOG(WARNING) << __func__ << ": invalid burst byte count: " << fmqByteCount;
             }
             break;
         case Tag::drain:
-            LOG(DEBUG) << __func__ << ": received DRAIN write command";
             if (mState == StreamDescriptor::State::ACTIVE) {
                 usleep(1000);  // Simulate a blocking call into the driver.
                 populateReply(&reply, mIsConnected);
                 // Can switch the state to ERROR if a driver error occurs.
-                mState = StreamDescriptor::State::IDLE;
-                // Since there is no actual hardware that would be draining the buffer,
-                // in order to simplify the reference code, we assume that draining
-                // happens instantly, thus skipping the 'DRAINING' state.
-                // TODO: Provide parametrization on the duration of draining to test
-                //       handling of commands during the 'DRAINING' state.
+                switchToTransientState(StreamDescriptor::State::DRAINING);
             } else {
-                LOG(WARNING) << __func__ << ": DRAIN command can not be handled in the state "
-                             << toString(mState);
-                reply.status = STATUS_INVALID_OPERATION;
+                populateReplyWrongState(&reply, command);
             }
             break;
         case Tag::standby:
-            LOG(DEBUG) << __func__ << ": received STANDBY write command";
             if (mState == StreamDescriptor::State::IDLE) {
                 usleep(1000);  // Simulate a blocking call into the driver.
                 populateReply(&reply, mIsConnected);
                 // Can switch the state to ERROR if a driver error occurs.
                 mState = StreamDescriptor::State::STANDBY;
             } else {
-                LOG(WARNING) << __func__ << ": STANDBY command can not be handled in the state "
-                             << toString(mState);
-                reply.status = STATUS_INVALID_OPERATION;
+                populateReplyWrongState(&reply, command);
             }
             break;
         case Tag::pause:
-            LOG(DEBUG) << __func__ << ": received PAUSE write command";
             if (mState == StreamDescriptor::State::ACTIVE ||
                 mState == StreamDescriptor::State::DRAINING) {
                 populateReply(&reply, mIsConnected);
@@ -368,21 +366,16 @@
                                  ? StreamDescriptor::State::PAUSED
                                  : StreamDescriptor::State::DRAIN_PAUSED;
             } else {
-                LOG(WARNING) << __func__ << ": PAUSE command can not be handled in the state "
-                             << toString(mState);
-                reply.status = STATUS_INVALID_OPERATION;
+                populateReplyWrongState(&reply, command);
             }
             break;
         case Tag::flush:
-            LOG(DEBUG) << __func__ << ": received FLUSH write command";
             if (mState == StreamDescriptor::State::PAUSED ||
                 mState == StreamDescriptor::State::DRAIN_PAUSED) {
                 populateReply(&reply, mIsConnected);
                 mState = StreamDescriptor::State::IDLE;
             } else {
-                LOG(WARNING) << __func__ << ": FLUSH command can not be handled in the state "
-                             << toString(mState);
-                reply.status = STATUS_INVALID_OPERATION;
+                populateReplyWrongState(&reply, command);
             }
             break;
     }
@@ -450,9 +443,8 @@
 void StreamCommon<Metadata, StreamWorker>::stopWorker() {
     if (auto commandMQ = mContext.getCommandMQ(); commandMQ != nullptr) {
         LOG(DEBUG) << __func__ << ": asking the worker to exit...";
-        auto cmd =
-                StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::hal_reserved_exit>(
-                        mContext.getInternalCommandCookie());
+        auto cmd = StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::halReservedExit>(
+                mContext.getInternalCommandCookie());
         // 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 5ee0f82..bcbabad 100644
--- a/audio/aidl/default/include/core-impl/Stream.h
+++ b/audio/aidl/default/include/core-impl/Stream.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <atomic>
+#include <chrono>
 #include <cstdlib>
 #include <map>
 #include <memory>
@@ -59,24 +60,27 @@
 
     StreamContext() = default;
     StreamContext(std::unique_ptr<CommandMQ> commandMQ, std::unique_ptr<ReplyMQ> replyMQ,
-                  size_t frameSize, std::unique_ptr<DataMQ> dataMQ)
+                  size_t frameSize, std::unique_ptr<DataMQ> dataMQ, int transientStateDelayMs)
         : mCommandMQ(std::move(commandMQ)),
           mInternalCommandCookie(std::rand()),
           mReplyMQ(std::move(replyMQ)),
           mFrameSize(frameSize),
-          mDataMQ(std::move(dataMQ)) {}
+          mDataMQ(std::move(dataMQ)),
+          mTransientStateDelayMs(transientStateDelayMs) {}
     StreamContext(StreamContext&& other)
         : mCommandMQ(std::move(other.mCommandMQ)),
           mInternalCommandCookie(other.mInternalCommandCookie),
           mReplyMQ(std::move(other.mReplyMQ)),
           mFrameSize(other.mFrameSize),
-          mDataMQ(std::move(other.mDataMQ)) {}
+          mDataMQ(std::move(other.mDataMQ)),
+          mTransientStateDelayMs(other.mTransientStateDelayMs) {}
     StreamContext& operator=(StreamContext&& other) {
         mCommandMQ = std::move(other.mCommandMQ);
         mInternalCommandCookie = other.mInternalCommandCookie;
         mReplyMQ = std::move(other.mReplyMQ);
         mFrameSize = other.mFrameSize;
         mDataMQ = std::move(other.mDataMQ);
+        mTransientStateDelayMs = other.mTransientStateDelayMs;
         return *this;
     }
 
@@ -86,6 +90,7 @@
     size_t getFrameSize() const { return mFrameSize; }
     int getInternalCommandCookie() const { return mInternalCommandCookie; }
     ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
+    int getTransientStateDelayMs() const { return mTransientStateDelayMs; }
     bool isValid() const;
     void reset();
 
@@ -95,6 +100,7 @@
     std::unique_ptr<ReplyMQ> mReplyMQ;
     size_t mFrameSize;
     std::unique_ptr<DataMQ> mDataMQ;
+    int mTransientStateDelayMs;
 };
 
 class StreamWorkerCommonLogic : public ::android::hardware::audio::common::StreamLogic {
@@ -111,9 +117,16 @@
           mFrameSize(context.getFrameSize()),
           mCommandMQ(context.getCommandMQ()),
           mReplyMQ(context.getReplyMQ()),
-          mDataMQ(context.getDataMQ()) {}
+          mDataMQ(context.getDataMQ()),
+          mTransientStateDelayMs(context.getTransientStateDelayMs()) {}
     std::string init() override;
     void populateReply(StreamDescriptor::Reply* reply, bool isConnected) const;
+    void populateReplyWrongState(StreamDescriptor::Reply* reply,
+                                 const StreamDescriptor::Command& command) const;
+    void switchToTransientState(StreamDescriptor::State state) {
+        mState = state;
+        mTransientStateStart = std::chrono::steady_clock::now();
+    }
 
     // Atomic fields are used both by the main and worker threads.
     std::atomic<bool> mIsConnected = false;
@@ -125,6 +138,8 @@
     StreamContext::CommandMQ* mCommandMQ;
     StreamContext::ReplyMQ* mReplyMQ;
     StreamContext::DataMQ* mDataMQ;
+    const std::chrono::duration<int, std::milli> mTransientStateDelayMs;
+    std::chrono::time_point<std::chrono::steady_clock> mTransientStateStart;
     // We use an array and the "size" field instead of a vector to be able to detect
     // memory allocation issues.
     std::unique_ptr<int8_t[]> mDataBuffer;
diff --git a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
index 2f72bb0..a058069 100644
--- a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
@@ -1557,8 +1557,7 @@
 
     void SendInvalidCommandImpl(const AudioPortConfig& portConfig) {
         std::vector<StreamDescriptor::Command> commands = {
-                StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::hal_reserved_exit>(
-                        0),
+                StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::halReservedExit>(0),
                 // TODO: For proper testing of input streams, need to put the stream into
                 // a state which accepts BURST commands.
                 StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::burst>(-1),
@@ -1695,7 +1694,8 @@
     std::string mUnexpectedTransition;
 };
 
-using NamedCommandSequence = std::pair<std::string, std::vector<CommandAndState>>;
+enum { NAMED_CMD_NAME, NAMED_CMD_DELAY_MS, NAMED_CMD_CMDS };
+using NamedCommandSequence = std::tuple<std::string, int, std::vector<CommandAndState>>;
 enum { PARAM_MODULE_NAME, PARAM_CMD_SEQ, PARAM_SETUP_SEQ };
 using StreamIoTestParameters =
         std::tuple<std::string /*moduleName*/, NamedCommandSequence, bool /*useSetupSequence2*/>;
@@ -1716,7 +1716,12 @@
         }
         for (const auto& portConfig : allPortConfigs) {
             SCOPED_TRACE(portConfig.toString());
-            const auto& commandsAndStates = std::get<PARAM_CMD_SEQ>(GetParam()).second;
+            WithDebugFlags delayTransientStates = WithDebugFlags::createNested(debug);
+            delayTransientStates.flags().streamTransientStateDelayMs =
+                    std::get<NAMED_CMD_DELAY_MS>(std::get<PARAM_CMD_SEQ>(GetParam()));
+            ASSERT_NO_FATAL_FAILURE(delayTransientStates.SetUp(module.get()));
+            const auto& commandsAndStates =
+                    std::get<NAMED_CMD_CMDS>(std::get<PARAM_CMD_SEQ>(GetParam()));
             if (!std::get<PARAM_SETUP_SEQ>(GetParam())) {
                 ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates));
             } else {
@@ -1974,6 +1979,11 @@
                          android::PrintInstanceNameToString);
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamOut);
 
+// This is the value used in test sequences for which the test needs to ensure
+// that the HAL stays in a transient state long enough to receive the next command.
+static const int kStreamTransientStateTransitionDelayMs = 3000;
+static const StreamDescriptor::Command kGetStatusCommand =
+        StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::getStatus>(Void{});
 static const StreamDescriptor::Command kStartCommand =
         StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::start>(Void{});
 static const StreamDescriptor::Command kBurstCommand =
@@ -1987,83 +1997,100 @@
 static const StreamDescriptor::Command kFlushCommand =
         StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::flush>(Void{});
 static const NamedCommandSequence kReadOrWriteSeq =
-        std::make_pair(std::string("ReadOrWrite"),
-                       std::vector<CommandAndState>{
-                               std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-                               std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
-                               std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
-                               std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE)});
+        std::make_tuple(std::string("ReadOrWrite"), 0,
+                        std::vector<CommandAndState>{
+                                std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+                                std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
+                                std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
+                                std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE)});
 static const NamedCommandSequence kDrainInSeq =
-        std::make_pair(std::string("Drain"),
-                       std::vector<CommandAndState>{
-                               std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-                               std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
-                               std::make_pair(kDrainCommand, StreamDescriptor::State::DRAINING),
-                               std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
-                               std::make_pair(kDrainCommand, StreamDescriptor::State::DRAINING),
-                               // TODO: This will need to be changed once DRAIN starts taking time.
-                               std::make_pair(kBurstCommand, StreamDescriptor::State::STANDBY)});
+        std::make_tuple(std::string("Drain"), 0,
+                        std::vector<CommandAndState>{
+                                std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+                                std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
+                                std::make_pair(kDrainCommand, StreamDescriptor::State::DRAINING),
+                                std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
+                                std::make_pair(kDrainCommand, StreamDescriptor::State::DRAINING)
+                                // TODO: Test that from DRAINING the stream goes either to DRAINING
+                                // or to STANDBY. Supporting this in a generic would complicate the
+                                // test code because after state bifurcation we probably need to use
+                                // different commands for each state, this way a sequence of
+                                // commands and states becomes a tree.
+                        });
 static const NamedCommandSequence kDrainOutSeq =
-        std::make_pair(std::string("Drain"),
-                       std::vector<CommandAndState>{
-                               std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-                               std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
-                               // TODO: This will need to be changed once DRAIN starts taking time.
-                               std::make_pair(kDrainCommand, StreamDescriptor::State::IDLE)});
-// TODO: This will need to be changed once DRAIN starts taking time so we can pause it.
-static const NamedCommandSequence kDrainPauseOutSeq = std::make_pair(
-        std::string("DrainPause"),
-        std::vector<CommandAndState>{std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-                                     std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
-                                     std::make_pair(kDrainCommand, StreamDescriptor::State::IDLE)});
+        std::make_tuple(std::string("Drain"), 0,
+                        std::vector<CommandAndState>{
+                                std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+                                std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
+                                std::make_pair(kDrainCommand, StreamDescriptor::State::DRAINING),
+                                // Draining is synchronous, the stream switches to IDLE afterwards.
+                                std::make_pair(kGetStatusCommand, StreamDescriptor::State::IDLE)});
+static const NamedCommandSequence kDrainPauseOutSeq = std::make_tuple(
+        std::string("DrainPause"), kStreamTransientStateTransitionDelayMs,
+        std::vector<CommandAndState>{
+                std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+                std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
+                std::make_pair(kDrainCommand, StreamDescriptor::State::DRAINING),
+                std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED),
+                std::make_pair(kStartCommand, StreamDescriptor::State::DRAINING),
+                std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED),
+                std::make_pair(kBurstCommand, StreamDescriptor::State::PAUSED)});
 static const NamedCommandSequence kStandbySeq =
-        std::make_pair(std::string("Standby"),
-                       std::vector<CommandAndState>{
-                               std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-                               std::make_pair(kStandbyCommand, StreamDescriptor::State::STANDBY),
-                               // Perform a read or write in order to advance observable position
-                               // (this is verified by tests).
-                               std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-                               std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE)});
+        std::make_tuple(std::string("Standby"), 0,
+                        std::vector<CommandAndState>{
+                                std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+                                std::make_pair(kStandbyCommand, StreamDescriptor::State::STANDBY),
+                                // Perform a read or write in order to advance observable position
+                                // (this is verified by tests).
+                                std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+                                std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE)});
 static const NamedCommandSequence kPauseInSeq =
-        std::make_pair(std::string("Pause"),
-                       std::vector<CommandAndState>{
-                               std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-                               std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
-                               std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
-                               std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
-                               std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
-                               std::make_pair(kFlushCommand, StreamDescriptor::State::STANDBY)});
+        std::make_tuple(std::string("Pause"), 0,
+                        std::vector<CommandAndState>{
+                                std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+                                std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
+                                std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
+                                std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
+                                std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
+                                std::make_pair(kFlushCommand, StreamDescriptor::State::STANDBY)});
 static const NamedCommandSequence kPauseOutSeq =
-        std::make_pair(std::string("Pause"),
-                       std::vector<CommandAndState>{
-                               std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-                               std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
-                               std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
-                               std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
-                               std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
-                               std::make_pair(kBurstCommand, StreamDescriptor::State::PAUSED),
-                               std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
-                               std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED)});
+        std::make_tuple(std::string("Pause"), 0,
+                        std::vector<CommandAndState>{
+                                std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+                                std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
+                                std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
+                                std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
+                                std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
+                                std::make_pair(kBurstCommand, StreamDescriptor::State::PAUSED),
+                                std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
+                                std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED)});
 static const NamedCommandSequence kFlushInSeq =
-        std::make_pair(std::string("Flush"),
-                       std::vector<CommandAndState>{
-                               std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-                               std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
-                               std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
-                               std::make_pair(kFlushCommand, StreamDescriptor::State::STANDBY)});
-static const NamedCommandSequence kFlushOutSeq = std::make_pair(
-        std::string("Flush"),
+        std::make_tuple(std::string("Flush"), 0,
+                        std::vector<CommandAndState>{
+                                std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+                                std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
+                                std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
+                                std::make_pair(kFlushCommand, StreamDescriptor::State::STANDBY)});
+static const NamedCommandSequence kFlushOutSeq = std::make_tuple(
+        std::string("Flush"), 0,
         std::vector<CommandAndState>{std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
                                      std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
                                      std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
                                      std::make_pair(kFlushCommand, StreamDescriptor::State::IDLE)});
+static const NamedCommandSequence kDrainPauseFlushOutSeq = std::make_tuple(
+        std::string("DrainPauseFlush"), kStreamTransientStateTransitionDelayMs,
+        std::vector<CommandAndState>{
+                std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+                std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
+                std::make_pair(kDrainCommand, StreamDescriptor::State::DRAINING),
+                std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED),
+                std::make_pair(kFlushCommand, StreamDescriptor::State::IDLE)});
 std::string GetStreamIoTestName(const testing::TestParamInfo<StreamIoTestParameters>& info) {
     return android::PrintInstanceNameToString(
                    testing::TestParamInfo<std::string>{std::get<PARAM_MODULE_NAME>(info.param),
                                                        info.index})
             .append("_")
-            .append(std::get<PARAM_CMD_SEQ>(info.param).first)
+            .append(std::get<NAMED_CMD_NAME>(std::get<PARAM_CMD_SEQ>(info.param)))
             .append("_SetupSeq")
             .append(std::get<PARAM_SETUP_SEQ>(info.param) ? "2" : "1");
 }
@@ -2079,7 +2106,8 @@
         AudioStreamIoOutTest, AudioStreamIoOut,
         testing::Combine(testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)),
                          testing::Values(kReadOrWriteSeq, kDrainOutSeq, kDrainPauseOutSeq,
-                                         kStandbySeq, kPauseOutSeq, kFlushOutSeq),
+                                         kStandbySeq, kPauseOutSeq, kFlushOutSeq,
+                                         kDrainPauseFlushOutSeq),
                          testing::Values(false, true)),
         GetStreamIoTestName);
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamIoOut);