Merge "audio: Make StreamDescriptor::Command a union" am: 2b68543625 am: 224a3b1755

Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/2296167

Change-Id: I29a46c3c67df1311d9e1d9ed88e2b85b2de16f45
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
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 da24a10..3a77ad1 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
@@ -55,19 +55,15 @@
     DRAIN_PAUSED = 6,
     ERROR = 100,
   }
-  @Backing(type="int") @VintfStability
-  enum CommandCode {
-    START = 1,
-    BURST = 2,
-    DRAIN = 3,
-    STANDBY = 4,
-    PAUSE = 5,
-    FLUSH = 6,
-  }
   @FixedSize @VintfStability
-  parcelable Command {
-    android.hardware.audio.core.StreamDescriptor.CommandCode code = android.hardware.audio.core.StreamDescriptor.CommandCode.START;
-    int fmqByteCount;
+  union Command {
+    int hal_reserved_exit;
+    android.media.audio.common.Void start;
+    int burst;
+    android.media.audio.common.Void drain;
+    android.media.audio.common.Void standby;
+    android.media.audio.common.Void pause;
+    android.media.audio.common.Void flush;
   }
   @FixedSize @VintfStability
   parcelable Reply {
diff --git a/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl b/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl
index e5e56fc..2b1fc99 100644
--- a/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl
+++ b/audio/aidl/android/hardware/audio/core/StreamDescriptor.aidl
@@ -19,6 +19,7 @@
 import android.hardware.audio.core.MmapBufferDescriptor;
 import android.hardware.common.fmq.MQDescriptor;
 import android.hardware.common.fmq.SynchronizedReadWrite;
+import android.media.audio.common.Void;
 
 /**
  * Stream descriptor contains fast message queues and buffers used for sending
@@ -177,76 +178,41 @@
         ERROR = 100,
     }
 
-    @VintfStability
-    @Backing(type="int")
-    enum CommandCode {
-        /**
-         * See the state machines on the applicability of this command to
-         * different states. The 'fmqByteCount' field must always be set to 0.
-         */
-        START = 1,
-        /**
-         * The BURST command used for audio I/O, see 'AudioBuffer'. Differences
-         * for the MMap No IRQ mode:
-         *
-         *  - this command only provides updated positions and latency because
-         *    actual audio I/O is done via the 'AudioBuffer.mmap' shared buffer.
-         *    The client does not synchronize reads and writes into the buffer
-         *    with sending of this command.
-         *
-         *  - the 'fmqByteCount' must always be set to 0.
-         */
-        BURST = 2,
-        /**
-         * See the state machines on the applicability of this command to
-         * different states. The 'fmqByteCount' field must always be set to 0.
-         */
-        DRAIN = 3,
-        /**
-         * See the state machines on the applicability of this command to
-         * different states. The 'fmqByteCount' field must always be set to 0.
-         *
-         * Note that it's left on the discretion of the HAL implementation to
-         * assess all the necessary conditions that could prevent hardware from
-         * being suspended. Even if it can not be suspended, the state machine
-         * must still enter the 'STANDBY' state for consistency. Since the
-         * buffer must remain empty in this state, even if capturing hardware is
-         * still active, captured data must be discarded.
-         */
-        STANDBY = 4,
-        /**
-         * See the state machines on the applicability of this command to
-         * different states. The 'fmqByteCount' field must always be set to 0.
-         */
-        PAUSE = 5,
-        /**
-         * See the state machines on the applicability of this command to
-         * different states. The 'fmqByteCount' field must always be set to 0.
-         */
-        FLUSH = 6,
-    }
-
     /**
      * Used for sending commands to the HAL module. The client writes into
      * the queue, the HAL module reads. The queue can only contain a single
      * command.
+     *
+     * Variants of type 'Void' correspond to commands without
+     * arguments. Variants of other types correspond to commands with an
+     * argument. Would in future a need for a command with multiple argument
+     * arise, a Parcelable type should be used for the corresponding variant.
      */
     @VintfStability
     @FixedSize
-    parcelable Command {
+    union Command {
         /**
-         * The code of the command.
+         * Reserved for the HAL implementation to allow unblocking the wait on a
+         * command and exiting the I/O thread. A command of this variant must
+         * never be sent from the client side. To prevent that, the
+         * implementation must pass a random cookie as the command argument,
+         * which is only known to the implementation.
          */
-        CommandCode code = CommandCode.START;
+        int hal_reserved_exit;
         /**
-         * This field is only used for the BURST command. For all other commands
-         * it must be set to 0. The following description applies to the use
-         * of this field for the BURST command.
+         * See the state machines on the applicability of this command to
+         * different states.
+         */
+        Void start;
+        /**
+         * The 'burst' command used for audio I/O, see 'AudioBuffer'. The value
+         * specifies:
          *
-         * For output streams: the amount of bytes that the client requests the
-         *   HAL module to use out of the data contained in the 'audio.fmq' queue.
-         * For input streams: the amount of bytes requested by the client to
-         *   read from the hardware into the 'audio.fmq' queue.
+         *  - for output streams: the amount of bytes that the client requests the
+         *    HAL module to use out of the data contained in the 'audio.fmq' queue.
+         *
+         *  - for input streams: the amount of bytes requested by the client to
+         *    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
@@ -258,8 +224,44 @@
          * 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:
+         *
+         *  - this command only provides updated positions and latency because
+         *    actual audio I/O is done via the 'AudioBuffer.mmap' shared buffer.
+         *    The client does not synchronize reads and writes into the buffer
+         *    with sending of this command.
+         *
+         *  - the value must always be set to 0.
          */
-        int fmqByteCount;
+        int burst;
+        /**
+         * See the state machines on the applicability of this command to
+         * different states.
+         */
+        Void drain;
+        /**
+         * See the state machines on the applicability of this command to
+         * different states.
+         *
+         * Note that it's left on the discretion of the HAL implementation to
+         * assess all the necessary conditions that could prevent hardware from
+         * being suspended. Even if it can not be suspended, the state machine
+         * must still enter the 'STANDBY' state for consistency. Since the
+         * buffer must remain empty in this state, even if capturing hardware is
+         * still active, captured data must be discarded.
+         */
+        Void standby;
+        /**
+         * See the state machines on the applicability of this command to
+         * different states.
+         */
+        Void pause;
+        /**
+         * See the state machines on the applicability of this command to
+         * different states.
+         */
+        Void flush;
     }
     MQDescriptor<Command, SynchronizedReadWrite> command;
 
@@ -293,15 +295,15 @@
          */
         int status;
         /**
-         * Used with the BURST command only.
+         * Used with the 'burst' command only.
          *
          * For output streams: the amount of bytes of data actually consumed
          *   by the HAL module.
          * For input streams: the amount of bytes actually provided by the HAL
          *   in the 'audio.fmq' queue.
          *
-         * The returned value must not exceed the value passed in the
-         * 'fmqByteCount' field of the corresponding command or be negative.
+         * The returned value must not exceed the value passed as the
+         * argument of the corresponding command, or be negative.
          */
         int fmqByteCount;
         /**
diff --git a/audio/aidl/android/hardware/audio/core/stream-in-sm.gv b/audio/aidl/android/hardware/audio/core/stream-in-sm.gv
index 889a14b..805dc32 100644
--- a/audio/aidl/android/hardware/audio/core/stream-in-sm.gv
+++ b/audio/aidl/android/hardware/audio/core/stream-in-sm.gv
@@ -23,16 +23,16 @@
     node [style=dashed] ANY_STATE;
     node [fillcolor=lightblue style=filled];
     I -> STANDBY;
-    STANDBY -> IDLE [label="START"];    // producer -> active
-    IDLE -> STANDBY [label="STANDBY"];  // producer -> passive, buffer is cleared
-    IDLE -> ACTIVE [label="BURST"];     // consumer -> active
-    ACTIVE -> ACTIVE [label="BURST"];
-    ACTIVE -> PAUSED [label="PAUSE"];   // consumer -> passive
-    ACTIVE -> DRAINING [label="DRAIN"]; // producer -> passive
-    PAUSED -> ACTIVE [label="BURST"];   // consumer -> active
-    PAUSED -> STANDBY [label="FLUSH"];  // producer -> passive, buffer is cleared
-    DRAINING -> DRAINING [label="BURST"];
-    DRAINING -> ACTIVE [label="START"];  // producer -> active
+    STANDBY -> IDLE [label="start"];    // producer -> active
+    IDLE -> STANDBY [label="standby"];  // producer -> passive, buffer is cleared
+    IDLE -> ACTIVE [label="burst"];     // consumer -> active
+    ACTIVE -> ACTIVE [label="burst"];
+    ACTIVE -> PAUSED [label="pause"];   // consumer -> passive
+    ACTIVE -> DRAINING [label="drain"]; // producer -> passive
+    PAUSED -> ACTIVE [label="burst"];   // consumer -> active
+    PAUSED -> STANDBY [label="flush"];  // producer -> passive, buffer is cleared
+    DRAINING -> DRAINING [label="burst"];
+    DRAINING -> ACTIVE [label="start"];  // producer -> active
     DRAINING -> STANDBY [label="<empty buffer>"];  // consumer deactivates
     IDLE -> ERROR [label="<hardware failure>"];
     ACTIVE -> ERROR [label="<hardware failure>"];
diff --git a/audio/aidl/android/hardware/audio/core/stream-out-sm.gv b/audio/aidl/android/hardware/audio/core/stream-out-sm.gv
index 56dd5290..6aa5c61 100644
--- a/audio/aidl/android/hardware/audio/core/stream-out-sm.gv
+++ b/audio/aidl/android/hardware/audio/core/stream-out-sm.gv
@@ -24,22 +24,22 @@
     node [style=dashed] ANY_STATE;
     node [fillcolor=lightblue style=filled];
     I -> STANDBY;
-    STANDBY -> IDLE [label="START"];           // consumer -> active
-    STANDBY -> PAUSED [label="BURST"];         // producer -> active
-    IDLE -> STANDBY [label="STANDBY"];         // consumer -> passive
-    IDLE -> ACTIVE [label="BURST"];            // producer -> active
-    ACTIVE -> ACTIVE [label="BURST"];
-    ACTIVE -> PAUSED [label="PAUSE"];          // consumer -> passive (not consuming)
-    ACTIVE -> DRAINING [label="DRAIN"];        // producer -> passive
-    PAUSED -> PAUSED [label="BURST"];
-    PAUSED -> ACTIVE [label="START"];          // consumer -> active
-    PAUSED -> IDLE [label="FLUSH"];            // producer -> passive, buffer is cleared
+    STANDBY -> IDLE [label="start"];           // consumer -> active
+    STANDBY -> PAUSED [label="burst"];         // producer -> active
+    IDLE -> STANDBY [label="standby"];         // consumer -> passive
+    IDLE -> ACTIVE [label="burst"];            // producer -> active
+    ACTIVE -> ACTIVE [label="burst"];
+    ACTIVE -> PAUSED [label="pause"];          // consumer -> passive (not consuming)
+    ACTIVE -> DRAINING [label="drain"];        // producer -> passive
+    PAUSED -> PAUSED [label="burst"];
+    PAUSED -> ACTIVE [label="start"];          // consumer -> active
+    PAUSED -> IDLE [label="flush"];            // producer -> passive, buffer is cleared
     DRAINING -> IDLE [label="<empty buffer>"];
-    DRAINING -> ACTIVE [label="BURST"];        // producer -> active
-    DRAINING -> DRAIN_PAUSED [label="PAUSE"];  // consumer -> passive (not consuming)
-    DRAIN_PAUSED -> DRAINING [label="START"];  // consumer -> active
-    DRAIN_PAUSED -> PAUSED [label="BURST"];    // producer -> active
-    DRAIN_PAUSED -> IDLE [label="FLUSH"];      // buffer is cleared
+    DRAINING -> ACTIVE [label="burst"];        // producer -> active
+    DRAINING -> DRAIN_PAUSED [label="pause"];  // consumer -> passive (not consuming)
+    DRAIN_PAUSED -> DRAINING [label="start"];  // consumer -> active
+    DRAIN_PAUSED -> PAUSED [label="burst"];    // producer -> active
+    DRAIN_PAUSED -> IDLE [label="flush"];      // buffer is cleared
     IDLE -> ERROR [label="<hardware failure>"];
     ACTIVE -> ERROR [label="<hardware failure>"];
     DRAINING -> ERROR [label="<hardware failure>"];
diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp
index 7b544a1..21dc4b6 100644
--- a/audio/aidl/default/Stream.cpp
+++ b/audio/aidl/default/Stream.cpp
@@ -106,101 +106,116 @@
         return Status::ABORT;
     }
     StreamDescriptor::Reply reply{};
-    if (static_cast<int32_t>(command.code) == StreamContext::COMMAND_EXIT &&
-        command.fmqByteCount == mInternalCommandCookie) {
-        LOG(DEBUG) << __func__ << ": received EXIT command";
-        setClosed();
-        // This is an internal command, no need to reply.
-        return Status::EXIT;
-    } else if (command.code == StreamDescriptor::CommandCode::START && command.fmqByteCount >= 0) {
-        LOG(DEBUG) << __func__ << ": received START read command";
-        if (mState == StreamDescriptor::State::STANDBY ||
-            mState == StreamDescriptor::State::DRAINING) {
-            populateReply(&reply, mIsConnected);
-            mState = mState == StreamDescriptor::State::STANDBY ? 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;
-        }
-    } else if (command.code == StreamDescriptor::CommandCode::BURST && command.fmqByteCount >= 0) {
-        LOG(DEBUG) << __func__ << ": received BURST read command for " << command.fmqByteCount
-                   << " bytes";
-        if (mState == StreamDescriptor::State::IDLE || mState == StreamDescriptor::State::ACTIVE ||
-            mState == StreamDescriptor::State::PAUSED ||
-            mState == StreamDescriptor::State::DRAINING) {
-            if (!read(command.fmqByteCount, &reply)) {
-                mState = StreamDescriptor::State::ERROR;
+    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>();
+                cookie == mInternalCommandCookie) {
+                LOG(DEBUG) << __func__ << ": received EXIT command";
+                setClosed();
+                // This is an internal command, no need to reply.
+                return Status::EXIT;
+            } else {
+                LOG(WARNING) << __func__ << ": EXIT command has a bad cookie: " << cookie;
             }
-            if (mState == StreamDescriptor::State::IDLE ||
-                mState == StreamDescriptor::State::PAUSED) {
-                mState = StreamDescriptor::State::ACTIVE;
-            } 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.
+            break;
+        case Tag::start:
+            LOG(DEBUG) << __func__ << ": received START read command";
+            if (mState == StreamDescriptor::State::STANDBY ||
+                mState == StreamDescriptor::State::DRAINING) {
+                populateReply(&reply, mIsConnected);
+                mState = mState == StreamDescriptor::State::STANDBY
+                                 ? 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;
+            }
+            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";
+                if (mState == StreamDescriptor::State::IDLE ||
+                    mState == StreamDescriptor::State::ACTIVE ||
+                    mState == StreamDescriptor::State::PAUSED ||
+                    mState == StreamDescriptor::State::DRAINING) {
+                    if (!read(fmqByteCount, &reply)) {
+                        mState = StreamDescriptor::State::ERROR;
+                    }
+                    if (mState == StreamDescriptor::State::IDLE ||
+                        mState == StreamDescriptor::State::PAUSED) {
+                        mState = StreamDescriptor::State::ACTIVE;
+                    } 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.
+                        mState = StreamDescriptor::State::STANDBY;
+                    }
+                } else {
+                    LOG(WARNING) << __func__ << ": BURST command can not be handled in the state "
+                                 << toString(mState);
+                    reply.status = STATUS_INVALID_OPERATION;
+                }
+            } 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;
+            }
+            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;
             }
-        } else {
-            LOG(WARNING) << __func__ << ": BURST command can not be handled in the state "
-                         << toString(mState);
-            reply.status = STATUS_INVALID_OPERATION;
-        }
-    } else if (command.code == StreamDescriptor::CommandCode::DRAIN && command.fmqByteCount == 0) {
-        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;
-        }
-    } else if (command.code == StreamDescriptor::CommandCode::PAUSE && command.fmqByteCount == 0) {
-        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;
-        }
-    } else if (command.code == StreamDescriptor::CommandCode::FLUSH && command.fmqByteCount == 0) {
-        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;
-        }
-    } else if (command.code == StreamDescriptor::CommandCode::STANDBY &&
-               command.fmqByteCount == 0) {
-        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;
-        }
-    } else {
-        LOG(WARNING) << __func__ << ": invalid command (" << command.toString()
-                     << ") or count: " << command.fmqByteCount;
-        reply.status = STATUS_BAD_VALUE;
+            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;
+            }
+            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;
+            }
+            break;
     }
     reply.state = mState;
     LOG(DEBUG) << __func__ << ": writing reply " << reply.toString();
@@ -253,109 +268,123 @@
         return Status::ABORT;
     }
     StreamDescriptor::Reply reply{};
-    if (static_cast<int32_t>(command.code) == StreamContext::COMMAND_EXIT &&
-        command.fmqByteCount == mInternalCommandCookie) {
-        LOG(DEBUG) << __func__ << ": received EXIT command";
-        setClosed();
-        // This is an internal command, no need to reply.
-        return Status::EXIT;
-    } else if (command.code == StreamDescriptor::CommandCode::START && command.fmqByteCount >= 0) {
-        LOG(DEBUG) << __func__ << ": received START read command";
-        switch (mState) {
-            case StreamDescriptor::State::STANDBY:
+    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>();
+                cookie == mInternalCommandCookie) {
+                LOG(DEBUG) << __func__ << ": received EXIT command";
+                setClosed();
+                // This is an internal command, no need to reply.
+                return Status::EXIT;
+            } else {
+                LOG(WARNING) << __func__ << ": EXIT command has a bad cookie: " << cookie;
+            }
+            break;
+        case Tag::start:
+            LOG(DEBUG) << __func__ << ": received START write command";
+            switch (mState) {
+                case StreamDescriptor::State::STANDBY:
+                    mState = StreamDescriptor::State::IDLE;
+                    break;
+                case StreamDescriptor::State::PAUSED:
+                    mState = StreamDescriptor::State::ACTIVE;
+                    break;
+                case StreamDescriptor::State::DRAIN_PAUSED:
+                    mState = StreamDescriptor::State::PAUSED;
+                    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);
+            }
+            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";
+                if (mState !=
+                    StreamDescriptor::State::ERROR) {  // BURST can be handled in all valid states
+                    if (!write(fmqByteCount, &reply)) {
+                        mState = StreamDescriptor::State::ERROR;
+                    }
+                    if (mState == StreamDescriptor::State::STANDBY ||
+                        mState == StreamDescriptor::State::DRAIN_PAUSED) {
+                        mState = StreamDescriptor::State::PAUSED;
+                    } else if (mState == StreamDescriptor::State::IDLE ||
+                               mState == StreamDescriptor::State::DRAINING) {
+                        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;
+                }
+            } 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;
-                break;
-            case StreamDescriptor::State::PAUSED:
-                mState = StreamDescriptor::State::ACTIVE;
-                break;
-            case StreamDescriptor::State::DRAIN_PAUSED:
-                mState = StreamDescriptor::State::PAUSED;
-                break;
-            default:
-                LOG(WARNING) << __func__ << ": START command can not be handled in the state "
+                // 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.
+            } else {
+                LOG(WARNING) << __func__ << ": DRAIN command can not be handled in the state "
                              << toString(mState);
                 reply.status = STATUS_INVALID_OPERATION;
-        }
-        if (reply.status != STATUS_INVALID_OPERATION) {
-            populateReply(&reply, mIsConnected);
-        }
-    } else if (command.code == StreamDescriptor::CommandCode::BURST && command.fmqByteCount >= 0) {
-        LOG(DEBUG) << __func__ << ": received BURST write command for " << command.fmqByteCount
-                   << " bytes";
-        if (mState != StreamDescriptor::State::ERROR) {  // BURST can be handled in all valid states
-            if (!write(command.fmqByteCount, &reply)) {
-                mState = StreamDescriptor::State::ERROR;
             }
-            if (mState == StreamDescriptor::State::STANDBY ||
+            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;
+            }
+            break;
+        case Tag::pause:
+            LOG(DEBUG) << __func__ << ": received PAUSE write command";
+            if (mState == StreamDescriptor::State::ACTIVE ||
+                mState == StreamDescriptor::State::DRAINING) {
+                populateReply(&reply, mIsConnected);
+                mState = mState == StreamDescriptor::State::ACTIVE
+                                 ? 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;
+            }
+            break;
+        case Tag::flush:
+            LOG(DEBUG) << __func__ << ": received FLUSH write command";
+            if (mState == StreamDescriptor::State::PAUSED ||
                 mState == StreamDescriptor::State::DRAIN_PAUSED) {
-                mState = StreamDescriptor::State::PAUSED;
-            } else if (mState == StreamDescriptor::State::IDLE ||
-                       mState == StreamDescriptor::State::DRAINING) {
-                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;
-        }
-    } else if (command.code == StreamDescriptor::CommandCode::DRAIN && command.fmqByteCount == 0) {
-        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.
-        } else {
-            LOG(WARNING) << __func__ << ": DRAIN command can not be handled in the state "
-                         << toString(mState);
-            reply.status = STATUS_INVALID_OPERATION;
-        }
-    } else if (command.code == StreamDescriptor::CommandCode::STANDBY &&
-               command.fmqByteCount == 0) {
-        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;
-        }
-    } else if (command.code == StreamDescriptor::CommandCode::PAUSE && command.fmqByteCount == 0) {
-        LOG(DEBUG) << __func__ << ": received PAUSE write command";
-        if (mState == StreamDescriptor::State::ACTIVE ||
-            mState == StreamDescriptor::State::DRAINING) {
-            populateReply(&reply, mIsConnected);
-            mState = mState == StreamDescriptor::State::ACTIVE
-                             ? 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;
-        }
-    } else if (command.code == StreamDescriptor::CommandCode::FLUSH && command.fmqByteCount == 0) {
-        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;
-        }
-    } else {
-        LOG(WARNING) << __func__ << ": invalid command (" << command.toString()
-                     << ") or count: " << command.fmqByteCount;
-        reply.status = STATUS_BAD_VALUE;
+                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;
+            }
+            break;
     }
     reply.state = mState;
     LOG(DEBUG) << __func__ << ": writing reply " << reply.toString();
@@ -421,9 +450,9 @@
 void StreamCommon<Metadata, StreamWorker>::stopWorker() {
     if (auto commandMQ = mContext.getCommandMQ(); commandMQ != nullptr) {
         LOG(DEBUG) << __func__ << ": asking the worker to exit...";
-        StreamDescriptor::Command cmd;
-        cmd.code = StreamDescriptor::CommandCode(StreamContext::COMMAND_EXIT);
-        cmd.fmqByteCount = mContext.getInternalCommandCookie();
+        auto cmd =
+                StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::hal_reserved_exit>(
+                        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 539fa8b..5ee0f82 100644
--- a/audio/aidl/default/include/core-impl/Stream.h
+++ b/audio/aidl/default/include/core-impl/Stream.h
@@ -54,8 +54,6 @@
             int8_t, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>
             DataMQ;
 
-    // Ensure that this value is not used by any of StreamDescriptor.CommandCode enums
-    static constexpr int32_t COMMAND_EXIT = -1;
     // Ensure that this value is not used by any of StreamDescriptor.State enums
     static constexpr int32_t STATE_CLOSED = -1;
 
diff --git a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
index 7faa93e..2f72bb0 100644
--- a/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreTargetTest.cpp
@@ -70,6 +70,7 @@
 using aidl::android::media::audio::common::AudioPortExt;
 using aidl::android::media::audio::common::AudioSource;
 using aidl::android::media::audio::common::AudioUsage;
+using aidl::android::media::audio::common::Void;
 using android::hardware::audio::common::isBitPositionFlagSet;
 using android::hardware::audio::common::StreamLogic;
 using android::hardware::audio::common::StreamWorker;
@@ -455,7 +456,9 @@
             LOG(ERROR) << __func__ << ": received error status: " << statusToString(reply.status);
             return Status::ABORT;
         }
-        if (reply.fmqByteCount < 0 || reply.fmqByteCount > command.fmqByteCount) {
+        if (reply.fmqByteCount < 0 ||
+            (command.getTag() == StreamDescriptor::Command::Tag::burst &&
+             reply.fmqByteCount > command.get<StreamDescriptor::Command::Tag::burst>())) {
             LOG(ERROR) << __func__
                        << ": received invalid byte count in the reply: " << reply.fmqByteCount;
             return Status::ABORT;
@@ -532,7 +535,9 @@
             LOG(ERROR) << __func__ << ": received error status: " << statusToString(reply.status);
             return Status::ABORT;
         }
-        if (reply.fmqByteCount < 0 || reply.fmqByteCount > command.fmqByteCount) {
+        if (reply.fmqByteCount < 0 ||
+            (command.getTag() == StreamDescriptor::Command::Tag::burst &&
+             reply.fmqByteCount > command.get<StreamDescriptor::Command::Tag::burst>())) {
             LOG(ERROR) << __func__
                        << ": received invalid byte count in the reply: " << reply.fmqByteCount;
             return Status::ABORT;
@@ -1551,18 +1556,15 @@
     }
 
     void SendInvalidCommandImpl(const AudioPortConfig& portConfig) {
-        std::vector<StreamDescriptor::Command> commands(6);
-        commands[0].code = StreamDescriptor::CommandCode(-1);
-        commands[1].code = StreamDescriptor::CommandCode(
-                static_cast<int32_t>(StreamDescriptor::CommandCode::START) - 1);
-        commands[2].code = StreamDescriptor::CommandCode(std::numeric_limits<int32_t>::min());
-        commands[3].code = StreamDescriptor::CommandCode(std::numeric_limits<int32_t>::max());
-        // TODO: For proper testing of input streams, need to put the stream into
-        // a state which accepts BURST commands.
-        commands[4].code = StreamDescriptor::CommandCode::BURST;
-        commands[4].fmqByteCount = -1;
-        commands[5].code = StreamDescriptor::CommandCode::BURST;
-        commands[5].fmqByteCount = std::numeric_limits<int32_t>::min();
+        std::vector<StreamDescriptor::Command> commands = {
+                StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::hal_reserved_exit>(
+                        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),
+                StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::burst>(
+                        std::numeric_limits<int32_t>::min()),
+        };
         WithStream<Stream> stream(portConfig);
         ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
         StreamLogicDriverInvalidCommand driver(commands);
@@ -1628,7 +1630,7 @@
             << "when no offload info is provided for a compressed offload mix port";
 }
 
-using CommandAndState = std::pair<StreamDescriptor::CommandCode, StreamDescriptor::State>;
+using CommandAndState = std::pair<StreamDescriptor::Command, StreamDescriptor::State>;
 
 class StreamLogicDefaultDriver : public StreamLogicDriver {
   public:
@@ -1643,15 +1645,17 @@
 
     bool done() override { return mNextCommand >= mCommands.size(); }
     StreamDescriptor::Command getNextCommand(int maxDataSize, int* actualSize) override {
-        StreamDescriptor::Command command{};
-        command.code = mCommands[mNextCommand++].first;
-        const int dataSize = command.code == StreamDescriptor::CommandCode::BURST ? maxDataSize : 0;
-        command.fmqByteCount = dataSize;
-        if (actualSize != nullptr) {
-            // In the output scenario, reduce slightly the fmqByteCount to verify
-            // that the HAL module always consumes all data from the MQ.
-            if (command.fmqByteCount > 1) command.fmqByteCount--;
-            *actualSize = dataSize;
+        auto command = mCommands[mNextCommand++].first;
+        if (command.getTag() == StreamDescriptor::Command::Tag::burst) {
+            if (actualSize != nullptr) {
+                // In the output scenario, reduce slightly the fmqByteCount to verify
+                // that the HAL module always consumes all data from the MQ.
+                if (maxDataSize > 1) maxDataSize--;
+                *actualSize = maxDataSize;
+            }
+            command.set<StreamDescriptor::Command::Tag::burst>(maxDataSize);
+        } else {
+            if (actualSize != nullptr) *actualSize = 0;
         }
         return command;
     }
@@ -1673,7 +1677,7 @@
                                     .append(" to ")
                                     .append(toString(reply.state))
                                     .append(" caused by the command ")
-                                    .append(toString(lastCommandState.first));
+                                    .append(lastCommandState.first.toString());
             LOG(ERROR) << __func__ << ": " << s;
             mUnexpectedTransition = std::move(s);
             return false;
@@ -1970,112 +1974,90 @@
                          android::PrintInstanceNameToString);
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioStreamOut);
 
-static const NamedCommandSequence kReadOrWriteSeq = std::make_pair(
-        std::string("ReadOrWrite"),
-        std::vector<CommandAndState>{
-                std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
-                std::make_pair(StreamDescriptor::CommandCode::BURST,
-                               StreamDescriptor::State::ACTIVE),
-                std::make_pair(StreamDescriptor::CommandCode::BURST,
-                               StreamDescriptor::State::ACTIVE),
-                std::make_pair(StreamDescriptor::CommandCode::BURST,
-                               StreamDescriptor::State::ACTIVE)});
-static const NamedCommandSequence kDrainInSeq = std::make_pair(
-        std::string("Drain"),
-        std::vector<CommandAndState>{
-                std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
-                std::make_pair(StreamDescriptor::CommandCode::BURST,
-                               StreamDescriptor::State::ACTIVE),
-                std::make_pair(StreamDescriptor::CommandCode::DRAIN,
-                               StreamDescriptor::State::DRAINING),
-                std::make_pair(StreamDescriptor::CommandCode::START,
-                               StreamDescriptor::State::ACTIVE),
-                std::make_pair(StreamDescriptor::CommandCode::DRAIN,
-                               StreamDescriptor::State::DRAINING),
-                // TODO: This will need to be changed once DRAIN starts taking time.
-                std::make_pair(StreamDescriptor::CommandCode::BURST,
-                               StreamDescriptor::State::STANDBY)});
-static const NamedCommandSequence kDrainOutSeq = std::make_pair(
-        std::string("Drain"),
-        std::vector<CommandAndState>{
-                std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
-                std::make_pair(StreamDescriptor::CommandCode::BURST,
-                               StreamDescriptor::State::ACTIVE),
-                // TODO: This will need to be changed once DRAIN starts taking time.
-                std::make_pair(StreamDescriptor::CommandCode::DRAIN,
-                               StreamDescriptor::State::IDLE)});
+static const StreamDescriptor::Command kStartCommand =
+        StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::start>(Void{});
+static const StreamDescriptor::Command kBurstCommand =
+        StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::burst>(0);
+static const StreamDescriptor::Command kDrainCommand =
+        StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::drain>(Void{});
+static const StreamDescriptor::Command kStandbyCommand =
+        StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::standby>(Void{});
+static const StreamDescriptor::Command kPauseCommand =
+        StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::pause>(Void{});
+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)});
+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)});
+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(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
-                std::make_pair(StreamDescriptor::CommandCode::BURST,
-                               StreamDescriptor::State::ACTIVE),
-                std::make_pair(StreamDescriptor::CommandCode::DRAIN,
-                               StreamDescriptor::State::IDLE)});
-static const NamedCommandSequence kStandbySeq = std::make_pair(
-        std::string("Standby"),
-        std::vector<CommandAndState>{
-                std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
-                std::make_pair(StreamDescriptor::CommandCode::STANDBY,
-                               StreamDescriptor::State::STANDBY),
-                // Perform a read or write in order to advance observable position
-                // (this is verified by tests).
-                std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
-                std::make_pair(StreamDescriptor::CommandCode::BURST,
-                               StreamDescriptor::State::ACTIVE)});
-static const NamedCommandSequence kPauseInSeq = std::make_pair(
-        std::string("Pause"),
-        std::vector<CommandAndState>{
-                std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
-                std::make_pair(StreamDescriptor::CommandCode::BURST,
-                               StreamDescriptor::State::ACTIVE),
-                std::make_pair(StreamDescriptor::CommandCode::PAUSE,
-                               StreamDescriptor::State::PAUSED),
-                std::make_pair(StreamDescriptor::CommandCode::BURST,
-                               StreamDescriptor::State::ACTIVE),
-                std::make_pair(StreamDescriptor::CommandCode::PAUSE,
-                               StreamDescriptor::State::PAUSED),
-                std::make_pair(StreamDescriptor::CommandCode::FLUSH,
-                               StreamDescriptor::State::STANDBY)});
-static const NamedCommandSequence kPauseOutSeq = std::make_pair(
-        std::string("Pause"),
-        std::vector<CommandAndState>{
-                std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
-                std::make_pair(StreamDescriptor::CommandCode::BURST,
-                               StreamDescriptor::State::ACTIVE),
-                std::make_pair(StreamDescriptor::CommandCode::PAUSE,
-                               StreamDescriptor::State::PAUSED),
-                std::make_pair(StreamDescriptor::CommandCode::START,
-                               StreamDescriptor::State::ACTIVE),
-                std::make_pair(StreamDescriptor::CommandCode::PAUSE,
-                               StreamDescriptor::State::PAUSED),
-                std::make_pair(StreamDescriptor::CommandCode::BURST,
-                               StreamDescriptor::State::PAUSED),
-                std::make_pair(StreamDescriptor::CommandCode::START,
-                               StreamDescriptor::State::ACTIVE),
-                std::make_pair(StreamDescriptor::CommandCode::PAUSE,
-                               StreamDescriptor::State::PAUSED)});
-static const NamedCommandSequence kFlushInSeq = std::make_pair(
-        std::string("Flush"),
-        std::vector<CommandAndState>{
-                std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
-                std::make_pair(StreamDescriptor::CommandCode::BURST,
-                               StreamDescriptor::State::ACTIVE),
-                std::make_pair(StreamDescriptor::CommandCode::PAUSE,
-                               StreamDescriptor::State::PAUSED),
-                std::make_pair(StreamDescriptor::CommandCode::FLUSH,
-                               StreamDescriptor::State::STANDBY)});
+        std::vector<CommandAndState>{std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
+                                     std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
+                                     std::make_pair(kDrainCommand, StreamDescriptor::State::IDLE)});
+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)});
+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)});
+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)});
+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::vector<CommandAndState>{
-                std::make_pair(StreamDescriptor::CommandCode::START, StreamDescriptor::State::IDLE),
-                std::make_pair(StreamDescriptor::CommandCode::BURST,
-                               StreamDescriptor::State::ACTIVE),
-                std::make_pair(StreamDescriptor::CommandCode::PAUSE,
-                               StreamDescriptor::State::PAUSED),
-                std::make_pair(StreamDescriptor::CommandCode::FLUSH,
-                               StreamDescriptor::State::IDLE)});
+        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)});
 std::string GetStreamIoTestName(const testing::TestParamInfo<StreamIoTestParameters>& info) {
     return android::PrintInstanceNameToString(
                    testing::TestParamInfo<std::string>{std::get<PARAM_MODULE_NAME>(info.param),