Refactor AudioStream methods to handle closed state
diff --git a/include/oboe/AudioStream.h b/include/oboe/AudioStream.h
index 46a496e..cb54a1c 100644
--- a/include/oboe/AudioStream.h
+++ b/include/oboe/AudioStream.h
@@ -140,11 +140,16 @@
     }
 
     /**
+     * @return true if XRun counts are supported on the stream
+     */
+    virtual bool isXRunCountSupported() const = 0;
+
+    /**
      * Query the number of frames that are read or written by the endpoint at one time.
      *
      * @return burst size
      */
-    virtual int32_t getFramesPerBurst() const = 0;
+    virtual int32_t getFramesPerBurst() = 0;
 
     bool isPlaying();
 
@@ -156,9 +161,9 @@
      * This monotonic counter will never get reset.
      * @return the number of frames written so far
      */
-    virtual int64_t getFramesWritten() const { return mFramesWritten; }
+    virtual int64_t getFramesWritten() { return mFramesWritten; }
 
-    virtual int64_t getFramesRead() const { return mFramesRead; }
+    virtual int64_t getFramesRead() { return mFramesRead; }
 
     /**
      * Calculate the latency of a stream based on getTimestamp().
@@ -282,10 +287,12 @@
     // These do not change after open.
     AudioFormat mNativeFormat = AudioFormat::Invalid;
 
-private:
     // TODO these should be atomic like in AAudio
     int64_t              mFramesWritten = 0;
     int64_t              mFramesRead = 0;
+
+private:
+    // TODO these should be atomic like in AAudio
     int                  mPreviousScheduler = -1;
 };
 
diff --git a/include/oboe/AudioStreamBase.h b/include/oboe/AudioStreamBase.h
index 230aadb..9b38fa5 100644
--- a/include/oboe/AudioStreamBase.h
+++ b/include/oboe/AudioStreamBase.h
@@ -71,13 +71,11 @@
 
     /**
      * Query the maximum number of frames that can be filled without blocking.
+     * If the stream has been closed the last known value will be returned.
      *
-     * @return buffer size or a negative error.
+     * @return buffer size
      */
-    virtual int32_t getBufferSizeInFrames() const {
-        // By default assume the effective size is the same as capacity.
-        return getBufferCapacityInFrames();
-    }
+    virtual int32_t getBufferSizeInFrames() { return mBufferSizeInFrames; };
 
     /**
      * @return capacityInFrames or OBOE_UNSPECIFIED
@@ -109,6 +107,8 @@
     int32_t                         mSampleRate = kUnspecified;
     int32_t                         mDeviceId = kUnspecified;
     int32_t                         mBufferCapacityInFrames = kUnspecified;
+    int32_t                         mBufferSizeInFrames = kUnspecified;
+    int32_t                         mFramesPerBurst = kUnspecified;
 
     SharingMode                     mSharingMode = SharingMode::Shared;
     AudioFormat                     mFormat = AudioFormat::Unspecified;
diff --git a/include/oboe/Definitions.h b/include/oboe/Definitions.h
index 7724f5d..1637981 100644
--- a/include/oboe/Definitions.h
+++ b/include/oboe/Definitions.h
@@ -96,6 +96,7 @@
         ErrorOutOfRange = AAUDIO_ERROR_OUT_OF_RANGE,
         ErrorNoService = AAUDIO_ERROR_NO_SERVICE,
         ErrorInvalidRate = AAUDIO_ERROR_INVALID_RATE,
+        ErrorClosed,
     };
 
     enum class SharingMode : aaudio_sharing_mode_t {
diff --git a/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp b/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp
index 3dd58c1..1647380 100644
--- a/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp
+++ b/samples/hello-oboe/src/main/cpp/PlayAudioEngine.cpp
@@ -163,8 +163,8 @@
     if (mBufferSizeSelection == kBufferSizeAutomatic) {
         mLatencyTuner->tune();
     } else if (bufferSize != (mBufferSizeSelection * mFramesPerBurst)) {
-        audioStream->setBufferSizeInFrames(mBufferSizeSelection * mFramesPerBurst);
-        bufferSize = audioStream->getBufferSizeInFrames();
+        auto setBufferResult = audioStream->setBufferSizeInFrames(mBufferSizeSelection * mFramesPerBurst);
+        if (setBufferResult == oboe::Result::OK) bufferSize = setBufferResult.value();
     }
 
     /**
diff --git a/src/aaudio/AudioStreamAAudio.cpp b/src/aaudio/AudioStreamAAudio.cpp
index dd29f9b..8ea76ff 100644
--- a/src/aaudio/AudioStreamAAudio.cpp
+++ b/src/aaudio/AudioStreamAAudio.cpp
@@ -164,7 +164,7 @@
         goto error2;
     }
 
-    // Query and cache the values that will not change.
+    // Query and cache the stream properties
     mDeviceId = mLibLoader->stream_getDeviceId(mAAudioStream);
     mChannelCount = mLibLoader->stream_getChannelCount(mAAudioStream);
     mSampleRate = mLibLoader->stream_getSampleRate(mAAudioStream);
@@ -176,6 +176,8 @@
     mPerformanceMode = static_cast<PerformanceMode>(
             mLibLoader->stream_getPerformanceMode(mAAudioStream));
     mBufferCapacityInFrames = mLibLoader->stream_getBufferCapacity(mAAudioStream);
+    mBufferSizeInFrames = mLibLoader->stream_getBufferSize(mAAudioStream);
+
 
     // These were added in P so we have to check for the function pointer.
     if (mLibLoader->stream_getUsage != nullptr) {
@@ -213,13 +215,14 @@
     // is being executed because of a disconnect. The close will delete the stream,
     // which could otherwise cause the requestStop() to crash.
     std::lock_guard<std::mutex> lock(mLock);
-    Result result = Result::OK;
+
     // This will delete the AAudio stream object so we need to null out the pointer.
     AAudioStream *stream = mAAudioStream.exchange(nullptr);
     if (stream != nullptr) {
-        result = static_cast<Result>(mLibLoader->stream_close(stream));
+        return static_cast<Result>(mLibLoader->stream_close(stream));
+    } else {
+        return Result::ErrorClosed;
     }
-    return result;
 }
 
 DataCallbackResult AudioStreamAAudio::callOnAudioReady(AAudioStream *stream,
@@ -268,7 +271,7 @@
     if (stream != nullptr) {
         return static_cast<Result>(mLibLoader->stream_requestStart(stream));
     } else {
-        return Result::ErrorNull;
+        return Result::ErrorClosed;
     }
 }
 
@@ -278,7 +281,7 @@
     if (stream != nullptr) {
         return static_cast<Result>(mLibLoader->stream_requestPause(stream));
     } else {
-        return Result::ErrorNull;
+        return Result::ErrorClosed;
     }
 }
 
@@ -288,7 +291,7 @@
     if (stream != nullptr) {
         return static_cast<Result>(mLibLoader->stream_requestFlush(stream));
     } else {
-        return Result::ErrorNull;
+        return Result::ErrorClosed;
     }
 }
 
@@ -298,7 +301,7 @@
     if (stream != nullptr) {
         return static_cast<Result>(mLibLoader->stream_requestStop(stream));
     } else {
-        return Result::ErrorNull;
+        return Result::ErrorClosed;
     }
 }
 
@@ -343,7 +346,7 @@
         *nextState = static_cast<StreamState>(aaudioNextState);
         return static_cast<Result>(result);
     } else {
-        return Result::ErrorNull;
+        return Result::ErrorClosed;
     }
 }
 
@@ -352,6 +355,10 @@
         requestedFrames = mBufferCapacityInFrames;
     }
     int32_t newBufferSize = mLibLoader->stream_setBufferSize(mAAudioStream, requestedFrames);
+
+    // Cache the result if it's valid
+    if (newBufferSize > 0) mBufferSizeInFrames = newBufferSize;
+
     return ResultWithValue<int32_t>::createBasedOnSign(newBufferSize);
 }
 
@@ -364,40 +371,36 @@
     }
 }
 
-int32_t AudioStreamAAudio::getBufferSizeInFrames() const {
+int32_t AudioStreamAAudio::getBufferSizeInFrames() {
     AAudioStream *stream = mAAudioStream.load();
     if (stream != nullptr) {
-        return mLibLoader->stream_getBufferSize(stream);
-    } else {
-        return static_cast<int32_t>(Result::ErrorNull);
+        mBufferSizeInFrames = mLibLoader->stream_getBufferSize(stream);
     }
+    return mBufferSizeInFrames;
 }
 
-int32_t AudioStreamAAudio::getFramesPerBurst() const {
+int32_t AudioStreamAAudio::getFramesPerBurst() {
     AAudioStream *stream = mAAudioStream.load();
     if (stream != nullptr) {
-        return mLibLoader->stream_getFramesPerBurst(stream);
-    } else {
-        return static_cast<int32_t>(Result::ErrorNull);
+        mFramesPerBurst = mLibLoader->stream_getFramesPerBurst(stream);
     }
+    return mFramesPerBurst;
 }
 
-int64_t AudioStreamAAudio::getFramesRead() const {
+int64_t AudioStreamAAudio::getFramesRead() {
     AAudioStream *stream = mAAudioStream.load();
     if (stream != nullptr) {
-        return mLibLoader->stream_getFramesRead(stream);
-    } else {
-        return static_cast<int32_t>(Result::ErrorNull);
+        mFramesRead = mLibLoader->stream_getFramesRead(stream);
     }
+    return mFramesRead;
 }
 
-int64_t AudioStreamAAudio::getFramesWritten() const {
+int64_t AudioStreamAAudio::getFramesWritten() {
     AAudioStream *stream = mAAudioStream.load();
     if (stream != nullptr) {
-        return mLibLoader->stream_getFramesWritten(stream);
-    } else {
-        return static_cast<int64_t>(Result::ErrorNull);
+        mFramesWritten = mLibLoader->stream_getFramesWritten(stream);
     }
+    return mFramesWritten;
 }
 
 ResultWithValue<int32_t> AudioStreamAAudio::getXRunCount() const {
diff --git a/src/aaudio/AudioStreamAAudio.h b/src/aaudio/AudioStreamAAudio.h
index 3752e91..5f5aeff 100644
--- a/src/aaudio/AudioStreamAAudio.h
+++ b/src/aaudio/AudioStreamAAudio.h
@@ -69,12 +69,13 @@
                  int64_t timeoutNanoseconds) override;
 
     ResultWithValue<int32_t> setBufferSizeInFrames(int32_t requestedFrames) override;
-    int32_t getBufferSizeInFrames() const override;
-    int32_t getFramesPerBurst() const override;
+    int32_t getBufferSizeInFrames() override;
+    int32_t getFramesPerBurst() override;
     ResultWithValue<int32_t> getXRunCount() const override;
+    bool isXRunCountSupported() const override { return true; }
 
-    int64_t getFramesRead() const override;
-    int64_t getFramesWritten() const override;
+    int64_t getFramesRead() override;
+    int64_t getFramesWritten() override;
 
     ResultWithValue<double> calculateLatencyMillis() override;
 
diff --git a/src/common/AudioStream.cpp b/src/common/AudioStream.cpp
index 1c468dd..42a1f97 100644
--- a/src/common/AudioStream.cpp
+++ b/src/common/AudioStream.cpp
@@ -65,17 +65,21 @@
                                            int64_t timeoutNanoseconds)
 {
     StreamState state = getState();
-    StreamState nextState = state;
-    if (state == startingState && state != endingState) {
-        Result result = waitForStateChange(state, &nextState, timeoutNanoseconds);
-        if (result != Result::OK) {
-            return result;
-        }
-    }
-    if (nextState != endingState) {
-        return Result::ErrorInvalidState;
+    if (state == StreamState::Closed) {
+        return Result::ErrorClosed;
     } else {
-        return Result::OK;
+        StreamState nextState = state;
+        if (state == startingState && state != endingState) {
+            Result result = waitForStateChange(state, &nextState, timeoutNanoseconds);
+            if (result != Result::OK) {
+                return result;
+            }
+        }
+        if (nextState != endingState) {
+            return Result::ErrorInvalidState;
+        } else {
+            return Result::OK;
+        }
     }
 }
 
diff --git a/src/opensles/AudioInputStreamOpenSLES.cpp b/src/opensles/AudioInputStreamOpenSLES.cpp
index 469f002..424cf8b 100644
--- a/src/opensles/AudioInputStreamOpenSLES.cpp
+++ b/src/opensles/AudioInputStreamOpenSLES.cpp
@@ -182,50 +182,70 @@
 }
 
 Result AudioInputStreamOpenSLES::close() {
-    requestStop();
-    mRecordInterface = NULL;
-    return AudioStreamOpenSLES::close();
+
+    if (mState == StreamState::Closed){
+        return Result::ErrorClosed;
+    } else {
+        requestStop();
+        mRecordInterface = NULL;
+        return AudioStreamOpenSLES::close();
+    }
 }
 
 Result AudioInputStreamOpenSLES::setRecordState(SLuint32 newState) {
-    Result result = Result::OK;
-    LOGD("AudioInputStreamOpenSLES::setRecordState(%d)", newState);
-    if (mRecordInterface == nullptr) {
-        LOGE("AudioInputStreamOpenSLES::SetRecordState() mRecordInterface is null");
-        return Result::ErrorInvalidState;
-    }
-    SLresult slResult = (*mRecordInterface)->SetRecordState(mRecordInterface, newState);
-    if (SL_RESULT_SUCCESS != slResult) {
-        LOGE("AudioInputStreamOpenSLES::SetRecordState() returned %s", getSLErrStr(slResult));
-        result = Result::ErrorInvalidState; // TODO review
+
+    if (mState == StreamState::Closed){
+        return Result::ErrorClosed;
     } else {
-        setState(StreamState::Pausing);
+        Result result = Result::OK;
+        LOGD("AudioInputStreamOpenSLES::setRecordState(%d)", newState);
+        if (mRecordInterface == nullptr) {
+            LOGE("AudioInputStreamOpenSLES::SetRecordState() mRecordInterface is null");
+            return Result::ErrorInvalidState;
+        }
+        SLresult slResult = (*mRecordInterface)->SetRecordState(mRecordInterface, newState);
+        if (SL_RESULT_SUCCESS != slResult) {
+            LOGE("AudioInputStreamOpenSLES::SetRecordState() returned %s", getSLErrStr(slResult));
+            result = Result::ErrorInvalidState; // TODO review
+        } else {
+            setState(StreamState::Pausing);
+        }
+        return result;
     }
-    return result;
 }
 
 Result AudioInputStreamOpenSLES::requestStart() {
     LOGD("AudioInputStreamOpenSLES::requestStart()");
-    Result result = setRecordState(SL_RECORDSTATE_RECORDING);
-    if (result == Result::OK) {
-        // Enqueue the first buffer so that we have data ready in the callback.
-        enqueueCallbackBuffer(mSimpleBufferQueueInterface);
-        setState(StreamState::Starting);
+
+    if (mState == StreamState::Closed){
+        return Result::ErrorClosed;
+    } else {
+        Result result = setRecordState(SL_RECORDSTATE_RECORDING);
+        if (result == Result::OK) {
+            // Enqueue the first buffer so that we have data ready in the callback.
+            enqueueCallbackBuffer(mSimpleBufferQueueInterface);
+            setState(StreamState::Starting);
+        }
+        return result;
     }
-    return result;
 }
 
 
 Result AudioInputStreamOpenSLES::requestPause() {
-    LOGD("AudioInputStreamOpenSLES::requestStop()");
-    Result result = setRecordState(SL_RECORDSTATE_PAUSED);
-    if (result != Result::OK) {
-        result = Result::ErrorInvalidState; // TODO review
+
+    if (mState == StreamState::Closed){
+        return Result::ErrorClosed;
     } else {
-        setState(StreamState::Pausing);
-        mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when paused.
+        LOGD("AudioInputStreamOpenSLES::requestStop()");
+        Result result = setRecordState(SL_RECORDSTATE_PAUSED);
+        if (result != Result::OK) {
+            result = Result::ErrorInvalidState; // TODO review
+        } else {
+            setState(StreamState::Pausing);
+            mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when paused.
+        }
+        return result;
     }
-    return result;
 }
 
 Result AudioInputStreamOpenSLES::requestFlush() {
@@ -234,17 +254,22 @@
 
 Result AudioInputStreamOpenSLES::requestStop() {
     LOGD("AudioInputStreamOpenSLES::requestStop()");
-    Result result = setRecordState(SL_RECORDSTATE_STOPPED);
-    if (result != Result::OK) {
-        result = Result::ErrorInvalidState; // TODO review
+
+    if (mState == StreamState::Closed){
+        return Result::ErrorClosed;
     } else {
-        setState(StreamState::Stopping);
-        mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped.
+        Result result = setRecordState(SL_RECORDSTATE_STOPPED);
+        if (result != Result::OK) {
+            result = Result::ErrorInvalidState; // TODO review
+        } else {
+            setState(StreamState::Stopping);
+            mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped.
+        }
+        return result;
     }
-    return result;
 }
 
-int64_t AudioInputStreamOpenSLES::getFramesWritten() const {
+int64_t AudioInputStreamOpenSLES::getFramesWritten() {
     return getFramesProcessedByServer();
 }
 
@@ -252,7 +277,9 @@
                                                     StreamState *nextState,
                                                     int64_t timeoutNanoseconds) {
     LOGD("AudioInputStreamOpenSLES::waitForStateChange()");
-    if (mRecordInterface == NULL) {
+    if (getState() == StreamState::Closed){
+        return Result::ErrorClosed;
+    } else if (mRecordInterface == NULL) {
         return Result::ErrorInvalidState;
     }
     return Result::ErrorUnimplemented; // TODO
diff --git a/src/opensles/AudioInputStreamOpenSLES.h b/src/opensles/AudioInputStreamOpenSLES.h
index 0cd95e4..7410c4b 100644
--- a/src/opensles/AudioInputStreamOpenSLES.h
+++ b/src/opensles/AudioInputStreamOpenSLES.h
@@ -49,7 +49,7 @@
                               StreamState *nextState,
                               int64_t timeoutNanoseconds) override;
 
-    int64_t getFramesWritten() const override;
+    int64_t getFramesWritten() override;
 
 protected:
 
diff --git a/src/opensles/AudioOutputStreamOpenSLES.cpp b/src/opensles/AudioOutputStreamOpenSLES.cpp
index ebeacf9..8316bd8 100644
--- a/src/opensles/AudioOutputStreamOpenSLES.cpp
+++ b/src/opensles/AudioOutputStreamOpenSLES.cpp
@@ -173,78 +173,112 @@
 }
 
 Result AudioOutputStreamOpenSLES::close() {
-    requestPause();
-    // invalidate any interfaces
-    mPlayInterface = NULL;
-    return AudioStreamOpenSLES::close();
+
+    if (mState == StreamState::Closed){
+        return Result::ErrorClosed;
+    } else {
+        requestPause();
+        // invalidate any interfaces
+        mPlayInterface = NULL;
+        return AudioStreamOpenSLES::close();
+    }
 }
 
 Result AudioOutputStreamOpenSLES::setPlayState(SLuint32 newState) {
-    Result result = Result::OK;
+
     LOGD("AudioOutputStreamOpenSLES(): setPlayState()");
-    if (mPlayInterface == NULL) {
-        return Result::ErrorInvalidState;
-    }
-    SLresult slResult = (*mPlayInterface)->SetPlayState(mPlayInterface, newState);
-    if(SL_RESULT_SUCCESS != slResult) {
-        LOGD("AudioOutputStreamOpenSLES(): setPlayState() returned %s", getSLErrStr(slResult));
-        result = Result::ErrorInvalidState; // TODO review
+
+    if (mState == StreamState::Closed){
+        return Result::ErrorClosed;
     } else {
-        setState(StreamState::Pausing);
+        Result result = Result::OK;
+        if (mPlayInterface == NULL) {
+            return Result::ErrorInvalidState;
+        }
+        SLresult slResult = (*mPlayInterface)->SetPlayState(mPlayInterface, newState);
+        if (SL_RESULT_SUCCESS != slResult) {
+            LOGD("AudioOutputStreamOpenSLES(): setPlayState() returned %s", getSLErrStr(slResult));
+            result = Result::ErrorInvalidState; // TODO review
+        } else {
+            setState(StreamState::Pausing);
+        }
+        return result;
     }
-    return result;
 }
 
 Result AudioOutputStreamOpenSLES::requestStart() {
+
     LOGD("AudioOutputStreamOpenSLES(): requestStart()");
-    Result result = setPlayState(SL_PLAYSTATE_PLAYING);
-    if(result != Result::OK) {
-        result = Result::ErrorInvalidState; // TODO review
+
+    if (mState == StreamState::Closed){
+        return Result::ErrorClosed;
     } else {
-        processBufferCallback(mSimpleBufferQueueInterface);
-        setState(StreamState::Starting);
+        Result result = setPlayState(SL_PLAYSTATE_PLAYING);
+        if (result != Result::OK) {
+            result = Result::ErrorInvalidState; // TODO review
+        } else {
+            processBufferCallback(mSimpleBufferQueueInterface);
+            setState(StreamState::Starting);
+        }
+        return result;
     }
-    return result;
 }
 
 Result AudioOutputStreamOpenSLES::requestPause() {
+
     LOGD("AudioOutputStreamOpenSLES(): requestPause()");
-    Result result = setPlayState(SL_PLAYSTATE_PAUSED);
-    if(result != Result::OK) {
-        result = Result::ErrorInvalidState; // TODO review
+
+    if (mState == StreamState::Closed){
+        return Result::ErrorClosed;
     } else {
-        setState(StreamState::Pausing);
-        // Note that OpenSL ES does NOT reset its millisecond position when OUTPUT is paused.
-        int64_t framesWritten = getFramesWritten();
-        if (framesWritten >= 0) {
-            setFramesRead(framesWritten);
+        Result result = setPlayState(SL_PLAYSTATE_PAUSED);
+        if (result != Result::OK) {
+            result = Result::ErrorInvalidState; // TODO review
+        } else {
+            setState(StreamState::Pausing);
+            // Note that OpenSL ES does NOT reset its millisecond position when OUTPUT is paused.
+            int64_t framesWritten = getFramesWritten();
+            if (framesWritten >= 0) {
+                setFramesRead(framesWritten);
+            }
         }
+        return result;
     }
-    return result;
 }
 
 Result AudioOutputStreamOpenSLES::requestFlush() {
     LOGD("AudioOutputStreamOpenSLES(): requestFlush()");
-    if (mPlayInterface == NULL) {
-        return Result::ErrorInvalidState;
+
+    if (mState == StreamState::Closed){
+        return Result::ErrorClosed;
+    } else {
+        if (mPlayInterface == NULL) {
+            return Result::ErrorInvalidState;
+        }
+        return Result::ErrorUnimplemented; // TODO
     }
-    return Result::ErrorUnimplemented; // TODO
 }
 
 Result AudioOutputStreamOpenSLES::requestStop() {
     LOGD("AudioOutputStreamOpenSLES(): requestStop()");
-    Result result = setPlayState(SL_PLAYSTATE_STOPPED);
-    if(result != Result::OK) {
-        result = Result::ErrorInvalidState; // TODO review
+
+    if (mState == StreamState::Closed){
+        return Result::ErrorClosed;
     } else {
-        setState(StreamState::Stopping);
-        mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped.
-        int64_t framesWritten = getFramesWritten();
-        if (framesWritten >= 0) {
-            setFramesRead(framesWritten);
+
+        Result result = setPlayState(SL_PLAYSTATE_STOPPED);
+        if (result != Result::OK) {
+            result = Result::ErrorInvalidState; // TODO review
+        } else {
+            setState(StreamState::Stopping);
+            mPositionMillis.reset32(); // OpenSL ES resets its millisecond position when stopped.
+            int64_t framesWritten = getFramesWritten();
+            if (framesWritten >= 0) {
+                setFramesRead(framesWritten);
+            }
         }
+        return result;
     }
-    return result;
 }
 
 void AudioOutputStreamOpenSLES::setFramesRead(int64_t framesRead) {
@@ -252,7 +286,7 @@
     mPositionMillis.set(millisWritten);
 }
 
-int64_t AudioOutputStreamOpenSLES::getFramesRead() const {
+int64_t AudioOutputStreamOpenSLES::getFramesRead() {
     return getFramesProcessedByServer();
 }
 
@@ -260,7 +294,10 @@
                                                StreamState *nextState,
                                                int64_t timeoutNanoseconds) {
     LOGD("AudioOutputStreamOpenSLES::waitForStateChange()");
-    if (mPlayInterface == NULL) {
+
+    if (getState() == StreamState::Closed){
+        return Result::ErrorClosed;
+    } else if (mPlayInterface == NULL) {
         return Result::ErrorInvalidState;
     }
     return Result::ErrorUnimplemented; // TODO
diff --git a/src/opensles/AudioOutputStreamOpenSLES.h b/src/opensles/AudioOutputStreamOpenSLES.h
index 58222da..225fbb0 100644
--- a/src/opensles/AudioOutputStreamOpenSLES.h
+++ b/src/opensles/AudioOutputStreamOpenSLES.h
@@ -49,7 +49,7 @@
                               int64_t timeoutNanoseconds) override;
 
 
-    int64_t getFramesRead() const override;
+    int64_t getFramesRead() override;
 
 protected:
 
diff --git a/src/opensles/AudioStreamBuffered.cpp b/src/opensles/AudioStreamBuffered.cpp
index 76358b1..d1b0054 100644
--- a/src/opensles/AudioStreamBuffered.cpp
+++ b/src/opensles/AudioStreamBuffered.cpp
@@ -44,20 +44,18 @@
     }
 }
 
-int64_t AudioStreamBuffered::getFramesWritten() const {
+int64_t AudioStreamBuffered::getFramesWritten() {
     if (usingFIFO()) {
-        return (int64_t) mFifoBuffer->getWriteCounter();
-    } else {
-        return AudioStream::getFramesWritten();
+        mFramesWritten = (int64_t) mFifoBuffer->getWriteCounter();
     }
+    return mFramesWritten;
 }
 
-int64_t AudioStreamBuffered::getFramesRead() const {
+int64_t AudioStreamBuffered::getFramesRead() {
     if (usingFIFO()) {
-        return (int64_t) mFifoBuffer->getReadCounter();
-    } else {
-        return AudioStream::getFramesRead();
+        mFramesRead = (int64_t) mFifoBuffer->getReadCounter();
     }
+    return mFramesRead;
 }
 
 // This is called by the OpenSL ES callback to read or write the back end of the FIFO.
@@ -96,7 +94,7 @@
 
 // Common code for read/write.
 // @return Result::OK with frames read/written, or Result::Error*
-ResultWithValue<int32_t>  AudioStreamBuffered::transfer(void *buffer,
+ResultWithValue<int32_t> AudioStreamBuffered::transfer(void *buffer,
                                       int32_t numFrames,
                                       int64_t timeoutNanoseconds) {
     // Validate arguments.
@@ -173,11 +171,7 @@
         }
     } while(repeat);
 
-    if (result < 0) {
-        return ResultWithValue<int32_t>(static_cast<Result>(result));
-    } else {
-        return ResultWithValue<int32_t>((numFrames - framesLeft));
-    }
+    return ResultWithValue<int32_t>::createBasedOnSign(result);
 }
 
 // Write to the FIFO so the callback can read from it.
@@ -218,12 +212,12 @@
     }
 }
 
-int32_t AudioStreamBuffered::getBufferSizeInFrames() const {
+int32_t AudioStreamBuffered::getBufferSizeInFrames() {
+
     if (mFifoBuffer != nullptr) {
-        return mFifoBuffer->getThresholdFrames();
-    } else {
-        return AudioStream::getBufferSizeInFrames();
+        mBufferSizeInFrames = mFifoBuffer->getThresholdFrames();
     }
+    return mBufferSizeInFrames;
 }
 
 int32_t AudioStreamBuffered::getBufferCapacityInFrames() const {
diff --git a/src/opensles/AudioStreamBuffered.h b/src/opensles/AudioStreamBuffered.h
index ebb3bc0..6c2effe 100644
--- a/src/opensles/AudioStreamBuffered.h
+++ b/src/opensles/AudioStreamBuffered.h
@@ -47,7 +47,7 @@
 
     ResultWithValue<int32_t> setBufferSizeInFrames(int32_t requestedFrames) override;
 
-    int32_t getBufferSizeInFrames() const override;
+    int32_t getBufferSizeInFrames() override;
 
     int32_t getBufferCapacityInFrames() const override;
 
@@ -55,9 +55,11 @@
         return ResultWithValue<int32_t>(mXRunCount);
     }
 
-    int64_t getFramesWritten() const override;
+    bool isXRunCountSupported() const override { return false; }
 
-    int64_t getFramesRead() const override;
+    int64_t getFramesWritten() override;
+
+    int64_t getFramesRead() override;
 
 protected:
 
diff --git a/src/opensles/AudioStreamOpenSLES.cpp b/src/opensles/AudioStreamOpenSLES.cpp
index 3e7789f..ef96a06 100644
--- a/src/opensles/AudioStreamOpenSLES.cpp
+++ b/src/opensles/AudioStreamOpenSLES.cpp
@@ -176,20 +176,26 @@
 }
 
 Result AudioStreamOpenSLES::close() {
-    onBeforeDestroy();
 
-    if (mObjectInterface != nullptr) {
-        (*mObjectInterface)->Destroy(mObjectInterface);
-        mObjectInterface = nullptr;
+    if (mState == StreamState::Closed){
+        return Result::ErrorClosed;
+    } else {
+        onBeforeDestroy();
 
+        if (mObjectInterface != nullptr) {
+            (*mObjectInterface)->Destroy(mObjectInterface);
+            mObjectInterface = nullptr;
+
+        }
+
+        onAfterDestroy();
+
+        mSimpleBufferQueueInterface = nullptr;
+        EngineOpenSLES::getInstance().close();
+
+        mState = StreamState::Closed;
+        return Result::OK;
     }
-
-    onAfterDestroy();
-
-    mSimpleBufferQueueInterface = nullptr;
-    EngineOpenSLES::getInstance().close();
-
-    return Result::OK;
 }
 
 SLresult AudioStreamOpenSLES::enqueueCallbackBuffer(SLAndroidSimpleBufferQueueItf bq) {
@@ -233,7 +239,7 @@
     return result;
 }
 
-int32_t AudioStreamOpenSLES::getFramesPerBurst() const {
+int32_t AudioStreamOpenSLES::getFramesPerBurst() {
     return mFramesPerBurst;
 }
 
diff --git a/src/opensles/AudioStreamOpenSLES.h b/src/opensles/AudioStreamOpenSLES.h
index e7dd1d4..b1da5c5 100644
--- a/src/opensles/AudioStreamOpenSLES.h
+++ b/src/opensles/AudioStreamOpenSLES.h
@@ -57,7 +57,7 @@
      */
     StreamState getState() override { return mState; }
 
-    int32_t getFramesPerBurst() const override;
+    int32_t getFramesPerBurst() override;
 
 
     AudioApi getAudioApi() const override {
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index bd9b5cf..70ad965 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -15,5 +15,6 @@
 add_executable(testOboe
         testUtilities.cpp
         testStreamClosedMethods.cpp
+        testXRunBehaviour.cpp
         )
 target_link_libraries(testOboe gtest oboe)
\ No newline at end of file
diff --git a/tests/run_tests.sh b/tests/run_tests.sh
index c4a8a81..5feab06 100755
--- a/tests/run_tests.sh
+++ b/tests/run_tests.sh
@@ -39,7 +39,9 @@
 BUILD_DIR=build
 CMAKE=cmake
 TEST_BINARY_FILENAME=testOboe
-REMOTE_DIR=/data/local/tmp/oboe
+REMOTE_TEMP_DIR=/data/local/tmp
+TEST_PACKAGE_NAME=com.google.sample.oboe.liveeffect
+REMOTE_DIR=/data/data/${TEST_PACKAGE_NAME}
 
 # Check prerequisites
 if [ -z "$ANDROID_NDK" ]; then
@@ -102,9 +104,12 @@
 
 
 # Push the test binary to the device and run it
-echo "Pushing test binary to device/emulator"
 adb shell mkdir -p ${REMOTE_DIR}
-adb push ${BUILD_DIR}/${TEST_BINARY_FILENAME} ${REMOTE_DIR}
+adb push ${BUILD_DIR}/${TEST_BINARY_FILENAME} ${REMOTE_TEMP_DIR}
+echo "Pushed ${BUILD_DIR}/${TEST_BINARY_FILENAME} to ${REMOTE_TEMP_DIR}"
+
+echo "Copying ${TEST_BINARY_FILENAME} from ${REMOTE_TEMP_DIR} to ${REMOTE_DIR}"
+adb shell run-as ${TEST_PACKAGE_NAME} cp ${REMOTE_TEMP_DIR}/${TEST_BINARY_FILENAME} ${REMOTE_DIR}
 
 echo "Running test binary"
-adb shell ${REMOTE_DIR}/${TEST_BINARY_FILENAME}
\ No newline at end of file
+adb shell run-as ${TEST_PACKAGE_NAME} ${REMOTE_DIR}/${TEST_BINARY_FILENAME}
\ No newline at end of file
diff --git a/tests/testStreamClosedMethods.cpp b/tests/testStreamClosedMethods.cpp
index 6e88ad1..e148ee5 100644
--- a/tests/testStreamClosedMethods.cpp
+++ b/tests/testStreamClosedMethods.cpp
@@ -14,74 +14,308 @@
  * limitations under the License.
  */
 
+/*
+ * TODO:
+ Create test runner APK
+ Remove hardcoded ANDROID_NDK from cmake config
+ */
 
 #include <gtest/gtest.h>
-#include <oboe/Definitions.h>
-#include <oboe/Utilities.h>
-
-/**
- * Tests needing to be written:
- *
- * getChannelCount
- * getDirection
-getSampleRate
-getFramesPerCallback
-getFormat
-getBufferSizeInFrames
-getBufferCapacityInFrames
-getSharingMode
-getPerformanceMode
-getDeviceId
-getCallback
-getUsage
-getContentType
-getInputPreset
-getSessionId
-getState
-getXRunCount
-getFramesPerBurst
-isPlaying
-getBytesPerFrame
-getBytesPerSample
-getFramesWritten
-getFramesRead
-getTimestamp
-getAudioApi
-usesAAudio
-
-Stream state control
-open
-close
-start
-pause
-flush
-stop
-requestStart
-requestPause
-requestFlush
-requestStop
-waitForStateChange
-
-Setters
-setBufferSizeInFrames
-
-Helpers
-calculateLatencyMillis
-
-I/O
-write
-read
- */
+#include <oboe/Oboe.h>
 
 using namespace oboe;
 
+class MyCallback : public AudioStreamCallback {
+public:
+    DataCallbackResult onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override {
+        return DataCallbackResult::Continue;
+    }
+};
+
+
+
 class StreamClosedReturnValues : public ::testing::Test {
 
+protected:
 
+    void SetUp(){
+
+    }
+
+    bool openStream(){
+        Result r = mBuilder.openStream(&mStream);
+        EXPECT_EQ(r, Result::OK) << "Failed to open stream " << convertToText(r);
+        return (r == Result::OK);
+    }
+
+    void closeStream(){
+        Result r = mStream->close();
+        if (r != Result::OK){
+            FAIL() << "Failed to close stream. " << convertToText(r);
+        }
+    }
+
+    void openAndCloseStream(){
+
+        openStream();
+        closeStream();
+        ASSERT_EQ(mStream->getState(), StreamState::Closed) << "Stream state " << convertToText(mStream->getState());
+    }
+
+    AudioStreamBuilder mBuilder;
+    AudioStream *mStream = nullptr;
 
 };
 
 TEST_F(StreamClosedReturnValues, GetChannelCountReturnsLastKnownValue){
 
-    FAIL();
-}
\ No newline at end of file
+    mBuilder.setChannelCount(2);
+    openAndCloseStream();
+    ASSERT_EQ(mStream->getChannelCount(), 2);
+}
+
+TEST_F(StreamClosedReturnValues, GetDirectionReturnsLastKnownValue){
+
+    // Note that when testing on the emulator setting the direction to Input will result in ErrorInternal when
+    // opening the stream
+    mBuilder.setDirection(Direction::Input);
+    openAndCloseStream();
+    ASSERT_EQ(mStream->getDirection(), Direction::Input);
+}
+
+TEST_F(StreamClosedReturnValues, GetSampleRateReturnsLastKnownValue){
+
+    mBuilder.setSampleRate(8000);
+    openAndCloseStream();
+    ASSERT_EQ(mStream->getSampleRate(), 8000);
+}
+
+TEST_F(StreamClosedReturnValues, GetFramesPerCallbackReturnsLastKnownValue) {
+
+    mBuilder.setFramesPerCallback(192);
+    openAndCloseStream();
+    ASSERT_EQ(mStream->getFramesPerCallback(), 192);
+}
+
+TEST_F(StreamClosedReturnValues, GetFormatReturnsLastKnownValue) {
+
+    mBuilder.setFormat(AudioFormat::I16);
+    openAndCloseStream();
+    ASSERT_EQ(mStream->getFormat(), AudioFormat::I16);
+}
+
+TEST_F(StreamClosedReturnValues, GetBufferSizeInFramesReturnsLastKnownValue) {
+
+    openStream();
+    int32_t bufferSize = mStream->getBufferSizeInFrames();
+    closeStream();
+    ASSERT_EQ(mStream->getBufferSizeInFrames(), bufferSize);
+}
+
+TEST_F(StreamClosedReturnValues, GetBufferCapacityInFramesReturnsLastKnownValue) {
+
+    openStream();
+    int32_t bufferCapacity = mStream->getBufferCapacityInFrames();
+    closeStream();
+    ASSERT_EQ(mStream->getBufferCapacityInFrames(), bufferCapacity);
+}
+
+TEST_F(StreamClosedReturnValues, GetSharingModeReturnsLastKnownValue) {
+
+    openStream();
+    SharingMode s = mStream->getSharingMode();
+    closeStream();
+    ASSERT_EQ(mStream->getSharingMode(), s);
+}
+
+TEST_F(StreamClosedReturnValues, GetPerformanceModeReturnsLastKnownValue) {
+
+    openStream();
+    PerformanceMode p = mStream->getPerformanceMode();
+    closeStream();
+    ASSERT_EQ(mStream->getPerformanceMode(), p);
+}
+
+TEST_F(StreamClosedReturnValues, GetDeviceIdReturnsLastKnownValue) {
+
+    openStream();
+    int32_t d = mStream->getDeviceId();
+    closeStream();
+    ASSERT_EQ(mStream->getDeviceId(), d);
+}
+
+TEST_F(StreamClosedReturnValues, GetCallbackReturnsLastKnownValue) {
+
+    AudioStreamCallback *callback = new MyCallback();
+    mBuilder.setCallback(callback);
+    openAndCloseStream();
+
+    AudioStreamCallback *callback2 = mStream->getCallback();
+    ASSERT_EQ(callback, callback2);
+}
+
+TEST_F(StreamClosedReturnValues, GetUsageReturnsLastKnownValue){
+    openStream();
+    Usage u = mStream->getUsage();
+    closeStream();
+    ASSERT_EQ(mStream->getUsage(), u);
+}
+
+TEST_F(StreamClosedReturnValues, GetContentTypeReturnsLastKnownValue){
+    openStream();
+    ContentType c = mStream->getContentType();
+    closeStream();
+    ASSERT_EQ(mStream->getContentType(), c);
+}
+
+TEST_F(StreamClosedReturnValues, GetInputPresetReturnsLastKnownValue){
+    openStream();
+    auto i = mStream->getInputPreset();
+    closeStream();
+    ASSERT_EQ(mStream->getInputPreset(), i);
+}
+
+TEST_F(StreamClosedReturnValues, GetSessionIdReturnsLastKnownValue){
+    openStream();
+    auto s = mStream->getSessionId();
+    closeStream();
+    ASSERT_EQ(mStream->getSessionId(), s);
+}
+
+TEST_F(StreamClosedReturnValues, StreamStateIsClosed){
+    openAndCloseStream();
+    ASSERT_EQ(mStream->getState(), StreamState::Closed);
+}
+
+TEST_F(StreamClosedReturnValues, GetXRunCountReturnsLastKnownValue){
+
+    openStream();
+    if (mStream->isXRunCountSupported()){
+        auto i = mStream->getXRunCount();
+        ASSERT_EQ(mStream->getXRunCount(), i);
+    }
+    closeStream();
+}
+
+TEST_F(StreamClosedReturnValues, GetFramesPerBurstReturnsLastKnownValue){
+
+    openStream();
+    auto f = mStream->getFramesPerBurst();
+    closeStream();
+    ASSERT_EQ(mStream->getFramesPerBurst(), f);
+}
+
+TEST_F(StreamClosedReturnValues, IsPlayingReturnsFalse){
+
+    openAndCloseStream();
+    ASSERT_FALSE(mStream->isPlaying());
+}
+
+TEST_F(StreamClosedReturnValues, GetBytesPerFrameReturnsLastKnownValue){
+    openStream();
+    auto f = mStream->getBytesPerFrame();
+    closeStream();
+    ASSERT_EQ(mStream->getBytesPerFrame(), f);
+}
+
+TEST_F(StreamClosedReturnValues, GetBytesPerSampleReturnsLastKnownValue){
+    openStream();
+    auto f = mStream->getBytesPerSample();
+    closeStream();
+    ASSERT_EQ(mStream->getBytesPerSample(), f);
+}
+
+TEST_F(StreamClosedReturnValues, GetFramesWrittenReturnsLastKnownValue){
+    mBuilder.setFormat(AudioFormat::I16);
+    mBuilder.setChannelCount(1);
+    openStream();
+    mStream->start();
+
+    int16_t buffer[4] = { 1, 2, 3, 4 };
+    Result r = mStream->write(&buffer, 4, 0);
+    if (r != Result::OK){
+        FAIL() << "Could not write to audio stream";
+    }
+
+    auto f = mStream->getFramesWritten();
+    ASSERT_EQ(f, 4);
+
+    closeStream();
+    ASSERT_EQ(mStream->getFramesWritten(), f);
+}
+
+// TODO: Reading a positive value doesn't work on OpenSL ES in this test - why?
+TEST_F(StreamClosedReturnValues, GetFramesReadReturnsLastKnownValue) {
+
+    mBuilder.setDirection(Direction::Input);
+    mBuilder.setFormat(AudioFormat::I16);
+    mBuilder.setChannelCount(1);
+
+    if (openStream()){
+        mStream->start();
+
+/*
+        int16_t buffer[192];
+        auto r = mStream->read(&buffer, 192, 0);
+        ASSERT_EQ(r.value(), 192);
+*/
+
+        auto f = mStream->getFramesRead();
+//        ASSERT_EQ(f, 192);
+
+        closeStream();
+        ASSERT_EQ(mStream->getFramesRead(), f);
+    };
+}
+
+TEST_F(StreamClosedReturnValues, GetTimestampReturnsErrorClosedIfSupported){
+
+    openStream();
+
+    int64_t framePosition;
+    int64_t presentationTime;
+
+    auto r = mStream->getTimestamp(CLOCK_MONOTONIC, &framePosition, &presentationTime);
+    bool isTimestampSupported = (r == Result::OK);
+
+    closeStream();
+
+    if (isTimestampSupported){
+        ASSERT_EQ(mStream->getTimestamp(CLOCK_MONOTONIC, &framePosition, &presentationTime), Result::ErrorClosed);
+    }
+}
+
+TEST_F(StreamClosedReturnValues, GetAudioApiReturnsLastKnownValue){
+    openStream();
+    AudioApi a = mStream->getAudioApi();
+    closeStream();
+    ASSERT_EQ(mStream->getAudioApi(), a);
+}
+
+TEST_F(StreamClosedReturnValues, GetUsesAAudioReturnsLastKnownValue){
+    openStream();
+    bool a = mStream->usesAAudio();
+    closeStream();
+    ASSERT_EQ(mStream->usesAAudio(), a);
+}
+
+TEST_F(StreamClosedReturnValues, StreamStateControlsReturnClosed){
+
+    openAndCloseStream();
+    EXPECT_EQ(mStream->close(), Result::ErrorClosed);
+    EXPECT_EQ(mStream->start(), Result::ErrorClosed);
+    EXPECT_EQ(mStream->pause(), Result::ErrorClosed);
+    EXPECT_EQ(mStream->flush(), Result::ErrorClosed);
+    EXPECT_EQ(mStream->stop(), Result::ErrorClosed);
+    EXPECT_EQ(mStream->requestStart(), Result::ErrorClosed);
+    EXPECT_EQ(mStream->requestPause(), Result::ErrorClosed);
+    EXPECT_EQ(mStream->requestFlush(), Result::ErrorClosed);
+    EXPECT_EQ(mStream->requestStop(), Result::ErrorClosed);
+}
+
+TEST_F(StreamClosedReturnValues, WaitForStateChangeReturnsClosed){
+
+    openAndCloseStream();
+    StreamState next;
+    ASSERT_EQ(mStream->waitForStateChange(StreamState::Open, &next, 0), Result::ErrorClosed);
+}
diff --git a/tests/testXRunBehaviour.cpp b/tests/testXRunBehaviour.cpp
new file mode 100644
index 0000000..472a5a6
--- /dev/null
+++ b/tests/testXRunBehaviour.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include <gtest/gtest.h>
+#include <oboe/Oboe.h>
+
+using namespace oboe;
+
+class XRunBehaviour : public ::testing::Test {
+
+protected:
+
+    void SetUp(){
+
+    }
+
+
+    void openStream(){
+        Result r = mBuilder.openStream(&mStream);
+        if (r != Result::OK){
+            FAIL() << "Failed to open stream. " << convertToText(r);
+        }
+    }
+
+    void closeStream(){
+        Result r = mStream->close();
+        if (r != Result::OK){
+            FAIL() << "Failed to close stream. " << convertToText(r);
+        }
+    }
+
+    void openAndCloseStream(){
+
+        openStream();
+        closeStream();
+        ASSERT_EQ(mStream->getState(), StreamState::Closed);
+    }
+
+    AudioStreamBuilder mBuilder;
+    AudioStream *mStream = nullptr;
+
+};
+
+// TODO figure out this behaviour - On OpenSLES xRuns are supported withing AudioStreamBuffered, however, these aren't
+// the same as the actual stream underruns
+TEST_F(XRunBehaviour, SupportedWhenStreamIsUsingAAudio){
+
+    openStream();
+    if (mStream->getAudioApi() == AudioApi::AAudio){
+        ASSERT_TRUE(mStream->isXRunCountSupported());
+    }
+}
+
+TEST_F(XRunBehaviour, NotSupportedWhenStreamIsUsingOpenSLES){
+
+    openStream();
+    if (mStream->getAudioApi() == AudioApi::OpenSLES){
+        ASSERT_FALSE(mStream->isXRunCountSupported());
+    }
+}
\ No newline at end of file