Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 98334439 authored by Mikhail Naganov's avatar Mikhail Naganov
Browse files

audio: Make StreamDescriptor::Command a union

Previously StreamDescriptor::Command used to be a parcelable
holding a command code (enum) + a command argument which was
used for one command only. This got replaced with an equivalent
union, where each command w/o arguments is represented by a
'Void' variant, and the command with an argument with a variant
of a corresponding type.

This has the following benefits:

  - the union guarantees that the argument is only set for
    commands that actually use it, thus all related comments
    and validations can be removed;

  - the command dispatch can be turned into an exhaustive
    switch.

To be able to use an exhaustive switch, the 'exit' command
has been exposed at the interface, but marked as 'hal_reserved'.

Bug: 205884982
Test: atest VtsHalAudioCoreTargetTest
Change-Id: I3f2c27a48e4d0b7cfce1171244b2eddc1637005f
parent b1f182cb
Loading
Loading
Loading
Loading
+8 −12
Original line number Diff line number Diff line
@@ -55,19 +55,15 @@ parcelable StreamDescriptor {
    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 {
+57 −55
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package android.hardware.audio.core;
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,34 +178,71 @@ parcelable StreamDescriptor {
        ERROR = 100,
    }

    /**
     * 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
    @Backing(type="int")
    enum CommandCode {
    @FixedSize
    union 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.
         */
        int hal_reserved_exit;
        /**
         * See the state machines on the applicability of this command to
         * different states. The 'fmqByteCount' field must always be set to 0.
         * different states.
         */
        START = 1,
        Void start;
        /**
         * The BURST command used for audio I/O, see 'AudioBuffer'. Differences
         * for the MMap No IRQ mode:
         * 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.
         *
         * 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.
         *
         * 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.
         *  - the value must always be set to 0.
         */
        BURST = 2,
        int burst;
        /**
         * See the state machines on the applicability of this command to
         * different states. The 'fmqByteCount' field must always be set to 0.
         * different states.
         */
        DRAIN = 3,
        Void drain;
        /**
         * See the state machines on the applicability of this command to
         * different states. The 'fmqByteCount' field must always be set to 0.
         * 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
@@ -213,53 +251,17 @@ parcelable StreamDescriptor {
         * buffer must remain empty in this state, even if capturing hardware is
         * still active, captured data must be discarded.
         */
        STANDBY = 4,
        Void standby;
        /**
         * See the state machines on the applicability of this command to
         * different states. The 'fmqByteCount' field must always be set to 0.
         * different states.
         */
        PAUSE = 5,
        Void pause;
        /**
         * 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.
     */
    @VintfStability
    @FixedSize
    parcelable Command {
        /**
         * The code of the command.
         * different states.
         */
        CommandCode code = CommandCode.START;
        /**
         * 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.
         *
         * 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
         * 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.
         */
        int fmqByteCount;
        Void flush;
    }
    MQDescriptor<Command, SynchronizedReadWrite> command;

@@ -293,15 +295,15 @@ parcelable StreamDescriptor {
         */
        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;
        /**
+10 −10
Original line number Diff line number Diff line
@@ -23,16 +23,16 @@ digraph stream_in_state_machine {
    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>"];
+15 −15
Original line number Diff line number Diff line
@@ -24,22 +24,22 @@ digraph stream_out_state_machine {
    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>"];
+222 −193
Original line number Diff line number Diff line
@@ -106,31 +106,43 @@ StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() {
        return Status::ABORT;
    }
    StreamDescriptor::Reply reply{};
    if (static_cast<int32_t>(command.code) == StreamContext::COMMAND_EXIT &&
        command.fmqByteCount == mInternalCommandCookie) {
    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 if (command.code == StreamDescriptor::CommandCode::START && command.fmqByteCount >= 0) {
            } else {
                LOG(WARNING) << __func__ << ": EXIT command has a bad cookie: " << cookie;
            }
            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
                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
            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 ||
                if (mState == StreamDescriptor::State::IDLE ||
                    mState == StreamDescriptor::State::ACTIVE ||
                    mState == StreamDescriptor::State::PAUSED ||
                    mState == StreamDescriptor::State::DRAINING) {
            if (!read(command.fmqByteCount, &reply)) {
                    if (!read(fmqByteCount, &reply)) {
                        mState = StreamDescriptor::State::ERROR;
                    }
                    if (mState == StreamDescriptor::State::IDLE ||
@@ -148,7 +160,11 @@ StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() {
                                 << toString(mState);
                    reply.status = STATUS_INVALID_OPERATION;
                }
    } else if (command.code == StreamDescriptor::CommandCode::DRAIN && command.fmqByteCount == 0) {
            } 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.
@@ -160,34 +176,36 @@ StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() {
                             << 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) {
            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::PAUSED;
                mState = StreamDescriptor::State::STANDBY;
            } else {
            LOG(WARNING) << __func__ << ": PAUSE command can not be handled in the state "
                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::FLUSH && command.fmqByteCount == 0) {
        LOG(DEBUG) << __func__ << ": received FLUSH read command";
        if (mState == StreamDescriptor::State::PAUSED) {
            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::STANDBY;
                mState = StreamDescriptor::State::PAUSED;
            } else {
            LOG(WARNING) << __func__ << ": FLUSH command can not be handled in the state "
                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::STANDBY &&
               command.fmqByteCount == 0) {
        LOG(DEBUG) << __func__ << ": received STANDBY read command";
        if (mState == StreamDescriptor::State::IDLE) {
            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.
@@ -197,10 +215,7 @@ StreamInWorkerLogic::Status StreamInWorkerLogic::cycle() {
                             << 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;
    }
    reply.state = mState;
    LOG(DEBUG) << __func__ << ": writing reply " << reply.toString();
@@ -253,14 +268,22 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
        return Status::ABORT;
    }
    StreamDescriptor::Reply reply{};
    if (static_cast<int32_t>(command.code) == StreamContext::COMMAND_EXIT &&
        command.fmqByteCount == mInternalCommandCookie) {
    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 if (command.code == StreamDescriptor::CommandCode::START && command.fmqByteCount >= 0) {
        LOG(DEBUG) << __func__ << ": received START read command";
            } 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;
@@ -279,11 +302,14 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
            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
            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(command.fmqByteCount, &reply)) {
                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 ||
@@ -298,7 +324,11 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
                                 << toString(mState);
                    reply.status = STATUS_INVALID_OPERATION;
                }
    } else if (command.code == StreamDescriptor::CommandCode::DRAIN && command.fmqByteCount == 0) {
            } 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.
@@ -315,8 +345,8 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
                             << toString(mState);
                reply.status = STATUS_INVALID_OPERATION;
            }
    } else if (command.code == StreamDescriptor::CommandCode::STANDBY &&
               command.fmqByteCount == 0) {
            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.
@@ -328,7 +358,8 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
                             << toString(mState);
                reply.status = STATUS_INVALID_OPERATION;
            }
    } else if (command.code == StreamDescriptor::CommandCode::PAUSE && command.fmqByteCount == 0) {
            break;
        case Tag::pause:
            LOG(DEBUG) << __func__ << ": received PAUSE write command";
            if (mState == StreamDescriptor::State::ACTIVE ||
                mState == StreamDescriptor::State::DRAINING) {
@@ -341,7 +372,8 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
                             << toString(mState);
                reply.status = STATUS_INVALID_OPERATION;
            }
    } else if (command.code == StreamDescriptor::CommandCode::FLUSH && command.fmqByteCount == 0) {
            break;
        case Tag::flush:
            LOG(DEBUG) << __func__ << ": received FLUSH write command";
            if (mState == StreamDescriptor::State::PAUSED ||
                mState == StreamDescriptor::State::DRAIN_PAUSED) {
@@ -352,10 +384,7 @@ StreamOutWorkerLogic::Status StreamOutWorkerLogic::cycle() {
                             << 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;
    }
    reply.state = mState;
    LOG(DEBUG) << __func__ << ": writing reply " << reply.toString();
@@ -421,9 +450,9 @@ template <class Metadata, class StreamWorker>
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
Loading