Addressing more code reviews
diff --git a/.ci_tools/build_samples.sh b/.ci_tools/build_samples.sh
index 4c300a6..0f28978 100755
--- a/.ci_tools/build_samples.sh
+++ b/.ci_tools/build_samples.sh
@@ -18,12 +18,11 @@
 
 
 # Check the apks that all get built fine
-# TBD: add echo after it is merged
-#        samples/echo/build/outputs/apk/debug/echo-debug.apk
 declare apks=(
     samples/hello-oboe/build/outputs/apk/debug/hello-oboe-debug.apk
     samples/MegaDrone/build/outputs/apk/debug/MegaDrone-debug.apk
     samples/RhythmGame/build/outputs/apk/debug/RhythmGame-debug.apk
+    samples/LiveEffect/build/outputs/apk/debug/LiveEffect-debug.apk
 )
 
 rm -fr ${BUILD_RESULT_FILE}
diff --git a/samples/LiveEffect/README.md b/samples/LiveEffect/README.md
new file mode 100644
index 0000000..f699ccf
--- /dev/null
+++ b/samples/LiveEffect/README.md
@@ -0,0 +1,23 @@
+LiveEffect Sample
+============
+
+This sample simply loops audio from input stream to output stream to demonstrate
+the usage of the 2 stream interfaces.
+
+Screenshots
+-----------
+
+![Screenshot](screenshot.png)
+
+
+### Stream Configurations
+- 48kHz
+- oboe::I16
+- stereo or mono
+
+
+### Caveats
+When first time starting audio devices, the stream may not be stable.
+The symptom is the strange callback pattern. This sample waits half a second
+for audio system to stablize. It is an estimate, it would vary on different platforms.
+
diff --git a/samples/echo/build.gradle b/samples/LiveEffect/build.gradle
similarity index 60%
rename from samples/echo/build.gradle
rename to samples/LiveEffect/build.gradle
index b952cf4..c612f3b 100644
--- a/samples/echo/build.gradle
+++ b/samples/LiveEffect/build.gradle
@@ -1,28 +1,26 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdkVersion 27
+    compileSdkVersion 26
 
     defaultConfig {
-        applicationId 'com.google.sample.oboe.echo'
-        minSdkVersion 24
-        targetSdkVersion 27
+        applicationId 'com.google.sample.oboe.liveeffect'
+        minSdkVersion 16
+        targetSdkVersion 26
         versionCode 1
         versionName '1.0'
         ndk {
-            abiFilters 'armeabi-v7a', 'arm64-v8a'
+            abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
         }
         externalNativeBuild {
             cmake {
-                arguments '-DANDROID_STL=c++_shared', '-DANDROID_TOOLCHAIN=clang',
-                          '-DANDROID_PLATFORM=android-26'
+                arguments '-DANDROID_TOOLCHAIN=clang'
             }
         }
     }
     buildTypes {
         release {
             minifyEnabled false
-            proguardFiles getDefaultProguardFile('proguard-android.txt')
         }
     }
     externalNativeBuild {
diff --git a/samples/LiveEffect/screenshot.png b/samples/LiveEffect/screenshot.png
new file mode 100644
index 0000000..f64a883
--- /dev/null
+++ b/samples/LiveEffect/screenshot.png
Binary files differ
diff --git a/samples/echo/src/main/AndroidManifest.xml b/samples/LiveEffect/src/main/AndroidManifest.xml
similarity index 89%
rename from samples/echo/src/main/AndroidManifest.xml
rename to samples/LiveEffect/src/main/AndroidManifest.xml
index bd55980..04cba7e 100644
--- a/samples/echo/src/main/AndroidManifest.xml
+++ b/samples/LiveEffect/src/main/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.google.sample.oboe.echo" >
+    package="com.google.sample.oboe.liveEffect" >
 
     <uses-feature android:name="android.hardware.microphone" android:required="true" />
     <uses-feature android:name="android.hardware.audio.output" android:required="true" />
@@ -14,7 +14,7 @@
         android:label="@string/app_name"
         android:theme="@style/AppTheme" >
       <activity
-          android:name="com.google.sample.oboe.echo.MainActivity"
+          android:name="com.google.sample.oboe.liveEffect.MainActivity"
           android:label="@string/app_name"
           android:screenOrientation="portrait">
         <intent-filter>
diff --git a/samples/echo/src/main/cpp/CMakeLists.txt b/samples/LiveEffect/src/main/cpp/CMakeLists.txt
similarity index 76%
rename from samples/echo/src/main/cpp/CMakeLists.txt
rename to samples/LiveEffect/src/main/cpp/CMakeLists.txt
index fe6db7c..a5a3e96 100644
--- a/samples/echo/src/main/cpp/CMakeLists.txt
+++ b/samples/LiveEffect/src/main/cpp/CMakeLists.txt
@@ -14,7 +14,7 @@
 # limitations under the License.
 #
 cmake_minimum_required(VERSION 3.4.1)
-project(echo LANGUAGES C CXX)
+project(liveEffect LANGUAGES C CXX)
 
 get_filename_component(SAMPLE_ROOT_DIR
     ${CMAKE_CURRENT_SOURCE_DIR}/../../../.. ABSOLUTE)
@@ -23,25 +23,22 @@
 set (OBOE_DIR ${SAMPLE_ROOT_DIR}/..)
 add_subdirectory(${OBOE_DIR} ./oboe-bin)
 
-add_library(echo
+add_library(liveEffect
     SHARED
-        EchoAudioEngine.cpp
+        LiveEffectEngine.cpp
         jni_bridge.cpp
-        AudioEffect.cpp
-        ${SAMPLE_ROOT_DIR}/debug-utils/trace.cpp
-        ${SAMPLE_ROOT_DIR}/common/audio_common.cpp)
-target_include_directories(echo
+        ${SAMPLE_ROOT_DIR}/debug-utils/trace.cpp)
+target_include_directories(liveEffect
     PRIVATE
-        ${SAMPLE_ROOT_DIR}/common
         ${SAMPLE_ROOT_DIR}/debug-utils
         ${OBOE_DIR}/include)
-target_link_libraries(echo
+target_link_libraries(liveEffect
     PRIVATE
         oboe
         android
         atomic
         log)
 
-target_compile_options(echo
+target_compile_options(liveEffect
     PRIVATE
         -Wall -Werror)
diff --git a/samples/LiveEffect/src/main/cpp/LiveEffectEngine.cpp b/samples/LiveEffect/src/main/cpp/LiveEffectEngine.cpp
new file mode 100644
index 0000000..b83081b
--- /dev/null
+++ b/samples/LiveEffect/src/main/cpp/LiveEffectEngine.cpp
@@ -0,0 +1,373 @@
+/**
+ * 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 "LiveEffectEngine.h"
+#include <assert.h>
+#include <logging_macros.h>
+#include <climits>
+
+/**
+ * Duplex is not very stable right after starting up:
+ *   the callbacks may not be happening at the right times
+ * The time to get it stable varies on different systems. Half second
+ * is used for this sample, during the time this sample plays silence.
+ */
+const float kSystemWarmupTime = 0.5f;
+
+LiveEffectEngine::LiveEffectEngine() {
+    assert(mOutputChannelCount == mInputChannelCount);
+}
+
+LiveEffectEngine::~LiveEffectEngine() {
+    stopStream(mPlayStream);
+    stopStream(mRecordingStream);
+
+    closeStream(mPlayStream);
+
+    closeStream(mRecordingStream);
+}
+
+void LiveEffectEngine::setRecordingDeviceId(int32_t deviceId) {
+    mRecordingDeviceId = deviceId;
+}
+
+void LiveEffectEngine::setPlaybackDeviceId(int32_t deviceId) {
+    mPlaybackDeviceId = deviceId;
+}
+
+bool LiveEffectEngine::isAAudioSupported() {
+    oboe::AudioStreamBuilder builder;
+    return builder.isAAudioSupported();
+}
+bool LiveEffectEngine::setAudioApi(oboe::AudioApi api) {
+    if (mIsEffectOn) return false;
+
+    mAudioApi = api;
+    return true;
+}
+void LiveEffectEngine::setEffectOn(bool isOn) {
+    if (isOn != mIsEffectOn) {
+        mIsEffectOn = isOn;
+
+        if (isOn) {
+            openAllStreams();
+        } else {
+            closeAllStreams();
+        }
+    }
+}
+
+void LiveEffectEngine::openAllStreams() {
+    // Note: The order of stream creation is important. We create the playback
+    // stream first, then use properties from the playback stream
+    // (e.g. sample rate) to create the recording stream. By matching the
+    // properties we should get the lowest latency path
+    openPlaybackStream();
+    openRecordingStream();
+    // Now start the recording stream first so that we can read from it during
+    // the playback stream's dataCallback
+    if (mRecordingStream && mPlayStream) {
+        startStream(mRecordingStream);
+        startStream(mPlayStream);
+    } else {
+        LOGE("Failed to create recording (%p) and/or playback (%p) stream",
+             mRecordingStream, mPlayStream);
+        closeAllStreams();
+    }
+}
+
+/**
+ * Stops and closes the playback and recording streams.
+ */
+void LiveEffectEngine::closeAllStreams() {
+    /**
+     * Note: The order of events is important here.
+     * The playback stream must be closed before the recording stream. If the
+     * recording stream were to be closed first the playback stream's
+     * callback may attempt to read from the recording stream
+     * which would cause the app to crash since the recording stream would be
+     * null.
+     */
+
+    if (mPlayStream != nullptr) {
+        closeStream(mPlayStream);  // Calling close will also stop the stream
+        mPlayStream = nullptr;
+    }
+
+    if (mRecordingStream != nullptr) {
+        closeStream(mRecordingStream);
+        mRecordingStream = nullptr;
+    }
+}
+
+/**
+ * Creates an audio stream for recording. The audio device used will depend on
+ * mRecordingDeviceId.
+ * If the value is set to oboe::Unspecified then the default recording device
+ * will be used.
+ */
+void LiveEffectEngine::openRecordingStream() {
+    // To create a stream we use a stream builder. This allows us to specify all
+    // the parameters for the stream prior to opening it
+    oboe::AudioStreamBuilder builder;
+
+    setupRecordingStreamParameters(&builder);
+
+    // Now that the parameters are set up we can open the stream
+    oboe::Result result = builder.openStream(&mRecordingStream);
+    if (result == oboe::Result::OK && mRecordingStream) {
+        assert(mRecordingStream->getChannelCount() == mInputChannelCount);
+        assert(mRecordingStream->getSampleRate() == mSampleRate);
+        assert(mRecordingStream->getFormat() == oboe::AudioFormat::I16);
+
+        warnIfNotLowLatency(mRecordingStream);
+    } else {
+        LOGE("Failed to create recording stream. Error: %s",
+             oboe::convertToText(result));
+    }
+}
+
+/**
+ * Creates an audio stream for playback. The audio device used will depend on
+ * mPlaybackDeviceId.
+ * If the value is set to oboe::Unspecified then the default playback device
+ * will be used.
+ */
+void LiveEffectEngine::openPlaybackStream() {
+    oboe::AudioStreamBuilder builder;
+
+    setupPlaybackStreamParameters(&builder);
+    oboe::Result result = builder.openStream(&mPlayStream);
+    if (result == oboe::Result::OK && mPlayStream) {
+        mSampleRate = mPlayStream->getSampleRate();
+
+        assert(mPlayStream->getFormat() == oboe::AudioFormat::I16);
+        assert(mOutputChannelCount == mPlayStream->getChannelCount());
+
+        mSystemStartupFrames =
+            static_cast<uint64_t>(mSampleRate * kSystemWarmupTime);
+        mProcessedFrameCount = 0;
+
+        warnIfNotLowLatency(mPlayStream);
+
+    } else {
+        LOGE("Failed to create playback stream. Error: %s",
+             oboe::convertToText(result));
+    }
+}
+
+/**
+ * Sets the stream parameters which are specific to recording,
+ * including the sample rate which is determined from the
+ * playback stream.
+ *
+ * @param builder The recording stream builder
+ */
+oboe::AudioStreamBuilder *LiveEffectEngine::setupRecordingStreamParameters(
+    oboe::AudioStreamBuilder *builder) {
+    // This sample uses blocking read() by setting callback to null
+    builder->setCallback(nullptr)
+        ->setDeviceId(mRecordingDeviceId)
+        ->setDirection(oboe::Direction::Input)
+        ->setSampleRate(mSampleRate)
+        ->setChannelCount(mInputChannelCount);
+    return setupCommonStreamParameters(builder);
+}
+
+/**
+ * Sets the stream parameters which are specific to playback, including device
+ * id and the dataCallback function, which must be set for low latency
+ * playback.
+ * @param builder The playback stream builder
+ */
+oboe::AudioStreamBuilder *LiveEffectEngine::setupPlaybackStreamParameters(
+    oboe::AudioStreamBuilder *builder) {
+    builder->setCallback(this)
+        ->setDeviceId(mPlaybackDeviceId)
+        ->setDirection(oboe::Direction::Output)
+        ->setChannelCount(mOutputChannelCount);
+
+    return setupCommonStreamParameters(builder);
+}
+
+/**
+ * Set the stream parameters which are common to both recording and playback
+ * streams.
+ * @param builder The playback or recording stream builder
+ */
+oboe::AudioStreamBuilder *LiveEffectEngine::setupCommonStreamParameters(
+    oboe::AudioStreamBuilder *builder) {
+    // We request EXCLUSIVE mode since this will give us the lowest possible
+    // latency.
+    // If EXCLUSIVE mode isn't available the builder will fall back to SHARED
+    // mode.
+    builder->setAudioApi(mAudioApi)
+        ->setFormat(mFormat)
+        ->setSharingMode(oboe::SharingMode::Exclusive)
+        ->setPerformanceMode(oboe::PerformanceMode::LowLatency);
+    return builder;
+}
+
+void LiveEffectEngine::startStream(oboe::AudioStream *stream) {
+    assert(stream);
+    if (stream) {
+        oboe::Result result = stream->requestStart();
+        if (result != oboe::Result::OK) {
+            LOGE("Error starting stream. %s", oboe::convertToText(result));
+        }
+    }
+}
+
+void LiveEffectEngine::stopStream(oboe::AudioStream *stream) {
+    if (stream) {
+        oboe::Result result = stream->start(0L);
+        if (result != oboe::Result::OK) {
+            LOGE("Error stopping stream. %s", oboe::convertToText(result));
+        }
+    }
+}
+
+/**
+ * Close the stream. AudioStream::close() is a blocking call so
+ * the application does not need to add synchronization between
+ * onAudioReady() function and the thread calling close().
+ * [the closing thread is the UI thread in this sample].
+ * @param stream the stream to close
+ */
+void LiveEffectEngine::closeStream(oboe::AudioStream *stream) {
+    if (stream) {
+        oboe::Result result = stream->close();
+        if (result != oboe::Result::OK) {
+            LOGE("Error closing stream. %s", oboe::convertToText(result));
+        }
+    }
+}
+
+/**
+ * Restart the streams. During the restart operation subsequent calls to this
+ * method will output a warning.
+ */
+void LiveEffectEngine::restartStreams() {
+    LOGI("Restarting streams");
+
+    if (mRestartingLock.try_lock()) {
+        closeAllStreams();
+        openAllStreams();
+        mRestartingLock.unlock();
+    } else {
+        LOGW(
+            "Restart stream operation already in progress - ignoring this "
+            "request");
+        // We were unable to obtain the restarting lock which means the restart
+        // operation is currently
+        // active. This is probably because we received successive "stream
+        // disconnected" events.
+        // Internal issue b/63087953
+    }
+}
+
+/**
+ * Warn in logcat if non-low latency stream is created
+ * @param stream: newly created stream
+ *
+ */
+void LiveEffectEngine::warnIfNotLowLatency(oboe::AudioStream *stream) {
+    if (stream->getPerformanceMode() != oboe::PerformanceMode::LowLatency) {
+        LOGW(
+            "Stream is NOT low latency."
+            "Check your requested format, sample rate and channel count");
+    }
+}
+
+/**
+ * Handles playback stream's audio request. In this sample, we simply block-read
+ * from the record stream for the required samples.
+ *
+ * @param oboeStream: the playback stream that requesting additional samples
+ * @param audioData:  the buffer to load audio samples for playback stream
+ * @param numFrames:  number of frames to load to audioData buffer
+ * @return: DataCallbackResult::Continue.
+ */
+oboe::DataCallbackResult LiveEffectEngine::onAudioReady(
+    oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) {
+    assert(oboeStream == mPlayStream);
+
+    int32_t prevFrameRead = 0, framesRead = 0;
+    if (mProcessedFrameCount < mSystemStartupFrames) {
+        do {
+            // Drain the audio for the starting up period, half second for
+            // this sample.
+            prevFrameRead = framesRead;
+
+            oboe::ResultWithValue<int32_t> status =
+                mRecordingStream->read(audioData, numFrames, 0);
+            framesRead = (!status) ? 0 : status.value();
+            if (framesRead == 0) break;
+
+        } while (framesRead);
+
+        framesRead = prevFrameRead;
+    } else {
+        oboe::ResultWithValue<int32_t> status =
+            mRecordingStream->read(audioData, numFrames, 0);
+        if (!status) {
+            LOGE("input stream read error: %s",
+                 oboe::convertToText(status.error()));
+            return oboe::DataCallbackResult ::Stop;
+        }
+        framesRead = status.value();
+    }
+
+    if (framesRead < numFrames) {
+        int32_t bytesPerFrame = mRecordingStream->getChannelCount() *
+                                oboeStream->getBytesPerSample();
+        uint8_t *padPos =
+            static_cast<uint8_t *>(audioData) + framesRead * bytesPerFrame;
+        memset(padPos, 0, (size_t)(numFrames - framesRead) * bytesPerFrame);
+    }
+
+    // add your audio processing here
+
+    mProcessedFrameCount += numFrames;
+
+    return oboe::DataCallbackResult::Continue;
+}
+
+/**
+ * Oboe notifies the application for "about to close the stream".
+ *
+ * @param oboeStream: the stream to close
+ * @param error: oboe's reason for closing the stream
+ */
+void LiveEffectEngine::onErrorBeforeClose(oboe::AudioStream *oboeStream,
+                                          oboe::Result error) {
+    LOGE("%s stream Error before close: %s",
+         oboe::convertToText(oboeStream->getDirection()),
+         oboe::convertToText(error));
+}
+
+/**
+ * Oboe notifies application that "the stream is closed"
+ *
+ * @param oboeStream
+ * @param error
+ */
+void LiveEffectEngine::onErrorAfterClose(oboe::AudioStream *oboeStream,
+                                         oboe::Result error) {
+    LOGE("%s stream Error after close: %s",
+         oboe::convertToText(oboeStream->getDirection()),
+         oboe::convertToText(error));
+}
diff --git a/samples/LiveEffect/src/main/cpp/LiveEffectEngine.h b/samples/LiveEffect/src/main/cpp/LiveEffectEngine.h
new file mode 100644
index 0000000..9faff7d
--- /dev/null
+++ b/samples/LiveEffect/src/main/cpp/LiveEffectEngine.h
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+#ifndef OBOE_LIVEEFFECTENGINE_H
+#define OBOE_LIVEEFFECTENGINE_H
+
+#include <jni.h>
+#include <oboe/Oboe.h>
+#include <string>
+#include <thread>
+
+constexpr int kMonoChannelCount = 1;
+constexpr int kStereoChannelCount = 2;
+
+class LiveEffectEngine : public oboe::AudioStreamCallback {
+   public:
+    LiveEffectEngine();
+    ~LiveEffectEngine();
+    void setRecordingDeviceId(int32_t deviceId);
+    void setPlaybackDeviceId(int32_t deviceId);
+    void setEffectOn(bool isOn);
+
+    /*
+     * oboe::AudioStreamCallback interface implementation
+     */
+    oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream,
+                                          void *audioData, int32_t numFrames);
+    void onErrorBeforeClose(oboe::AudioStream *oboeStream, oboe::Result error);
+    void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error);
+
+    bool setAudioApi(oboe::AudioApi);
+    bool isAAudioSupported(void);
+
+   private:
+    bool mIsEffectOn = false;
+    uint64_t mProcessedFrameCount = 0;
+    uint64_t mSystemStartupFrames = 0;
+    int32_t mRecordingDeviceId = oboe::kUnspecified;
+    int32_t mPlaybackDeviceId = oboe::kUnspecified;
+    oboe::AudioFormat mFormat = oboe::AudioFormat::I16;
+    int32_t mSampleRate = oboe::kUnspecified;
+    int32_t mInputChannelCount = kStereoChannelCount;
+    int32_t mOutputChannelCount = kStereoChannelCount;
+    oboe::AudioStream *mRecordingStream = nullptr;
+    oboe::AudioStream *mPlayStream = nullptr;
+    std::mutex mRestartingLock;
+    oboe::AudioApi mAudioApi = oboe::AudioApi::AAudio;
+
+    void openRecordingStream();
+    void openPlaybackStream();
+
+    void startStream(oboe::AudioStream *stream);
+    void stopStream(oboe::AudioStream *stream);
+    void closeStream(oboe::AudioStream *stream);
+
+    void openAllStreams();
+    void closeAllStreams();
+    void restartStreams();
+
+    oboe::AudioStreamBuilder *setupCommonStreamParameters(
+        oboe::AudioStreamBuilder *builder);
+    oboe::AudioStreamBuilder *setupRecordingStreamParameters(
+        oboe::AudioStreamBuilder *builder);
+    oboe::AudioStreamBuilder *setupPlaybackStreamParameters(
+        oboe::AudioStreamBuilder *builder);
+    void warnIfNotLowLatency(oboe::AudioStream *stream);
+};
+
+#endif  // OBOE_LIVEEFFECTENGINE_H
diff --git a/samples/LiveEffect/src/main/cpp/jni_bridge.cpp b/samples/LiveEffect/src/main/cpp/jni_bridge.cpp
new file mode 100644
index 0000000..ec5bfae
--- /dev/null
+++ b/samples/LiveEffect/src/main/cpp/jni_bridge.cpp
@@ -0,0 +1,124 @@
+/**
+ * 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 <jni.h>
+#include <logging_macros.h>
+#include "LiveEffectEngine.h"
+
+static const int kOboeApiAAudio = 0;
+static const int kOboeApiOpenSLES = 1;
+
+static LiveEffectEngine *engine = nullptr;
+
+extern "C" {
+
+JNIEXPORT bool JNICALL
+Java_com_google_sample_oboe_liveEffect_LiveEffectEngine_create(JNIEnv *env,
+                                                               jclass) {
+    if (engine == nullptr) {
+        engine = new LiveEffectEngine();
+    }
+
+    return (engine != nullptr);
+}
+
+JNIEXPORT void JNICALL
+Java_com_google_sample_oboe_liveEffect_LiveEffectEngine_delete(JNIEnv *env,
+                                                               jclass) {
+    delete engine;
+    engine = nullptr;
+}
+
+JNIEXPORT void JNICALL
+Java_com_google_sample_oboe_liveEffect_LiveEffectEngine_setEffectOn(
+    JNIEnv *env, jclass, jboolean isEffectOn) {
+    if (engine == nullptr) {
+        LOGE(
+            "Engine is null, you must call createEngine before calling this "
+            "method");
+        return;
+    }
+
+    engine->setEffectOn(isEffectOn);
+}
+
+JNIEXPORT void JNICALL
+Java_com_google_sample_oboe_liveEffect_LiveEffectEngine_setRecordingDeviceId(
+    JNIEnv *env, jclass, jint deviceId) {
+    if (engine == nullptr) {
+        LOGE(
+            "Engine is null, you must call createEngine before calling this "
+            "method");
+        return;
+    }
+
+    engine->setRecordingDeviceId(deviceId);
+}
+
+JNIEXPORT void JNICALL
+Java_com_google_sample_oboe_liveEffect_LiveEffectEngine_setPlaybackDeviceId(
+    JNIEnv *env, jclass, jint deviceId) {
+    if (engine == nullptr) {
+        LOGE(
+            "Engine is null, you must call createEngine before calling this "
+            "method");
+        return;
+    }
+
+    engine->setPlaybackDeviceId(deviceId);
+}
+
+JNIEXPORT jboolean JNICALL
+Java_com_google_sample_oboe_liveEffect_LiveEffectEngine_setAPI(JNIEnv *env,
+                                                               jclass type,
+                                                               jint apiType) {
+    if (engine == nullptr) {
+        LOGE(
+            "Engine is null, you must call createEngine "
+            "before calling this method");
+        return JNI_FALSE;
+    }
+
+    oboe::AudioApi audioApi;
+    switch (apiType) {
+        case kOboeApiAAudio:
+            audioApi = oboe::AudioApi::AAudio;
+            break;
+        case kOboeApiOpenSLES:
+            audioApi = oboe::AudioApi::OpenSLES;
+            break;
+        default:
+            LOGE("Unknown API selection to setAPI() %d", apiType);
+            return JNI_FALSE;
+    }
+
+    return static_cast<jboolean>(engine->setAudioApi(audioApi) ? JNI_TRUE
+                                                               : JNI_FALSE);
+}
+
+JNIEXPORT jboolean JNICALL
+Java_com_google_sample_oboe_liveEffect_LiveEffectEngine_isAAudioSupported(
+    JNIEnv *env, jclass type) {
+    if (engine == nullptr) {
+        LOGE(
+            "Engine is null, you must call createEngine "
+            "before calling this method");
+        return JNI_FALSE;
+    }
+    return static_cast<jboolean>(engine->isAAudioSupported() ? JNI_TRUE
+                                                             : JNI_FALSE);
+}
+}
diff --git a/samples/echo/src/main/cpp/ndk-stl-config.cmake b/samples/LiveEffect/src/main/cpp/ndk-stl-config.cmake
similarity index 100%
rename from samples/echo/src/main/cpp/ndk-stl-config.cmake
rename to samples/LiveEffect/src/main/cpp/ndk-stl-config.cmake
diff --git a/samples/echo/src/main/java/com/google/sample/oboe/echo/EchoEngine.java b/samples/LiveEffect/src/main/java/com/google/sample/oboe/liveEffect/LiveEffectEngine.java
similarity index 70%
rename from samples/echo/src/main/java/com/google/sample/oboe/echo/EchoEngine.java
rename to samples/LiveEffect/src/main/java/com/google/sample/oboe/liveEffect/LiveEffectEngine.java
index 58ddbb0..227f5d7 100644
--- a/samples/echo/src/main/java/com/google/sample/oboe/echo/EchoEngine.java
+++ b/samples/LiveEffect/src/main/java/com/google/sample/oboe/liveEffect/LiveEffectEngine.java
@@ -1,6 +1,6 @@
-package com.google.sample.oboe.echo;
+package com.google.sample.oboe.liveEffect;
 /*
- * Copyright 2017 The Android Open Source Project
+ * 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.
@@ -22,24 +22,20 @@
 import android.media.AudioRecord;
 import android.util.Log;
 
-public enum EchoEngine {
+public enum LiveEffectEngine {
 
     INSTANCE;
 
     // Load native library
     static {
-        System.loadLibrary("echo");
+        System.loadLibrary("liveEffect");
     }
 
     // Native methods
     static native boolean create();
     static native boolean isAAudioSupported();
     static native boolean setAPI(int apiType);
-    static native void setStreamFile(AssetManager assetMgr, String fileName,
-                                         int channelCount, int frequency);
-    static native void setMixer(float progress);
-    static native boolean setEchoControls(float delay, float decay);
-    static native void setEchoOn(boolean isEchoOn);
+    static native void setEffectOn(boolean isEffectOn);
     static native void setRecordingDeviceId(int deviceId);
     static native void setPlaybackDeviceId(int deviceId);
     static native void delete();
diff --git a/samples/LiveEffect/src/main/java/com/google/sample/oboe/liveEffect/MainActivity.java b/samples/LiveEffect/src/main/java/com/google/sample/oboe/liveEffect/MainActivity.java
new file mode 100644
index 0000000..136f1b9
--- /dev/null
+++ b/samples/LiveEffect/src/main/java/com/google/sample/oboe/liveEffect/MainActivity.java
@@ -0,0 +1,244 @@
+/*
+ * 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.
+ */
+
+package com.google.sample.oboe.liveEffect;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.google.sample.audio_device.AudioDeviceListEntry;
+import com.google.sample.audio_device.AudioDeviceSpinner;
+
+/**
+ * TODO: Update README.md and go through and comment sample
+ */
+public class MainActivity extends Activity
+        implements ActivityCompat.OnRequestPermissionsResultCallback {
+
+    private static final String TAG = MainActivity.class.getName();
+    private static final int AUDIO_EFFECT_REQUEST = 0;
+    private static final int OBOE_API_AAUDIO = 0;
+    private static final int OBOE_API_OPENSL_ES=1;
+
+    private TextView statusText;
+    private Button toggleEffectButton;
+    private AudioDeviceSpinner recordingDeviceSpinner;
+    private AudioDeviceSpinner playbackDeviceSpinner;
+    private boolean isPlaying = false;
+
+    private int apiSelection = OBOE_API_AAUDIO;
+    private boolean aaudioSupported = true;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        statusText = findViewById(R.id.status_view_text);
+        toggleEffectButton = findViewById(R.id.button_toggle_effect);
+        toggleEffectButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                toggleEffect();
+            }
+        });
+        toggleEffectButton.setText(getString(R.string.start_effect));
+
+        recordingDeviceSpinner = findViewById(R.id.recording_devices_spinner);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            recordingDeviceSpinner.setDirectionType(AudioManager.GET_DEVICES_INPUTS);
+            recordingDeviceSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+                @Override
+                public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
+                    LiveEffectEngine.setRecordingDeviceId(getRecordingDeviceId());
+                }
+
+                @Override
+                public void onNothingSelected(AdapterView<?> adapterView) {
+                    // Do nothing
+                }
+            });
+        }
+
+        playbackDeviceSpinner = findViewById(R.id.playback_devices_spinner);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            playbackDeviceSpinner.setDirectionType(AudioManager.GET_DEVICES_OUTPUTS);
+            playbackDeviceSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+                @Override
+                public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
+                    LiveEffectEngine.setPlaybackDeviceId(getPlaybackDeviceId());
+                }
+
+                @Override
+                public void onNothingSelected(AdapterView<?> adapterView) {
+                    // Do nothing
+                }
+            });
+        }
+
+        ((RadioGroup)findViewById(R.id.apiSelectionGroup)).check(R.id.aaudioButton);
+        findViewById(R.id.aaudioButton).setOnClickListener(new RadioButton.OnClickListener(){
+            @Override
+            public void onClick(View v) {
+                if (((RadioButton)v).isChecked()) {
+                    apiSelection = OBOE_API_AAUDIO;
+                }
+            }
+        });
+        findViewById(R.id.slesButton).setOnClickListener(new RadioButton.OnClickListener(){
+            @Override
+            public void onClick(View v) {
+                if (((RadioButton)v).isChecked()) {
+                    apiSelection = OBOE_API_OPENSL_ES;
+                }
+            }
+        });
+
+        LiveEffectEngine.create();
+        aaudioSupported = LiveEffectEngine.isAAudioSupported();
+        EnableAudioApiUI(true);
+        LiveEffectEngine.setAPI(apiSelection);
+    }
+
+    private void EnableAudioApiUI(boolean enable) {
+        if(apiSelection == OBOE_API_AAUDIO && !aaudioSupported)
+        {
+            apiSelection = OBOE_API_OPENSL_ES;
+        }
+        findViewById(R.id.slesButton).setEnabled(enable);
+        if(!aaudioSupported) {
+          findViewById(R.id.aaudioButton).setEnabled(false);
+        } else {
+            findViewById(R.id.aaudioButton).setEnabled(enable);
+        }
+
+        ((RadioGroup)findViewById(R.id.apiSelectionGroup))
+          .check(apiSelection == OBOE_API_AAUDIO ? R.id.aaudioButton : R.id.slesButton);
+    }
+    @Override
+    protected void onStart() {
+        super.onStart();
+        setVolumeControlStream(AudioManager.STREAM_MUSIC);
+    }
+
+    @Override
+    protected void onDestroy() {
+        LiveEffectEngine.delete();
+        super.onDestroy();
+    }
+
+    public void toggleEffect() {
+        if (isPlaying) {
+            stopEffect();
+            EnableAudioApiUI(true);
+        } else {
+            EnableAudioApiUI(false);
+            LiveEffectEngine.setAPI(apiSelection);
+            startEffect();
+        }
+    }
+
+    private void startEffect() {
+        Log.d(TAG, "Attempting to start");
+
+        if (!isRecordPermissionGranted()){
+            requestRecordPermission();
+            return;
+        }
+
+        setSpinnersEnabled(false);
+        LiveEffectEngine.setEffectOn(true);
+        statusText.setText(R.string.status_playing);
+        toggleEffectButton.setText(R.string.stop_effect);
+        isPlaying = true;
+    }
+
+    private void stopEffect() {
+        Log.d(TAG, "Playing, attempting to stop");
+        LiveEffectEngine.setEffectOn(false);
+        resetStatusView();
+        toggleEffectButton.setText(R.string.start_effect);
+        isPlaying = false;
+        setSpinnersEnabled(true);
+    }
+
+    private void setSpinnersEnabled(boolean isEnabled){
+        recordingDeviceSpinner.setEnabled(isEnabled);
+        playbackDeviceSpinner.setEnabled(isEnabled);
+    }
+
+    private int getRecordingDeviceId(){
+        return ((AudioDeviceListEntry)recordingDeviceSpinner.getSelectedItem()).getId();
+    }
+
+    private int getPlaybackDeviceId(){
+        return ((AudioDeviceListEntry)playbackDeviceSpinner.getSelectedItem()).getId();
+    }
+
+    private boolean isRecordPermissionGranted() {
+        return (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) ==
+                PackageManager.PERMISSION_GRANTED);
+    }
+
+    private void requestRecordPermission(){
+        ActivityCompat.requestPermissions(
+                this,
+                new String[]{Manifest.permission.RECORD_AUDIO},
+                AUDIO_EFFECT_REQUEST);
+    }
+    private void resetStatusView() {
+        statusText.setText(R.string.status_warning);
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+                                           @NonNull int[] grantResults) {
+
+        if (AUDIO_EFFECT_REQUEST != requestCode) {
+            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+            return;
+        }
+
+        if (grantResults.length != 1 ||
+                grantResults[0] != PackageManager.PERMISSION_GRANTED) {
+
+            // User denied the permission, without this we cannot record audio
+            // Show a toast and update the status accordingly
+            statusText.setText(R.string.status_record_audio_denied);
+            Toast.makeText(getApplicationContext(),
+                    getString(R.string.need_record_audio_permission),
+                    Toast.LENGTH_SHORT)
+                    .show();
+        } else {
+            // Permission was granted, start live effect
+            toggleEffect();
+        }
+    }
+}
diff --git a/samples/echo/src/main/res/drawable/balance_seekbar.xml b/samples/LiveEffect/src/main/res/drawable/balance_seekbar.xml
similarity index 100%
rename from samples/echo/src/main/res/drawable/balance_seekbar.xml
rename to samples/LiveEffect/src/main/res/drawable/balance_seekbar.xml
diff --git a/samples/LiveEffect/src/main/res/layout-v21/activity_main.xml b/samples/LiveEffect/src/main/res/layout-v21/activity_main.xml
new file mode 100644
index 0000000..4ecd2e3
--- /dev/null
+++ b/samples/LiveEffect/src/main/res/layout-v21/activity_main.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 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.
+  -->
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="com.google.sample.oboe.liveEffect.MainActivity"
+    tools:layout_editor_absoluteY="81dp">
+
+    <TextView
+        android:id="@+id/recDeviceLabel"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/activity_horizontal_margin"
+        android:layout_marginLeft="@dimen/activity_horizontal_margin"
+        android:layout_marginTop="@dimen/activity_vertical_margin"
+        android:text="@string/recording_device"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <com.google.sample.audio_device.AudioDeviceSpinner
+        android:id="@+id/recording_devices_spinner"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/activity_horizontal_margin"
+        android:layout_marginLeft="@dimen/activity_horizontal_margin"
+        android:layout_marginTop="0dp"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/recDeviceLabel" />
+
+    <TextView
+        android:id="@+id/playDeviceLabel"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/activity_horizontal_margin"
+        android:layout_marginLeft="@dimen/activity_horizontal_margin"
+        android:layout_marginTop="@dimen/activity_vertical_margin"
+        android:text="@string/playback_device"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/recording_devices_spinner" />
+
+    <com.google.sample.audio_device.AudioDeviceSpinner
+        android:id="@+id/playback_devices_spinner"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/activity_horizontal_margin"
+        android:layout_marginLeft="@dimen/activity_horizontal_margin"
+        android:layout_marginTop="0dp"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/playDeviceLabel" />
+
+    <RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/apiSelectionGroup"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/activity_horizontal_margin"
+        android:layout_marginLeft="@dimen/activity_horizontal_margin"
+        android:layout_marginTop="@dimen/activity_vertical_margin"
+        android:orientation="horizontal"
+        app:layout_constraintTop_toBottomOf="@+id/playback_devices_spinner"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent">
+
+        <TextView
+            android:id="@+id/apiTextView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/apiSelection" />
+
+        <RadioButton
+            android:id="@+id/aaudioButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:layout_marginLeft="16dp"
+            android:text="@string/aaudio" />
+
+        <RadioButton
+            android:id="@+id/slesButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:layout_marginLeft="16dp"
+            android:text="@string/sles" />
+    </RadioGroup>
+
+    <Button
+        android:id="@+id/button_toggle_effect"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="72dp"
+        android:gravity="center"
+        android:text="@string/start_effect"
+        android:textAllCaps="false"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintHorizontal_bias="0.53"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/apiSelectionGroup" />
+
+    <TextView
+        android:id="@+id/status_view_text"
+        android:layout_width="0dp"
+        android:layout_height="60dp"
+        android:layout_marginStart="@dimen/activity_horizontal_margin"
+        android:layout_marginEnd="@dimen/activity_horizontal_margin"
+        android:layout_marginTop="@dimen/activity_vertical_group_margin"
+        android:lines="6"
+        android:text="@string/status_warning"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button_toggle_effect"
+        app:layout_constraintBottom_toBottomOf="parent" />
+
+</android.support.constraint.ConstraintLayout>
diff --git a/samples/LiveEffect/src/main/res/layout/activity_main.xml b/samples/LiveEffect/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..e7a9e32
--- /dev/null
+++ b/samples/LiveEffect/src/main/res/layout/activity_main.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 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.
+  -->
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="com.google.sample.oboe.liveEffect.MainActivity"
+    tools:layout_editor_absoluteY="81dp">
+
+    <TextView
+        android:id="@+id/recDeviceLabel"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/activity_horizontal_margin"
+        android:layout_marginLeft="@dimen/activity_horizontal_margin"
+        android:layout_marginTop="@dimen/activity_vertical_margin"
+        android:text="@string/recording_device"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <com.google.sample.audio_device.AudioDeviceSpinner
+        android:id="@+id/recording_devices_spinner"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/activity_horizontal_margin"
+        android:layout_marginLeft="@dimen/activity_horizontal_margin"
+        android:layout_marginTop="0dp"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/recDeviceLabel" />
+
+    <TextView
+        android:id="@+id/playDeviceLabel"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/activity_horizontal_margin"
+        android:layout_marginLeft="@dimen/activity_horizontal_margin"
+        android:layout_marginTop="@dimen/activity_vertical_margin"
+        android:text="@string/playback_device"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/recording_devices_spinner" />
+
+    <com.google.sample.audio_device.AudioDeviceSpinner
+        android:id="@+id/playback_devices_spinner"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/activity_horizontal_margin"
+        android:layout_marginLeft="@dimen/activity_horizontal_margin"
+        android:layout_marginTop="0dp"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/playDeviceLabel" />
+
+    <RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/apiSelectionGroup"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/activity_horizontal_margin"
+        android:layout_marginLeft="@dimen/activity_horizontal_margin"
+        android:layout_marginTop="@dimen/activity_vertical_margin"
+        android:orientation="horizontal"
+        app:layout_constraintTop_toBottomOf="@+id/playback_devices_spinner"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent">
+
+        <TextView
+            android:id="@+id/apiTextView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/apiSelection" />
+
+        <RadioButton
+            android:id="@+id/aaudioButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:layout_marginLeft="16dp"
+            android:text="@string/aaudio" />
+
+        <RadioButton
+            android:id="@+id/slesButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:layout_marginLeft="16dp"
+            android:text="@string/sles" />
+    </RadioGroup>
+
+    <Button
+        android:id="@+id/button_toggle_effect"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:layout_marginTop="@dimen/activity_vertical_margin"
+        android:textAllCaps="false"
+        android:text="@string/start_effect"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/apiSelectionGroup" />
+
+    <TextView
+        android:id="@+id/status_view_text"
+        android:layout_width="0dp"
+        android:layout_height="60dp"
+        android:layout_marginStart="@dimen/activity_horizontal_margin"
+        android:layout_marginEnd="@dimen/activity_horizontal_margin"
+        android:layout_marginTop="@dimen/activity_vertical_group_margin"
+        android:lines="6"
+        android:text="@string/status_warning"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/button_toggle_effect"
+        app:layout_constraintBottom_toBottomOf="parent" />
+
+</android.support.constraint.ConstraintLayout>
diff --git a/samples/echo/src/main/res/mipmap-hdpi/ic_launcher.png b/samples/LiveEffect/src/main/res/mipmap-hdpi/ic_launcher.png
similarity index 100%
rename from samples/echo/src/main/res/mipmap-hdpi/ic_launcher.png
rename to samples/LiveEffect/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/samples/echo/src/main/res/mipmap-mdpi/ic_launcher.png b/samples/LiveEffect/src/main/res/mipmap-mdpi/ic_launcher.png
similarity index 100%
rename from samples/echo/src/main/res/mipmap-mdpi/ic_launcher.png
rename to samples/LiveEffect/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/samples/echo/src/main/res/mipmap-xhdpi/ic_launcher.png b/samples/LiveEffect/src/main/res/mipmap-xhdpi/ic_launcher.png
similarity index 100%
rename from samples/echo/src/main/res/mipmap-xhdpi/ic_launcher.png
rename to samples/LiveEffect/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/echo/src/main/res/mipmap-xxhdpi/ic_launcher.png b/samples/LiveEffect/src/main/res/mipmap-xxhdpi/ic_launcher.png
similarity index 100%
rename from samples/echo/src/main/res/mipmap-xxhdpi/ic_launcher.png
rename to samples/LiveEffect/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/samples/echo/src/main/res/values-v21/styles.xml b/samples/LiveEffect/src/main/res/values-v21/styles.xml
similarity index 100%
rename from samples/echo/src/main/res/values-v21/styles.xml
rename to samples/LiveEffect/src/main/res/values-v21/styles.xml
diff --git a/samples/echo/src/main/res/values-w820dp/dimens.xml b/samples/LiveEffect/src/main/res/values-w820dp/dimens.xml
similarity index 100%
rename from samples/echo/src/main/res/values-w820dp/dimens.xml
rename to samples/LiveEffect/src/main/res/values-w820dp/dimens.xml
diff --git a/samples/echo/src/main/res/values/colors.xml b/samples/LiveEffect/src/main/res/values/colors.xml
similarity index 100%
rename from samples/echo/src/main/res/values/colors.xml
rename to samples/LiveEffect/src/main/res/values/colors.xml
diff --git a/samples/echo/src/main/res/values/dimens.xml b/samples/LiveEffect/src/main/res/values/dimens.xml
similarity index 100%
rename from samples/echo/src/main/res/values/dimens.xml
rename to samples/LiveEffect/src/main/res/values/dimens.xml
diff --git a/samples/echo/src/main/res/values/strings.xml b/samples/LiveEffect/src/main/res/values/strings.xml
similarity index 62%
rename from samples/echo/src/main/res/values/strings.xml
rename to samples/LiveEffect/src/main/res/values/strings.xml
index 9025491..a1a97b6 100644
--- a/samples/echo/src/main/res/values/strings.xml
+++ b/samples/LiveEffect/src/main/res/values/strings.xml
@@ -1,10 +1,10 @@
 <resources>
-    <string name="app_name">Oboe Echo</string>
+    <string name="app_name">LiveEffect</string>
     <string name="action_settings">Settings</string>
-    <string name="start_echo">Start</string>
-    <string name="stop_echo">Stop</string>
+    <string name="start_effect">Start</string>
+    <string name="stop_effect">Stop</string>
     <string name="need_record_audio_permission">"This sample needs RECORD_AUDIO permission"</string>
-    <string name="status_echoing">Engine Echoing ....</string>
+    <string name="status_playing">Engine Playing ....</string>
     <string name="status_record_audio_denied">Error: Permission for RECORD_AUDIO was denied</string>
     <string name="status_touch_to_begin">RECORD_AUDIO permission granted, touch START to begin</string>
     <string name="status_warning">Warning: If you run this sample using the built-in microphone
@@ -15,10 +15,4 @@
     <string name="apiSelection">APIs</string>
     <string name="aaudio">AAudio</string>
     <string name="sles">OpenSL ES</string>
-    <string name="voice_label">Voice</string>
-    <string name="music_label">Music</string>
-    <string name="echo_init_delay">0.5</string>
-    <string name="echo_delay_label">Delay(sec)</string>
-    <string name="echo_init_decay">0.1</string>
-    <string name="echo_decay_label">Decay</string>
 </resources>
diff --git a/samples/echo/src/main/res/values/styles.xml b/samples/LiveEffect/src/main/res/values/styles.xml
similarity index 100%
rename from samples/echo/src/main/res/values/styles.xml
rename to samples/LiveEffect/src/main/res/values/styles.xml
diff --git a/samples/README.md b/samples/README.md
index 20431c5..7b1706d 100644
--- a/samples/README.md
+++ b/samples/README.md
@@ -6,6 +6,7 @@
 sine wave when you tap the screen
 1. [RhythmGame](RhythmGame): A simple rhythm game where you copy the clap patterns you hear by tapping on the screen
 1. [MegaDrone](MegaDrone): A one hundred oscillator synthesizer, demonstrates low latency and CPU performance
+1. [LiveEffect](LiveEffect): loops audio from input stream to output stream to demonstrate duplex capability
 
 Pre-requisites
 -------------
diff --git a/samples/common/audio_common.cpp b/samples/common/audio_common.cpp
deleted file mode 100644
index 3d296af..0000000
--- a/samples/common/audio_common.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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 "audio_common.h"
-#include <logging_macros.h>
-#include <string>
-#include <cinttypes>
-
-uint16_t SampleFormatToBpp(oboe::AudioFormat format) {
-    switch (format) {
-      case oboe::AudioFormat::I16:
-        return 16;
-      case oboe::AudioFormat::Float:
-        return 32;
-      default:
-        return 0;
-    }
-}
-
-void PrintAudioStreamInfo(const oboe::AudioStream *stream) {
-  LOGI("StreamID: %p", stream);
-  oboe::Direction  dir = stream->getDirection();
-  LOGI("Direction: %s", oboe::convertToText(dir));
-  LOGI("API type: %s", oboe::convertToText(stream->getAudioApi()));
-  LOGI("BufferCapacity: %d", stream->getBufferCapacityInFrames());
-  LOGI("BufferSize: %d", stream->getBufferSizeInFrames());
-  LOGI("FramesPerBurst: %d", stream->getFramesPerBurst());
-  LOGI("XRunCount: %d", stream->getXRunCount().value());
-  LOGI("SampleRate: %d", stream->getSampleRate());
-  LOGI("SamplesPerFrame: %d", stream->getChannelCount());
-  LOGI("DeviceId: %d", stream->getDeviceId());
-  LOGI("Format: %s",  oboe::convertToText(stream->getFormat()));
-  LOGI("SharingMode: %s", oboe::convertToText(stream->getSharingMode()));
-  LOGI("PerformanceMode: %s", oboe::convertToText(stream->getPerformanceMode()));
-
-  if (dir == oboe::Direction ::Output) {
-      LOGI("FramesReadByDevice: %" PRIx64, stream->getFramesRead());
-      LOGI("FramesWriteByApp: %d", (int32_t)stream->getFramesWritten());
-  } else {
-      LOGI("FramesReadByApp: %d", (int32_t)stream->getFramesRead());
-      LOGI("FramesWriteByDevice: %d", (int32_t)stream->getFramesWritten());
-  }
-}
-
-int64_t timestamp_to_nanoseconds(timespec ts) {
-  return (ts.tv_sec * oboe::kNanosPerSecond) + ts.tv_nsec;
-}
-
-int64_t get_time_nanoseconds(clockid_t clockid) {
-  timespec ts;
-  clock_gettime(clockid, &ts);
-  return timestamp_to_nanoseconds(ts);
-}
-
-void ConvertMonoToStereo(int16_t *buffer, int32_t numFrames) {
-    for (int i = numFrames - 1; i >= 0; i--) {
-        buffer[i*2] = buffer[i];
-        buffer[(i*2)+1] = buffer[i];
-    }
-}
\ No newline at end of file
diff --git a/samples/common/audio_common.h b/samples/common/audio_common.h
deleted file mode 100644
index 83d3fda..0000000
--- a/samples/common/audio_common.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.
- */
-
-
-#ifndef AUDIO_COMMON_H
-#define AUDIO_COMMON_H
-
-#include <chrono>
-#include <oboe/Oboe.h>
-
-
-constexpr int kMonoChannelCount = 1;
-constexpr int kStereoChannelCount = 2;
-
-uint16_t SampleFormatToBpp(oboe::AudioFormat format);
-/*
- * GetSystemTicks(void):  return the time in micro sec
- */
-__inline__ uint64_t GetSystemTicks(void) {
-    struct timeval Time;
-    gettimeofday( &Time, NULL );
-
-    return (static_cast<uint64_t>(oboe::kNanosPerMillisecond) * Time.tv_sec + Time.tv_usec);
-}
-
-/*
- * flag to enable file dumping
- */
-// #define ENABLE_LOG  1
-
-void PrintAudioStreamInfo(const oboe::AudioStream *stream);
-
-int64_t timestamp_to_nanoseconds(timespec ts);
-
-int64_t get_time_nanoseconds(clockid_t clockid);
-
-// Note: buffer must be at least double the length of numFrames to accommodate the stereo data
-void ConvertMonoToStereo(int16_t *buffer, int32_t numFrames);
-
-#endif // AUDIO_COMMON_H
diff --git a/samples/echo/proguard-rules.pro b/samples/echo/proguard-rules.pro
deleted file mode 100644
index 7dc6c7f..0000000
--- a/samples/echo/proguard-rules.pro
+++ /dev/null
@@ -1,17 +0,0 @@
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /Users/gfan/dev/android-sdk/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
-#
-# For more details, see
-#   http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-#   public *;
-#}
diff --git a/samples/echo/screenshot.png b/samples/echo/screenshot.png
deleted file mode 100644
index 411af67..0000000
--- a/samples/echo/screenshot.png
+++ /dev/null
Binary files differ
diff --git a/samples/echo/src/main/assets/sample_s16_pcm_dc_48khz.raw b/samples/echo/src/main/assets/sample_s16_pcm_dc_48khz.raw
deleted file mode 100644
index c63b397..0000000
--- a/samples/echo/src/main/assets/sample_s16_pcm_dc_48khz.raw
+++ /dev/null
Binary files differ
diff --git a/samples/echo/src/main/cpp/AudioEffect.cpp b/samples/echo/src/main/cpp/AudioEffect.cpp
deleted file mode 100644
index 3080237..0000000
--- a/samples/echo/src/main/cpp/AudioEffect.cpp
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * 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 "AudioEffect.h"
-#include <logging_macros.h>
-#include <climits>
-#include <cstring>
-#include <audio_common.h>
-
-#define CLAMP_FOR_I16(x) ((x)>SHRT_MAX ? SHRT_MAX : \
-                         ((x)<SHRT_MIN ? SHRT_MIN : (x)))
-
-/*
- * Mixing Audio in integer domain to avoid FP calculation
- *   (FG * ( MixFactor * 16 ) + BG * ( (1.0f-MixFactor) * 16 )) / 16
- */
-static const int32_t kFloatToIntMapFactor = 128;
-
-AudioMixer::AudioMixer() :
-    AudioFormat(48000, 2, oboe::AudioFormat::I16) {
-
-  bgMixFactorInt_ = (int32_t)
-                   (bgMixFactor_ * kFloatToIntMapFactor + 0.5f);
-  fgMixFactorInt_ = kFloatToIntMapFactor - bgMixFactorInt_;
-}
-
-/**
- * destructor: release memory for audio samples
- */
-AudioMixer::~AudioMixer() {
-}
-/**
- * Set mix factor for the 2 streams( background and forground );
- * blending:
- *    recordedAudio * fgMix +
- *    backgroundMusic * ( 1.0f - fgMix )
- * @param fgMix is background music mixer
- */
-void  AudioMixer::setBackgroundMixer(float mixer) {
-  if (mixer >= 0.0f && mixer <= 1.0f) {
-    bgMixFactor_ = mixer;
-    if (bgMixFactor_ < 0.05f) {
-      bgMixFactor_ = 0.0f;
-      bgMixFactorInt_ = 0;
-    } else {
-      bgMixFactorInt_ = (int32_t)
-        (bgMixFactor_ * kFloatToIntMapFactor + 0.5f);
-    }
-    fgMixFactorInt_ = kFloatToIntMapFactor - bgMixFactorInt_;
-  }
-}
-
-/**
- * Insert a raw PCM audio buffer to blend with live audio
- *
- * @param samples points to PCM audio buffer
- * @param sampleCount is total samples pointed by samples
- * @param channelCount channels for PCM audio pointed by samples
- * @param freq is PCM audio frequency (48000hz for this sample)
- */
-bool  AudioMixer::addStream(std::unique_ptr<int16_t[]>samples, size_t sampleCount,
-          int32_t sampleRate, int32_t channelCount, oboe::AudioFormat format) {
-  // Wait for lock, from user context.
-  std::lock_guard<std::mutex> lock(lock_);
-  bgAudio_ = std::move(samples);
-  bgAudioSampleCount_ = sampleCount;
-  sampleRate_ = sampleRate;
-  format_ = format;
-  channelCount_ = channelCount;
-
-  curPosition_ = 0;
-
-  return true;
-}
-
-/**
- * Adding audio processing into the live audio
- * @param liveAudio is recorded audio
- * @param samplesPerFrame is same as channelCount.
- * @param numFrames represents frames pointed by liveAudio
- *        total samples = numFrames * samplesPerFrame
- */
-void AudioMixer::process(int16_t *liveAudio, int32_t channelCount,
-                          int32_t numFrames) {
-  if(!bgAudio_ || !liveAudio) {
-    return;
-  }
-
-  if ((numFrames * channelCount) > bgAudioSampleCount_ ||
-      channelCount != channelCount_ ||
-      bgMixFactorInt_ == 0) {
-    return;
-  }
-
-  if (!lock_.try_lock()) {
-    // UI thread still updating the stream, skip blending
-    return;
-  }
-
-  size_t sampleCount  = numFrames * channelCount;
-  int32_t curSample;
-  for (int i = 0; i < sampleCount ; i++) {
-    curSample = liveAudio[i];
-    curSample = curSample * fgMixFactorInt_ +
-                bgAudio_[curPosition_] * bgMixFactorInt_;
-    curSample /= kFloatToIntMapFactor;
-
-    curSample = CLAMP_FOR_I16(curSample);
-    liveAudio[i] = (int16_t)curSample;
-    curPosition_ = (curPosition_ + 1 ) % bgAudioSampleCount_;
-  }
-
-  lock_.unlock();
-}
-
-/**
- * query for audio format supportability
- */
-bool AudioMixer::AudioFormatSupported(int32_t sampleRate,
-                  int32_t channels, oboe::AudioFormat format) {
-  if (sampleRate != sampleRate_ || format != format_) {
-    return false;
-  }
-
-  if (channels  == channelCount_ ) {
-    return true;
-  }
-
-  if(channelCount_ == channels * 2) {
-    size_t dst = 0, src = 0;
-    size_t totalFrames = bgAudioSampleCount_ / channelCount_;
-    for(size_t frame = 0; frame < totalFrames; frame++) {
-      for (int32_t c = 0; c < channelCount_; c += 2) {
-        int32_t sample = bgAudio_[src] + bgAudio_[src + 1];
-        src += 2;
-        sample /= 2;
-        bgAudio_[dst++] = static_cast<int16_t>(CLAMP_FOR_I16(sample));
-      }
-    }
-    channelCount_ >>= 1;
-    bgAudioSampleCount_ >>= 1;
-    return true;
-  }
-
-  return false;
-}
-
-/**
- * Constructor for AudioDelay
- * @param sampleRate
- * @param channelCount
- * @param format
- * @param delay
- * @param decay
- */
-AudioDelay::AudioDelay(int32_t sampleRate,
-                       int32_t channelCount,
-                       oboe::AudioFormat format,
-                       float  delay,
-                       float  decay) :
-    AudioFormat(sampleRate, channelCount, format),
-    delay_(delay), decay_(decay) {
-
-  feedbackFactor_ = static_cast<int32_t>(decay_ * kFloatToIntMapFactor);
-  liveAudioFactor_ = kFloatToIntMapFactor - feedbackFactor_;
-  allocateBuffer();
-}
-
-/**
- * Destructor
- */
-AudioDelay::~AudioDelay() {
-  delete buffer_;
-}
-
-/**
- * Configure for delay time ( in second ). It is possible to dynamically
- * adjust the value
- * @param delay in seconds
- * @return true if delay time is set successfully
- */
-bool AudioDelay::setDelay(float delay) {
-  float delta = delay - delay_;
-  if ( delta > -0.022f && delta < 0.022f) {
-    return true;
-  }
-
-  std::lock_guard<std::mutex> lock(lock_);
-
-  delete (buffer_);
-
-  delay_  = delay;
-  allocateBuffer();
-  return buffer_ != nullptr;
-}
-
-/**
- * Internal helper function to allocate buffer for the delay
- *  - calculate the buffer size for the delay time
- *  - allocate and zero out buffer (0 means silent audio)
- *  - configure bufSize_ to be size of audioFrames
- */
-void AudioDelay::allocateBuffer(void) {
-
-  float fNumFrames = sampleRate_ * delay_;
-
-  size_t sampleCount = static_cast<uint32_t>(fNumFrames + 0.5f) * channelCount_;
-
-  uint32_t bytePerSample = SampleFormatToBpp(format_) / 8;
-  assert(bytePerSample <= 4);
-
-  uint32_t bytePerFrame =  channelCount_ * bytePerSample;
-
-  // get bufCapacity in bytes
-  bufCapacity_ = sampleCount * bytePerSample;
-  bufCapacity_ = ((bufCapacity_ + bytePerFrame - 1) / bytePerFrame) * bytePerFrame;
-
-  buffer_ = new uint8_t[bufCapacity_];
-  assert(buffer_);
-
-  if (buffer_) {
-    memset(buffer_, 0, bufCapacity_);
-  }
-  curPos_ = 0;
-
-  // bufSize_ is in Frames ( not samples, not bytes )
-  bufSize_ = bufCapacity_ / bytePerFrame;
-}
-
-float AudioDelay::getDelay(void) const {
-  return delay_;
-}
-
-/**
- * SetFeedbackRatio(): set the decay factor
- * ratio: value of 0.0 -- 1.0f;
- *
- * the calculation is in integer ( not in float )
- * for performance purpose
- */
-void AudioDelay::setDecay(float decay) {
-  if (decay > 0.0f && decay < 1.0f) {
-    float feedback = (decay * kFloatToIntMapFactor + 0.5f);
-    feedbackFactor_ = static_cast<int32_t>(feedback);
-    liveAudioFactor_ = kFloatToIntMapFactor - feedbackFactor_;
-  }
-}
-
-float AudioDelay::getDecay(float) const {
-  return decay_;
-}
-
-/**
- * process() filter live audio with "echo" effect:
- *   delay time is run-time adjustable
- *   decay time could also be adjustable, but not used
- *   in this sample, hardcoded to .5
- *
- * @param liveAudio is recorded audio stream
- * @param channelCount for liveAudio, must be 2 for stereo
- * @param numFrames is length of liveAudio in Frames ( not in byte )
- */
-void AudioDelay::process(int16_t *liveAudio,
-                         int32_t channelCount,
-                         int32_t numFrames) {
-
-  if (!buffer_ || !liveAudio ||
-      feedbackFactor_ == 0 ||
-      channelCount != channelCount_ ||
-      bufSize_ < numFrames) {
-    return;
-  }
-
-  if(!lock_.try_lock()) {
-    return;
-  }
-
-  if (numFrames + curPos_  > bufSize_) {
-    curPos_ = 0;
-  }
-
-  // process every sample
-  int32_t sampleCount = channelCount * numFrames;
-  int16_t* samples =  & reinterpret_cast<int16_t*>(buffer_)[curPos_ * channelCount_];
-  for (size_t idx = 0; idx < sampleCount; idx++) {
-    int32_t curSample = (samples[idx] * feedbackFactor_ +
-                liveAudio[idx] * liveAudioFactor_) / kFloatToIntMapFactor;
-    CLAMP_FOR_I16(curSample);
-    liveAudio[idx] = samples[idx];
-    samples[idx] = static_cast<int16_t>(curSample);
-  }
-
-  curPos_ += numFrames;
-  lock_.unlock();
-}
-
diff --git a/samples/echo/src/main/cpp/AudioEffect.h b/samples/echo/src/main/cpp/AudioEffect.h
deleted file mode 100644
index 8ca5769..0000000
--- a/samples/echo/src/main/cpp/AudioEffect.h
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef EFFECT_PROCESSOR_H
-#define EFFECT_PROCESSOR_H
-
-#include <cstdint>
-#include <atomic>
-#include <oboe/Oboe.h>
-#include <mutex>
-
-class AudioFormat {
-  protected:
-    int32_t sampleRate_ = 48000;
-    int32_t channelCount_ = 2;
-    oboe::AudioFormat format_ = oboe::AudioFormat::I16;
-
-    AudioFormat(int32_t sampleRate, int32_t channelCount,
-                oboe::AudioFormat format) :
-      sampleRate_(sampleRate), channelCount_(channelCount),
-      format_(format) {};
-    virtual ~AudioFormat() {}
-};
-
-/**
- * An Audio Mixer that mixing input audio stream with a background
- * music. Only works with:
- *   - One background stream
- *   - I16, 48000Hz, dual channel
- *   - raw PCM format without headers
- */
-class AudioMixer : public AudioFormat {
-  public:
-    AudioMixer();
-    ~AudioMixer();
-    void process(int16_t *liveAudio, int32_t channelCount,
-                 int32_t numFrames);
-    bool addStream(std::unique_ptr<int16_t[]>samples, size_t sampleCount,
-                   int32_t sampleRate, int32_t channelCount,
-                   oboe::AudioFormat format);
-    void setBackgroundMixer(float bgMix);
-    bool AudioFormatSupported(int32_t sampleRate, int32_t channels,
-                              oboe::AudioFormat format);
-  private:
-    std::unique_ptr<int16_t[]> bgAudio_;
-    size_t bgAudioSampleCount_ = 0;
-    size_t curPosition_ = 0;
-    float bgMixFactor_ = 0.5f;
-    int32_t  fgMixFactorInt_;
-    int32_t  bgMixFactorInt_;
-    std::mutex lock_;
-};
-
-/**
- * An audio delay effect:
- *   - decay is how strong echo should be
- *   - delay time is how long to hear the first echo
- *
- *   It is a simple mixing:
- *     new sample = newly_recorded_audio * ( 1 - decay ) +
- *                  feedback_audio * decay
- */
-class AudioDelay : public AudioFormat {
-  public:
-    ~AudioDelay();
-
-    explicit AudioDelay(int32_t sampleRate,
-                int32_t channelCount,
-                oboe::AudioFormat format,
-                float delay,
-                float decay);
-    void process(int16_t *liveAudio, int32_t channelCount,
-               int32_t numFrames);
-
-    bool   setDelay(float delayInSec);
-    void   setDecay(float delay);
-    float  getDelay(void) const;
-    float  getDecay(float)const;
-
-  private:
-    float delay_ = 0.0f;
-    float decay_ = 0.1f;
-    uint8_t *buffer_ = nullptr;
-    size_t bufCapacity_ = 0;
-    size_t bufSize_ = 0;
-    size_t curPos_ = 0;
-    std::mutex lock_;
-    int32_t feedbackFactor_;
-    int32_t liveAudioFactor_;
-    void allocateBuffer(void);
-
-};
-#endif  // EFFECT_PROCESSOR_H
diff --git a/samples/echo/src/main/cpp/EchoAudioEngine.cpp b/samples/echo/src/main/cpp/EchoAudioEngine.cpp
deleted file mode 100644
index d2775b2..0000000
--- a/samples/echo/src/main/cpp/EchoAudioEngine.cpp
+++ /dev/null
@@ -1,420 +0,0 @@
-/**
- * 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 "EchoAudioEngine.h"
-#include <audio_common.h>
-#include <logging_macros.h>
-#include <climits>
-#include <assert.h>
-
-/**
- * Duplex is not very stable right after starting up:
- *   the callbacks may not be happening at the right times
- * The time to get it stable varies on different systems. Half second
- * is used for this sample, during the time this sample plays silence.
- */
-const float kSystemWarmupTime = 0.5f;
-
-EchoAudioEngine::EchoAudioEngine() {
-  assert(outputChannelCount_ == inputChannelCount_);
-  mixerEffect_ = std::unique_ptr<AudioMixer>(new AudioMixer);
-}
-
-EchoAudioEngine::~EchoAudioEngine() {
-  stopStream(playStream_);
-  stopStream(recordingStream_);
-
-  closeStream(playStream_);
-
-  closeStream(recordingStream_);
-}
-
-void EchoAudioEngine::setRecordingDeviceId(int32_t deviceId) {
-  recordingDeviceId_ = deviceId;
-}
-
-void EchoAudioEngine::setPlaybackDeviceId(int32_t deviceId) {
-  playbackDeviceId_ = deviceId;
-}
-
-bool EchoAudioEngine::isAAudioSupported() {
-  oboe::AudioStreamBuilder builder;
-  return builder.isAAudioSupported();
-}
-bool EchoAudioEngine::setAudioApi(oboe::AudioApi api) {
-  if (isEchoOn_)
-    return false;
-
-  audioApi_ = api;
-  return true;
-}
-void EchoAudioEngine::setEchoOn(bool isEchoOn) {
-  if (isEchoOn != isEchoOn_) {
-    isEchoOn_ = isEchoOn;
-
-    if (isEchoOn) {
-      openAllStreams();
-    } else {
-      closeAllStreams();
-    }
-  }
-}
-
-void EchoAudioEngine::openAllStreams() {
-  // Note: The order of stream creation is important. We create the playback
-  // stream first, then use properties from the playback stream
-  // (e.g. sample rate) to create the recording stream. By matching the
-  // properties we should get the lowest latency path
-  openPlaybackStream();
-  openRecordingStream();
-  // Now start the recording stream first so that we can read from it during the
-  // playback stream's dataCallback
-  if (recordingStream_ && playStream_) {
-    mixAudio_ = mixerEffect_->AudioFormatSupported(playStream_->getSampleRate(),
-      playStream_->getChannelCount(), playStream_->getFormat());
-
-    startStream(recordingStream_);
-    startStream(playStream_);
-  } else {
-    LOGE("Failed to create recording (%p) and/or playback (%p) stream",
-         recordingStream_, playStream_);
-    closeAllStreams();
-  }
-}
-
-/**
- * Stops and closes the playback and recording streams.
- */
-void EchoAudioEngine::closeAllStreams() {
-  /**
-   * Note: The order of events is important here.
-   * The playback stream must be closed before the recording stream. If the
-   * recording stream were to be closed first the playback stream's
-   * callback may attempt to read from the recording stream
-   * which would cause the app to crash since the recording stream would be
-   * null.
-   */
-
-  if (playStream_ != nullptr) {
-    closeStream(playStream_);  // Calling close will also stop the stream
-    playStream_ = nullptr;
-  }
-
-  if (recordingStream_ != nullptr) {
-    closeStream(recordingStream_);
-    recordingStream_ = nullptr;
-  }
-  mixAudio_ = false;
-}
-
-/**
- * Creates an audio stream for recording. The audio device used will depend on
- * recordingDeviceId_.
- * If the value is set to oboe::Unspecified then the default recording device
- * will be used.
- */
-void EchoAudioEngine::openRecordingStream() {
-  // To create a stream we use a stream builder. This allows us to specify all
-  // the parameters for the stream prior to opening it
-  oboe::AudioStreamBuilder builder;
-
-  setupRecordingStreamParameters(&builder);
-
-  // Now that the parameters are set up we can open the stream
-  oboe::Result result = builder.openStream(&recordingStream_);
-  if (result == oboe::Result::OK && recordingStream_) {
-    assert(recordingStream_->getChannelCount() == inputChannelCount_);
-    assert(recordingStream_->getSampleRate() == sampleRate_);
-    assert(recordingStream_->getFormat() == oboe::AudioFormat::I16);
-
-    warnIfNotLowLatency(recordingStream_);
-    PrintAudioStreamInfo(recordingStream_);
-  } else {
-    LOGE("Failed to create recording stream. Error: %s", convertToText(result));
-  }
-}
-
-/**
- * Creates an audio stream for playback. The audio device used will depend on
- * playbackDeviceId_.
- * If the value is set to oboe::Unspecified then the default playback device
- * will be used.
- */
-void EchoAudioEngine::openPlaybackStream() {
-  oboe::AudioStreamBuilder builder;
-
-  setupPlaybackStreamParameters(&builder);
-  oboe::Result result = builder.openStream(&playStream_);
-  if (result == oboe::Result::OK && playStream_) {
-    sampleRate_ = playStream_->getSampleRate();
-
-    assert(sampleRate_ == kLoopbackSampleRate);
-    assert(playStream_->getFormat() == oboe::AudioFormat::I16);
-    assert(outputChannelCount_ == playStream_->getChannelCount());
-
-    systemStartupFrames_ = static_cast<uint64_t>
-                             (sampleRate_ * kSystemWarmupTime);
-    processedFrameCount_ = 0;
-
-    framesPerBurst_ = playStream_->getFramesPerBurst();
-
-    delayEffect_ = std::unique_ptr<AudioDelay>(new AudioDelay(
-      sampleRate_,outputChannelCount_, format_, echoDelay_, echoDecay_));
-    assert(delayEffect_ && mixerEffect_);
-
-    warnIfNotLowLatency(playStream_);
-
-    PrintAudioStreamInfo(playStream_);
-  } else {
-    LOGE("Failed to create playback stream. Error: %s",
-         oboe::convertToText(result));
-  }
-}
-
-/**
- * Sets the stream parameters which are specific to recording,
- * including the sample rate which is determined from the
- * playback stream.
- *
- * @param builder The recording stream builder
- */
-oboe::AudioStreamBuilder *EchoAudioEngine::setupRecordingStreamParameters(
-    oboe::AudioStreamBuilder *builder) {
-  // This sample uses blocking read() by setting callback to null
-  builder->setCallback(nullptr)
-      ->setDeviceId(recordingDeviceId_)
-      ->setDirection(oboe::Direction::Input)
-      ->setSampleRate(sampleRate_)
-      ->setChannelCount(inputChannelCount_);
-  return setupCommonStreamParameters(builder);
-}
-
-/**
- * Sets the stream parameters which are specific to playback, including device
- * id and the dataCallback function, which must be set for low latency
- * playback.
- * @param builder The playback stream builder
- */
-oboe::AudioStreamBuilder *EchoAudioEngine::setupPlaybackStreamParameters(
-    oboe::AudioStreamBuilder *builder) {
-  builder->setCallback(this)
-      ->setDeviceId(playbackDeviceId_)
-      ->setDirection(oboe::Direction::Output)
-      ->setChannelCount(outputChannelCount_)
-      ->setSampleRate(sampleRate_)
-      ->setFramesPerCallback(framesPerBurst_);
-
-  return setupCommonStreamParameters(builder);
-}
-
-/**
- * Set the stream parameters which are common to both recording and playback
- * streams.
- * @param builder The playback or recording stream builder
- */
-oboe::AudioStreamBuilder *EchoAudioEngine::setupCommonStreamParameters(
-    oboe::AudioStreamBuilder *builder) {
-  // We request EXCLUSIVE mode since this will give us the lowest possible
-  // latency.
-  // If EXCLUSIVE mode isn't available the builder will fall back to SHARED
-  // mode.
-  builder->setAudioApi(audioApi_)
-      ->setFormat(format_)
-      ->setSharingMode(oboe::SharingMode::Exclusive)
-      ->setPerformanceMode(oboe::PerformanceMode::LowLatency);
-  return builder;
-}
-
-void EchoAudioEngine::startStream(oboe::AudioStream *stream) {
-  assert(stream);
-  if (stream) {
-    oboe::Result result = stream->requestStart();
-    if (result != oboe::Result::OK) {
-      LOGE("Error starting stream. %s", convertToText(result));
-    }
-  }
-}
-
-void EchoAudioEngine::stopStream(oboe::AudioStream *stream) {
-  if (stream) {
-    oboe::Result result = stream->start(0L);
-    if (result != oboe::Result::OK) {
-      LOGE("Error stopping stream. %s", oboe::convertToText(result));
-    }
-  }
-}
-
-/**
- * Close the stream. AudioStream::close() is a blocking call so
- * the application does not need to add synchronization between
- * onAudioReady() function and the thread calling close().
- * [the closing thread is the UI thread in this sample].
- * @param stream the stream to close
- */
-void EchoAudioEngine::closeStream(oboe::AudioStream *stream) {
-  if (stream) {
-    oboe::Result result = stream->close();
-    if (result != oboe::Result::OK) {
-      LOGE("Error closing stream. %s", convertToText(result));
-    }
-  }
-}
-
-/**
- * Restart the streams. During the restart operation subsequent calls to this
- * method will output a warning.
- */
-void EchoAudioEngine::restartStreams() {
-  LOGI("Restarting streams");
-
-  if (restartingLock_.try_lock()) {
-    closeAllStreams();
-    openAllStreams();
-    restartingLock_.unlock();
-  } else {
-    LOGW(
-        "Restart stream operation already in progress - ignoring this request");
-    // We were unable to obtain the restarting lock which means the restart
-    // operation is currently
-    // active. This is probably because we received successive "stream
-    // disconnected" events.
-    // Internal issue b/63087953
-  }
-}
-
-/**
- * Warn in logcat if non-low latency stream is created
- * @param stream: newly created stream
- *
- */
-void EchoAudioEngine::warnIfNotLowLatency(oboe::AudioStream *stream) {
-  if (stream->getPerformanceMode() != oboe::PerformanceMode::LowLatency) {
-    LOGW(
-        "Stream is NOT low latency."
-        "Check your requested format, sample rate and channel count");
-  }
-}
-
-/**
- * Handles playback stream's audio request. In this sample, we simply block-read
- * from the record stream for the required samples.
- *
- * @param oboeStream: the playback stream that requesting additional samples
- * @param audioData:  the buffer to load audio samples for playback stream
- * @param numFrames:  number of frames to load to audioData buffer
- * @return: DataCallbackResult::Continue.
- */
-oboe::DataCallbackResult EchoAudioEngine::onAudioReady(
-    oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) {
-
-  assert(oboeStream == playStream_);
-
-  int32_t prevFrameRead = 0, framesRead = 0;
-  if (processedFrameCount_ < systemStartupFrames_) {
-    do {
-      // Drain the audio for the starting up period, half second for
-      // this sample.
-      prevFrameRead = framesRead;
-
-      oboe::ResultWithValue<int32_t> status =
-        recordingStream_->read(audioData, numFrames, 0);
-      framesRead = (!status) ? 0 : status.value();
-      if (framesRead == 0)
-        break;
-
-    } while (framesRead);
-
-    framesRead = prevFrameRead;
-  } else {
-    oboe::ResultWithValue<int32_t> status =
-      recordingStream_->read(audioData, numFrames, 0);
-
-    framesRead = (!status) ? 0 : status.value();
-  }
-
-  if (framesRead < numFrames) {
-    int32_t bytesPerFrame = recordingStream_->getChannelCount() *
-                            SampleFormatToBpp(oboeStream->getFormat()) / 8;
-    uint8_t *padPos = static_cast<uint8_t*>(audioData) +
-                      framesRead * bytesPerFrame;
-    memset(padPos, 0, (size_t)(numFrames - framesRead) * bytesPerFrame);
-  }
-
-  // Processing audio: padded silence audio treated as valid audio
-  //                   glitch would be felt by turning off mixer
-  delayEffect_->process(static_cast<int16_t *>(audioData),
-                        outputChannelCount_, numFrames);
-  if (mixAudio_) {
-    mixerEffect_->process(static_cast<int16_t *>(audioData),
-                          outputChannelCount_, numFrames);
-  }
-  processedFrameCount_ += numFrames;
-
-  return oboe::DataCallbackResult::Continue;
-}
-
-/**
- * Oboe notifies the application for "about to close the stream".
- *
- * @param oboeStream: the stream to close
- * @param error: oboe's reason for closing the stream
- */
-void EchoAudioEngine::onErrorBeforeClose(oboe::AudioStream *oboeStream,
-                                         oboe::Result error) {
-  LOGE("%s stream Error before close: %s",
-       oboe::convertToText(oboeStream->getDirection()),
-       oboe::convertToText(error));
-}
-
-/**
- * Oboe notifies application that "the stream is closed"
- *
- * @param oboeStream
- * @param error
- */
-void EchoAudioEngine::onErrorAfterClose(oboe::AudioStream *oboeStream,
-                                        oboe::Result error) {
-  LOGE("%s stream Error after close: %s",
-       oboe::convertToText(oboeStream->getDirection()),
-       oboe::convertToText(error));
-}
-
-void EchoAudioEngine::setBackgroundStream(
-      std::unique_ptr<int16_t[]> samples, size_t sampleCount,
-      int32_t sampleRate, int32_t channelCount) {
-
-  mixerEffect_->addStream(std::move(samples), sampleCount, sampleRate,
-                         channelCount, oboe::AudioFormat::I16);
-}
-
-void EchoAudioEngine::setBackgroundMixer(float bgFactor) {
-  mixerEffect_->setBackgroundMixer(bgFactor);
-}
-
-/**
- *  Configure echo delay and decay value
- *  @param delay: delay in second
- *  @param decay: decay in second
- */
-void EchoAudioEngine::setEchoControls(float delay, float decay) {
-  echoDelay_ = delay;
-  echoDecay_ = decay;
-  if (delayEffect_) {
-    delayEffect_->setDelay(echoDelay_);
-    delayEffect_->setDecay(echoDecay_);
-  }
-}
-
diff --git a/samples/echo/src/main/cpp/EchoAudioEngine.h b/samples/echo/src/main/cpp/EchoAudioEngine.h
deleted file mode 100644
index 6a7a628..0000000
--- a/samples/echo/src/main/cpp/EchoAudioEngine.h
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef OBOE_ECHOAUDIOENGINE_H
-#define OBOE_ECHOAUDIOENGINE_H
-
-#include <thread>
-#include <jni.h>
-#include <string>
-#include "audio_common.h"
-#include "AudioEffect.h"
-
-const int32_t kLoopbackSampleRate = 48000;
-
-class EchoAudioEngine : public oboe::AudioStreamCallback {
- public:
-  EchoAudioEngine();
-  ~EchoAudioEngine();
-  void setRecordingDeviceId(int32_t deviceId);
-  void setPlaybackDeviceId(int32_t deviceId);
-  void setEchoOn(bool isEchoOn);
-
-  /*
-   * oboe::AudioStreamCallback interface implementation
-   */
-  oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream,
-                                        void *audioData, int32_t numFrames);
-  void onErrorBeforeClose(oboe::AudioStream *oboeStream, oboe::Result error);
-  void onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result error);
-
-  /*
-   * handle fileStream
-   */
-  void setBackgroundStream(std::unique_ptr<int16_t[]>samples, size_t sampleCount,
-                           int32_t sampleRate, int32_t channelCount);
-  void setBackgroundMixer(float bgFactor);
-  void setEchoControls(float delay, float decay);
-  bool setAudioApi(oboe::AudioApi);
-  bool isAAudioSupported(void);
-
- private:
-  bool isEchoOn_ = false;
-  uint64_t processedFrameCount_ = 0;
-  uint64_t systemStartupFrames_ = 0;
-  int32_t recordingDeviceId_ = oboe::kUnspecified;
-  int32_t playbackDeviceId_ = oboe::kUnspecified;
-  oboe::AudioFormat format_ = oboe::AudioFormat::I16;
-  int32_t sampleRate_ = kLoopbackSampleRate;
-  int32_t inputChannelCount_ = kStereoChannelCount;
-  int32_t outputChannelCount_ = kStereoChannelCount;
-  oboe::AudioStream *recordingStream_ = nullptr;
-  oboe::AudioStream *playStream_ = nullptr;
-  int32_t framesPerBurst_;
-  std::mutex restartingLock_;
-  std::unique_ptr<AudioMixer> mixerEffect_;
-  std::unique_ptr<AudioDelay> delayEffect_;
-  oboe::AudioApi audioApi_ = oboe::AudioApi::AAudio;
-  float echoDelay_ = 0.5f;
-  float echoDecay_ = 0.1f;
-
-  bool mixAudio_ = false;
-
-  void openRecordingStream();
-  void openPlaybackStream();
-
-  void startStream(oboe::AudioStream *stream);
-  void stopStream(oboe::AudioStream *stream);
-  void closeStream(oboe::AudioStream *stream);
-
-  void openAllStreams();
-  void closeAllStreams();
-  void restartStreams();
-
-  oboe::AudioStreamBuilder *setupCommonStreamParameters(
-      oboe::AudioStreamBuilder *builder);
-  oboe::AudioStreamBuilder *setupRecordingStreamParameters(
-      oboe::AudioStreamBuilder *builder);
-  oboe::AudioStreamBuilder *setupPlaybackStreamParameters(
-      oboe::AudioStreamBuilder *builder);
-  void warnIfNotLowLatency(oboe::AudioStream *stream);
-};
-
-#endif  // ECHO_ECHOAUDIOENGINE_H
diff --git a/samples/echo/src/main/cpp/jni_bridge.cpp b/samples/echo/src/main/cpp/jni_bridge.cpp
deleted file mode 100644
index 742b381..0000000
--- a/samples/echo/src/main/cpp/jni_bridge.cpp
+++ /dev/null
@@ -1,175 +0,0 @@
-/**
- * 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 <jni.h>
-#include <logging_macros.h>
-#include "EchoAudioEngine.h"
-#include <android/asset_manager.h>
-#include <android/asset_manager_jni.h>
-
-static EchoAudioEngine *engine = nullptr;
-
-static const int OBOE_API_AAUDIO = 0;
-static const int OBOE_API_OPENSL_ES = 1;
-
-extern "C" {
-
-JNIEXPORT bool JNICALL
-Java_com_google_sample_oboe_echo_EchoEngine_create(JNIEnv *env, jclass) {
-  if (engine == nullptr) {
-    engine = new EchoAudioEngine();
-  }
-
-  return (engine != nullptr);
-}
-
-JNIEXPORT void JNICALL
-Java_com_google_sample_oboe_echo_EchoEngine_delete(JNIEnv *env, jclass) {
-  delete engine;
-  engine = nullptr;
-}
-
-JNIEXPORT void JNICALL Java_com_google_sample_oboe_echo_EchoEngine_setEchoOn(
-    JNIEnv *env, jclass, jboolean isEchoOn) {
-  if (engine == nullptr) {
-    LOGE(
-        "Engine is null, you must call createEngine before calling this "
-        "method");
-    return;
-  }
-
-  engine->setEchoOn(isEchoOn);
-}
-
-JNIEXPORT void JNICALL
-Java_com_google_sample_oboe_echo_EchoEngine_setRecordingDeviceId(
-    JNIEnv *env, jclass, jint deviceId) {
-  if (engine == nullptr) {
-    LOGE(
-        "Engine is null, you must call createEngine before calling this "
-        "method");
-    return;
-  }
-
-  engine->setRecordingDeviceId(deviceId);
-}
-
-JNIEXPORT void JNICALL
-Java_com_google_sample_oboe_echo_EchoEngine_setPlaybackDeviceId(
-    JNIEnv *env, jclass, jint deviceId) {
-  if (engine == nullptr) {
-    LOGE(
-        "Engine is null, you must call createEngine before calling this "
-        "method");
-    return;
-  }
-
-  engine->setPlaybackDeviceId(deviceId);
-}
-
-JNIEXPORT void JNICALL
-Java_com_google_sample_oboe_echo_EchoEngine_setStreamFile(
-  JNIEnv *env, jclass type, jobject assetMgr, jstring fileName,
-  jint channelCount, jint sampleRate) {
-
-  if (engine == nullptr) {
-    LOGE("Engine is null, you must call createEngine "
-         "before calling this method");
-    return;
-  }
-  const char *jniFileName = env->GetStringUTFChars(fileName, 0);
-
-  AAssetManager *jniAssetManager = AAssetManager_fromJava(env, assetMgr);
-  AAsset *sampleAsset = AAssetManager_open(jniAssetManager, jniFileName,
-                                           AASSET_MODE_UNKNOWN);
-  size_t sampleCount = static_cast<size_t>
-                       (AAsset_getLength(sampleAsset)/2);
-
-  // allocate memory to holds the full clip; the memory is released
-  // by the AudioMixer object when it is done.
-  std::unique_ptr<int16_t[]> samples =
-        std::unique_ptr<int16_t[]>(new int16_t[sampleCount]);
-  AAsset_read(sampleAsset, samples.get(), sampleCount * 2);
-
-  engine->setBackgroundStream(std::move(samples), sampleCount,
-                              sampleRate, channelCount);
-  AAsset_close(sampleAsset);
-
-  env->ReleaseStringUTFChars(fileName, jniFileName);
-}
-
-JNIEXPORT void JNICALL
-Java_com_google_sample_oboe_echo_EchoEngine_setMixer(
-      JNIEnv *env, jclass type, jfloat progress) {
-
-  if (engine == nullptr) {
-    LOGE("Engine is null, you must call createEngine "
-         "before calling this method");
-    return;
-  }
-  engine->setBackgroundMixer(progress);
-}
-
-JNIEXPORT void JNICALL
-Java_com_google_sample_oboe_echo_EchoEngine_setEchoControls(
-  JNIEnv *env, jclass type, jfloat delay, jfloat decay) {
-
-  if (engine == nullptr) {
-    LOGE("Engine is null, you must call createEngine "
-           "before calling this method");
-    return;
-  }
-  engine->setEchoControls(delay, decay);
-}
-
-
-JNIEXPORT jboolean JNICALL
-Java_com_google_sample_oboe_echo_EchoEngine_setAPI(JNIEnv *env, jclass type, jint apiType) {
-  if (engine == nullptr) {
-    LOGE("Engine is null, you must call createEngine "
-           "before calling this method");
-    return JNI_FALSE;
-  }
-
-  oboe::AudioApi audioApi;
-  switch (apiType) {
-    case OBOE_API_AAUDIO:
-      audioApi  = oboe::AudioApi::AAudio;
-      break;
-    case OBOE_API_OPENSL_ES:
-      audioApi = oboe::AudioApi::OpenSLES;
-      break;
-    default:
-      LOGE("Unknown API selection to setAPI() %d", apiType);
-      return JNI_FALSE;
-  }
-
-  return static_cast<jboolean>
-            (engine->setAudioApi(audioApi) ? JNI_TRUE : JNI_FALSE);
-}
-
-JNIEXPORT jboolean JNICALL
-Java_com_google_sample_oboe_echo_EchoEngine_isAAudioSupported(JNIEnv *env, jclass type) {
-  if (engine == nullptr) {
-    LOGE("Engine is null, you must call createEngine "
-           "before calling this method");
-    return JNI_FALSE;
-  }
-  return static_cast<jboolean >(engine->isAAudioSupported() ? JNI_TRUE : JNI_FALSE);
-}
-
-}
-
diff --git a/samples/echo/src/main/java/com/google/sample/oboe/echo/MainActivity.java b/samples/echo/src/main/java/com/google/sample/oboe/echo/MainActivity.java
deleted file mode 100644
index 09ab432..0000000
--- a/samples/echo/src/main/java/com/google/sample/oboe/echo/MainActivity.java
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Copyright 2015 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.
- */
-
-package com.google.sample.oboe.echo;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.pm.PackageManager;
-import android.content.res.AssetManager;
-import android.media.AudioManager;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v4.app.ActivityCompat;
-import android.util.Log;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.Button;
-import android.widget.RadioButton;
-import android.widget.RadioGroup;
-import android.widget.SeekBar;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.google.sample.audio_device.AudioDeviceListEntry;
-import com.google.sample.audio_device.AudioDeviceSpinner;
-
-import java.io.IOException;
-
-/**
- * TODO: Update README.md and go through and comment sample
- */
-public class MainActivity extends Activity
-        implements ActivityCompat.OnRequestPermissionsResultCallback {
-
-    private static final String TAG = MainActivity.class.getName();
-    private static final int AUDIO_ECHO_REQUEST = 0;
-    private static final int OBOE_API_AAUDIO = 0;
-    private static final int OBOE_API_OPENSL_ES=1;
-
-    private TextView statusText;
-    private Button toggleEchoButton;
-    private AudioDeviceSpinner recordingDeviceSpinner;
-    private AudioDeviceSpinner playbackDeviceSpinner;
-    private boolean isPlaying = false;
-
-    private int apiSelection = OBOE_API_AAUDIO;
-    private boolean aaudioSupported = true;
-    private SeekBar mixerSeekbar, echoDelaySeekbar, echoDecaySeekbar;
-    private TextView echoDelayTV, echoDecayTV;
-    float   mixerProgress, echoDelayProgress, echoDecayProgress;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_main);
-
-        statusText = findViewById(R.id.status_view_text);
-        toggleEchoButton = findViewById(R.id.button_toggle_echo);
-        toggleEchoButton.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                toggleEcho();
-            }
-        });
-        toggleEchoButton.setText(getString(R.string.start_echo));
-
-        recordingDeviceSpinner = findViewById(R.id.recording_devices_spinner);
-        recordingDeviceSpinner.setDirectionType(AudioManager.GET_DEVICES_INPUTS);
-        recordingDeviceSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
-            @Override
-            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
-                EchoEngine.setRecordingDeviceId(getRecordingDeviceId());
-            }
-
-            @Override
-            public void onNothingSelected(AdapterView<?> adapterView) {
-                // Do nothing
-            }
-        });
-
-        playbackDeviceSpinner = findViewById(R.id.playback_devices_spinner);
-        playbackDeviceSpinner.setDirectionType(AudioManager.GET_DEVICES_OUTPUTS);
-        playbackDeviceSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
-            @Override
-            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
-                EchoEngine.setPlaybackDeviceId(getPlaybackDeviceId());
-            }
-
-            @Override
-            public void onNothingSelected(AdapterView<?> adapterView) {
-                // Do nothing
-            }
-        });
-
-        ((RadioGroup)findViewById(R.id.apiSelectionGroup)).check(R.id.aaudioButton);
-        findViewById(R.id.aaudioButton).setOnClickListener(new RadioButton.OnClickListener(){
-            @Override
-            public void onClick(View v) {
-                if (((RadioButton)v).isChecked()) {
-                    apiSelection = OBOE_API_AAUDIO;
-                }
-            }
-        });
-        findViewById(R.id.slesButton).setOnClickListener(new RadioButton.OnClickListener(){
-            @Override
-            public void onClick(View v) {
-                if (((RadioButton)v).isChecked()) {
-                    apiSelection = OBOE_API_OPENSL_ES;
-                }
-            }
-        });
-
-        mixerSeekbar = findViewById(R.id.mixerSeekBar);
-        mixerProgress = mixerSeekbar.getProgress()/((float)mixerSeekbar.getMax());
-        mixerSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-            @Override
-            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                if (!fromUser) return;
-
-                Log.d(TAG, "mixerSeekBar value = " + progress);
-                mixerProgress = progress / (float)mixerSeekbar.getMax();
-                EchoEngine.setMixer(mixerProgress);
-            }
-
-            @Override
-            public void onStartTrackingTouch(SeekBar seekBar) {
-            }
-
-            @Override
-            public void onStopTrackingTouch(SeekBar seekBar) {
-            }
-        });
-
-        echoDelaySeekbar = findViewById(R.id.echoDelaySeekBar);
-        echoDelayTV = findViewById(R.id.echoCurDelay);
-        echoDelayProgress = (float)echoDelaySeekbar.getProgress()
-                             / echoDelaySeekbar.getMax();
-        echoDelaySeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-            @Override
-            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-
-                float curVal = (float)progress/seekBar.getMax();
-                echoDelayTV.setText(String.format("%s", curVal));
-                setSeekBarPromptPosition(echoDelaySeekbar, echoDelayTV);
-
-                if (!fromUser) return;
-                echoDelayProgress = curVal;
-                EchoEngine.setEchoControls(echoDelayProgress, echoDecayProgress);
-            }
-
-            @Override
-            public void onStartTrackingTouch(SeekBar seekBar) {
-            }
-
-            @Override
-            public void onStopTrackingTouch(SeekBar seekBar) {
-            }
-        });
-        echoDelaySeekbar.post(new Runnable() {
-            @Override
-            public void run() {
-                setSeekBarPromptPosition(echoDelaySeekbar, echoDelayTV);
-            }
-        });
-
-        echoDecaySeekbar = findViewById(R.id.echoDecaySeekBar);
-        echoDecayTV = findViewById(R.id.echoCurDecay);
-        echoDecayProgress = (float)echoDecaySeekbar.getProgress() / echoDecaySeekbar.getMax();
-        echoDecaySeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-            @Override
-            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                float curVal = (float)progress / seekBar.getMax();
-                echoDecayTV.setText(String.format("%s", curVal));
-                setSeekBarPromptPosition(echoDecaySeekbar, echoDecayTV);
-                if (!fromUser)
-                    return;
-
-                echoDecayProgress = curVal;
-                EchoEngine.setEchoControls(echoDelayProgress, echoDecayProgress);
-            }
-            @Override
-            public void onStartTrackingTouch(SeekBar seekBar) {}
-            @Override
-            public void onStopTrackingTouch(SeekBar seekBar) {}
-        });
-        echoDecaySeekbar.post(new Runnable() {
-            @Override
-            public void run() {
-                setSeekBarPromptPosition(echoDecaySeekbar, echoDecayTV);
-            }
-        });
-        EchoEngine.create();
-        aaudioSupported = EchoEngine.isAAudioSupported();
-        EnableAudioApiUI(true);
-        EchoEngine.setAPI(apiSelection);
-
-        try {
-            AssetManager assetManager = getAssets();
-            String[] assets = assetManager.list("");
-            int channels = 2;
-            int frequency = 48000;
-            EchoEngine.setStreamFile(assetManager, assets[1], channels, frequency);
-            EchoEngine.setMixer(mixerProgress);
-        } catch (IOException e) {
-            Log.e(TAG, e.getMessage());
-        }
-        EchoEngine.setEchoControls(echoDelayProgress, echoDecayProgress);
-    }
-    private void setSeekBarPromptPosition(SeekBar seekBar, TextView label) {
-        float thumbX = (float)seekBar.getProgress()/ seekBar.getMax() *
-                seekBar.getWidth() + seekBar.getX();
-        label.setX(thumbX - label.getWidth()/2.0f);
-    }
-    private void EnableAudioApiUI(boolean enable) {
-        if(apiSelection == OBOE_API_AAUDIO && !aaudioSupported)
-        {
-            apiSelection = OBOE_API_OPENSL_ES;
-        }
-        findViewById(R.id.slesButton).setEnabled(enable);
-        if(!aaudioSupported) {
-          findViewById(R.id.aaudioButton).setEnabled(false);
-        } else {
-            findViewById(R.id.aaudioButton).setEnabled(enable);
-        }
-
-        ((RadioGroup)findViewById(R.id.apiSelectionGroup))
-          .check(apiSelection == OBOE_API_AAUDIO ? R.id.aaudioButton : R.id.slesButton);
-    }
-    @Override
-    protected void onStart() {
-        super.onStart();
-        setVolumeControlStream(AudioManager.STREAM_MUSIC);
-    }
-
-    @Override
-    protected void onDestroy() {
-        EchoEngine.delete();
-        super.onDestroy();
-    }
-
-    public void toggleEcho() {
-        if (isPlaying) {
-            stopEchoing();
-            EnableAudioApiUI(true);
-        } else {
-            EnableAudioApiUI(false);
-            EchoEngine.setAPI(apiSelection);
-            startEchoing();
-        }
-    }
-
-    private void startEchoing() {
-        Log.d(TAG, "Attempting to start");
-
-        if (!isRecordPermissionGranted()){
-            requestRecordPermission();
-            return;
-        }
-
-        setSpinnersEnabled(false);
-        EchoEngine.setEchoControls(echoDelayProgress, echoDecayProgress);
-        EchoEngine.setEchoOn(true);
-        statusText.setText(R.string.status_echoing);
-        toggleEchoButton.setText(R.string.stop_echo);
-        isPlaying = true;
-    }
-
-    private void stopEchoing() {
-        Log.d(TAG, "Playing, attempting to stop");
-        EchoEngine.setEchoOn(false);
-        resetStatusView();
-        toggleEchoButton.setText(R.string.start_echo);
-        isPlaying = false;
-        setSpinnersEnabled(true);
-    }
-
-    private void setSpinnersEnabled(boolean isEnabled){
-        recordingDeviceSpinner.setEnabled(isEnabled);
-        playbackDeviceSpinner.setEnabled(isEnabled);
-    }
-
-    private int getRecordingDeviceId(){
-        return ((AudioDeviceListEntry)recordingDeviceSpinner.getSelectedItem()).getId();
-    }
-
-    private int getPlaybackDeviceId(){
-        return ((AudioDeviceListEntry)playbackDeviceSpinner.getSelectedItem()).getId();
-    }
-
-    private boolean isRecordPermissionGranted() {
-        return (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) ==
-                PackageManager.PERMISSION_GRANTED);
-    }
-
-    private void requestRecordPermission(){
-        ActivityCompat.requestPermissions(
-                this,
-                new String[]{Manifest.permission.RECORD_AUDIO},
-                AUDIO_ECHO_REQUEST);
-    }
-    private void resetStatusView() {
-        statusText.setText(R.string.status_warning);
-    }
-
-    @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
-                                           @NonNull int[] grantResults) {
-
-        if (AUDIO_ECHO_REQUEST != requestCode) {
-            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
-            return;
-        }
-
-        if (grantResults.length != 1 ||
-                grantResults[0] != PackageManager.PERMISSION_GRANTED) {
-
-            // User denied the permission, without this we cannot record audio
-            // Show a toast and update the status accordingly
-            statusText.setText(R.string.status_record_audio_denied);
-            Toast.makeText(getApplicationContext(),
-                    getString(R.string.need_record_audio_permission),
-                    Toast.LENGTH_SHORT)
-                    .show();
-        } else {
-            // Permission was granted, start echoing
-            toggleEcho();
-        }
-    }
-}
diff --git a/samples/echo/src/main/res/layout/activity_main.xml b/samples/echo/src/main/res/layout/activity_main.xml
deleted file mode 100644
index de5b51b..0000000
--- a/samples/echo/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,232 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 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.
-  -->
-<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    tools:context="com.google.sample.oboe.echo.MainActivity"
-    tools:layout_editor_absoluteY="81dp">
-
-    <TextView
-        android:id="@+id/textView"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/activity_horizontal_margin"
-        android:layout_marginTop="@dimen/activity_vertical_margin"
-        android:text="@string/recording_device"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-
-    <com.google.sample.audio_device.AudioDeviceSpinner
-        android:id="@+id/recording_devices_spinner"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/activity_horizontal_margin"
-        android:layout_marginTop="0dp"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/textView" />
-
-    <TextView
-        android:id="@+id/textView2"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/activity_horizontal_margin"
-        android:layout_marginTop="@dimen/activity_vertical_margin"
-        android:text="@string/playback_device"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/recording_devices_spinner" />
-
-    <com.google.sample.audio_device.AudioDeviceSpinner
-        android:id="@+id/playback_devices_spinner"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/activity_horizontal_margin"
-        android:layout_marginTop="0dp"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/textView2" />
-
-    <RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/apiSelectionGroup"
-        android:layout_width="fill_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/activity_horizontal_margin"
-        android:layout_marginTop="@dimen/activity_vertical_margin"
-        android:orientation="horizontal"
-        app:layout_constraintTop_toBottomOf="@+id/playback_devices_spinner"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent">
-
-        <TextView
-            android:id="@+id/apiTextView"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/apiSelection" />
-
-        <RadioButton
-            android:id="@+id/aaudioButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dp"
-            android:text="@string/aaudio" />
-
-        <RadioButton
-            android:id="@+id/slesButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dp"
-            android:text="@string/sles" />
-    </RadioGroup>
-
-    <TextView
-        android:id="@+id/voiceLabel"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/activity_horizontal_margin"
-        android:layout_marginTop="@dimen/activity_vertical_group_margin"
-        android:layout_marginEnd="0dp"
-        android:text="@string/voice_label"
-        android:visibility="visible"
-        app:layout_constraintTop_toBottomOf="@+id/apiSelectionGroup"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintVertical_bias="0.0" />
-
-    <SeekBar
-        android:id="@+id/mixerSeekBar"
-        android:layout_width="0dp"
-        android:layout_height="17dp"
-        android:layout_marginTop="5dp"
-        android:layout_marginStart="1dp"
-        android:layout_marginEnd="1dp"
-        android:maxHeight="3dp"
-        android:minHeight="3dp"
-        android:max="10"
-        android:min="0"
-        android:progress="5"
-        android:progressDrawable="@drawable/balance_seekbar"
-        app:layout_constraintStart_toEndOf="@+id/voiceLabel"
-        app:layout_constraintEnd_toStartOf="@+id/musicLabel"
-        app:layout_constraintTop_toTopOf="@+id/voiceLabel" />
-
-    <TextView
-        android:id="@+id/musicLabel"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="end"
-        android:text="@string/music_label"
-        android:visibility="visible"
-        app:layout_constraintRight_toRightOf="parent"
-        app:layout_constraintTop_toTopOf="@+id/voiceLabel"
-        app:layout_constraintVertical_bias="0.0" />
-
-    <TextView
-        android:id="@+id/echoCurDelay"
-        android:layout_gravity="center"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/activity_vertical_group_margin"
-        android:text="@string/echo_init_delay"
-        android:visibility="visible"
-        app:layout_constraintTop_toBottomOf="@+id/voiceLabel"
-        app:layout_constraintStart_toEndOf="@+id/echoDelayLabel" />
-
-    <TextView
-        android:id="@+id/echoDelayLabel"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/activity_horizontal_margin"
-        android:text="@string/echo_delay_label"
-        android:visibility="visible"
-        app:layout_constraintTop_toBottomOf="@+id/echoCurDelay"
-        app:layout_constraintStart_toStartOf="parent" />
-
-    <SeekBar
-        android:id="@+id/echoDelaySeekBar"
-        android:layout_width="0dp"
-        android:layout_height="17dp"
-        android:layout_marginTop="5dp"
-        android:layout_marginEnd="@dimen/activity_horizontal_margin"
-        android:max="10"
-        android:min="0"
-        android:progress="5"
-        app:layout_constraintStart_toEndOf="@+id/echoDelayLabel"
-        app:layout_constraintTop_toTopOf="@+id/echoDelayLabel"
-        app:layout_constraintEnd_toEndOf="parent" />
-
-    <TextView
-        android:id="@+id/echoCurDecay"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:layout_marginTop="@dimen/activity_vertical_margin"
-        android:text="@string/echo_init_decay"
-        android:visibility="visible"
-        app:layout_constraintTop_toBottomOf="@+id/echoDelayLabel"
-        app:layout_constraintStart_toEndOf="@+id/echoDecayLabel"/>
-
-    <TextView
-        android:id="@+id/echoDecayLabel"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="start"
-        android:layout_marginStart="@dimen/activity_horizontal_margin"
-        android:text="@string/echo_decay_label"
-        android:visibility="visible"
-        app:layout_constraintTop_toBottomOf="@+id/echoCurDecay"
-        app:layout_constraintStart_toStartOf="parent"/>
-
-    <SeekBar
-        android:id="@+id/echoDecaySeekBar"
-        android:layout_width="0dp"
-        android:layout_height="17dp"
-        android:layout_marginTop="5dp"
-        android:layout_marginEnd="@dimen/activity_horizontal_margin"
-        android:max="10"
-        android:min="0"
-        android:progress="1"
-        app:layout_constraintTop_toTopOf="@+id/echoDecayLabel"
-        app:layout_constraintBottom_toBottomOf="@+id/echoDecayLabel"
-        app:layout_constraintStart_toEndOf="@+id/echoDecayLabel"
-        app:layout_constraintEnd_toEndOf="parent"/>
-
-    <TextView
-        android:id="@+id/status_view_text"
-        android:layout_width="0dp"
-        android:layout_height="60dp"
-        android:layout_marginStart="@dimen/activity_horizontal_margin"
-        android:layout_marginEnd="@dimen/activity_horizontal_margin"
-        android:layout_marginTop="@dimen/activity_vertical_group_margin"
-        android:lines="6"
-        android:text="@string/status_warning"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/echoDecayLabel" />
-
-    <Button
-        android:id="@+id/button_toggle_echo"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:gravity="center"
-        android:layout_marginTop="@dimen/activity_vertical_margin"
-        android:textAllCaps="false"
-        android:text="@string/start_echo"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toBottomOf="@+id/status_view_text" />
-
-</android.support.constraint.ConstraintLayout>
diff --git a/samples/settings.gradle b/samples/settings.gradle
index 647caee..2024e54 100644
--- a/samples/settings.gradle
+++ b/samples/settings.gradle
@@ -19,5 +19,5 @@
 include ':hello-oboe'
 include ':RhythmGame'
 include ':MegaDrone'
-include 'echo'
+include ':LiveEffect'