oboe: lock waitForStateChange for AAudio

Call AAudio with timeout zero to avoid blocking.
Prevent crashes if close() called from another thread.

Possible fix for #406
diff --git a/src/aaudio/AudioStreamAAudio.cpp b/src/aaudio/AudioStreamAAudio.cpp
index b8c4394..af43218 100644
--- a/src/aaudio/AudioStreamAAudio.cpp
+++ b/src/aaudio/AudioStreamAAudio.cpp
@@ -20,6 +20,7 @@
 
 #include "aaudio/AAudioLoader.h"
 #include "aaudio/AudioStreamAAudio.h"
+#include "common/AudioClock.h"
 #include "common/OboeDebug.h"
 #include "oboe/Utilities.h"
 
@@ -370,25 +371,75 @@
     }
 }
 
+
+// AAudioStream_waitForStateChange() can crash if it is waiting on a stream and that stream
+// is closed from another thread.  We do not want to lock the stream for the duration of the call.
+// So we call AAudioStream_waitForStateChange() with a timeout of zero so that it will not block.
+// Then we can do our own sleep with the lock unlocked.
 Result AudioStreamAAudio::waitForStateChange(StreamState currentState,
                                         StreamState *nextState,
                                         int64_t timeoutNanoseconds) {
-    AAudioStream *stream = mAAudioStream.load();
-    if (stream != nullptr) {
+    Result oboeResult = (timeoutNanoseconds <= 0) ? Result::OK : Result::ErrorTimeout;
+    int64_t durationNanos = 20 * kNanosPerMillisecond; // arbitrary
+    aaudio_stream_state_t currentAAudioState = static_cast<aaudio_stream_state_t>(currentState);
 
-        aaudio_stream_state_t aaudioNextState;
-        aaudio_result_t result = mLibLoader->stream_waitForStateChange(
-                        mAAudioStream,
-                        static_cast<aaudio_stream_state_t>(currentState),
-                        &aaudioNextState,
-                        timeoutNanoseconds);
-        *nextState = static_cast<StreamState>(aaudioNextState);
-        return static_cast<Result>(result);
-    } else {
-        *nextState = StreamState::Closed;
+    aaudio_stream_state_t aaudioNextState;
+    aaudio_result_t result = AAUDIO_OK;
+    int64_t timeLeftNanos = timeoutNanoseconds;
+
+    mLock.lock();
+    while (true) {
+        // Do we still have an AAudio stream? If not then stream must have been closed.
+        AAudioStream *stream = mAAudioStream.load();
+        if (stream == nullptr) {
+            if (nextState != nullptr) {
+                *nextState = StreamState::Closed;
+            }
+            oboeResult = Result::ErrorClosed;
+            break;
+        }
+
+        // Update and query state change with no blocking.
+        result = mLibLoader->stream_waitForStateChange(
+                mAAudioStream,
+                currentAAudioState,
+                &aaudioNextState,
+                0); // timeout=0 for non-blocking
+        if (nextState != nullptr) {
+            *nextState = static_cast<StreamState>(aaudioNextState);
+        }
+        if (result != AAUDIO_OK) {
+            if (result == AAUDIO_ERROR_TIMEOUT) {
+                // bug in AAudio pre-Q caused it to return this
+                result = AAUDIO_OK;
+            } else {
+                LOGD("%s() stream_waitForStateChange returned %d", __func__, result);
+                oboeResult = static_cast<Result>(result);
+                break;
+            }
+        }
+        if (currentAAudioState != aaudioNextState) { // state changed?
+            oboeResult = Result::OK;
+            break;
+        }
+
+        // Did we timeout or did user ask for non-blocking?
+        if (timeLeftNanos <= 0) {
+            break;
+        }
+
+        // No change yet so sleep.
+        mLock.unlock(); // Don't sleep while locked.
+        if (durationNanos > timeLeftNanos) {
+            durationNanos = timeLeftNanos; // last little bit
+        }
+        AudioClock::sleepForNanos(durationNanos);
+        timeLeftNanos -= durationNanos;
+        mLock.lock();
     }
 
-    return (currentState != *nextState) ? Result::OK : Result::ErrorTimeout;
+    mLock.unlock();
+    return oboeResult;
 }
 
 ResultWithValue<int32_t> AudioStreamAAudio::setBufferSizeInFrames(int32_t requestedFrames) {
diff --git a/src/common/AudioStream.cpp b/src/common/AudioStream.cpp
index 5dfafb1..63f3c44 100644
--- a/src/common/AudioStream.cpp
+++ b/src/common/AudioStream.cpp
@@ -75,9 +75,13 @@
                                            StreamState endingState,
                                            int64_t timeoutNanoseconds)
 {
-    StreamState state = getState();
-    if (state == StreamState::Closed) {
-        return Result::ErrorClosed;
+    StreamState state;
+    {
+        std::lock_guard<std::mutex> lock(mLock);
+        state = getState();
+        if (state == StreamState::Closed || state == StreamState::Disconnected) {
+            return Result::ErrorClosed;
+        }
     }
 
     StreamState nextState = state;
@@ -87,6 +91,7 @@
             return result;
         }
     }
+
     if (nextState != endingState) {
         return Result::ErrorInvalidState;
     } else {
@@ -98,6 +103,7 @@
 {
     Result result = requestStart();
     if (result != Result::OK) return result;
+    if (timeoutNanoseconds <= 0) return result;
     return waitForStateTransition(StreamState::Starting,
                                   StreamState::Started, timeoutNanoseconds);
 }
@@ -106,6 +112,7 @@
 {
     Result result = requestPause();
     if (result != Result::OK) return result;
+    if (timeoutNanoseconds <= 0) return result;
     return waitForStateTransition(StreamState::Pausing,
                                   StreamState::Paused, timeoutNanoseconds);
 }
@@ -114,6 +121,7 @@
 {
     Result result = requestFlush();
     if (result != Result::OK) return result;
+    if (timeoutNanoseconds <= 0) return result;
     return waitForStateTransition(StreamState::Flushing,
                                   StreamState::Flushed, timeoutNanoseconds);
 }
@@ -122,6 +130,7 @@
 {
     Result result = requestStop();
     if (result != Result::OK) return result;
+    if (timeoutNanoseconds <= 0) return result;
     return waitForStateTransition(StreamState::Stopping,
                                   StreamState::Stopped, timeoutNanoseconds);
 }