• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.cts.verifier.audio;
18 
19 import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
20 import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
21 
22 import android.content.res.Resources;
23 import android.media.AudioDeviceCallback;
24 import android.media.AudioDeviceInfo;
25 import android.media.AudioManager;
26 import android.media.MediaRecorder;
27 import android.os.Build;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.util.Log;
32 import android.view.View;
33 import android.view.View.OnClickListener;
34 import android.widget.Button;
35 import android.widget.ProgressBar;
36 import android.widget.SeekBar;
37 import android.widget.TextView;
38 
39 import com.android.compatibility.common.util.CddTest;
40 import com.android.compatibility.common.util.ResultType;
41 import com.android.compatibility.common.util.ResultUnit;
42 import com.android.cts.verifier.CtsVerifierReportLog;
43 import com.android.cts.verifier.PassFailButtons;
44 import com.android.cts.verifier.R;
45 import com.android.cts.verifier.audio.audiolib.AudioSystemFlags;
46 import com.android.cts.verifier.audio.audiolib.AudioUtils;
47 import com.android.cts.verifier.audio.audiolib.StatUtils;
48 
49 /**
50  * CtsVerifier Audio Loopback Latency Test
51  */
52 @CddTest(requirement = "5.10/C-1-2,C-1-5")
53 public class AudioLoopbackLatencyActivity extends PassFailButtons.Activity {
54     private static final String TAG = "AudioLoopbackLatencyActivity";
55 
56     // JNI load
57     static {
58         try {
59             System.loadLibrary("audioloopback_jni");
60         } catch (UnsatisfiedLinkError e) {
61             Log.e(TAG, "Error loading Audio Loopback JNI library");
62             Log.e(TAG, "e: " + e);
63             e.printStackTrace();
64         }
65 
66         /* TODO: gracefully fail/notify if the library can't be loaded */
67     }
68     protected AudioManager mAudioManager;
69 
70     // UI
71     TextView[] mResultsText = new TextView[NUM_TEST_ROUTES];
72 
73     TextView mAudioLevelText;
74     SeekBar mAudioLevelSeekbar;
75 
76     TextView mTestStatusText;
77     ProgressBar mProgressBar;
78     int mMaxLevel;
79 
80     private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
81     private Button[] mStartButtons = new Button[NUM_TEST_ROUTES];
82 
83     String mYesString;
84     String mNoString;
85 
86     String mPassString;
87     String mFailString;
88     String mNotTestedString;
89     String mNotRequiredString;
90     String mRequiredString;
91 
92     // These flags determine the maximum allowed latency
93     private boolean mClaimsProAudio;
94     private boolean mClaimsLowLatency;
95     private boolean mClaimsMediaPerformance;
96     private boolean mClaimsOutput;
97     private boolean mClaimsInput;
98 
99     // Useful info
100     private boolean mSupportsMMAP = AudioUtils.isMMapSupported();
101     private boolean mSupportsMMAPExclusive = AudioUtils.isMMapExclusiveSupported();
102 
103     // Peripheral(s)
104     private static final int NUM_TEST_ROUTES =       3;
105     private static final int TESTROUTE_DEVICE =      0; // device speaker + mic
106     private static final int TESTROUTE_ANALOG_JACK = 1;
107     private static final int TESTROUTE_USB =         2;
108     private int mTestRoute = TESTROUTE_DEVICE;
109 
110     // Loopback Logic
111     private NativeAnalyzerThread mNativeAnalyzerThread = null;
112 
113     protected static final int NUM_TEST_PHASES = 5;
114     protected int mTestPhase = 0;
115 
116     private static final double CONFIDENCE_THRESHOLD_AMBIENT = 0.6;
117     private static final double CONFIDENCE_THRESHOLD_WIRED = 0.6;
118 
119     public static final double LATENCY_NOT_MEASURED = 0.0;
120     public static final double LATENCY_BASIC = 500.0;
121     public static final double LATENCY_PRO_AUDIO_AT_LEAST_ONE = 25.0;
122     public static final double LATENCY_PRO_AUDIO_ANALOG = 20.0;
123     public static final double LATENCY_PRO_AUDIO_USB = 25.0;
124     public static final double LATENCY_MPC_AT_LEAST_ONE = 80.0;
125 
126     // The audio stream callback threads should stop and close
127     // in less than a few hundred msec. This is a generous timeout value.
128     private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000;
129 
130     private TestSpec[] mTestSpecs = new TestSpec[NUM_TEST_ROUTES];
131     class TestSpec {
132         private static final String TAG = "AudioLoopbackLatencyActivity.TestSpec";
133         // impossibly low latencies (indicating something in the test went wrong).
134         protected static final double LOWEST_REASONABLE_LATENCY_MILLIS = 1.0;
135 
136         final int mRouteId;
137 
138         // runtime assigned device ID
139         static final int DEVICEID_NONE = -1;
140         int mInputDeviceId;
141         int mOutputDeviceId;
142 
143         String mDeviceName;
144 
145         double[] mLatencyMS = new double[NUM_TEST_PHASES];
146         double[] mConfidence = new double[NUM_TEST_PHASES];
147 
148         double mMeanLatencyMS;
149         double mMeanAbsoluteDeviation;
150         double mMeanConfidence;
151         double mRequiredConfidence;
152 
153         boolean mRouteAvailable; // Have we seen this route/device at any time
154         boolean mRouteConnected; // is the route available NOW
155         boolean mTestRun;
156 
TestSpec(int routeId, double requiredConfidence)157         TestSpec(int routeId, double requiredConfidence) {
158             mRouteId = routeId;
159             mRequiredConfidence = requiredConfidence;
160 
161             mInputDeviceId = DEVICEID_NONE;
162             mOutputDeviceId = DEVICEID_NONE;
163         }
164 
startTest()165         void startTest() {
166             mTestRun = true;
167 
168             java.util.Arrays.fill(mLatencyMS, 0.0);
169             java.util.Arrays.fill(mConfidence, 0.0);
170         }
171 
recordPhase(int phase, double latencyMS, double confidence)172         void recordPhase(int phase, double latencyMS, double confidence) {
173             mLatencyMS[phase] = latencyMS;
174             mConfidence[phase] = confidence;
175         }
176 
handleTestCompletion()177         void handleTestCompletion() {
178             mMeanLatencyMS = StatUtils.calculateMean(mLatencyMS);
179             mMeanAbsoluteDeviation =
180                     StatUtils.calculateMeanAbsoluteDeviation(
181                             mMeanLatencyMS, mLatencyMS, mLatencyMS.length);
182             mMeanConfidence = StatUtils.calculateMean(mConfidence);
183         }
184 
isMeasurementValid()185         boolean isMeasurementValid() {
186             return mTestRun && mMeanLatencyMS > 1.0 && mMeanConfidence >= mRequiredConfidence;
187         }
188 
getResultString()189         String getResultString() {
190             String result;
191 
192             if (!mRouteAvailable) {
193                 result = "Route Not Available";
194             } else if (!mTestRun) {
195                 result = "Test Not Run";
196             } else if (mMeanConfidence < mRequiredConfidence) {
197                 result = String.format(
198                         "Test Finished\nInsufficient Confidence (%.2f < %.2f). No Results.",
199                         mMeanConfidence, mRequiredConfidence);
200             } else if (mMeanLatencyMS <= LOWEST_REASONABLE_LATENCY_MILLIS) {
201                 result = String.format(
202                         "Test Finished\nLatency unrealistically low (%.2f < %.2f). No Results.",
203                         mMeanLatencyMS, LOWEST_REASONABLE_LATENCY_MILLIS);
204             } else {
205                 result = String.format(
206                         "Test Finished\nMean Latency:%.2f ms\n"
207                                 + "Mean Absolute Deviation: %.2f\n"
208                                 + "Confidence: %.2f\n"
209                                 + "Low Latency Path: %s",
210                         mMeanLatencyMS,
211                         mMeanAbsoluteDeviation,
212                         mMeanConfidence,
213                         mNativeAnalyzerThread.isLowLatencyStream() ? mYesString : mNoString);
214             }
215 
216             return result;
217         }
218 
219         // ReportLog Schema (per route)
220         private static final String KEY_ROUTEINDEX = "route_index";
221         private static final String KEY_LATENCY = "latency";
222         private static final String KEY_CONFIDENCE = "confidence";
223         private static final String KEY_MEANABSDEVIATION = "mean_absolute_deviation";
224         private static final String KEY_IS_PERIPHERAL_ATTACHED = "is_peripheral_attached";
225         private static final String KEY_INPUT_PERIPHERAL_NAME = "input_peripheral";
226         private static final String KEY_OUTPUT_PERIPHERAL_NAME = "output_peripheral";
227         private static final String KEY_TEST_PERIPHERAL = "test_peripheral";
228 
recordTestResults(CtsVerifierReportLog reportLog)229         void recordTestResults(CtsVerifierReportLog reportLog) {
230             reportLog.addValue(
231                     KEY_ROUTEINDEX,
232                     mRouteId,
233                     ResultType.NEUTRAL,
234                     ResultUnit.NONE);
235 
236             reportLog.addValue(
237                     KEY_LATENCY,
238                     mMeanLatencyMS,
239                     ResultType.LOWER_BETTER,
240                     ResultUnit.MS);
241 
242             reportLog.addValue(
243                     KEY_CONFIDENCE,
244                     mMeanConfidence,
245                     ResultType.HIGHER_BETTER,
246                     ResultUnit.NONE);
247 
248             reportLog.addValue(
249                     KEY_MEANABSDEVIATION,
250                     mMeanAbsoluteDeviation,
251                     ResultType.NEUTRAL,
252                     ResultUnit.NONE);
253 
254             reportLog.addValue(
255                     KEY_TEST_PERIPHERAL,
256                     mDeviceName,
257                     ResultType.NEUTRAL,
258                     ResultUnit.NONE);
259         }
260     }
261 
262     @Override
onCreate(Bundle savedInstanceState)263     protected void onCreate(Bundle savedInstanceState) {
264         super.onCreate(savedInstanceState);
265 
266         setContentView(R.layout.audio_loopback_latency_activity);
267 
268         setPassFailButtonClickListeners();
269         getPassButton().setEnabled(false);
270         setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1);
271 
272         mRequireReportLogToPass = true;
273 
274         mClaimsOutput = AudioSystemFlags.claimsOutput(this);
275         mClaimsInput = AudioSystemFlags.claimsInput(this);
276         mClaimsProAudio = AudioSystemFlags.claimsProAudio(this);
277         mClaimsLowLatency = AudioSystemFlags.claimsLowLatencyAudio(this);
278         mClaimsMediaPerformance = Build.VERSION.MEDIA_PERFORMANCE_CLASS != 0;
279 
280         // Setup test specifications
281         double mustLatency;
282 
283         // Speaker/Mic Path
284         mTestSpecs[TESTROUTE_DEVICE] =
285                 new TestSpec(TESTROUTE_DEVICE, CONFIDENCE_THRESHOLD_AMBIENT);
286         mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true;    // Always
287 
288         // Analog Jack Path
289         mTestSpecs[TESTROUTE_ANALOG_JACK] =
290                 new TestSpec(TESTROUTE_ANALOG_JACK, CONFIDENCE_THRESHOLD_WIRED);
291 
292         // USB Path
293         mTestSpecs[TESTROUTE_USB] =
294                 new TestSpec(TESTROUTE_USB, CONFIDENCE_THRESHOLD_WIRED);
295 
296         // Setup UI
297         Resources resources = getResources();
298         mYesString = resources.getString(R.string.audio_general_yes);
299         mNoString = resources.getString(R.string.audio_general_no);
300         mPassString = resources.getString(R.string.audio_general_pass);
301         mFailString = resources.getString(R.string.audio_general_fail);
302         mNotTestedString = resources.getString(R.string.audio_general_not_tested);
303         mNotRequiredString = resources.getString(R.string.audio_general_not_required);
304         mRequiredString = resources.getString(R.string.audio_general_required);
305 
306         // Pro Audio
307         ((TextView) findViewById(R.id.audio_loopback_pro_audio)).setText(
308                 (mClaimsProAudio ? mYesString : mNoString));
309 
310         // Low Latency
311         ((TextView) findViewById(R.id.audio_loopback_low_latency)).setText(
312                 (mClaimsLowLatency ? mYesString : mNoString));
313 
314         // Media Performance Class
315         ((TextView) findViewById(R.id.audio_loopback_mpc)).setText(
316                 (mClaimsMediaPerformance ? mYesString : mNoString));
317 
318         // MMAP
319         ((TextView) findViewById(R.id.audio_loopback_mmap)).setText(
320                 (mSupportsMMAP ? mYesString : mNoString));
321         ((TextView) findViewById(R.id.audio_loopback_mmap_exclusive)).setText(
322                 (mSupportsMMAPExclusive ? mYesString : mNoString));
323 
324         // Individual Test Results
325         mResultsText[TESTROUTE_DEVICE] =
326                 (TextView) findViewById(R.id.audio_loopback_speakermicpath_info);
327         mResultsText[TESTROUTE_ANALOG_JACK] =
328                 (TextView) findViewById(R.id.audio_loopback_headsetpath_info);
329         mResultsText[TESTROUTE_USB] =
330                 (TextView) findViewById(R.id.audio_loopback_usbpath_info);
331 
332         mStartButtons[TESTROUTE_DEVICE] =
333                 (Button) findViewById(R.id.audio_loopback_speakermicpath_btn);
334         mStartButtons[TESTROUTE_DEVICE].setOnClickListener(mBtnClickListener);
335 
336         mStartButtons[TESTROUTE_ANALOG_JACK] =
337                 (Button) findViewById(R.id.audio_loopback_headsetpath_btn);
338         mStartButtons[TESTROUTE_ANALOG_JACK].setOnClickListener(mBtnClickListener);
339 
340         mStartButtons[TESTROUTE_USB] = (Button) findViewById(R.id.audio_loopback_usbpath_btn);
341         mStartButtons[TESTROUTE_USB].setOnClickListener(mBtnClickListener);
342 
343         mAudioManager = getSystemService(AudioManager.class);
344 
345         mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
346 
347         connectLoopbackUI();
348 
349         enableStartButtons(true);
350 
351         handleTestCompletion(false);
352     }
353 
354     //
355     // UI State
356     //
enableStartButtons(boolean enable)357     private void enableStartButtons(boolean enable) {
358         if (enable) {
359             for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) {
360                 mStartButtons[routeId].setEnabled(mTestSpecs[routeId].mRouteConnected);
361             }
362         } else {
363             for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) {
364                 mStartButtons[routeId].setEnabled(false);
365             }
366         }
367     }
368 
connectLoopbackUI()369     private void connectLoopbackUI() {
370         mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text);
371         mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar);
372         mMaxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
373         mAudioLevelSeekbar.setMax(mMaxLevel);
374         mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0);
375         refreshLevel();
376 
377         mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
378             @Override
379             public void onStopTrackingTouch(SeekBar seekBar) {}
380 
381             @Override
382             public void onStartTrackingTouch(SeekBar seekBar) {}
383 
384             @Override
385             public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
386                 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
387                         progress, 0);
388                 Log.i(TAG,"Level set to: " + progress);
389                 refreshLevel();
390             }
391         });
392 
393         mTestStatusText = (TextView) findViewById(R.id.audio_loopback_status_text);
394         mProgressBar = (ProgressBar) findViewById(R.id.audio_loopback_progress_bar);
395         showWait(false);
396     }
397 
398     //
399     // Peripheral Connection Logic
400     //
clearDeviceIds()401     void clearDeviceIds() {
402         for (TestSpec testSpec : mTestSpecs) {
403             testSpec.mInputDeviceId = testSpec.mInputDeviceId = TestSpec.DEVICEID_NONE;
404         }
405     }
406 
clearDeviceConnected()407     void clearDeviceConnected() {
408         for (TestSpec testSpec : mTestSpecs) {
409             testSpec.mRouteConnected = false;
410         }
411     }
412 
scanPeripheralList(AudioDeviceInfo[] devices)413     void scanPeripheralList(AudioDeviceInfo[] devices) {
414         clearDeviceIds();
415         clearDeviceConnected();
416 
417         for (AudioDeviceInfo devInfo : devices) {
418             switch (devInfo.getType()) {
419                 // TESTROUTE_DEVICE (i.e. Speaker & Mic)
420                 case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
421                 case AudioDeviceInfo.TYPE_BUILTIN_MIC:
422                     if (devInfo.isSink()) {
423                         mTestSpecs[TESTROUTE_DEVICE].mOutputDeviceId = devInfo.getId();
424                     } else if (devInfo.isSource()) {
425                         mTestSpecs[TESTROUTE_DEVICE].mInputDeviceId = devInfo.getId();
426                     }
427                     mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true;
428                     mTestSpecs[TESTROUTE_DEVICE].mRouteConnected = true;
429                     mTestSpecs[TESTROUTE_DEVICE].mDeviceName = devInfo.getProductName().toString();
430                     break;
431 
432                 // TESTROUTE_ANALOG_JACK
433                 case AudioDeviceInfo.TYPE_WIRED_HEADSET:
434                 case AudioDeviceInfo.TYPE_AUX_LINE:
435                     if (devInfo.isSink()) {
436                         mTestSpecs[TESTROUTE_ANALOG_JACK].mOutputDeviceId = devInfo.getId();
437                     } else if (devInfo.isSource()) {
438                         mTestSpecs[TESTROUTE_ANALOG_JACK].mInputDeviceId = devInfo.getId();
439                     }
440                     mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteAvailable = true;
441                     mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteConnected = true;
442                     mTestSpecs[TESTROUTE_ANALOG_JACK].mDeviceName =
443                             devInfo.getProductName().toString();
444                     break;
445 
446                 // TESTROUTE_USB
447                 case AudioDeviceInfo.TYPE_USB_DEVICE:
448                 case AudioDeviceInfo.TYPE_USB_HEADSET:
449                     if (devInfo.isSink()) {
450                         mTestSpecs[TESTROUTE_USB].mOutputDeviceId = devInfo.getId();
451                     } else if (devInfo.isSource()) {
452                         mTestSpecs[TESTROUTE_USB].mInputDeviceId = devInfo.getId();
453                     }
454                     mTestSpecs[TESTROUTE_USB].mRouteAvailable = true;
455                     mTestSpecs[TESTROUTE_USB].mRouteConnected = true;
456                     mTestSpecs[TESTROUTE_USB].mDeviceName = devInfo.getProductName().toString();
457             }
458 
459             enableStartButtons(true);
460         }
461     }
462 
463     private class ConnectListener extends AudioDeviceCallback {
ConnectListener()464         ConnectListener() {}
465 
466         //
467         // AudioDevicesManager.OnDeviceConnectionListener
468         //
469         @Override
onAudioDevicesAdded(AudioDeviceInfo[] addedDevices)470         public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
471             scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
472         }
473 
474         @Override
onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices)475         public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
476             scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
477         }
478     }
479 
480     /**
481      * refresh Audio Level seekbar and text
482      */
refreshLevel()483     private void refreshLevel() {
484         int currentLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
485         mAudioLevelSeekbar.setProgress(currentLevel);
486 
487         String levelText = String.format("%s: %d/%d",
488                 getResources().getString(R.string.audio_loopback_level_text),
489                 currentLevel, mMaxLevel);
490         mAudioLevelText.setText(levelText);
491     }
492 
493     //
494     // show active progress bar
495     //
showWait(boolean show)496     protected void showWait(boolean show) {
497         mProgressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
498     }
499 
500     //
501     // Common logging
502     //
503 
504     @Override
getTestId()505     public String getTestId() {
506         return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
507     }
508 
509     @Override
requiresReportLog()510     public boolean requiresReportLog() {
511         return true;
512     }
513 
514     @Override
getReportFileName()515     public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; }
516 
517     @Override
getReportSectionName()518     public final String getReportSectionName() {
519         return setTestNameSuffix(sCurrentDisplayMode, "audio_loopback_latency_activity");
520     }
521 
522     // Test-Schema
523     private static final String KEY_SAMPLE_RATE = "sample_rate";
524     private static final String KEY_IS_PRO_AUDIO = "is_pro_audio";
525     private static final String KEY_IS_LOW_LATENCY = "is_low_latency";
526     private static final String KEY_TEST_MMAP = "supports_mmap";
527     private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive";
528     private static final String KEY_LEVEL = "level";
529 
recordRouteResults(int routeIndex)530     private void recordRouteResults(int routeIndex) {
531         if (mTestSpecs[routeIndex].mTestRun) {
532             CtsVerifierReportLog reportLog = getReportLog();
533 
534             int audioLevel = mAudioLevelSeekbar.getProgress();
535             reportLog.addValue(
536                     KEY_LEVEL,
537                     audioLevel,
538                     ResultType.NEUTRAL,
539                     ResultUnit.NONE);
540 
541             reportLog.addValue(
542                     KEY_IS_PRO_AUDIO,
543                     mClaimsProAudio,
544                     ResultType.NEUTRAL,
545                     ResultUnit.NONE);
546 
547             reportLog.addValue(
548                     KEY_TEST_MMAP,
549                     mSupportsMMAP,
550                     ResultType.NEUTRAL,
551                     ResultUnit.NONE);
552 
553             reportLog.addValue(
554                     KEY_TEST_MMAPEXCLUSIVE,
555                     mSupportsMMAPExclusive,
556                     ResultType.NEUTRAL,
557                     ResultUnit.NONE);
558 
559             reportLog.addValue(
560                     KEY_SAMPLE_RATE,
561                     mNativeAnalyzerThread.getSampleRate(),
562                     ResultType.NEUTRAL,
563                     ResultUnit.NONE);
564 
565             reportLog.addValue(
566                     KEY_IS_LOW_LATENCY,
567                     mNativeAnalyzerThread.isLowLatencyStream(),
568                     ResultType.NEUTRAL,
569                     ResultUnit.NONE);
570 
571             mTestSpecs[routeIndex].recordTestResults(reportLog);
572 
573             reportLog.submit();
574         }
575     }
576 
577     @Override
recordTestResults()578     public void recordTestResults() {
579         for (int route = 0; route < NUM_TEST_ROUTES; route++) {
580             recordRouteResults(route);
581         }
582     }
583 
startAudioTest(Handler messageHandler, int testRouteId)584     private void startAudioTest(Handler messageHandler, int testRouteId) {
585         enableStartButtons(false);
586         mResultsText[testRouteId].setText("Running...");
587 
588         mTestRoute = testRouteId;
589 
590         mTestSpecs[mTestRoute].startTest();
591 
592         getPassButton().setEnabled(false);
593 
594         mTestPhase = 0;
595 
596         mNativeAnalyzerThread = new NativeAnalyzerThread(this);
597         if (mNativeAnalyzerThread != null) {
598             mNativeAnalyzerThread.setMessageHandler(messageHandler);
599             // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
600             mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION);
601             startTestPhase();
602         } else {
603             Log.e(TAG, "Couldn't allocate native analyzer thread");
604             mTestStatusText.setText(getResources().getString(R.string.audio_loopback_failure));
605         }
606     }
607 
startTestPhase()608     private void startTestPhase() {
609         if (mNativeAnalyzerThread != null) {
610             Log.i(TAG, "mTestRoute: " + mTestRoute
611                     + " mInputDeviceId: " + mTestSpecs[mTestRoute].mInputDeviceId
612                     + " mOutputDeviceId: " + mTestSpecs[mTestRoute].mOutputDeviceId);
613             mNativeAnalyzerThread.startTest(
614                     mTestSpecs[mTestRoute].mInputDeviceId, mTestSpecs[mTestRoute].mOutputDeviceId);
615 
616             // what is this for?
617             try {
618                 Thread.sleep(200);
619             } catch (InterruptedException e) {
620                 e.printStackTrace();
621             }
622         }
623     }
624 
handleTestPhaseCompletion()625     private void handleTestPhaseCompletion() {
626         if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) {
627             double latency = mNativeAnalyzerThread.getLatencyMillis();
628             double confidence = mNativeAnalyzerThread.getConfidence();
629             TestSpec testSpec = mTestSpecs[mTestRoute];
630             testSpec.recordPhase(mTestPhase, latency, confidence);
631 
632             String result = String.format(
633                     "Test %d Finished\nLatency: %.2f ms\nConfidence: %.2f\n",
634                     mTestPhase, latency, confidence);
635 
636             mTestStatusText.setText(result);
637             try {
638                 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
639                 // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500);
640             } catch (InterruptedException e) {
641                 e.printStackTrace();
642             }
643 
644 
645             mTestPhase++;
646             if (mTestPhase >= NUM_TEST_PHASES) {
647                 handleTestCompletion(true);
648             } else {
649                 startTestPhase();
650             }
651         }
652     }
653 
handleTestCompletion(boolean showResult)654     private void handleTestCompletion(boolean showResult) {
655         TestSpec testSpec = mTestSpecs[mTestRoute];
656         testSpec.handleTestCompletion();
657 
658         // Make sure the test thread is finished. It should already be done.
659         if (mNativeAnalyzerThread != null) {
660             try {
661                 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
662             } catch (InterruptedException e) {
663                 e.printStackTrace();
664             }
665         }
666 
667         mResultsText[mTestRoute].setText(testSpec.getResultString());
668 
669         LoopbackLatencyRequirements requirements = new LoopbackLatencyRequirements();
670         boolean pass = isReportLogOkToPass()
671                 && requirements.evaluate(mClaimsProAudio,
672                         Build.VERSION.MEDIA_PERFORMANCE_CLASS,
673                         mTestSpecs[TESTROUTE_DEVICE].isMeasurementValid()
674                                 ? mTestSpecs[TESTROUTE_DEVICE].mMeanLatencyMS : 0.0,
675                         mTestSpecs[TESTROUTE_ANALOG_JACK].isMeasurementValid()
676                                 ? mTestSpecs[TESTROUTE_ANALOG_JACK].mMeanLatencyMS :  0.0,
677                         mTestSpecs[TESTROUTE_USB].isMeasurementValid()
678                                 ? mTestSpecs[TESTROUTE_USB].mMeanLatencyMS : 0.0);
679 
680         getPassButton().setEnabled(pass);
681 
682         StringBuilder sb = new StringBuilder();
683         if (!isReportLogOkToPass()) {
684             sb.append(getResources().getString(R.string.audio_general_reportlogtest) + "\n");
685         }
686         sb.append(requirements.getResultsString());
687         if (showResult) {
688             sb.append("\n" + (pass ? mPassString : mFailString));
689         }
690         mTestStatusText.setText(sb.toString());
691 
692         showWait(false);
693         enableStartButtons(true);
694     }
695 
696     /**
697      * handler for messages from audio thread
698      */
699     private Handler mMessageHandler = new Handler() {
700         public void handleMessage(Message msg) {
701             super.handleMessage(msg);
702             switch(msg.what) {
703                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
704                     Log.v(TAG,"got message native rec started!!");
705                     showWait(true);
706                     mTestStatusText.setText(String.format("[phase: %d] - Test Running...",
707                             (mTestPhase + 1)));
708                     break;
709                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR:
710                     Log.v(TAG,"got message native rec can't start!!");
711                     mTestStatusText.setText("Test Error opening streams.");
712                     handleTestCompletion(true);
713                     break;
714                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR:
715                     Log.v(TAG,"got message native rec can't start!!");
716                     mTestStatusText.setText("Test Error while recording.");
717                     handleTestCompletion(true);
718                     break;
719                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
720                     mTestStatusText.setText("Test FAILED due to errors.");
721                     handleTestCompletion(true);
722                     break;
723                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING:
724                     mTestStatusText.setText(String.format("[phase: %d] - Analyzing ...",
725                             mTestPhase + 1));
726                     break;
727                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
728                     handleTestPhaseCompletion();
729                     break;
730                 default:
731                     break;
732             }
733         }
734     };
735 
736     private class OnBtnClickListener implements OnClickListener {
737         @Override
onClick(View v)738         public void onClick(View v) {
739             int id = v.getId();
740             if (id == R.id.audio_loopback_speakermicpath_btn) {
741                 startAudioTest(mMessageHandler, TESTROUTE_DEVICE);
742             } else if (id == R.id.audio_loopback_headsetpath_btn) {
743                 startAudioTest(mMessageHandler, TESTROUTE_ANALOG_JACK);
744             }  else if (id == R.id.audio_loopback_usbpath_btn) {
745                 startAudioTest(mMessageHandler, TESTROUTE_USB);
746             }
747         }
748     }
749 
750     class LoopbackLatencyRequirements {
751         public static final int MPC_NONE = 0;
752         public static final int MPC_R = Build.VERSION_CODES.R;
753         public static final int MPC_S = Build.VERSION_CODES.S;
754         public static final int MPC_T = Build.VERSION_CODES.TIRAMISU;
755 
756         String mResultsString = new String();
757 
getResultsString()758         String getResultsString() {
759             return mResultsString;
760         }
761 
checkLatency(double measured, double limit)762         private boolean checkLatency(double measured, double limit) {
763             return measured == LATENCY_NOT_MEASURED || measured <= limit;
764         }
765 
evaluate(boolean proAudio, int mediaPerformanceClass, double deviceLatency, double analogLatency, double usbLatency)766         public boolean evaluate(boolean proAudio,
767                                        int mediaPerformanceClass,
768                                        double deviceLatency,
769                                        double analogLatency,
770                                        double usbLatency) {
771 
772             // Required to test the Mic/Speaker path
773             boolean internalPathRun = deviceLatency != LATENCY_NOT_MEASURED;
774 
775             // All devices must be under the basic limit.
776             boolean basicPass = checkLatency(deviceLatency, LATENCY_BASIC)
777                     && checkLatency(analogLatency, LATENCY_BASIC)
778                     && checkLatency(usbLatency, LATENCY_BASIC);
779 
780             // For Media Performance Class T the RT latency must be <= 80 msec on one path.
781             boolean mpcAtLeastOnePass = (mediaPerformanceClass < MPC_T)
782                     || checkLatency(deviceLatency, LATENCY_MPC_AT_LEAST_ONE)
783                     || checkLatency(analogLatency, LATENCY_MPC_AT_LEAST_ONE)
784                     || checkLatency(usbLatency, LATENCY_MPC_AT_LEAST_ONE);
785 
786             // For ProAudio, the RT latency must be <= 25 msec on one path.
787             boolean proAudioAtLeastOnePass = !proAudio
788                     || checkLatency(deviceLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE)
789                     || checkLatency(analogLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE)
790                     || checkLatency(usbLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE);
791 
792             String supplementalText = "";
793             // For ProAudio, analog and USB have specific limits
794             boolean proAudioLimitsPass = !proAudio;
795             if (proAudio) {
796                 if (analogLatency > 0.0) {
797                     proAudioLimitsPass = analogLatency <= LATENCY_PRO_AUDIO_ANALOG;
798                 } else if (usbLatency > 0.0) {
799                     // USB audio must be supported if 3.5mm jack not supported
800                     proAudioLimitsPass =  usbLatency <= LATENCY_PRO_AUDIO_USB;
801                 }
802             }
803 
804             boolean pass =
805                     internalPathRun &&
806                     basicPass &&
807                     mpcAtLeastOnePass &&
808                     proAudioAtLeastOnePass &&
809                     proAudioLimitsPass;
810 
811             // Build the results explanation
812             StringBuilder sb = new StringBuilder();
813             if (proAudio) {
814                 sb.append("[Pro Audio]");
815             } else if (mediaPerformanceClass != MPC_NONE) {
816                 sb.append("[MPC %d]" + mediaPerformanceClass);
817             } else {
818                 sb.append("[Basic Audio]");
819             }
820             sb.append(" ");
821 
822             sb.append("\nSpeaker/Mic: " + (deviceLatency != LATENCY_NOT_MEASURED
823                     ? String.format("%.2fms ", deviceLatency)
824                     : (mNotTestedString + " - " + mRequiredString)));
825             sb.append("\nHeadset: " + (analogLatency != LATENCY_NOT_MEASURED
826                     ? String.format("%.2fms ", analogLatency)
827                     : (mNotTestedString + " - " + mNotRequiredString)));
828             sb.append("\nUSB: " + (usbLatency != LATENCY_NOT_MEASURED
829                     ? String.format("%.2fms ", usbLatency)
830                     : (mNotTestedString + " - " + mNotRequiredString)));
831 
832             sb.append(supplementalText);
833             mResultsString = sb.toString();
834 
835             return pass;
836         }
837     }
838 }
839