Merge "Revert "Camera: Collect MPC test results in CTS and ITS"" am: de559c6929

Original change: https://android-review.googlesource.com/c/platform/cts/+/1959731

Change-Id: Icf0b76a4b7a93302c092b27729b21a51cbc68729
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
index b321c60..4bd642d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
@@ -70,10 +70,7 @@
     private static final String INFO_DIALOG_MESSAGE_ID = "infoDialogMessageId";
 
     // ReportLog file for CTS-Verifier. The "stream" name gets mapped to the test class name.
-    public static final String GENERAL_TESTS_REPORT_LOG_NAME = "CtsVerifierGeneralTestCases";
-    public static final String AUDIO_TESTS_REPORT_LOG_NAME = "CtsVerifierAudioTestCases";
-
-    private static final String SECTION_UNDEFINED = "undefined_section_name";
+    private static final String REPORT_LOG_NAME = "CTS-Verifier-Log";
 
     // Interface mostly for making documentation and refactoring easier...
     public interface PassFailActivity {
@@ -115,16 +112,10 @@
         void setTestResultAndFinish(boolean passed);
 
         /**
-         * @return The name of the file to store the (suite of) ReportLog information.
+         * @return A unique name (derived from the test class name) to serve as a section
+         * header in the CtsVerifierReportLog file.
          */
-        public String getReportFileName();
-
-        /**
-         * @return A unique name to serve as a section header in the CtsVerifierReportLog file.
-         * Tests need to conform to the underscore_delineated_name standard for use with
-         * the protobuff/json ReportLog parsing in Google3
-         */
-        public String getReportSectionName();
+        String getReportSectionName();
 
         /**
          * Test subclasses can override this to record their CtsVerifierReportLogs.
@@ -147,7 +138,7 @@
         private final TestResultHistoryCollection mHistoryCollection;
 
         public Activity() {
-            this.mReportLog = new CtsVerifierReportLog(getReportFileName(), getReportSectionName());
+            this.mReportLog = new CtsVerifierReportLog(REPORT_LOG_NAME, getReportSectionName());
             this.mHistoryCollection = new TestResultHistoryCollection();
         }
 
@@ -211,15 +202,9 @@
             return mReportLog;
         }
 
-        /**
-         * @return The name of the file to store the (suite of) ReportLog information.
-         */
         @Override
-        public String getReportFileName() { return GENERAL_TESTS_REPORT_LOG_NAME; }
-
-        @Override
-        public String getReportSectionName() {
-            return setTestNameSuffix(sCurrentDisplayMode, SECTION_UNDEFINED);
+        public final String getReportSectionName() {
+            return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
         }
 
         @Override
@@ -255,7 +240,7 @@
         private final TestResultHistoryCollection mHistoryCollection;
 
         public ListActivity() {
-            this.mReportLog = new CtsVerifierReportLog(getReportFileName(), getReportSectionName());
+            this.mReportLog = new CtsVerifierReportLog(REPORT_LOG_NAME, getReportSectionName());
             this.mHistoryCollection = new TestResultHistoryCollection();
         }
 
@@ -301,15 +286,9 @@
             return mReportLog;
         }
 
-        /**
-         * @return The name of the file to store the (suite of) ReportLog information.
-         */
         @Override
-        public String getReportFileName() { return GENERAL_TESTS_REPORT_LOG_NAME; }
-
-        @Override
-        public String getReportSectionName() {
-            return setTestNameSuffix(sCurrentDisplayMode, SECTION_UNDEFINED);
+        public final String getReportSectionName() {
+            return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
         }
 
         @Override
@@ -347,7 +326,9 @@
         public TestListActivity() {
             // TODO(b/186555602): temporary hack^H^H^H^H workaround to fix crash
             // This DOES NOT in fact fix that bug.
-            this.mReportLog = new CtsVerifierReportLog(getReportFileName(), getReportSectionName());
+            // if (true) this.mReportLog = new CtsVerifierReportLog(REPORT_LOG_NAME, "42"); else
+
+            this.mReportLog = new CtsVerifierReportLog(REPORT_LOG_NAME, getReportSectionName());
         }
 
         @Override
@@ -392,18 +373,11 @@
             return mReportLog;
         }
 
-        /**
-         * @return The name of the file to store the (suite of) ReportLog information.
-         */
         @Override
-        public String getReportFileName() { return GENERAL_TESTS_REPORT_LOG_NAME; }
-
-        @Override
-        public String getReportSectionName() {
-            return setTestNameSuffix(sCurrentDisplayMode, SECTION_UNDEFINED);
+        public final String getReportSectionName() {
+            return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
         }
 
-
         /**
          * Get existing test history to aggregate.
          */
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
index 495c3ef..fe314c4 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
@@ -23,7 +23,6 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
-import android.util.Log;
 
 import com.android.compatibility.common.util.FileUtil;
 import com.android.compatibility.common.util.IInvocationResult;
@@ -50,8 +49,6 @@
  * Background task to generate a report and save it to external storage.
  */
 class ReportExporter extends AsyncTask<Void, Void, String> {
-    private static final String TAG = ReportExporter.class.getSimpleName();
-    private static final boolean DEBUG = true;
 
     public static final String REPORT_DIRECTORY = "VerifierReports";
     public static final String LOGS_DIRECTORY = "ReportLogFiles";
@@ -79,15 +76,11 @@
     // so that they will get ZIPped into the transmitted file.
     //
     private void copyReportFiles(File tempDir) {
-        if (DEBUG) {
-            Log.d(TAG, "copyReportFiles(" + tempDir.getAbsolutePath() + ")");
-        }
-
         File externalStorageDirectory = Environment.getExternalStorageDirectory();
         File reportLogFolder =
                 new File(Environment.getExternalStorageDirectory().getAbsolutePath()
                         + File.separator
-                        + LOGS_DIRECTORY);
+                        + REPORT_DIRECTORY);
         File[] reportLogFiles = reportLogFolder.listFiles();
 
         // if no ReportLog files have been created (i.e. the folder doesn't exist)
@@ -163,9 +156,6 @@
     }
 
     private void saveReportOnInternalStorage(File reportZipFile) {
-        if (DEBUG) {
-            Log.d(TAG, "---- saveReportOnInternalStorage(" + reportZipFile.getAbsolutePath() + ")");
-        }
         try {
             ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
                     reportZipFile, ParcelFileDescriptor.MODE_READ_ONLY);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
index 6233f2c..4ce2a12 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
@@ -400,8 +400,6 @@
             }
         }
 
-        reportHeadsetPort(mHeadsetDeviceInfo != null);
-
         showConnectedDevice();
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
new file mode 100644
index 0000000..78e34d3
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
@@ -0,0 +1,686 @@
+/*
+ * Copyright (C) 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.android.cts.verifier.audio;
+
+import android.app.AlertDialog;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.MediaRecorder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.cts.verifier.audio.audiolib.AudioSystemFlags;
+import com.android.cts.verifier.audio.audiolib.StatUtils;
+import com.android.cts.verifier.audio.audiolib.AudioUtils;
+import com.android.cts.verifier.CtsVerifierReportLog;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
+import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
+
+/**
+ * Base class for testing activitiees that require audio loopback hardware..
+ */
+public class AudioLoopbackBaseActivity extends PassFailButtons.Activity {
+    private static final String TAG = "AudioLoopbackBaseActivity";
+
+    // JNI load
+    static {
+        try {
+            System.loadLibrary("audioloopback_jni");
+        } catch (UnsatisfiedLinkError e) {
+            Log.e(TAG, "Error loading Audio Loopback JNI library");
+            Log.e(TAG, "e: " + e);
+            e.printStackTrace();
+        }
+
+        /* TODO: gracefully fail/notify if the library can't be loaded */
+    }
+    protected AudioManager mAudioManager;
+
+    // UI
+    TextView mInputDeviceTxt;
+    TextView mOutputDeviceTxt;
+
+    TextView mAudioLevelText;
+    SeekBar mAudioLevelSeekbar;
+
+    TextView mTestPathTxt;
+
+    TextView mResultText;
+    ProgressBar mProgressBar;
+    int mMaxLevel;
+
+    OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
+    protected Button mTestButton;
+
+    String mYesString;
+    String mNoString;
+
+    // These flags determine the maximum allowed latency
+    private boolean mClaimsProAudio;
+    private boolean mClaimsOutput;
+    private boolean mClaimsInput;
+
+    // Useful info
+    private boolean mSupportsMMAP = AudioUtils.isMMapSupported();
+    private boolean mSupportsMMAPExclusive = AudioUtils.isMMapExclusiveSupported();
+
+    // Peripheral(s)
+    boolean mIsPeripheralAttached;  // CDD ProAudio section C-1-3
+    AudioDeviceInfo mOutputDevInfo;
+    AudioDeviceInfo mInputDevInfo;
+
+    protected static final int TESTPERIPHERAL_INVALID       = -1;
+    protected static final int TESTPERIPHERAL_NONE          = 0;
+    protected static final int TESTPERIPHERAL_ANALOG_JACK   = 1;
+    protected static final int TESTPERIPHERAL_USB           = 2;
+    protected static final int TESTPERIPHERAL_DEVICE        = 3; // device speaker + mic
+    protected int mTestPeripheral = TESTPERIPHERAL_NONE;
+
+    // Loopback Logic
+    NativeAnalyzerThread mNativeAnalyzerThread = null;
+
+    protected static final int NUM_TEST_PHASES = 5;
+    protected int mTestPhase = 0;
+
+    protected double[] mLatencyMillis = new double[NUM_TEST_PHASES];
+    protected double[] mConfidence = new double[NUM_TEST_PHASES];
+
+    protected double mMeanLatencyMillis;
+    protected double mMeanAbsoluteDeviation;
+    protected double mMeanConfidence;
+
+    protected static final double CONFIDENCE_THRESHOLD = 0.6;
+    // impossibly low latencies (indicating something in the test went wrong).
+    protected static final float EPSILON = 1.0f;
+    protected static final double PROAUDIO_RECOMMENDED_LATENCY_MS = 20.0;
+    protected static final double PROAUDIO_RECOMMENDED_USB_LATENCY_MS = 25.0;
+    protected static final double PROAUDIO_MUST_LATENCY_MS = 20.0;
+    protected static final double BASIC_RECOMMENDED_LATENCY_MS = 50.0;
+    protected static final double BASIC_MUST_LATENCY_MS = 800.0;
+    protected double mMustLatency;
+    protected double mRecommendedLatency;
+
+    // The audio stream callback threads should stop and close
+    // in less than a few hundred msec. This is a generous timeout value.
+    private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000;
+
+    //
+    // Common UI Handling
+    //
+    private void connectLoopbackUI() {
+        // Connected Device
+        mInputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackInputLbl));
+        mOutputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackOutputLbl));
+
+        mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text);
+        mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar);
+        mMaxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        mAudioLevelSeekbar.setMax(mMaxLevel);
+        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0);
+        refreshLevel();
+
+        mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {}
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {}
+
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
+                        progress, 0);
+                Log.i(TAG,"Level set to: " + progress);
+                refreshLevel();
+            }
+        });
+
+        mResultText = (TextView)findViewById(R.id.audio_loopback_results_text);
+        mProgressBar = (ProgressBar)findViewById(R.id.audio_loopback_progress_bar);
+        showWait(false);
+    }
+
+    //
+    // Peripheral Connection Logic
+    //
+    protected void scanPeripheralList(AudioDeviceInfo[] devices) {
+        // CDD Section C-1-3: USB port, host-mode support
+
+        // Can't just use the first record because then we will only get
+        // Source OR sink, not both even on devices that are both.
+        mOutputDevInfo = null;
+        mInputDevInfo = null;
+
+        // Any valid peripherals
+        // Do we leave in the Headset test to support a USB-Dongle?
+        for (AudioDeviceInfo devInfo : devices) {
+            if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE || // USB Peripheral
+                    devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET || // USB dongle+LBPlug
+                    devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET || // Loopback Plug?
+                    devInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) { // Aux-cable loopback?
+                if (devInfo.isSink()) {
+                    mOutputDevInfo = devInfo;
+                }
+                if (devInfo.isSource()) {
+                    mInputDevInfo = devInfo;
+                }
+            }  else {
+                handleDeviceConnection(devInfo);
+            }
+        }
+
+        // need BOTH input and output to test
+        mIsPeripheralAttached = mOutputDevInfo != null && mInputDevInfo != null;
+        calculateTestPeripheral();
+        showConnectedAudioPeripheral();
+        calculateLatencyThresholds();
+        displayLatencyThresholds();
+    }
+
+    protected void handleDeviceConnection(AudioDeviceInfo deviceInfo) {
+        // NOP
+    }
+
+    private class ConnectListener extends AudioDeviceCallback {
+        /*package*/ ConnectListener() {}
+
+        //
+        // AudioDevicesManager.OnDeviceConnectionListener
+        //
+        @Override
+        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+        }
+
+        @Override
+        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+        }
+    }
+
+    private void calculateTestPeripheral() {
+        if (!mIsPeripheralAttached) {
+            if ((mOutputDevInfo != null && mInputDevInfo == null)
+                || (mOutputDevInfo == null && mInputDevInfo != null)) {
+                mTestPeripheral = TESTPERIPHERAL_INVALID;
+            } else {
+                mTestPeripheral = TESTPERIPHERAL_DEVICE;
+            }
+        } else if (!areIODevicesOnePeripheral()) {
+            mTestPeripheral = TESTPERIPHERAL_INVALID;
+        } else if (mInputDevInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE ||
+                mInputDevInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET) {
+            mTestPeripheral = TESTPERIPHERAL_USB;
+        } else if (mInputDevInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
+                mInputDevInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) {
+            mTestPeripheral = TESTPERIPHERAL_ANALOG_JACK;
+        } else {
+            // Huh?
+            Log.e(TAG, "No valid peripheral found!?");
+            mTestPeripheral = TESTPERIPHERAL_NONE;
+        }
+    }
+
+    private boolean isPeripheralValidForTest() {
+        return mTestPeripheral == TESTPERIPHERAL_ANALOG_JACK
+                    || mTestPeripheral == TESTPERIPHERAL_USB;
+    }
+
+    private void showConnectedAudioPeripheral() {
+        mInputDeviceTxt.setText(
+                mInputDevInfo != null ? mInputDevInfo.getProductName().toString()
+                        : "Not connected");
+        mOutputDeviceTxt.setText(
+                mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString()
+                        : "Not connected");
+
+        String pathName;
+        switch (mTestPeripheral) {
+            case TESTPERIPHERAL_INVALID:
+                pathName = "Invalid Test Peripheral";
+                break;
+
+            case TESTPERIPHERAL_ANALOG_JACK:
+                pathName = "Headset Jack";
+                break;
+
+            case TESTPERIPHERAL_USB:
+                pathName = "USB";
+                break;
+
+            case TESTPERIPHERAL_DEVICE:
+                pathName = "Device Speaker + Microphone";
+                break;
+
+            case TESTPERIPHERAL_NONE:
+            default:
+                pathName = "Error. Unknown Test Path";
+                break;
+        }
+        mTestPathTxt.setText(pathName);
+        mTestButton.setEnabled(
+                mTestPeripheral != TESTPERIPHERAL_INVALID && mTestPeripheral != TESTPERIPHERAL_NONE);
+
+    }
+
+    private boolean areIODevicesOnePeripheral() {
+        if (mOutputDevInfo == null || mInputDevInfo == null) {
+            return false;
+        }
+
+        return mOutputDevInfo.getProductName().toString().equals(
+                mInputDevInfo.getProductName().toString());
+    }
+
+    private void calculateLatencyThresholds() {
+        switch (mTestPeripheral) {
+            case TESTPERIPHERAL_ANALOG_JACK:
+                mRecommendedLatency = mClaimsProAudio
+                        ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
+                mMustLatency =  mClaimsProAudio
+                        ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_MUST_LATENCY_MS;
+                break;
+
+            case TESTPERIPHERAL_USB:
+                mRecommendedLatency = mClaimsProAudio
+                        ? PROAUDIO_RECOMMENDED_USB_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
+                mMustLatency = mClaimsProAudio
+                        ? PROAUDIO_RECOMMENDED_USB_LATENCY_MS : BASIC_MUST_LATENCY_MS;
+                break;
+
+            case TESTPERIPHERAL_DEVICE:
+                // This isn't a valid case so we won't pass it, but it can be run
+                mRecommendedLatency = mClaimsProAudio
+                        ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
+                mMustLatency = mClaimsProAudio
+                        ? PROAUDIO_RECOMMENDED_LATENCY_MS :BASIC_MUST_LATENCY_MS;
+                break;
+
+            case TESTPERIPHERAL_NONE:
+            default:
+                mRecommendedLatency = BASIC_RECOMMENDED_LATENCY_MS;
+                mMustLatency = BASIC_MUST_LATENCY_MS;
+                break;
+        }
+    }
+
+    private void displayLatencyThresholds() {
+        if (isPeripheralValidForTest()) {
+            ((TextView) findViewById(R.id.audio_loopback_must_latency)).setText("" + mMustLatency);
+            ((TextView) findViewById(R.id.audio_loopback_recommended_latency)).setText(
+                    "" + mRecommendedLatency);
+        } else {
+            String naStr = getResources().getString(R.string.audio_proaudio_NA);
+            ((TextView) findViewById(R.id.audio_loopback_must_latency)).setText(naStr);
+            ((TextView) findViewById(R.id.audio_loopback_recommended_latency)).setText(naStr);
+        }
+    }
+
+    /**
+     * refresh Audio Level seekbar and text
+     */
+    private void refreshLevel() {
+        int currentLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+        mAudioLevelSeekbar.setProgress(currentLevel);
+
+        String levelText = String.format("%s: %d/%d",
+                getResources().getString(R.string.audio_loopback_level_text),
+                currentLevel, mMaxLevel);
+        mAudioLevelText.setText(levelText);
+    }
+
+    //
+    // show active progress bar
+    //
+    protected void showWait(boolean show) {
+        mProgressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
+    }
+
+    //
+    // Common loging
+    //
+    // Schema
+    private static final String KEY_LATENCY = "latency";
+    private static final String KEY_CONFIDENCE = "confidence";
+    private static final String KEY_SAMPLE_RATE = "sample_rate";
+    private static final String KEY_IS_PRO_AUDIO = "is_pro_audio";
+    private static final String KEY_IS_LOW_LATENCY = "is_low_latency";
+    private static final String KEY_IS_PERIPHERAL_ATTACHED = "is_peripheral_attached";
+    private static final String KEY_INPUT_PERIPHERAL_NAME = "input_peripheral";
+    private static final String KEY_OUTPUT_PERIPHERAL_NAME = "output_peripheral";
+    private static final String KEY_TEST_PERIPHERAL = "test_peripheral";
+    private static final String KEY_TEST_MMAP = "supports_mmap";
+    private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive";
+    private static final String KEY_LEVEL = "level";
+    private static final String KEY_BUFFER_SIZE = "buffer_size_in_frames";
+
+    @Override
+    public String getTestId() {
+        return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
+    }
+
+    //
+    // Subclasses should call this explicitly. SubClasses should call submit() after their logs
+    //
+    @Override
+    public void recordTestResults() {
+        if (mNativeAnalyzerThread == null) {
+            return; // no results to report
+        }
+
+        CtsVerifierReportLog reportLog = getReportLog();
+        reportLog.addValue(
+                KEY_LATENCY,
+                mMeanLatencyMillis,
+                ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+
+        reportLog.addValue(
+                KEY_CONFIDENCE,
+                mMeanConfidence,
+                ResultType.HIGHER_BETTER,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                KEY_SAMPLE_RATE,
+                mNativeAnalyzerThread.getSampleRate(),
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                KEY_IS_LOW_LATENCY,
+                mNativeAnalyzerThread.isLowLatencyStream(),
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                KEY_IS_PERIPHERAL_ATTACHED,
+                mIsPeripheralAttached,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                KEY_IS_PRO_AUDIO,
+                mClaimsProAudio,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                KEY_TEST_PERIPHERAL,
+                mTestPeripheral,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                KEY_TEST_MMAP,
+                mSupportsMMAP,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                KEY_TEST_MMAPEXCLUSIVE ,
+                mSupportsMMAPExclusive,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        if (mIsPeripheralAttached) {
+            reportLog.addValue(
+                    KEY_INPUT_PERIPHERAL_NAME,
+                    mInputDevInfo != null ? mInputDevInfo.getProductName().toString() : "None",
+                    ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+
+            reportLog.addValue(
+                    KEY_OUTPUT_PERIPHERAL_NAME,
+                    mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString() : "None",
+                    ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+        }
+
+        int audioLevel = mAudioLevelSeekbar.getProgress();
+        reportLog.addValue(
+                KEY_LEVEL,
+                audioLevel,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.submit();
+    }
+
+    private void startAudioTest(Handler messageHandler) {
+        getPassButton().setEnabled(false);
+
+        mTestPhase = 0;
+        java.util.Arrays.fill(mLatencyMillis, 0.0);
+        java.util.Arrays.fill(mConfidence, 0.0);
+
+        mNativeAnalyzerThread = new NativeAnalyzerThread(this);
+        if (mNativeAnalyzerThread != null) {
+            mNativeAnalyzerThread.setMessageHandler(messageHandler);
+            // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
+            mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION);
+            startTestPhase();
+        } else {
+            Log.e(TAG, "Couldn't allocate native analyzer thread");
+            mResultText.setText(getResources().getString(R.string.audio_loopback_failure));
+        }
+    }
+
+    private void startTestPhase() {
+        if (mNativeAnalyzerThread != null) {
+            mNativeAnalyzerThread.startTest();
+
+            // what is this for?
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private void handleTestCompletion() {
+        mMeanLatencyMillis = StatUtils.calculateMean(mLatencyMillis);
+        mMeanAbsoluteDeviation =
+                StatUtils.calculateMeanAbsoluteDeviation(mMeanLatencyMillis, mLatencyMillis);
+        mMeanConfidence = StatUtils.calculateMean(mConfidence);
+
+        boolean pass = isPeripheralValidForTest()
+                && mMeanConfidence >= CONFIDENCE_THRESHOLD
+                && mMeanLatencyMillis > EPSILON
+                && mMeanLatencyMillis < mMustLatency;
+
+        getPassButton().setEnabled(pass);
+
+        String result;
+        if (mMeanConfidence < CONFIDENCE_THRESHOLD) {
+            result = String.format(
+                    "Test Finished\nInsufficient Confidence (%.2f < %.2f). No Results.",
+                    mMeanConfidence, CONFIDENCE_THRESHOLD);
+        } else {
+            result = String.format(
+                    "Test Finished - %s\nMean Latency:%.2f ms (required:%.2f)\n" +
+                            "Mean Absolute Deviation: %.2f\n" +
+                            " Confidence: %.2f\n" +
+                            " Low Latency Path: %s",
+                    (pass ? "PASS" : "FAIL"),
+                    mMeanLatencyMillis,
+                    mMustLatency,
+                    mMeanAbsoluteDeviation,
+                    mMeanConfidence,
+                    mNativeAnalyzerThread.isLowLatencyStream() ? mYesString : mNoString);
+        }
+
+        // Make sure the test thread is finished. It should already be done.
+        if (mNativeAnalyzerThread != null) {
+            try {
+                mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        mResultText.setText(result);
+
+        recordTestResults();
+
+        showWait(false);
+        mTestButton.setEnabled(true);
+    }
+
+    private void handleTestPhaseCompletion() {
+        if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) {
+            mLatencyMillis[mTestPhase] = mNativeAnalyzerThread.getLatencyMillis();
+            mConfidence[mTestPhase] = mNativeAnalyzerThread.getConfidence();
+
+            String result = String.format(
+                    "Test %d Finished\nLatency: %.2f ms\nConfidence: %.2f\n",
+                    mTestPhase,
+                    mLatencyMillis[mTestPhase],
+                    mConfidence[mTestPhase]);
+
+            mResultText.setText(result);
+            try {
+                mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
+                // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+
+            mTestPhase++;
+            if (mTestPhase >= NUM_TEST_PHASES) {
+                handleTestCompletion();
+            } else {
+                startTestPhase();
+            }
+        }
+    }
+
+    /**
+     * handler for messages from audio thread
+     */
+    private Handler mMessageHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            super.handleMessage(msg);
+            switch(msg.what) {
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
+                    Log.v(TAG,"got message native rec started!!");
+                    showWait(true);
+                    mResultText.setText(String.format("[phase: %d] - Test Running...",
+                            (mTestPhase + 1)));
+                    break;
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR:
+                    Log.v(TAG,"got message native rec can't start!!");
+                    mResultText.setText("Test Error opening streams.");
+                    handleTestCompletion();
+                    break;
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR:
+                    Log.v(TAG,"got message native rec can't start!!");
+                    mResultText.setText("Test Error while recording.");
+                    handleTestCompletion();
+                    break;
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
+                    mResultText.setText("Test FAILED due to errors.");
+                    handleTestCompletion();
+                    break;
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING:
+                    Log.i(TAG, "NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING");
+                    mResultText.setText(String.format("[phase: %d] - Analyzing ...",
+                            mTestPhase + 1));
+                    break;
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
+                    Log.i(TAG, "NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE");
+                    handleTestPhaseCompletion();
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.audio_loopback_latency_activity);
+
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+        setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1);
+
+        mClaimsOutput = AudioSystemFlags.claimsOutput(this);
+        mClaimsInput = AudioSystemFlags.claimsInput(this);
+        mClaimsProAudio = AudioSystemFlags.claimsProAudio(this);
+
+        mYesString = getResources().getString(R.string.audio_general_yes);
+        mNoString = getResources().getString(R.string.audio_general_no);
+
+        // Pro Audio
+        ((TextView)findViewById(R.id.audio_loopback_pro_audio)).setText(
+                "" + (mClaimsProAudio ? mYesString : mNoString));
+
+        // MMAP
+        ((TextView)findViewById(R.id.audio_loopback_mmap)).setText(
+                "" + (mSupportsMMAP ? mYesString : mNoString));
+        ((TextView)findViewById(R.id.audio_loopback_mmap_exclusive)).setText(
+                "" + (mSupportsMMAPExclusive ? mYesString : mNoString));
+
+        // Low Latency
+        ((TextView)findViewById(R.id.audio_loopback_low_latency)).setText(
+                "" + (AudioSystemFlags.claimsLowLatencyAudio(this) ? mYesString : mNoString));
+
+        mTestPathTxt = ((TextView)findViewById(R.id.audio_loopback_testpath));
+
+        mTestButton = (Button)findViewById(R.id.audio_loopback_test_btn);
+        mTestButton.setOnClickListener(mBtnClickListener);
+
+        mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
+
+        mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
+
+        connectLoopbackUI();
+
+        calculateLatencyThresholds();
+        displayLatencyThresholds();
+    }
+
+    private class OnBtnClickListener implements OnClickListener {
+        @Override
+        public void onClick(View v) {
+            switch (v.getId()) {
+                case R.id.audio_loopback_test_btn:
+                    Log.i(TAG, "audio loopback test");
+                    startAudioTest(mMessageHandler);
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java
index 8ee3446..9490091 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -16,679 +16,9 @@
 
 package com.android.cts.verifier.audio;
 
-import android.app.AlertDialog;
-import android.media.AudioDeviceCallback;
-import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
-import android.media.MediaRecorder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.ProgressBar;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.audio.audiolib.AudioSystemFlags;
-import com.android.cts.verifier.audio.audiolib.StatUtils;
-import com.android.cts.verifier.audio.audiolib.AudioUtils;
-import com.android.cts.verifier.CtsVerifierReportLog;
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
-import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
-
 /**
- * CtsVerifier Audio Loopback Latency Test
+ * Tests Audio Device roundtrip latency by using a loopback plug.
  */
-public class AudioLoopbackLatencyActivity extends PassFailButtons.Activity {
-    private static final String TAG = "AudioLoopbackLatencyActivity";
-
-    // JNI load
-    static {
-        try {
-            System.loadLibrary("audioloopback_jni");
-        } catch (UnsatisfiedLinkError e) {
-            Log.e(TAG, "Error loading Audio Loopback JNI library");
-            Log.e(TAG, "e: " + e);
-            e.printStackTrace();
-        }
-
-        /* TODO: gracefully fail/notify if the library can't be loaded */
-    }
-    protected AudioManager mAudioManager;
-
-    // UI
-    TextView mInputDeviceTxt;
-    TextView mOutputDeviceTxt;
-
-    TextView mAudioLevelText;
-    SeekBar mAudioLevelSeekbar;
-
-    TextView mTestPathTxt;
-
-    TextView mResultText;
-    ProgressBar mProgressBar;
-    int mMaxLevel;
-
-    OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
-    protected Button mTestButton;
-
-    String mYesString;
-    String mNoString;
-
-    // These flags determine the maximum allowed latency
-    private boolean mClaimsProAudio;
-    private boolean mClaimsOutput;
-    private boolean mClaimsInput;
-
-    // Useful info
-    private boolean mSupportsMMAP = AudioUtils.isMMapSupported();
-    private boolean mSupportsMMAPExclusive = AudioUtils.isMMapExclusiveSupported();
-
-    // Peripheral(s)
-    boolean mIsPeripheralAttached;  // CDD ProAudio section C-1-3
-    AudioDeviceInfo mOutputDevInfo;
-    AudioDeviceInfo mInputDevInfo;
-
-    protected static final int TESTPERIPHERAL_INVALID       = -1;
-    protected static final int TESTPERIPHERAL_NONE          = 0;
-    protected static final int TESTPERIPHERAL_ANALOG_JACK   = 1;
-    protected static final int TESTPERIPHERAL_USB           = 2;
-    protected static final int TESTPERIPHERAL_DEVICE        = 3; // device speaker + mic
-    protected int mTestPeripheral = TESTPERIPHERAL_NONE;
-
-    // Loopback Logic
-    NativeAnalyzerThread mNativeAnalyzerThread = null;
-
-    protected static final int NUM_TEST_PHASES = 5;
-    protected int mTestPhase = 0;
-
-    protected double[] mLatencyMillis = new double[NUM_TEST_PHASES];
-    protected double[] mConfidence = new double[NUM_TEST_PHASES];
-
-    protected double mMeanLatencyMillis;
-    protected double mMeanAbsoluteDeviation;
-    protected double mMeanConfidence;
-
-    protected static final double CONFIDENCE_THRESHOLD = 0.6;
-    // impossibly low latencies (indicating something in the test went wrong).
-    protected static final float EPSILON = 1.0f;
-    protected static final double PROAUDIO_RECOMMENDED_LATENCY_MS = 20.0;
-    protected static final double PROAUDIO_RECOMMENDED_USB_LATENCY_MS = 25.0;
-    protected static final double PROAUDIO_MUST_LATENCY_MS = 20.0;
-    protected static final double BASIC_RECOMMENDED_LATENCY_MS = 50.0;
-    protected static final double BASIC_MUST_LATENCY_MS = 800.0;
-    protected double mMustLatency;
-    protected double mRecommendedLatency;
-
-    // The audio stream callback threads should stop and close
-    // in less than a few hundred msec. This is a generous timeout value.
-    private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000;
-
-    //
-    // Common UI Handling
-    //
-    private void connectLoopbackUI() {
-        // Connected Device
-        mInputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackInputLbl));
-        mOutputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackOutputLbl));
-
-        mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text);
-        mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar);
-        mMaxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        mAudioLevelSeekbar.setMax(mMaxLevel);
-        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0);
-        refreshLevel();
-
-        mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-            @Override
-            public void onStopTrackingTouch(SeekBar seekBar) {}
-
-            @Override
-            public void onStartTrackingTouch(SeekBar seekBar) {}
-
-            @Override
-            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
-                        progress, 0);
-                Log.i(TAG,"Level set to: " + progress);
-                refreshLevel();
-            }
-        });
-
-        mResultText = (TextView)findViewById(R.id.audio_loopback_results_text);
-        mProgressBar = (ProgressBar)findViewById(R.id.audio_loopback_progress_bar);
-        showWait(false);
-    }
-
-    //
-    // Peripheral Connection Logic
-    //
-    protected void scanPeripheralList(AudioDeviceInfo[] devices) {
-        // CDD Section C-1-3: USB port, host-mode support
-
-        // Can't just use the first record because then we will only get
-        // Source OR sink, not both even on devices that are both.
-        mOutputDevInfo = null;
-        mInputDevInfo = null;
-
-        // Any valid peripherals
-        // Do we leave in the Headset test to support a USB-Dongle?
-        for (AudioDeviceInfo devInfo : devices) {
-            if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE || // USB Peripheral
-                    devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET || // USB dongle+LBPlug
-                    devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET || // Loopback Plug?
-                    devInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) { // Aux-cable loopback?
-                if (devInfo.isSink()) {
-                    mOutputDevInfo = devInfo;
-                }
-                if (devInfo.isSource()) {
-                    mInputDevInfo = devInfo;
-                }
-            }  else {
-                handleDeviceConnection(devInfo);
-            }
-        }
-
-        // need BOTH input and output to test
-        mIsPeripheralAttached = mOutputDevInfo != null && mInputDevInfo != null;
-        calculateTestPeripheral();
-        showConnectedAudioPeripheral();
-        calculateLatencyThresholds();
-        displayLatencyThresholds();
-    }
-
-    protected void handleDeviceConnection(AudioDeviceInfo deviceInfo) {
-        // NOP
-    }
-
-    private class ConnectListener extends AudioDeviceCallback {
-        /*package*/ ConnectListener() {}
-
-        //
-        // AudioDevicesManager.OnDeviceConnectionListener
-        //
-        @Override
-        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
-            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
-        }
-
-        @Override
-        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
-            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
-        }
-    }
-
-    private void calculateTestPeripheral() {
-        if (!mIsPeripheralAttached) {
-            if ((mOutputDevInfo != null && mInputDevInfo == null)
-                || (mOutputDevInfo == null && mInputDevInfo != null)) {
-                mTestPeripheral = TESTPERIPHERAL_INVALID;
-            } else {
-                mTestPeripheral = TESTPERIPHERAL_DEVICE;
-            }
-        } else if (!areIODevicesOnePeripheral()) {
-            mTestPeripheral = TESTPERIPHERAL_INVALID;
-        } else if (mInputDevInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE ||
-                mInputDevInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET) {
-            mTestPeripheral = TESTPERIPHERAL_USB;
-        } else if (mInputDevInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
-                mInputDevInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) {
-            mTestPeripheral = TESTPERIPHERAL_ANALOG_JACK;
-        } else {
-            // Huh?
-            Log.e(TAG, "No valid peripheral found!?");
-            mTestPeripheral = TESTPERIPHERAL_NONE;
-        }
-    }
-
-    private boolean isPeripheralValidForTest() {
-        return mTestPeripheral == TESTPERIPHERAL_ANALOG_JACK
-                    || mTestPeripheral == TESTPERIPHERAL_USB;
-    }
-
-    private void showConnectedAudioPeripheral() {
-        mInputDeviceTxt.setText(
-                mInputDevInfo != null ? mInputDevInfo.getProductName().toString()
-                        : "Not connected");
-        mOutputDeviceTxt.setText(
-                mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString()
-                        : "Not connected");
-
-        String pathName;
-        switch (mTestPeripheral) {
-            case TESTPERIPHERAL_INVALID:
-                pathName = "Invalid Test Peripheral";
-                break;
-
-            case TESTPERIPHERAL_ANALOG_JACK:
-                pathName = "Headset Jack";
-                break;
-
-            case TESTPERIPHERAL_USB:
-                pathName = "USB";
-                break;
-
-            case TESTPERIPHERAL_DEVICE:
-                pathName = "Device Speaker + Microphone";
-                break;
-
-            case TESTPERIPHERAL_NONE:
-            default:
-                pathName = "Error. Unknown Test Path";
-                break;
-        }
-        mTestPathTxt.setText(pathName);
-        mTestButton.setEnabled(
-                mTestPeripheral != TESTPERIPHERAL_INVALID && mTestPeripheral != TESTPERIPHERAL_NONE);
-
-    }
-
-    private boolean areIODevicesOnePeripheral() {
-        if (mOutputDevInfo == null || mInputDevInfo == null) {
-            return false;
-        }
-
-        return mOutputDevInfo.getProductName().toString().equals(
-                mInputDevInfo.getProductName().toString());
-    }
-
-    private void calculateLatencyThresholds() {
-        switch (mTestPeripheral) {
-            case TESTPERIPHERAL_ANALOG_JACK:
-                mRecommendedLatency = mClaimsProAudio
-                        ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
-                mMustLatency =  mClaimsProAudio
-                        ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_MUST_LATENCY_MS;
-                break;
-
-            case TESTPERIPHERAL_USB:
-                mRecommendedLatency = mClaimsProAudio
-                        ? PROAUDIO_RECOMMENDED_USB_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
-                mMustLatency = mClaimsProAudio
-                        ? PROAUDIO_RECOMMENDED_USB_LATENCY_MS : BASIC_MUST_LATENCY_MS;
-                break;
-
-            case TESTPERIPHERAL_DEVICE:
-                // This isn't a valid case so we won't pass it, but it can be run
-                mRecommendedLatency = mClaimsProAudio
-                        ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
-                mMustLatency = mClaimsProAudio
-                        ? PROAUDIO_RECOMMENDED_LATENCY_MS :BASIC_MUST_LATENCY_MS;
-                break;
-
-            case TESTPERIPHERAL_NONE:
-            default:
-                mRecommendedLatency = BASIC_RECOMMENDED_LATENCY_MS;
-                mMustLatency = BASIC_MUST_LATENCY_MS;
-                break;
-        }
-    }
-
-    private void displayLatencyThresholds() {
-        if (isPeripheralValidForTest()) {
-            ((TextView) findViewById(R.id.audio_loopback_must_latency)).setText("" + mMustLatency);
-            ((TextView) findViewById(R.id.audio_loopback_recommended_latency)).setText(
-                    "" + mRecommendedLatency);
-        } else {
-            String naStr = getResources().getString(R.string.audio_proaudio_NA);
-            ((TextView) findViewById(R.id.audio_loopback_must_latency)).setText(naStr);
-            ((TextView) findViewById(R.id.audio_loopback_recommended_latency)).setText(naStr);
-        }
-    }
-
-    /**
-     * refresh Audio Level seekbar and text
-     */
-    private void refreshLevel() {
-        int currentLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
-        mAudioLevelSeekbar.setProgress(currentLevel);
-
-        String levelText = String.format("%s: %d/%d",
-                getResources().getString(R.string.audio_loopback_level_text),
-                currentLevel, mMaxLevel);
-        mAudioLevelText.setText(levelText);
-    }
-
-    //
-    // show active progress bar
-    //
-    protected void showWait(boolean show) {
-        mProgressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
-    }
-
-    //
-    // Common logging
-    //
-    // Schema
-    private static final String KEY_LATENCY = "latency";
-    private static final String KEY_CONFIDENCE = "confidence";
-    private static final String KEY_SAMPLE_RATE = "sample_rate";
-    private static final String KEY_IS_PRO_AUDIO = "is_pro_audio";
-    private static final String KEY_IS_LOW_LATENCY = "is_low_latency";
-    private static final String KEY_IS_PERIPHERAL_ATTACHED = "is_peripheral_attached";
-    private static final String KEY_INPUT_PERIPHERAL_NAME = "input_peripheral";
-    private static final String KEY_OUTPUT_PERIPHERAL_NAME = "output_peripheral";
-    private static final String KEY_TEST_PERIPHERAL = "test_peripheral";
-    private static final String KEY_TEST_MMAP = "supports_mmap";
-    private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive";
-    private static final String KEY_LEVEL = "level";
-    private static final String KEY_BUFFER_SIZE = "buffer_size_in_frames";
-
-    @Override
-    public String getTestId() {
-        return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
-    }
-
-    @Override
-    public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; }
-
-    @Override
-    public final String getReportSectionName() {
-        return setTestNameSuffix(sCurrentDisplayMode, "audio_loopback_latency_activity");
-    }
-
-    //
-    // Subclasses should call this explicitly. SubClasses should call submit() after their logs
-    //
-    @Override
-    public void recordTestResults() {
-        if (mNativeAnalyzerThread == null) {
-            return; // no results to report
-        }
-
-        CtsVerifierReportLog reportLog = getReportLog();
-        reportLog.addValue(
-                KEY_LATENCY,
-                mMeanLatencyMillis,
-                ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-
-        reportLog.addValue(
-                KEY_CONFIDENCE,
-                mMeanConfidence,
-                ResultType.HIGHER_BETTER,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
-                KEY_SAMPLE_RATE,
-                mNativeAnalyzerThread.getSampleRate(),
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
-                KEY_IS_LOW_LATENCY,
-                mNativeAnalyzerThread.isLowLatencyStream(),
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
-                KEY_IS_PERIPHERAL_ATTACHED,
-                mIsPeripheralAttached,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
-                KEY_IS_PRO_AUDIO,
-                mClaimsProAudio,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
-                KEY_TEST_PERIPHERAL,
-                mTestPeripheral,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
-                KEY_TEST_MMAP,
-                mSupportsMMAP,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
-                KEY_TEST_MMAPEXCLUSIVE ,
-                mSupportsMMAPExclusive,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        if (mIsPeripheralAttached) {
-            reportLog.addValue(
-                    KEY_INPUT_PERIPHERAL_NAME,
-                    mInputDevInfo != null ? mInputDevInfo.getProductName().toString() : "None",
-                    ResultType.NEUTRAL,
-                    ResultUnit.NONE);
-
-            reportLog.addValue(
-                    KEY_OUTPUT_PERIPHERAL_NAME,
-                    mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString() : "None",
-                    ResultType.NEUTRAL,
-                    ResultUnit.NONE);
-        }
-
-        int audioLevel = mAudioLevelSeekbar.getProgress();
-        reportLog.addValue(
-                KEY_LEVEL,
-                audioLevel,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.submit();
-    }
-
-    private void startAudioTest(Handler messageHandler) {
-        getPassButton().setEnabled(false);
-
-        mTestPhase = 0;
-        java.util.Arrays.fill(mLatencyMillis, 0.0);
-        java.util.Arrays.fill(mConfidence, 0.0);
-
-        mNativeAnalyzerThread = new NativeAnalyzerThread(this);
-        if (mNativeAnalyzerThread != null) {
-            mNativeAnalyzerThread.setMessageHandler(messageHandler);
-            // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
-            mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION);
-            startTestPhase();
-        } else {
-            Log.e(TAG, "Couldn't allocate native analyzer thread");
-            mResultText.setText(getResources().getString(R.string.audio_loopback_failure));
-        }
-    }
-
-    private void startTestPhase() {
-        if (mNativeAnalyzerThread != null) {
-            mNativeAnalyzerThread.startTest();
-
-            // what is this for?
-            try {
-                Thread.sleep(200);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-        }
-    }
-
-    private void handleTestCompletion() {
-        mMeanLatencyMillis = StatUtils.calculateMean(mLatencyMillis);
-        mMeanAbsoluteDeviation =
-                StatUtils.calculateMeanAbsoluteDeviation(mMeanLatencyMillis, mLatencyMillis);
-        mMeanConfidence = StatUtils.calculateMean(mConfidence);
-
-        boolean pass = isPeripheralValidForTest()
-                && mMeanConfidence >= CONFIDENCE_THRESHOLD
-                && mMeanLatencyMillis > EPSILON
-                && mMeanLatencyMillis < mMustLatency;
-
-        getPassButton().setEnabled(pass);
-
-        String result;
-        if (mMeanConfidence < CONFIDENCE_THRESHOLD) {
-            result = String.format(
-                    "Test Finished\nInsufficient Confidence (%.2f < %.2f). No Results.",
-                    mMeanConfidence, CONFIDENCE_THRESHOLD);
-        } else {
-            result = String.format(
-                    "Test Finished - %s\nMean Latency:%.2f ms (required:%.2f)\n" +
-                            "Mean Absolute Deviation: %.2f\n" +
-                            " Confidence: %.2f\n" +
-                            " Low Latency Path: %s",
-                    (pass ? "PASS" : "FAIL"),
-                    mMeanLatencyMillis,
-                    mMustLatency,
-                    mMeanAbsoluteDeviation,
-                    mMeanConfidence,
-                    mNativeAnalyzerThread.isLowLatencyStream() ? mYesString : mNoString);
-        }
-
-        // Make sure the test thread is finished. It should already be done.
-        if (mNativeAnalyzerThread != null) {
-            try {
-                mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-        }
-        mResultText.setText(result);
-
-        recordTestResults();
-
-        showWait(false);
-        mTestButton.setEnabled(true);
-    }
-
-    private void handleTestPhaseCompletion() {
-        if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) {
-            mLatencyMillis[mTestPhase] = mNativeAnalyzerThread.getLatencyMillis();
-            mConfidence[mTestPhase] = mNativeAnalyzerThread.getConfidence();
-
-            String result = String.format(
-                    "Test %d Finished\nLatency: %.2f ms\nConfidence: %.2f\n",
-                    mTestPhase,
-                    mLatencyMillis[mTestPhase],
-                    mConfidence[mTestPhase]);
-
-            mResultText.setText(result);
-            try {
-                mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
-                // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-
-            mTestPhase++;
-            if (mTestPhase >= NUM_TEST_PHASES) {
-                handleTestCompletion();
-            } else {
-                startTestPhase();
-            }
-        }
-    }
-
-    /**
-     * handler for messages from audio thread
-     */
-    private Handler mMessageHandler = new Handler() {
-        public void handleMessage(Message msg) {
-            super.handleMessage(msg);
-            switch(msg.what) {
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
-                    Log.v(TAG,"got message native rec started!!");
-                    showWait(true);
-                    mResultText.setText(String.format("[phase: %d] - Test Running...",
-                            (mTestPhase + 1)));
-                    break;
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR:
-                    Log.v(TAG,"got message native rec can't start!!");
-                    mResultText.setText("Test Error opening streams.");
-                    handleTestCompletion();
-                    break;
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR:
-                    Log.v(TAG,"got message native rec can't start!!");
-                    mResultText.setText("Test Error while recording.");
-                    handleTestCompletion();
-                    break;
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
-                    mResultText.setText("Test FAILED due to errors.");
-                    handleTestCompletion();
-                    break;
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING:
-                    Log.i(TAG, "NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING");
-                    mResultText.setText(String.format("[phase: %d] - Analyzing ...",
-                            mTestPhase + 1));
-                    break;
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
-                    Log.i(TAG, "NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE");
-                    handleTestPhaseCompletion();
-                    break;
-                default:
-                    break;
-            }
-        }
-    };
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.audio_loopback_latency_activity);
-
-        setPassFailButtonClickListeners();
-        getPassButton().setEnabled(false);
-        setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1);
-
-        mClaimsOutput = AudioSystemFlags.claimsOutput(this);
-        mClaimsInput = AudioSystemFlags.claimsInput(this);
-        mClaimsProAudio = AudioSystemFlags.claimsProAudio(this);
-
-        mYesString = getResources().getString(R.string.audio_general_yes);
-        mNoString = getResources().getString(R.string.audio_general_no);
-
-        // Pro Audio
-        ((TextView)findViewById(R.id.audio_loopback_pro_audio)).setText(
-                "" + (mClaimsProAudio ? mYesString : mNoString));
-
-        // MMAP
-        ((TextView)findViewById(R.id.audio_loopback_mmap)).setText(
-                "" + (mSupportsMMAP ? mYesString : mNoString));
-        ((TextView)findViewById(R.id.audio_loopback_mmap_exclusive)).setText(
-                "" + (mSupportsMMAPExclusive ? mYesString : mNoString));
-
-        // Low Latency
-        ((TextView)findViewById(R.id.audio_loopback_low_latency)).setText(
-                "" + (AudioSystemFlags.claimsLowLatencyAudio(this) ? mYesString : mNoString));
-
-        mTestPathTxt = ((TextView)findViewById(R.id.audio_loopback_testpath));
-
-        mTestButton = (Button)findViewById(R.id.audio_loopback_test_btn);
-        mTestButton.setOnClickListener(mBtnClickListener);
-
-        mAudioManager = getSystemService(AudioManager.class);
-
-        mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
-
-        connectLoopbackUI();
-
-        calculateLatencyThresholds();
-        displayLatencyThresholds();
-    }
-
-    private class OnBtnClickListener implements OnClickListener {
-        @Override
-        public void onClick(View v) {
-            switch (v.getId()) {
-                case R.id.audio_loopback_test_btn:
-                    Log.i(TAG, "audio loopback test");
-                    startAudioTest(mMessageHandler);
-                    break;
-            }
-        }
-    }
+public class AudioLoopbackLatencyActivity extends AudioLoopbackBaseActivity {
+    private static final String TAG = AudioLoopbackLatencyActivity.class.getSimpleName();
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
index 9804cd3..d32749f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
@@ -67,7 +67,6 @@
     private static final String INFO_DIALOG_MESSAGE_ID = "infoDialogMessageId";
 
     // ReportLog Schema
-    private static final String SECTION_PRO_AUDIO_ACTIVITY = "pro_audio_activity";
     private static final String KEY_CLAIMS_PRO = "claims_pro_audio";
     private static final String KEY_CLAIMS_LOW_LATENCY = "claims_low_latency_audio";
     private static final String KEY_CLAIMS_MIDI = "claims_midi";
@@ -233,14 +232,6 @@
     // PassFailButtons Overrides
     //
     @Override
-    public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; }
-
-    @Override
-    public final String getReportSectionName() {
-        return setTestNameSuffix(sCurrentDisplayMode, SECTION_PRO_AUDIO_ACTIVITY);
-    }
-
-    @Override
     public void recordTestResults() {
 
         CtsVerifierReportLog reportLog = getReportLog();