Updates based on user feedback

- Checks that the playback volume is not zero.
- Experiments which generate multiple recordings (just GainLinearityExperiment
  currently) now attach their recordings to the results e-mail, just like
  single recording experiments. This adds another four attachments to the
  e-mail, but is required to investigate gain linearity problems.
- Clipping test renamed to Overflow test to conform with Diagnostic Suite
  terminology.
- Updated README with additional supported loudspeaker and better instructions.
- Fixed bug which caused the experiments list to be populated more than once
  under some circumstances.

Change-Id: I6ad05c4657194d04d7d99b9f86ef157942459de4
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 0c9b198..12211ee 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -107,7 +107,7 @@
     <string name="aq_spectrum_shape_exp">Spectrum shape test</string>
     <string name="aq_glitch_exp">Glitch test</string>
     <string name="aq_linearity_exp">Gain linearity test</string>
-    <string name="aq_clipping_exp">Clipping check</string>
+    <string name="aq_overflow_exp">Overflow check</string>
     <string name="aq_bias_exp">Bias measurement</string>
     
     <!-- Experiment outcomes -->
@@ -118,10 +118,10 @@
     <!-- Experiment reports -->
     <string name="aq_loopback_report">Experiment ran successfully.</string>
     <string name="aq_bias_report">Mean = %1$.3g, tolerance = +/- %2$.0f\nRMS = %3$.0f, duration = %4$.1fs</string>
-    <string name="aq_clipping_report_error">Overflow check unsuccessful</string>
-    <string name="aq_clipping_report_short">Insufficient tone detected.\nExpected %1$.1fs tone; observed %2$.1fs</string>
-    <string name="aq_clipping_report_fail">"Clipping check failed due to discontinuities.\nObserved %1$d bad frames\nTone duration %2$.1fs\nMin peak = %3$.0f, max = %4$.0f</string>
-    <string name="aq_clipping_report_pass">"Observed %1$d bad frames\nTone duration %2$.1fs\nMin peak = %3$.0f, max = %4$.0f</string>
+    <string name="aq_overflow_report_error">Overflow check unsuccessful</string>
+    <string name="aq_overflow_report_short">Insufficient tone detected.\nExpected %1$.1fs tone; observed %2$.1fs</string>
+    <string name="aq_overflow_report_fail">"Overflow check failed due to discontinuities.\nObserved %1$d bad frames\nTone duration %2$.1fs\nMin peak = %3$.0f, max = %4$.0f</string>
+    <string name="aq_overflow_report_pass">"Observed %1$d bad frames\nTone duration %2$.1fs\nMin peak = %3$.0f, max = %4$.0f</string>
     <string name="aq_linearity_report_error">Experiment failed, error code %1$g</string>
     <string name="aq_linearity_report_normal">Deviation from linearity = %1$.3g dB\nMax allowed = %2$.1f dB</string>
     <string name="aq_glitch_report_error">Error performing Glitch test.</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/AudioQualityVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/AudioQualityVerifierActivity.java
index 9487a50..8879a0f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/AudioQualityVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/AudioQualityVerifierActivity.java
@@ -26,7 +26,9 @@
 import android.graphics.Color;
 import android.graphics.Typeface;
 import android.media.AudioFormat;
+import android.media.AudioManager;
 import android.os.Bundle;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -50,6 +52,7 @@
     public static final int SAMPLE_RATE = 16000;
     public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
     public static final int BYTES_PER_SAMPLE = 2;
+    public static final int PLAYBACK_STREAM = AudioManager.STREAM_MUSIC;
 
     // Intent Extra definitions, which must match those in
     // com.google.android.voicesearch.speechservice.RecognitionController
@@ -127,12 +130,26 @@
         fillAdapter();
         mList.setAdapter(mAdapter);
         mList.setOnItemClickListener(this);
+        checkNotSilent();
     }
 
     @Override
     public void onResume() {
         super.onResume();
         mAdapter.notifyDataSetChanged(); // Update List UI
+        checkNotSilent();
+    }
+
+    private void checkNotSilent() {
+        AudioManager mgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+        mgr.setStreamMute(PLAYBACK_STREAM, false);
+        int volume = mgr.getStreamVolume(PLAYBACK_STREAM);
+        int max = mgr.getStreamMaxVolume(PLAYBACK_STREAM);
+        Log.i(TAG, "Volume " + volume + ", max " + max);
+        if (volume <= max / 10) {
+            // Volume level is silent or very quiet; increase to two-thirds
+            mgr.setStreamVolume(PLAYBACK_STREAM, (max * 2) / 3, AudioManager.FLAG_SHOW_UI);
+        }
     }
 
     // Called when an experiment has completed
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/BackgroundAudio.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/BackgroundAudio.java
index 067a3d5..e22d596 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/BackgroundAudio.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/BackgroundAudio.java
@@ -17,7 +17,6 @@
 package com.android.cts.verifier.audioquality;
 
 import android.media.AudioFormat;
-import android.media.AudioManager;
 import android.media.AudioTrack;
 import android.util.Log;
 
@@ -57,7 +56,7 @@
 
         // Start playback:
         Log.i(TAG, "Looping " + data.length + " bytes of audio, buffer size " + mBufferSize);
-        mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
+        mAudioTrack = new AudioTrack(AudioQualityVerifierActivity.PLAYBACK_STREAM,
                 AudioQualityVerifierActivity.SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO,
                 AudioQualityVerifierActivity.AUDIO_FORMAT, mBufferSize, AudioTrack.MODE_STREAM);
         if (mAudioTrack.getState() == AudioTrack.STATE_INITIALIZED) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Experiment.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Experiment.java
index 18727f4..6f2aa83 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Experiment.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Experiment.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier.audioquality;
 
+import java.util.ArrayList;
+import java.util.List;
 import com.android.cts.verifier.R;
 
 import android.content.Context;
@@ -32,7 +34,7 @@
     private String mName;
     private String mScore;
     private String mReport;
-    private String mAudioFileName;
+    private List<String> mAudioFileNames;
 
     enum Status { NotStarted, Running, Stopped, Completed }
     private Status mStatus;
@@ -68,7 +70,7 @@
         mStatus = Status.NotStarted;
         mScore = "";
         mReport = "";
-        mAudioFileName = null;
+        mAudioFileNames = new ArrayList<String>();
     }
 
     public void start() {
@@ -94,15 +96,21 @@
     }
 
     public void setRecording(byte[] data) {
-        // Save captured data to file
-        mAudioFileName = Utils.getExternalDir(mContext, this) + "/"
-            + Utils.cleanString(getName()) + ".raw";
-        Log.i(TAG, "Saving recorded data to " + mAudioFileName);
-        Utils.saveFile(mAudioFileName, data);
+        setRecording(data, -1);
     }
 
-    public String getAudioFileName() {
-        return mAudioFileName;
+    public void setRecording(byte[] data, int num) {
+        // Save captured data to file
+        String filename = Utils.getExternalDir(mContext, this) + "/"
+            + Utils.cleanString(getName())
+            + (num == -1 ? "" : "_" + String.valueOf(num)) + ".raw";
+        Log.i(TAG, "Saving recorded data to " + filename);
+        Utils.saveFile(filename, data);
+        mAudioFileNames.add(filename);
+    }
+
+    public List<String> getAudioFileNames() {
+        return mAudioFileNames;
     }
 
     // Timeout in seconds
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/README.txt b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/README.txt
new file mode 100644
index 0000000..3f9a59c
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/README.txt
@@ -0,0 +1,96 @@
+Android Audio Quality Verifier App
+==================================
+
+This app runs a set of audio quality tests on an Android device,
+to verify the end-end sound recording path.
+
+If any of these tests fail, the device is probably unsuitable
+for demanding audio tasks such as speech recognition.
+If all the tests pass, the device audio is of a good standard.
+Note that not all possible audio defects can be detected by this
+test suite, so passing does not guarantee ideal audio quality.
+
+Hardware setup
+--------------
+
+The required physical set-up consists of a powered speaker,
+connected to the Android's headphone output by a standard
+audio cable.
+
+For loudspeakers which come in pairs, you only need to use
+one speaker (typically the powered or master speaker); you
+can leave the second speaker disconnected.
+If the speakers are stereo within a single unit (sometimes
+with speakers facing in opposite directions), place the phone
+in front of either of them.
+Speakers with multiple drivers per channel (e.g. a tweeter
+and a woofer) are not suitable.
+
+The phone should be placed in front of the centre of the
+speaker cone. The distance from the speaker will be adjusted
+during calibration; typically you could expect it to be around
+3cm or so.
+Use a supporting platform such as a stack of books to raise
+the phone to the correct height to line up with the speaker.
+
+Bluetooth connection is possible but cable connection is
+usually preferable.
+
+Recommended loudspeakers
+------------------------
+
+Using suitable loudspeakers ensures that test failures highlight
+problems with the Android device under test, and not limitations
+of the loudspeakers. The following loudspeakers work well for this
+purpose:
+
+1. Yamaha NX-B02
+
+Use on AC power, not batteries.
+This speaker works well with Bluetooth as well as a wired connection.
+Note that it's not uncommon for the devices to exhibit different
+bugs under Bluetooth.
+
+2. Cakewalk MA-7A (Edirol / Roland)
+
+The "Bass Enhancer" feature MUST be switched off.
+Note that it turns itself on again every time the speakers are
+powered on, so it is easy to forget to switch it off!
+
+Software setup
+--------------
+
+1. Build the application's apk.
+2. Install the apk using adb.
+3. Run the app.
+4. Click "Calibrate". Position the phone as described in
+   Hardware setup above, with the microphone facing the speaker,
+   and adjust the volume of the speaker until the status message
+   indicates it is correct.
+5. Click on any test in the list to run it, or "Run All" to run
+   each test in sequence.
+6. Click "Results" to view the outcomes. A correctly functioning
+   device should pass all tests.
+7. Click "Send by email" from the results page to send the
+   results to an e-mail address of your choice. The recordings
+   made are also attached as raw 16 bit, 16 kHz audio files to
+   help you diagnose any failed tests.
+
+Q&A
+---
+
+Q. What if the sound level check fails?
+A. Go back to the calibration step before running any other test.
+   Make sure the device has not been moved.
+   We also recommend that once the setup is calibrated there are no
+   moving objects or people near the device under test, since these
+   will change the acoustic properties of the environment from the
+   calibrated state.
+
+Q. Some of the tests sound very loud. Is this normal?
+A. The clipping test will generally be very loud indeed;
+   the others should be at a moderate volume.
+
+Q. What sort of room should the tests be performed in?
+A. Any, as long as the background noise levels are kept low, to
+   avoid interference with the test recordings.
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Utils.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Utils.java
index a65373c..885e18c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Utils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/Utils.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.media.AudioFormat;
-import android.media.AudioManager;
 import android.media.AudioTrack;
 import android.os.Environment;
 import android.util.Log;
@@ -307,7 +306,7 @@
 
     public static void playRaw(byte[] data) {
         Log.i(TAG, "Playing " + data.length + " bytes of pre-recorded audio");
-        AudioTrack at = new AudioTrack(AudioManager.STREAM_MUSIC, AudioQualityVerifierActivity.SAMPLE_RATE,
+        AudioTrack at = new AudioTrack(AudioQualityVerifierActivity.PLAYBACK_STREAM, AudioQualityVerifierActivity.SAMPLE_RATE,
                 AudioFormat.CHANNEL_OUT_MONO, AudioQualityVerifierActivity.AUDIO_FORMAT,
                 data.length, AudioTrack.MODE_STREAM);
         writeAudio(at, data);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/VerifierExperiments.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/VerifierExperiments.java
index f0f0aa8..f800907 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/VerifierExperiments.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/VerifierExperiments.java
@@ -17,7 +17,7 @@
 package com.android.cts.verifier.audioquality;
 
 import com.android.cts.verifier.audioquality.experiments.BiasExperiment;
-import com.android.cts.verifier.audioquality.experiments.ClippingExperiment;
+import com.android.cts.verifier.audioquality.experiments.OverflowExperiment;
 import com.android.cts.verifier.audioquality.experiments.GainLinearityExperiment;
 import com.android.cts.verifier.audioquality.experiments.GlitchExperiment;
 import com.android.cts.verifier.audioquality.experiments.SoundLevelExperiment;
@@ -42,7 +42,7 @@
             mExperiments = new ArrayList<Experiment>();
             mExperiments.add(new SoundLevelExperiment());
             mExperiments.add(new BiasExperiment());
-            mExperiments.add(new ClippingExperiment());
+            mExperiments.add(new OverflowExperiment());
             mExperiments.add(new GainLinearityExperiment());
             mExperiments.add(new SpectrumShapeExperiment());
             mExperiments.add(new GlitchExperiment(0));
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/ViewResultsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/ViewResultsActivity.java
index 87901b2..6563335 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/ViewResultsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/ViewResultsActivity.java
@@ -30,6 +30,7 @@
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * This Activity allows the user to examine the results of the
@@ -79,8 +80,8 @@
 
         ArrayList<Parcelable> attachments = new ArrayList<Parcelable>();
         for (Experiment exp : mExperiments) {
-            String filename = exp.getAudioFileName();
-            if (filename != null) {
+            List<String> filenames = exp.getAudioFileNames();
+            for (String filename : filenames) {
                 attachments.add(Uri.fromFile(new File(filename)));
             }
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/ClippingExperiment.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/OverflowExperiment.java
similarity index 86%
rename from apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/ClippingExperiment.java
rename to apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/OverflowExperiment.java
index deb3709..71deac8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/ClippingExperiment.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/OverflowExperiment.java
@@ -32,7 +32,7 @@
  * by checking if the signal has any discontinuities (which might indicate
  * wraparound, for example).
  */
-public class ClippingExperiment extends LoopbackExperiment {
+public class OverflowExperiment extends LoopbackExperiment {
     private static final float FREQ = 250.0f;
     private static final float AMPL = 32768.0f * 1.1f * CalibrateVolumeActivity.OUTPUT_AMPL
             / CalibrateVolumeActivity.TARGET_AMPL;
@@ -40,13 +40,13 @@
     private static final float MIN_DURATION = DURATION * 0.9f;
     private static final float RAMP = 0.01f;
 
-    public ClippingExperiment() {
+    public OverflowExperiment() {
         super(true);
     }
 
     @Override
     protected String lookupName(Context context) {
-        return context.getString(R.string.aq_clipping_exp);
+        return context.getString(R.string.aq_overflow_exp);
     }
 
     @Override
@@ -68,18 +68,18 @@
 
         if (error < 0.0f) {
             setScore(getString(R.string.aq_fail));
-            setReport(getString(R.string.aq_clipping_report_error));
+            setReport(getString(R.string.aq_overflow_report_error));
         } else if (duration < MIN_DURATION) {
             setScore(getString(R.string.aq_fail));
-            setReport(String.format(getString(R.string.aq_clipping_report_short),
+            setReport(String.format(getString(R.string.aq_overflow_report_short),
                     DURATION, duration));
         } else if (numDeltas > 0) {
             setScore(getString(R.string.aq_fail));
-            setReport(String.format(getString(R.string.aq_clipping_report_fail),
+            setReport(String.format(getString(R.string.aq_overflow_report_fail),
                     numDeltas, duration, minPeak, maxPeak));
         } else {
             setScore(getString(R.string.aq_pass));
-            setReport(String.format(getString(R.string.aq_clipping_report_pass),
+            setReport(String.format(getString(R.string.aq_overflow_report_pass),
                     numDeltas, duration, minPeak, maxPeak));
         }
    }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/SequenceExperiment.java b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/SequenceExperiment.java
index 26a9a1d..0a3846c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/SequenceExperiment.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audioquality/experiments/SequenceExperiment.java
@@ -54,6 +54,7 @@
         for (int trial = 0; trial < n; trial++) {
             playbackData[trial] = getStim(mContext, trial);
             recordedData[trial] = loopback(playbackData[trial]);
+            setRecording(recordedData[trial], trial);
         }
         compare(playbackData, recordedData);
         mTerminator.terminate(false);