audio: Allow going to 'IDLE' for synchronous drain

For small buffers, the driver can perform draining
synhronously, returning control to the HAL only after
the buffer is empty. This makes going through
the 'DRAINING' state artificial. Thus, we allow going
to the 'IDLE' state directly.

In order to make sure that VTS handles both transitions:
to 'DRAINING' and to 'IDLE', correctly, add an "AOSP as
vendor" parameter "aosp.forceSynchronousDrain" to induce
this behavior in the default implementation.

Bug: 262402957
Test: atest VtsHalAudioCoreTargetTest
Change-Id: Ic8eaee53cb4596afb5317b4b905e004af3f112aa
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index 780d6a9..13b04cd 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -140,7 +140,8 @@
          !isBitPositionFlagSet(flags.get<AudioIoFlags::Tag::output>(),
                                AudioOutputFlags::MMAP_NOIRQ))) {
         StreamContext::DebugParameters params{mDebug.streamTransientStateDelayMs,
-                                              mVendorDebug.forceTransientBurst};
+                                              mVendorDebug.forceTransientBurst,
+                                              mVendorDebug.forceSynchronousDrain};
         StreamContext temp(
                 std::make_unique<StreamContext::CommandMQ>(1, true /*configureEventFlagWord*/),
                 std::make_unique<StreamContext::ReplyMQ>(1, true /*configureEventFlagWord*/),
@@ -980,6 +981,7 @@
 }
 
 const std::string Module::VendorDebug::kForceTransientBurstName = "aosp.forceTransientBurst";
+const std::string Module::VendorDebug::kForceSynchronousDrainName = "aosp.forceSynchronousDrain";
 
 ndk::ScopedAStatus Module::getVendorParameters(const std::vector<std::string>& in_ids,
                                                std::vector<VendorParameter>* _aidl_return) {
@@ -990,6 +992,10 @@
             VendorParameter forceTransientBurst{.id = id};
             forceTransientBurst.ext.setParcelable(Boolean{mVendorDebug.forceTransientBurst});
             _aidl_return->push_back(std::move(forceTransientBurst));
+        } else if (id == VendorDebug::kForceSynchronousDrainName) {
+            VendorParameter forceSynchronousDrain{.id = id};
+            forceSynchronousDrain.ext.setParcelable(Boolean{mVendorDebug.forceSynchronousDrain});
+            _aidl_return->push_back(std::move(forceSynchronousDrain));
         } else {
             allParametersKnown = false;
             LOG(ERROR) << __func__ << ": unrecognized parameter \"" << id << "\"";
@@ -999,6 +1005,23 @@
     return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
 }
 
+namespace {
+
+template <typename W>
+bool extractParameter(const VendorParameter& p, decltype(W::value)* v) {
+    std::optional<W> value;
+    binder_status_t result = p.ext.getParcelable(&value);
+    if (result == STATUS_OK && value.has_value()) {
+        *v = value.value().value;
+        return true;
+    }
+    LOG(ERROR) << __func__ << ": failed to read the value of the parameter \"" << p.id
+               << "\": " << result;
+    return false;
+}
+
+}  // namespace
+
 ndk::ScopedAStatus Module::setVendorParameters(const std::vector<VendorParameter>& in_parameters,
                                                bool in_async) {
     LOG(DEBUG) << __func__ << ": parameter count " << in_parameters.size()
@@ -1006,13 +1029,11 @@
     bool allParametersKnown = true;
     for (const auto& p : in_parameters) {
         if (p.id == VendorDebug::kForceTransientBurstName) {
-            std::optional<Boolean> value;
-            binder_status_t result = p.ext.getParcelable(&value);
-            if (result == STATUS_OK) {
-                mVendorDebug.forceTransientBurst = value.value().value;
-            } else {
-                LOG(ERROR) << __func__ << ": failed to read the value of the parameter \"" << p.id
-                           << "\"";
+            if (!extractParameter<Boolean>(p, &mVendorDebug.forceTransientBurst)) {
+                return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+            }
+        } else if (p.id == VendorDebug::kForceSynchronousDrainName) {
+            if (!extractParameter<Boolean>(p, &mVendorDebug.forceSynchronousDrain)) {
                 return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
             }
         } else {
diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp
index 9be8896..0520cba 100644
--- a/audio/aidl/default/Stream.cpp
+++ b/audio/aidl/default/Stream.cpp
@@ -402,7 +402,11 @@
                     usleep(1000);  // Simulate a blocking call into the driver.
                     populateReply(&reply, mIsConnected);
                     // Can switch the state to ERROR if a driver error occurs.
-                    switchToTransientState(StreamDescriptor::State::DRAINING);
+                    if (mState == StreamDescriptor::State::ACTIVE && mForceSynchronousDrain) {
+                        mState = StreamDescriptor::State::IDLE;
+                    } else {
+                        switchToTransientState(StreamDescriptor::State::DRAINING);
+                    }
                 } else if (mState == StreamDescriptor::State::TRANSFER_PAUSED) {
                     mState = StreamDescriptor::State::DRAIN_PAUSED;
                     populateReply(&reply, mIsConnected);
diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h
index 9c95ea8..000a704 100644
--- a/audio/aidl/default/include/core-impl/Module.h
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -38,7 +38,9 @@
   private:
     struct VendorDebug {
         static const std::string kForceTransientBurstName;
+        static const std::string kForceSynchronousDrainName;
         bool forceTransientBurst = false;
+        bool forceSynchronousDrain = false;
     };
 
     ndk::ScopedAStatus setModuleDebug(
diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h
index 29c1e2e..2cf5951 100644
--- a/audio/aidl/default/include/core-impl/Stream.h
+++ b/audio/aidl/default/include/core-impl/Stream.h
@@ -67,6 +67,8 @@
         int transientStateDelayMs = 0;
         // Force the "burst" command to move the SM to the TRANSFERRING state.
         bool forceTransientBurst = false;
+        // Force the "drain" command to be synchronous, going directly to the IDLE state.
+        bool forceSynchronousDrain = false;
     };
 
     StreamContext() = default;
@@ -115,6 +117,7 @@
         return mFormat;
     }
     bool getForceTransientBurst() const { return mDebugParameters.forceTransientBurst; }
+    bool getForceSynchronousDrain() const { return mDebugParameters.forceSynchronousDrain; }
     size_t getFrameSize() const;
     int getInternalCommandCookie() const { return mInternalCommandCookie; }
     ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
@@ -150,7 +153,8 @@
           mDataMQ(context.getDataMQ()),
           mAsyncCallback(context.getAsyncCallback()),
           mTransientStateDelayMs(context.getTransientStateDelayMs()),
-          mForceTransientBurst(context.getForceTransientBurst()) {}
+          mForceTransientBurst(context.getForceTransientBurst()),
+          mForceSynchronousDrain(context.getForceSynchronousDrain()) {}
     std::string init() override;
     void populateReply(StreamDescriptor::Reply* reply, bool isConnected) const;
     void populateReplyWrongState(StreamDescriptor::Reply* reply,
@@ -174,6 +178,7 @@
     const std::chrono::duration<int, std::milli> mTransientStateDelayMs;
     std::chrono::time_point<std::chrono::steady_clock> mTransientStateStart;
     const bool mForceTransientBurst;
+    const bool mForceSynchronousDrain;
     // We use an array and the "size" field instead of a vector to be able to detect
     // memory allocation issues.
     std::unique_ptr<int8_t[]> mDataBuffer;