aaudio: fix some state transitions

Now requestStop() and requestPause() do the appropriate thing
based on state.
Flush is allowed when OPEN, PAUSED or STOPPED because there might
be pre-roll data.

Bug: 69810494
Test: test_various.cpp
Change-Id: Ie1b306b17734a58fa71b1742bb186482893656b7
diff --git a/media/libaaudio/src/binding/AAudioServiceMessage.h b/media/libaaudio/src/binding/AAudioServiceMessage.h
index 9779f24..3981454 100644
--- a/media/libaaudio/src/binding/AAudioServiceMessage.h
+++ b/media/libaaudio/src/binding/AAudioServiceMessage.h
@@ -36,7 +36,6 @@
     AAUDIO_SERVICE_EVENT_PAUSED,
     AAUDIO_SERVICE_EVENT_STOPPED,
     AAUDIO_SERVICE_EVENT_FLUSHED,
-    AAUDIO_SERVICE_EVENT_CLOSED,
     AAUDIO_SERVICE_EVENT_DISCONNECTED,
     AAUDIO_SERVICE_EVENT_VOLUME,
     AAUDIO_SERVICE_EVENT_XRUN
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index 4980e97..b611160 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -340,8 +340,13 @@
     }
 }
 
-aaudio_result_t AudioStreamInternal::requestStopInternal()
+aaudio_result_t AudioStreamInternal::requestStop()
 {
+    aaudio_result_t result = stopCallback();
+    if (result != AAUDIO_OK) {
+        return result;
+    }
+
     if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
         ALOGE("requestStopInternal() mServiceStreamHandle invalid = 0x%08X",
               mServiceStreamHandle);
@@ -355,16 +360,6 @@
     return mServiceInterface.stopStream(mServiceStreamHandle);
 }
 
-aaudio_result_t AudioStreamInternal::requestStop()
-{
-    aaudio_result_t result = stopCallback();
-    if (result != AAUDIO_OK) {
-        return result;
-    }
-    result = requestStopInternal();
-    return result;
-}
-
 aaudio_result_t AudioStreamInternal::registerThread() {
     if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
         ALOGE("registerThread() mServiceStreamHandle invalid");
@@ -483,10 +478,6 @@
                 onFlushFromServer();
             }
             break;
-        case AAUDIO_SERVICE_EVENT_CLOSED:
-            ALOGD("%s - got AAUDIO_SERVICE_EVENT_CLOSED", __func__);
-            setState(AAUDIO_STREAM_STATE_CLOSED);
-            break;
         case AAUDIO_SERVICE_EVENT_DISCONNECTED:
             // Prevent hardware from looping on old data and making buzzing sounds.
             if (getDirection() == AAUDIO_DIRECTION_OUTPUT) {
diff --git a/media/libaaudio/src/client/AudioStreamInternal.h b/media/libaaudio/src/client/AudioStreamInternal.h
index 117756d..0f54f8c 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.h
+++ b/media/libaaudio/src/client/AudioStreamInternal.h
@@ -121,8 +121,6 @@
 
     aaudio_result_t processCommands();
 
-    aaudio_result_t requestStopInternal();
-
     aaudio_result_t stopCallback();
 
     virtual void advanceClientToMatchServerPosition() = 0;
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
index 5de6a11..5660c1b 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
@@ -38,9 +38,12 @@
 
 AudioStreamInternalPlay::~AudioStreamInternalPlay() {}
 
-
-aaudio_result_t AudioStreamInternalPlay::requestPauseInternal()
+aaudio_result_t AudioStreamInternalPlay::requestPause()
 {
+    aaudio_result_t result = stopCallback();
+    if (result != AAUDIO_OK) {
+        return result;
+    }
     if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
         ALOGE("AudioStreamInternal::requestPauseInternal() mServiceStreamHandle invalid = 0x%08X",
               mServiceStreamHandle);
@@ -53,16 +56,6 @@
     return mServiceInterface.pauseStream(mServiceStreamHandle);
 }
 
-aaudio_result_t AudioStreamInternalPlay::requestPause()
-{
-    aaudio_result_t result = stopCallback();
-    if (result != AAUDIO_OK) {
-        return result;
-    }
-    result = requestPauseInternal();
-    return result;
-}
-
 aaudio_result_t AudioStreamInternalPlay::requestFlush() {
     if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
         ALOGE("AudioStreamInternal::requestFlush() mServiceStreamHandle invalid = 0x%08X",
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.h b/media/libaaudio/src/client/AudioStreamInternalPlay.h
index d5c1b1e..04e4a62 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.h
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.h
@@ -37,6 +37,16 @@
 
     aaudio_result_t requestFlush() override;
 
+    bool isFlushSupported() const override {
+        // Only implement FLUSH for OUTPUT streams.
+        return true;
+    }
+
+    bool isPauseSupported() const override {
+        // Only implement PAUSE for OUTPUT streams.
+        return true;
+    }
+
     aaudio_result_t write(const void *buffer,
                           int32_t numFrames,
                           int64_t timeoutNanoseconds) override;
@@ -52,8 +62,6 @@
 
 protected:
 
-    aaudio_result_t requestPauseInternal();
-
     void advanceClientToMatchServerPosition() override;
 
     void onFlushFromServer() override;
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index 9a2405a..c4465fd 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -130,29 +130,106 @@
 }
 
 aaudio_result_t AudioStream::safePause() {
+    if (!isPauseSupported()) {
+        return AAUDIO_ERROR_UNIMPLEMENTED;
+    }
+
     std::lock_guard<std::mutex> lock(mStreamLock);
     if (collidesWithCallback()) {
         ALOGE("%s cannot be called from a callback!", __func__);
         return AAUDIO_ERROR_INVALID_STATE;
     }
+
+    switch (getState()) {
+        // Proceed with pausing.
+        case AAUDIO_STREAM_STATE_STARTING:
+        case AAUDIO_STREAM_STATE_STARTED:
+        case AAUDIO_STREAM_STATE_DISCONNECTED:
+            break;
+
+            // Transition from one inactive state to another.
+        case AAUDIO_STREAM_STATE_OPEN:
+        case AAUDIO_STREAM_STATE_STOPPED:
+        case AAUDIO_STREAM_STATE_FLUSHED:
+            setState(AAUDIO_STREAM_STATE_PAUSED);
+            return AAUDIO_OK;
+
+            // Redundant?
+        case AAUDIO_STREAM_STATE_PAUSING:
+        case AAUDIO_STREAM_STATE_PAUSED:
+            return AAUDIO_OK;
+
+            // Don't interfere with transitional states or when closed.
+        case AAUDIO_STREAM_STATE_STOPPING:
+        case AAUDIO_STREAM_STATE_FLUSHING:
+        case AAUDIO_STREAM_STATE_CLOSING:
+        case AAUDIO_STREAM_STATE_CLOSED:
+        default:
+            ALOGW("safePause() stream not running, state = %s",
+                  AAudio_convertStreamStateToText(getState()));
+            return AAUDIO_ERROR_INVALID_STATE;
+    }
+
     return requestPause();
 }
 
 aaudio_result_t AudioStream::safeFlush() {
+    if (!isFlushSupported()) {
+        ALOGE("flush not supported for this stream");
+        return AAUDIO_ERROR_UNIMPLEMENTED;
+    }
+
     std::lock_guard<std::mutex> lock(mStreamLock);
     if (collidesWithCallback()) {
-        ALOGE("%s cannot be called from a callback!", __func__);
+        ALOGE("stream cannot be flushed from a callback!");
         return AAUDIO_ERROR_INVALID_STATE;
     }
+
+    aaudio_result_t result = AAudio_isFlushAllowed(getState());
+    if (result != AAUDIO_OK) {
+        return result;
+    }
+
     return requestFlush();
 }
 
 aaudio_result_t AudioStream::safeStop() {
     std::lock_guard<std::mutex> lock(mStreamLock);
     if (collidesWithCallback()) {
-        ALOGE("%s cannot be called from a callback!", __func__);
+        ALOGE("stream cannot be stopped from a callback!");
         return AAUDIO_ERROR_INVALID_STATE;
     }
+
+    switch (getState()) {
+        // Proceed with stopping.
+        case AAUDIO_STREAM_STATE_STARTING:
+        case AAUDIO_STREAM_STATE_STARTED:
+        case AAUDIO_STREAM_STATE_DISCONNECTED:
+            break;
+
+        // Transition from one inactive state to another.
+        case AAUDIO_STREAM_STATE_OPEN:
+        case AAUDIO_STREAM_STATE_PAUSED:
+        case AAUDIO_STREAM_STATE_FLUSHED:
+            setState(AAUDIO_STREAM_STATE_STOPPED);
+            return AAUDIO_OK;
+
+        // Redundant?
+        case AAUDIO_STREAM_STATE_STOPPING:
+        case AAUDIO_STREAM_STATE_STOPPED:
+            return AAUDIO_OK;
+
+        // Don't interfere with transitional states or when closed.
+        case AAUDIO_STREAM_STATE_PAUSING:
+        case AAUDIO_STREAM_STATE_FLUSHING:
+        case AAUDIO_STREAM_STATE_CLOSING:
+        case AAUDIO_STREAM_STATE_CLOSED:
+        default:
+            ALOGW("requestStop() stream not running, state = %s",
+                  AAudio_convertStreamStateToText(getState()));
+            return AAUDIO_ERROR_INVALID_STATE;
+    }
+
     return requestStop();
 }
 
@@ -238,6 +315,7 @@
     if (err != 0) {
         return AAudioConvert_androidToAAudioResult(-errno);
     } else {
+        // TODO Use AAudioThread or maybe AndroidThread
         // Name the thread with an increasing index, "AAudio_#", for debugging.
         static std::atomic<uint32_t> nextThreadIndex{1};
         char name[16]; // max length for a pthread_name
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h
index c59ee6c..c0db0f9 100644
--- a/media/libaaudio/src/core/AudioStream.h
+++ b/media/libaaudio/src/core/AudioStream.h
@@ -73,6 +73,24 @@
      */
     virtual aaudio_result_t requestStart() = 0;
 
+    /**
+     * Check the state to see if Pause if currently legal.
+     *
+     * @param result pointer to return code
+     * @return true if OK to continue, if false then return result
+     */
+    bool checkPauseStateTransition(aaudio_result_t *result);
+
+    virtual bool isFlushSupported() const {
+        // Only implement FLUSH for OUTPUT streams.
+        return false;
+    }
+
+    virtual bool isPauseSupported() const {
+        // Only implement PAUSE for OUTPUT streams.
+        return false;
+    }
+
     virtual aaudio_result_t requestPause()
     {
         // Only implement this for OUTPUT streams.
@@ -341,11 +359,13 @@
         return mPlayerBase->getResult();
     }
 
+    // Pass pause request through PlayerBase for tracking.
     aaudio_result_t systemPause() {
         mPlayerBase->pause();
         return mPlayerBase->getResult();
     }
 
+    // Pass stop request through PlayerBase for tracking.
     aaudio_result_t systemStop() {
         mPlayerBase->stop();
         return mPlayerBase->getResult();
@@ -452,7 +472,14 @@
     }
 
     void setState(aaudio_stream_state_t state) {
-        mState = state;
+        if (mState == AAUDIO_STREAM_STATE_CLOSED) {
+            ; // CLOSED is a final state
+        } else if (mState == AAUDIO_STREAM_STATE_DISCONNECTED
+                && state != AAUDIO_STREAM_STATE_CLOSED) {
+            ; // Once DISCONNECTED, we can only move to CLOSED state.
+        } else {
+            mState = state;
+        }
     }
 
     void setDeviceId(int32_t deviceId) {
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.cpp b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
index 4d1b187..ee069ee 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
@@ -287,13 +287,8 @@
     if (mAudioTrack.get() == nullptr) {
         ALOGE("requestPause() no AudioTrack");
         return AAUDIO_ERROR_INVALID_STATE;
-    } else if (getState() != AAUDIO_STREAM_STATE_STARTING
-            && getState() != AAUDIO_STREAM_STATE_STARTED) {
-            // TODO What about DISCONNECTED?
-        ALOGE("requestPause(), called when state is %s",
-              AAudio_convertStreamStateToText(getState()));
-        return AAUDIO_ERROR_INVALID_STATE;
     }
+
     setState(AAUDIO_STREAM_STATE_PAUSING);
     mAudioTrack->pause();
     mCallbackEnabled.store(false);
@@ -308,10 +303,8 @@
     if (mAudioTrack.get() == nullptr) {
         ALOGE("requestFlush() no AudioTrack");
         return AAUDIO_ERROR_INVALID_STATE;
-    } else if (getState() != AAUDIO_STREAM_STATE_PAUSED) {
-        ALOGE("requestFlush() not paused");
-        return AAUDIO_ERROR_INVALID_STATE;
     }
+
     setState(AAUDIO_STREAM_STATE_FLUSHING);
     incrementFramesRead(getFramesWritten() - getFramesRead());
     mAudioTrack->flush();
@@ -325,6 +318,7 @@
         ALOGE("requestStop() no AudioTrack");
         return AAUDIO_ERROR_INVALID_STATE;
     }
+
     setState(AAUDIO_STREAM_STATE_STOPPING);
     incrementFramesRead(getFramesWritten() - getFramesRead()); // TODO review
     mTimestampPosition.set(getFramesWritten());
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.h b/media/libaaudio/src/legacy/AudioStreamTrack.h
index a871db4..68608de 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.h
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.h
@@ -48,6 +48,16 @@
     aaudio_result_t requestFlush() override;
     aaudio_result_t requestStop() override;
 
+    bool isFlushSupported() const override {
+        // Only implement FLUSH for OUTPUT streams.
+        return true;
+    }
+
+    bool isPauseSupported() const override {
+        // Only implement PAUSE for OUTPUT streams.
+        return true;
+    }
+
     aaudio_result_t getTimestamp(clockid_t clockId,
                                        int64_t *framePosition,
                                        int64_t *timeNanoseconds) override;
diff --git a/media/libaaudio/src/utility/AAudioUtilities.cpp b/media/libaaudio/src/utility/AAudioUtilities.cpp
index 2a34016..adc4904 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.cpp
+++ b/media/libaaudio/src/utility/AAudioUtilities.cpp
@@ -441,3 +441,31 @@
     }
     return prop;
 }
+
+aaudio_result_t AAudio_isFlushAllowed(aaudio_stream_state_t state) {
+    aaudio_result_t result = AAUDIO_OK;
+    switch (state) {
+// Proceed with flushing.
+        case AAUDIO_STREAM_STATE_OPEN:
+        case AAUDIO_STREAM_STATE_PAUSED:
+        case AAUDIO_STREAM_STATE_STOPPED:
+        case AAUDIO_STREAM_STATE_FLUSHED:
+            break;
+
+// Transition from one inactive state to another.
+        case AAUDIO_STREAM_STATE_STARTING:
+        case AAUDIO_STREAM_STATE_STARTED:
+        case AAUDIO_STREAM_STATE_STOPPING:
+        case AAUDIO_STREAM_STATE_PAUSING:
+        case AAUDIO_STREAM_STATE_FLUSHING:
+        case AAUDIO_STREAM_STATE_CLOSING:
+        case AAUDIO_STREAM_STATE_CLOSED:
+        case AAUDIO_STREAM_STATE_DISCONNECTED:
+        default:
+            ALOGE("can only flush stream when PAUSED, OPEN or STOPPED, state = %s",
+                  AAudio_convertStreamStateToText(state));
+            result =  AAUDIO_ERROR_INVALID_STATE;
+            break;
+    }
+    return result;
+}
diff --git a/media/libaaudio/src/utility/AAudioUtilities.h b/media/libaaudio/src/utility/AAudioUtilities.h
index d3a2ae9..3673c34 100644
--- a/media/libaaudio/src/utility/AAudioUtilities.h
+++ b/media/libaaudio/src/utility/AAudioUtilities.h
@@ -268,6 +268,14 @@
  */
 int32_t AAudioProperty_getHardwareBurstMinMicros();
 
+
+/**
+ * Is flush allowed for the given state?
+ * @param state
+ * @return AAUDIO_OK if allowed or an error
+ */
+aaudio_result_t AAudio_isFlushAllowed(aaudio_stream_state_t state);
+
 /**
  * Try a function f until it returns true.
  *