OboeTester: add fader to control delay time

in "Echo Input to Output" test.
diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.cpp b/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.cpp
index 1b34692..6ab28c2 100644
--- a/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.cpp
+++ b/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.cpp
@@ -18,9 +18,8 @@
 #include "FullDuplexEcho.h"
 
 oboe::Result  FullDuplexEcho::start() {
-    int32_t delaySize = 3 * getOutputStream()->getSampleRate();
-    mDelayFrames = delaySize - 2;
-    mDelayLine = std::make_unique<InterpolatingDelayLine>(delaySize);
+    int32_t delayFrames = (int32_t) (mDelayTimeSeconds * getOutputStream()->getSampleRate());
+    mDelayLine = std::make_unique<InterpolatingDelayLine>(delayFrames);
     return FullDuplexStream::start();
 }
 
@@ -38,14 +37,15 @@
     float *outputFloat = (float *)outputData;
     int32_t inputStride = getInputStream()->getChannelCount();
     int32_t outputStride = getOutputStream()->getChannelCount();
+    float delayFrames = mDelayTimeSeconds * getOutputStream()->getSampleRate();
     if (outputStride == 1) {
         while (framesToEcho-- > 0) {
-            *outputFloat++ = mDelayLine->process(mDelayFrames, *inputFloat); // mono delay
+            *outputFloat++ = mDelayLine->process(delayFrames, *inputFloat); // mono delay
             inputFloat += inputStride;
         }
     } else if (outputStride == 2) {
         while (framesToEcho-- > 0) {
-            *outputFloat++ = mDelayLine->process(mDelayFrames, *inputFloat); // mono delay
+            *outputFloat++ = mDelayLine->process(delayFrames, *inputFloat); // mono delay
             *outputFloat++ = 0.0f; // FIXME *inputFloat; // mono
             inputFloat += inputStride;
         }
diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.h b/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.h
index 7a755ec..bdd00ff 100644
--- a/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.h
+++ b/apps/OboeTester/app/src/main/cpp/FullDuplexEcho.h
@@ -41,9 +41,14 @@
 
     oboe::Result start() override;
 
+    void setDelayTime(double delayTimeSeconds) {
+        mDelayTimeSeconds = delayTimeSeconds;
+    }
+
 private:
     std::unique_ptr<InterpolatingDelayLine> mDelayLine;
-    float mDelayFrames = 0.0f;
+    static constexpr double kMaxDelayTimeSeconds = 4.0;
+    double mDelayTimeSeconds = kMaxDelayTimeSeconds;
 };
 
 
diff --git a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
index e0a273e..09441d1 100644
--- a/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
+++ b/apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
@@ -338,6 +338,12 @@
 
     void configureBuilder(bool isInput, oboe::AudioStreamBuilder &builder) override;
 
+    void setDelayTime(double delayTimeSeconds) {
+        if (mFullDuplexEcho) {
+            mFullDuplexEcho->setDelayTime(delayTimeSeconds);
+        }
+    }
+
 protected:
     void finishOpen(bool isInput, oboe::AudioStream *oboeStream) override;
 
@@ -379,6 +385,10 @@
         }
     }
 
+    void setDelayTime(double delayTimeMillis) {
+        mActivityEcho.setDelayTime(delayTimeMillis);
+    }
+
 private:
 
     // WARNING - must match definitions in TestAudioActivity.java
diff --git a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp
index 2779c6b..6b43d26 100644
--- a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp
+++ b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp
@@ -417,4 +417,11 @@
     engine.setActivityType(activityType);
 }
 
+JNIEXPORT void JNICALL
+Java_com_google_sample_oboe_manualtest_EchoActivity_setDelayTime(JNIEnv *env,
+                                                                         jobject instance,
+                                                                         jdouble delayTimeSeconds) {
+    engine.setDelayTime(delayTimeSeconds);
+}
+
 }
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AudioOutputTester.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AudioOutputTester.java
index 9e6df8a..63a6ed9 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AudioOutputTester.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AudioOutputTester.java
@@ -65,4 +65,5 @@
     public void setChannelEnabled(int channelIndex, boolean enabled)  {
         mOboeAudioOutputStream.setChannelEnabled(channelIndex, enabled);
     }
+
 }
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/EchoActivity.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/EchoActivity.java
index 5ad1c94..e4ab24f 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/EchoActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/EchoActivity.java
@@ -18,14 +18,41 @@
 
 import android.os.Bundle;
 import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
 
 /**
- * Activity to record and play back audio.
+ * Activity to capture audio and then send a delayed copy to output.
+ * There is a fader for setting delay time
  */
 public class EchoActivity extends TestInputActivity {
 
     AudioOutputTester mAudioOutTester;
 
+    protected TextView mTextDelayTime;
+    protected SeekBar mFaderDelayTime;
+    protected ExponentialTaper mTaperDelayTime;
+    private static final double MIN_DELAY_TIME_SECONDS = 0.004;
+    private static final double MAX_DELAY_TIME_SECONDS = 0.400;
+    private double mDelayTime;
+
+    protected static final int MAX_DELAY_TIME_PROGRESS = 1000;
+
+    private SeekBar.OnSeekBarChangeListener mDelayListener = new SeekBar.OnSeekBarChangeListener() {
+        @Override
+        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+            setDelayTimeByPosition(progress);
+        }
+
+        @Override
+        public void onStartTrackingTouch(SeekBar seekBar) {
+        }
+
+        @Override
+        public void onStopTrackingTouch(SeekBar seekBar) {
+        }
+    };
+
     @Override
     protected void inflateActivity() {
         setContentView(R.layout.activity_echo);
@@ -38,8 +65,23 @@
         updateEnabledWidgets();
 
         mAudioOutTester = addAudioOutputTester();
+
+        mTextDelayTime = (TextView) findViewById(R.id.text_delay_time);
+        mFaderDelayTime = (SeekBar) findViewById(R.id.fader_delay_time);
+        mFaderDelayTime.setOnSeekBarChangeListener(mDelayListener);
+        mTaperDelayTime = new ExponentialTaper(MAX_DELAY_TIME_PROGRESS,
+                MIN_DELAY_TIME_SECONDS, MAX_DELAY_TIME_SECONDS);
+        mFaderDelayTime.setProgress(MAX_DELAY_TIME_PROGRESS / 2);
     }
 
+    private void setDelayTimeByPosition(int progress) {
+        mDelayTime = mTaperDelayTime.linearToExponential(progress);
+        setDelayTime(mDelayTime);
+        mTextDelayTime.setText("DelayLine: " + (int)(mDelayTime * 1000) + " (msec)");
+    }
+
+    private native void setDelayTime(double delayTimeSeconds);
+
     @Override
     protected void onStart() {
         super.onStart();
@@ -49,6 +91,7 @@
     public void onStartEcho(View view) {
         openAudio();
         startAudio();
+        setDelayTime(mDelayTime);
     }
 
     public void onStopEcho(View view) {
diff --git a/apps/OboeTester/app/src/main/res/layout/activity_echo.xml b/apps/OboeTester/app/src/main/res/layout/activity_echo.xml
index 33e603c..9d77b7d 100644
--- a/apps/OboeTester/app/src/main/res/layout/activity_echo.xml
+++ b/apps/OboeTester/app/src/main/res/layout/activity_echo.xml
@@ -44,8 +44,25 @@
             android:layout_height="wrap_content"
             android:onClick="onStopEcho"
             android:text="@string/stopAudio" />
+    </LinearLayout>
 
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
 
+        <TextView
+            android:id="@+id/text_delay_time"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Delay (msec)" />
+
+        <SeekBar
+            android:id="@+id/fader_delay_time"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:max="1000"
+            android:progress="1000" />
     </LinearLayout>
 
 </LinearLayout>
diff --git a/apps/OboeTester/app/src/main/res/layout/merge_audio_common.xml b/apps/OboeTester/app/src/main/res/layout/merge_audio_common.xml
index 15812ea..31e90fb 100644
--- a/apps/OboeTester/app/src/main/res/layout/merge_audio_common.xml
+++ b/apps/OboeTester/app/src/main/res/layout/merge_audio_common.xml
@@ -18,7 +18,7 @@
             android:id="@+id/textThreshold"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="Threshold" />
+            android:text="BufferSize" />
 
         <SeekBar
             android:id="@+id/faderThreshold"