OboeTester: run latency test from intent

For more details, see:
  https://github.com/google/oboe/blob/master/apps/OboeTester/docs/AutomatedTesting.md
diff --git a/apps/OboeTester/app/src/main/AndroidManifest.xml b/apps/OboeTester/app/src/main/AndroidManifest.xml
index ea92ec8..74280b6 100644
--- a/apps/OboeTester/app/src/main/AndroidManifest.xml
+++ b/apps/OboeTester/app/src/main/AndroidManifest.xml
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.google.sample.oboe.manualtest"
-    android:versionCode="16"
-    android:versionName="1.4.06">
+    android:versionCode="17"
+    android:versionName="1.5.00">
     <!-- versionCode and versionName also have to be updated in build.gradle -->
 
     <uses-feature android:name="android.hardware.microphone" android:required="true" />
@@ -28,6 +28,7 @@
 
         <activity
             android:name="com.google.sample.oboe.manualtest.MainActivity"
+            android:launchMode="singleTask"
             android:label="@string/app_name"
             android:screenOrientation="portrait">
             <intent-filter>
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AnalyzerActivity.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AnalyzerActivity.java
index 37a0d43..7b5c90a 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AnalyzerActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AnalyzerActivity.java
@@ -16,21 +16,38 @@
 
 package com.google.sample.oboe.manualtest;
 
+import android.Manifest;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
 import android.view.View;
 import android.widget.Button;
 import android.widget.TextView;
 import android.widget.Toast;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
 /**
  * Activity to measure latency on a full duplex stream.
  */
 public class AnalyzerActivity extends TestInputActivity {
 
+    private static final int MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE = 1001;
+
+    protected static final String KEY_FILE_NAME = "file";
+    protected static final String KEY_BUFFER_BURSTS = "buffer_bursts";
+
     AudioOutputTester mAudioOutTester;
     protected BufferSizeView mBufferSizeView;
+    protected String mResultFileName;
+    private String mTestResults;
 
     // Note that these string must match the enum result_code in LatencyAnalyzer.h
     String resultCodeToString(int resultCode) {
@@ -72,7 +89,7 @@
     }
 
     public void startAudio() {
-        if (mBufferSizeView != null) {
+        if (mBufferSizeView != null && mBufferSizeView.isEnabled()) {
             mBufferSizeView.updateBufferSize();
             mBufferSizeView.setEnabled(false);
         }
@@ -96,4 +113,71 @@
 
     public void stopAudioTest() {
     }
+
+    void writeTestResultIfPermitted(String resultString) {
+        // Here, thisActivity is the current activity
+        if (ContextCompat.checkSelfPermission(this,
+                Manifest.permission.WRITE_EXTERNAL_STORAGE)
+                != PackageManager.PERMISSION_GRANTED) {
+            mTestResults = resultString;
+            ActivityCompat.requestPermissions(this,
+                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+                    MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE);
+        } else {
+            // Permission has already been granted
+            writeTestResult(resultString);
+        }
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode,
+                                           String[] permissions,
+                                           int[] grantResults) {
+        switch (requestCode) {
+            case MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE: {
+                // If request is cancelled, the result arrays are empty.
+                if (grantResults.length > 0
+                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                    writeTestResult(mTestResults);
+                } else {
+                    showToast("Writing external storage needed for test results.");
+                }
+                return;
+            }
+        }
+    }
+
+    private void writeTestInBackground(final String resultString) {
+        new Thread() {
+            public void run() {
+                writeTestResult(resultString);
+            }
+        }.start();
+    }
+
+    // Run this in a background thread.
+    private void writeTestResult(String resultString) {
+        File resultFile = new File(mResultFileName);
+        Writer writer = null;
+        try {
+            writer = new OutputStreamWriter(new FileOutputStream(resultFile));
+            writer.write(resultString);
+        } catch (
+                IOException e) {
+            e.printStackTrace();
+            showErrorToast(" writing result file. " + e.getMessage());
+        } finally {
+            if (writer != null) {
+                try {
+                    writer.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        mResultFileName = null;
+    }
+
+
 }
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AudioStreamBase.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AudioStreamBase.java
index d7cf6e8..ad405f1 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AudioStreamBase.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/AudioStreamBase.java
@@ -124,6 +124,10 @@
         return mActualStreamConfiguration.getChannelCount();
     }
 
+    public int getSampleRate() {
+        return mActualStreamConfiguration.getSampleRate();
+    }
+
     public int getFramesPerBurst() {
         return mActualStreamConfiguration.getFramesPerBurst();
     }
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/BufferSizeView.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/BufferSizeView.java
index 10a8291..3530e6a 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/BufferSizeView.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/BufferSizeView.java
@@ -92,24 +92,25 @@
         mTextThreshold = (TextView) findViewById(R.id.textThreshold);
         mFaderThreshold = (SeekBar) findViewById(R.id.faderThreshold);
         mFaderThreshold.setOnSeekBarChangeListener(mThresholdListener);
-        mTaperThreshold = new ExponentialTaper(FADER_THRESHOLD_MAX, 0.0, 1.0, 10.0);
+        mTaperThreshold = new ExponentialTaper(0.0, 1.0, 10.0);
         mFaderThreshold.setProgress(FADER_THRESHOLD_MAX);
     }
 
     private void setBufferSizeByPosition(int progress) {
         StringBuffer message = new StringBuffer();
-        double normalizedThreshold = mTaperThreshold.linearToExponential(progress);
+        double normalizedThreshold = mTaperThreshold.linearToExponential(
+                ((double)progress)/FADER_THRESHOLD_MAX);
         if (normalizedThreshold < 0.0) normalizedThreshold = 0.0;
         else if (normalizedThreshold > 1.0) normalizedThreshold = 1.0;
         message.append("bufferSize = ");
-        if (mAudioOutTester != null) {
+        if (getAudioOutTester() != null) {
             int percent = (int) (normalizedThreshold * 100);
             message.append(percent + "%");
-            int requested = mAudioOutTester.setNormalizedThreshold(normalizedThreshold);
+            int requested = getAudioOutTester().setNormalizedThreshold(normalizedThreshold);
             if (requested > 0) {
                 message.append(" = " + requested);
-                int bufferSize = mAudioOutTester.getCurrentAudioStream().getBufferSizeInFrames();
-                int bufferCapacity = mAudioOutTester.getCurrentAudioStream().getBufferCapacityInFrames();
+                int bufferSize = getAudioOutTester().getCurrentAudioStream().getBufferSizeInFrames();
+                int bufferCapacity = getAudioOutTester().getCurrentAudioStream().getBufferCapacityInFrames();
                 if (bufferSize >= 0) {
                     message.append(" / " + bufferSize + " / " + bufferCapacity);
                 }
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 e096b1a..261f2f7 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
@@ -76,15 +76,18 @@
         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, 100.0);
+        mTaperDelayTime = new ExponentialTaper(
+                MIN_DELAY_TIME_SECONDS,
+                MAX_DELAY_TIME_SECONDS,
+                100.0);
         mFaderDelayTime.setProgress(MAX_DELAY_TIME_PROGRESS / 2);
 
         hideSettingsViews();
     }
 
     private void setDelayTimeByPosition(int progress) {
-        mDelayTime = mTaperDelayTime.linearToExponential(progress);
+        mDelayTime = mTaperDelayTime.linearToExponential(
+                ((double)progress)/MAX_DELAY_TIME_PROGRESS);
         setDelayTime(mDelayTime);
         mTextDelayTime.setText("DelayLine: " + (int)(mDelayTime * 1000) + " (msec)");
     }
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/ExponentialTaper.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/ExponentialTaper.java
index 95834d2..9867751 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/ExponentialTaper.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/ExponentialTaper.java
@@ -34,18 +34,16 @@
  */
 
 public class ExponentialTaper {
-    private int mResolution;
     private double offset = 0.0;
     private double a = 1.0;
     private double b = 2.0;
     private static final double ROOT = 10.0; // because we are using log10
 
-    public ExponentialTaper(int res, double dmin, double dmax) {
-        this(res, dmin, dmax, 10000.0);
+    public ExponentialTaper(double dmin, double dmax) {
+        this(dmin, dmax, 10000.0);
     }
 
-    public ExponentialTaper(int res, double dmin, double dmax, double maxRatio) {
-        this.mResolution = res;
+    public ExponentialTaper(double dmin, double dmax, double maxRatio) {
         a = dmax;
         double curvature;
         if (dmax > dmin * maxRatio) {
@@ -59,9 +57,11 @@
         b = Math.log10(curvature);
     }
 
-    public double linearToExponential(int ival) {
-        double x = (double) ival / mResolution;
-        double y = a * Math.pow(ROOT,  b * x) - offset;
-        return y;
+    public double linearToExponential(double linear) {
+        return a * Math.pow(ROOT,  b * linear) - offset;
+    }
+
+    public double exponentialToLinear(double exponential) {
+        return Math.log((exponential + offset) / a) / (b * Math.log(ROOT));
     }
 }
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/MainActivity.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/MainActivity.java
index d7c47c4..c5ee327 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/MainActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/MainActivity.java
@@ -41,11 +41,14 @@
         System.loadLibrary("oboetester");
     }
 
+    private static final String KEY_TEST_NAME = "test";
+
     private Spinner mModeSpinner;
     private TextView mCallbackSizeTextView;
     protected TextView mDeviceView;
     private TextView mVersionTextView;
     private TextView mBuildTextView;
+    private Bundle mBundleFromIntent;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -84,6 +87,40 @@
 
         mBuildTextView = (TextView) findViewById(R.id.text_build_info);
         mBuildTextView.setText(Build.DISPLAY);
+
+        saveIntentBundleForLaterProcessing(getIntent());
+    }
+
+    @Override
+    public void onNewIntent(Intent intent) {
+        saveIntentBundleForLaterProcessing(intent);
+    }
+
+    // This will get processed during onResume.
+    private void saveIntentBundleForLaterProcessing(Intent intent) {
+        mBundleFromIntent = intent.getExtras();
+    }
+
+    private void processBundleFromIntent() {
+        if (mBundleFromIntent == null) {
+            return;
+        }
+
+        if (mBundleFromIntent.containsKey(KEY_TEST_NAME)) {
+            String testName = mBundleFromIntent.getString(KEY_TEST_NAME);
+            if ("latency".equals(testName)) {
+                Intent intent = new Intent(this, RoundTripLatencyActivity.class);
+                intent.putExtras(mBundleFromIntent);
+                startActivity(intent);
+            }
+        }
+        mBundleFromIntent = null;
+    }
+
+    @Override
+    public void onResume(){
+        super.onResume();
+        processBundleFromIntent();
     }
 
     private void updateNativeAudioUI() {
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/RoundTripLatencyActivity.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/RoundTripLatencyActivity.java
index 6bab98d..dffc6aa 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/RoundTripLatencyActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/RoundTripLatencyActivity.java
@@ -16,9 +16,14 @@
 
 package com.google.sample.oboe.manualtest;
 
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.support.annotation.NonNull;
 import android.view.View;
 import android.widget.Button;
 import android.widget.SeekBar;
@@ -36,8 +41,13 @@
     private Button mCancelButton;
     private Button mShareButton;
 
+    private boolean mTestRunningByIntent;
+    private Bundle mBundleFromIntent;
+    private int    mBufferBursts = -1;
+
     // Periodically query the status of the stream.
     protected class LatencySniffer {
+        private int counter = 0;
         public static final int SNIFFER_UPDATE_PERIOD_MSEC = 150;
         public static final int SNIFFER_UPDATE_DELAY_MSEC = 300;
 
@@ -47,22 +57,26 @@
         private Runnable runnableCode = new Runnable() {
             @Override
             public void run() {
-                String message = getProgressText();
-                if (getAnalyzerState() == STATE_GOT_DATA) {
-                    message += "Analyzing - please wait...\n";
-                }
-                setAnalyzerText(message);
+                String message;
 
                 if (isAnalyzerDone()) {
-                    onAnalyzerDone();
+                    message = onAnalyzerDone();
                 } else {
+                    message = getProgressText();
+                    message += "please wait... " + counter + "\n";
+                    if (getAnalyzerState() == STATE_GOT_DATA) {
+                        message += "ANALYZING\n";
+                    }
                     // Repeat this runnable code block again.
                     mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_PERIOD_MSEC);
                 }
+                setAnalyzerText(message);
+                counter++;
             }
         };
 
         private void startSniffer() {
+            counter = 0;
             // Start the initial runnable task by posting through the handler
             mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_DELAY_MSEC);
         }
@@ -82,27 +96,64 @@
                 progress, state, resetCount);
     }
 
-    private void onAnalyzerDone() {
-        int result = getMeasuredResult();
-        int latencyFrames = getMeasuredLatency();
-        double confidence = getMeasuredConfidence();
-        double latencyMillis = latencyFrames * 1000.0 / getSampleRate();
-        String message = getProgressText();
-        message += String.format("RMS: signal = %7.5f, noise = %7.5f\n",
-                getSignalRMS(), getBackgroundRMS());
-        message += String.format("result = %d = %s\n", result, resultCodeToString(result));
-        // Only report valid latencies.
-        if (result == 0) {
-            message += String.format("latency = %6d frames = %6.2f msec\n",
-                    latencyFrames, latencyMillis);
-        }
-        message += String.format("confidence = %6.3f", confidence);
-
-        setAnalyzerText(message);
-
+    private String onAnalyzerDone() {
+        String message = getResultString();
         mMeasureButton.setEnabled(true);
+        if (mTestRunningByIntent) {
+            // Add some extra information for the remote tester.
+            AudioStreamBase stream = mAudioOutTester.getCurrentAudioStream();
+            int framesPerBurst =stream.getFramesPerBurst();
+            String report = "build.fingerprint = " + Build.FINGERPRINT + "\n";
+            try {
+                PackageInfo pinfo = getPackageManager().getPackageInfo(getPackageName(), 0);
+                report += String.format("test.version = %s\n", pinfo.versionName);
+                report += String.format("test.version.code = %d\n", pinfo.versionCode);
+            } catch (PackageManager.NameNotFoundException e) {
+            }
+            report += String.format("burst.frames = %d\n", framesPerBurst);
+            int bufferSize = stream.getBufferSizeInFrames();
+            report += String.format("buffer.size.frames = %d\n", bufferSize);
+            int bufferCapacity = stream.getBufferCapacityInFrames();
+            report += String.format("buffer.capacity.frames = %d\n", bufferCapacity);
+            int sampleRate = stream.getSampleRate();
+            report += String.format("sample.rate = %d\n", sampleRate);
+            report += message;
+
+            maybeWriteTestResult(report);
+        }
+        mTestRunningByIntent = false;
 
         stopAudioTest();
+
+        return message;
+    }
+
+    @NonNull
+    private String getResultString() {
+        String message = String.format("rms.signal = %7.5f\n", getSignalRMS());
+        message += String.format("rms.noise = %7.5f\n", getBackgroundRMS());
+        int resetCount = getResetCount();
+        message += String.format("reset.count = %d\n", resetCount);
+
+        int result = getMeasuredResult();
+        message += String.format("result = %d\n", result);
+        message += String.format("result.text = %s\n", resultCodeToString(result));
+
+        // Only report valid latencies.
+        if (result == 0) {
+            int latencyFrames = getMeasuredLatency();
+            double latencyMillis = latencyFrames * 1000.0 / getSampleRate();
+            int bufferSize = mAudioOutTester.getCurrentAudioStream().getBufferSizeInFrames();
+            int latencyEmptyFrames = latencyFrames - bufferSize;
+            double latencyEmptyMillis = latencyEmptyFrames * 1000.0 / getSampleRate();
+            message += String.format("latency.empty.frames = %d\n", latencyEmptyFrames);
+            message += String.format("latency.empty.msec = %6.2f\n", latencyEmptyMillis);
+            message += String.format("latency.frames = %d\n", latencyFrames);
+            message += String.format("latency.msec = %6.2f\n", latencyMillis);
+        }
+        double confidence = getMeasuredConfidence();
+        message += String.format("confidence = %6.3f\n", confidence);
+        return message;
     }
 
     private LatencySniffer mLatencySniffer = new LatencySniffer();
@@ -135,6 +186,13 @@
         hideSettingsViews();
 
         mBufferSizeView.setFaderNormalizedProgress(0.0); // for lowest latency
+
+        mBundleFromIntent = getIntent().getExtras();
+    }
+
+    @Override
+    public void onNewIntent(Intent intent) {
+        mBundleFromIntent = intent.getExtras();
     }
 
     @Override
@@ -144,6 +202,46 @@
         mShareButton.setEnabled(false);
     }
 
+    private void maybeWriteTestResult(String resultString) {
+        if (mResultFileName == null) return;
+        writeTestResultIfPermitted(resultString);
+    }
+
+    private void processBundleFromIntent() {
+        if (mBundleFromIntent == null) {
+            return;
+        }
+        if (mTestRunningByIntent) {
+            return;
+        }
+
+        mResultFileName = null;
+        if (mBundleFromIntent.containsKey(KEY_FILE_NAME)) {
+            mTestRunningByIntent = true;
+            mResultFileName = mBundleFromIntent.getString(KEY_FILE_NAME);
+            getFirstInputStreamContext().configurationView.setExclusiveMode(true);
+            getFirstOutputStreamContext().configurationView.setExclusiveMode(true);
+            mBufferBursts = mBundleFromIntent.getInt(KEY_BUFFER_BURSTS, mBufferBursts);
+
+            // Delay the test start to avoid race conditions.
+            Handler handler = new Handler(Looper.getMainLooper()); // UI thread
+            handler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    onMeasure(null);
+                }
+            }, 500); // TODO where is the race, close->open?
+
+        }
+        mBundleFromIntent = null;
+    }
+
+    @Override
+    public void onResume(){
+        super.onResume();
+        processBundleFromIntent();
+    }
+
     @Override
     protected void onStop() {
         mLatencySniffer.stopSniffer();
@@ -152,6 +250,14 @@
 
     public void onMeasure(View view) {
         openAudio();
+        if (mBufferBursts >= 0) {
+            AudioStreamBase stream = mAudioOutTester.getCurrentAudioStream();
+            int framesPerBurst = stream.getFramesPerBurst();
+            stream.setBufferSizeInFrames(framesPerBurst * mBufferBursts);
+            // override buffer size fader
+            mBufferSizeView.setEnabled(false);
+            mBufferBursts = -1;
+        }
         startAudio();
         mLatencySniffer.startSniffer();
         mMeasureButton.setEnabled(false);
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/StreamConfigurationView.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/StreamConfigurationView.java
index 2579118..3c6e29f 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/StreamConfigurationView.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/StreamConfigurationView.java
@@ -346,4 +346,10 @@
         mActualConfiguration = configuration;
     }
 
+    public void setExclusiveMode(boolean b) {
+        mRequestedExclusiveView.setChecked(b);
+        mRequestedConfiguration.setSharingMode(b
+                ? StreamConfiguration.SHARING_MODE_EXCLUSIVE
+                : StreamConfiguration.SHARING_MODE_SHARED);
+    }
 }
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestAudioActivity.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestAudioActivity.java
index 96af881..8d48750 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestAudioActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestAudioActivity.java
@@ -262,6 +262,14 @@
         return null;
     }
 
+    StreamContext getFirstOutputStreamContext() {
+        for (StreamContext streamContext : mStreamContexts) {
+            if (!streamContext.isInput())
+                return streamContext;
+        }
+        return null;
+    }
+
     protected void findAudioCommon() {
         mOpenButton = (Button) findViewById(R.id.button_open);
         if (mOpenButton != null) {
diff --git a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestOutputActivityBase.java b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestOutputActivityBase.java
index 9ea8b09..028dbb8 100644
--- a/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestOutputActivityBase.java
+++ b/apps/OboeTester/app/src/main/java/com/google/sample/oboe/manualtest/TestOutputActivityBase.java
@@ -38,7 +38,8 @@
     private SeekBar.OnSeekBarChangeListener mAmplitudeListener = new SeekBar.OnSeekBarChangeListener() {
         @Override
         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-            double amplitude = mTaperAmplitude.linearToExponential(progress);
+            double amplitude = mTaperAmplitude.linearToExponential(
+                    ((double)progress)/FADER_THRESHOLD_MAX);
             mAudioOutTester.setAmplitude(amplitude);
             mTextAmplitude.setText("Amplitude = " + amplitude);
         }
@@ -59,7 +60,7 @@
         mTextAmplitude = (TextView) findViewById(R.id.textAmplitude);
         mFaderAmplitude = (SeekBar) findViewById(R.id.faderAmplitude);
         mFaderAmplitude.setOnSeekBarChangeListener(mAmplitudeListener);
-        mTaperAmplitude = new ExponentialTaper(FADER_THRESHOLD_MAX, 0.0, 4.0, 100.0);
+        mTaperAmplitude = new ExponentialTaper(0.0, 4.0, 100.0);
     }
 
     @Override
diff --git a/apps/OboeTester/app/src/main/res/layout/activity_main.xml b/apps/OboeTester/app/src/main/res/layout/activity_main.xml
index a35953d..d4c9f5f 100644
--- a/apps/OboeTester/app/src/main/res/layout/activity_main.xml
+++ b/apps/OboeTester/app/src/main/res/layout/activity_main.xml
@@ -63,7 +63,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:onClick="onLaunchManualGlitchTest"
-        android:text="Manual Glitch Test" />
+        android:text="Glitch Test" />
 
     <Button
         android:id="@+id/button_auto_glitches"
diff --git a/apps/OboeTester/app/src/main/res/layout/activity_rt_latency.xml b/apps/OboeTester/app/src/main/res/layout/activity_rt_latency.xml
index 46823e6..9196cfe 100644
--- a/apps/OboeTester/app/src/main/res/layout/activity_rt_latency.xml
+++ b/apps/OboeTester/app/src/main/res/layout/activity_rt_latency.xml
@@ -67,7 +67,7 @@
         android:id="@+id/text_analyzer_result"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:lines="8"
+        android:lines="12"
         android:text="@string/use_loopback"
         android:textSize="18sp"
         android:textStyle="bold" />