• 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(requirements = {"5.10/C-1-2,C-1-5", "5.6/H-1-3"})
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     TextView mTestInstructions;
81 
82     private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
83     private Button[] mStartButtons = new Button[NUM_TEST_ROUTES];
84 
85     String mYesString;
86     String mNoString;
87 
88     String mPassString;
89     String mFailString;
90     String mNotTestedString;
91     String mNotRequiredString;
92     String mRequiredString;
93 
94     // These flags determine the maximum allowed latency
95     private boolean mClaimsProAudio;
96     private boolean mClaimsLowLatency;
97     private boolean mClaimsMediaPerformance;
98     private boolean mClaimsOutput;
99     private boolean mClaimsInput;
100 
101     // Useful info
102     private boolean mSupportsMMAP = AudioUtils.isMMapSupported();
103     private boolean mSupportsMMAPExclusive = AudioUtils.isMMapExclusiveSupported();
104 
105     private boolean mIsWatch;
106     private boolean mIsTV;
107     private boolean mIsAutomobile;
108     private boolean mIsHandheld;
109     private int     mSpeakerDeviceId = AudioDeviceInfo.TYPE_UNKNOWN;
110     private int     mMicDeviceId = AudioDeviceInfo.TYPE_UNKNOWN;
111 
112     // Peripheral(s)
113     private static final int NUM_TEST_ROUTES =       3;
114     private static final int TESTROUTE_DEVICE =      0; // device speaker + mic
115     private static final int TESTROUTE_ANALOG_JACK = 1;
116     private static final int TESTROUTE_USB =         2;
117     private int mTestRoute = TESTROUTE_DEVICE;
118 
119     // Loopback Logic
120     private NativeAnalyzerThread mNativeAnalyzerThread = null;
121 
122     protected static final int NUM_TEST_PHASES = 5;
123     protected int mTestPhase = 0;
124 
125     private static final double CONFIDENCE_THRESHOLD_AMBIENT = 0.6;
126     private static final double CONFIDENCE_THRESHOLD_WIRED = 0.6;
127 
128     public static final double LATENCY_NOT_MEASURED = 0.0;
129     public static final double LATENCY_BASIC = 300.0;
130     public static final double LATENCY_PRO_AUDIO_AT_LEAST_ONE = 25.0;
131     public static final double LATENCY_PRO_AUDIO_ANALOG = 20.0;
132     public static final double LATENCY_PRO_AUDIO_USB = 25.0;
133     public static final double LATENCY_MPC_AT_LEAST_ONE = 80.0;
134 
135     // The audio stream callback threads should stop and close
136     // in less than a few hundred msec. This is a generous timeout value.
137     private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000;
138 
139     private TestSpec[] mTestSpecs = new TestSpec[NUM_TEST_ROUTES];
140     class TestSpec {
141         private static final String TAG = "AudioLoopbackLatencyActivity.TestSpec";
142         // impossibly low latencies (indicating something in the test went wrong).
143         protected static final double LOWEST_REASONABLE_LATENCY_MILLIS = 1.0;
144 
145         final int mRouteId;
146 
147         // runtime assigned device ID
148         static final int DEVICEID_NONE = -1;
149         int mInputDeviceId;
150         int mOutputDeviceId;
151 
152         String mDeviceName;
153 
154         double[] mLatencyMS = new double[NUM_TEST_PHASES];
155         double[] mConfidence = new double[NUM_TEST_PHASES];
156 
157         double mMeanLatencyMS;
158         double mMeanAbsoluteDeviation;
159         double mMeanConfidence;
160         double mRequiredConfidence;
161 
162         boolean mRouteAvailable; // Have we seen this route/device at any time
163         boolean mRouteConnected; // is the route available NOW
164         boolean mTestRun;
165 
TestSpec(int routeId, double requiredConfidence)166         TestSpec(int routeId, double requiredConfidence) {
167             mRouteId = routeId;
168             mRequiredConfidence = requiredConfidence;
169 
170             mInputDeviceId = DEVICEID_NONE;
171             mOutputDeviceId = DEVICEID_NONE;
172         }
173 
startTest()174         void startTest() {
175             mTestRun = true;
176 
177             java.util.Arrays.fill(mLatencyMS, 0.0);
178             java.util.Arrays.fill(mConfidence, 0.0);
179         }
180 
recordPhase(int phase, double latencyMS, double confidence)181         void recordPhase(int phase, double latencyMS, double confidence) {
182             mLatencyMS[phase] = latencyMS;
183             mConfidence[phase] = confidence;
184         }
185 
handleTestCompletion()186         void handleTestCompletion() {
187             mMeanLatencyMS = StatUtils.calculateMean(mLatencyMS);
188             mMeanAbsoluteDeviation =
189                     StatUtils.calculateMeanAbsoluteDeviation(
190                             mMeanLatencyMS, mLatencyMS, mLatencyMS.length);
191             mMeanConfidence = StatUtils.calculateMean(mConfidence);
192         }
193 
isMeasurementValid()194         boolean isMeasurementValid() {
195             return mTestRun && mMeanLatencyMS > 1.0 && mMeanConfidence >= mRequiredConfidence;
196         }
197 
has24BitHardwareSupport()198         boolean has24BitHardwareSupport() {
199             return (mNativeAnalyzerThread != null)
200                     && mNativeAnalyzerThread.has24BitHardwareSupport();
201         }
202 
getResultString()203         String getResultString() {
204             String result;
205 
206             if (!mRouteAvailable) {
207                 result = "Route Not Available";
208             } else if (!mTestRun) {
209                 result = "Test Not Run";
210             } else if (mMeanConfidence < mRequiredConfidence) {
211                 result = String.format(
212                         "Test Finished\nInsufficient Confidence (%.2f < %.2f). No Results.",
213                         mMeanConfidence, mRequiredConfidence);
214             } else if (mMeanLatencyMS <= LOWEST_REASONABLE_LATENCY_MILLIS) {
215                 result = String.format(
216                         "Test Finished\nLatency unrealistically low (%.2f < %.2f). No Results.",
217                         mMeanLatencyMS, LOWEST_REASONABLE_LATENCY_MILLIS);
218             } else {
219                 result = String.format(
220                         "Test Finished\nMean Latency:%.2f ms\n"
221                                 + "Mean Absolute Deviation: %.2f\n"
222                                 + "Confidence: %.2f\n"
223                                 + "Low Latency Path: %s\n"
224                                 + "24 Bit Hardware Support: %s",
225                         mMeanLatencyMS,
226                         mMeanAbsoluteDeviation,
227                         mMeanConfidence,
228                         mNativeAnalyzerThread.isLowLatencyStream() ? mYesString : mNoString,
229                         mNativeAnalyzerThread.has24BitHardwareSupport() ? mYesString : mNoString);
230             }
231 
232             return result;
233         }
234 
235         // ReportLog Schema (per route)
236         private static final String KEY_ROUTEINDEX = "route_index";
237         private static final String KEY_LATENCY = "latency";
238         private static final String KEY_CONFIDENCE = "confidence";
239         private static final String KEY_MEANABSDEVIATION = "mean_absolute_deviation";
240         private static final String KEY_IS_PERIPHERAL_ATTACHED = "is_peripheral_attached";
241         private static final String KEY_INPUT_PERIPHERAL_NAME = "input_peripheral";
242         private static final String KEY_OUTPUT_PERIPHERAL_NAME = "output_peripheral";
243         private static final String KEY_TEST_PERIPHERAL_NAME = "test_peripheral_name";
244 
recordTestResults(CtsVerifierReportLog reportLog)245         void recordTestResults(CtsVerifierReportLog reportLog) {
246             reportLog.addValue(
247                     KEY_ROUTEINDEX,
248                     mRouteId,
249                     ResultType.NEUTRAL,
250                     ResultUnit.NONE);
251 
252             reportLog.addValue(
253                     KEY_LATENCY,
254                     mMeanLatencyMS,
255                     ResultType.LOWER_BETTER,
256                     ResultUnit.MS);
257 
258             reportLog.addValue(
259                     KEY_CONFIDENCE,
260                     mMeanConfidence,
261                     ResultType.HIGHER_BETTER,
262                     ResultUnit.NONE);
263 
264             reportLog.addValue(
265                     KEY_MEANABSDEVIATION,
266                     mMeanAbsoluteDeviation,
267                     ResultType.NEUTRAL,
268                     ResultUnit.NONE);
269 
270             reportLog.addValue(
271                     KEY_TEST_PERIPHERAL_NAME,
272                     mDeviceName,
273                     ResultType.NEUTRAL,
274                     ResultUnit.NONE);
275         }
276     }
277 
278     @Override
onCreate(Bundle savedInstanceState)279     protected void onCreate(Bundle savedInstanceState) {
280         super.onCreate(savedInstanceState);
281 
282         setContentView(R.layout.audio_loopback_latency_activity);
283 
284         setPassFailButtonClickListeners();
285         setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1);
286 
287         mRequireReportLogToPass = true;
288 
289         mClaimsOutput = AudioSystemFlags.claimsOutput(this);
290         mClaimsInput = AudioSystemFlags.claimsInput(this);
291         mClaimsProAudio = AudioSystemFlags.claimsProAudio(this);
292         mClaimsLowLatency = AudioSystemFlags.claimsLowLatencyAudio(this);
293         mClaimsMediaPerformance = Build.VERSION.MEDIA_PERFORMANCE_CLASS != 0;
294         mIsWatch = AudioSystemFlags.isWatch(this);
295         mIsTV = AudioSystemFlags.isTV(this);
296         mIsAutomobile = AudioSystemFlags.isAutomobile(this);
297         mIsHandheld = AudioSystemFlags.isHandheld(this);
298 
299         // Setup test specifications
300         double mustLatency;
301 
302         // Speaker/Mic Path
303         mTestSpecs[TESTROUTE_DEVICE] =
304                 new TestSpec(TESTROUTE_DEVICE, CONFIDENCE_THRESHOLD_AMBIENT);
305         mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true;    // Always
306 
307         // Analog Jack Path
308         mTestSpecs[TESTROUTE_ANALOG_JACK] =
309                 new TestSpec(TESTROUTE_ANALOG_JACK, CONFIDENCE_THRESHOLD_WIRED);
310 
311         // USB Path
312         mTestSpecs[TESTROUTE_USB] =
313                 new TestSpec(TESTROUTE_USB, CONFIDENCE_THRESHOLD_WIRED);
314 
315         // Setup UI
316         Resources resources = getResources();
317         mYesString = resources.getString(R.string.audio_general_yes);
318         mNoString = resources.getString(R.string.audio_general_no);
319         mPassString = resources.getString(R.string.audio_general_teststatus_pass);
320         mFailString = resources.getString(R.string.audio_general_teststatus_fail);
321         mNotTestedString = resources.getString(R.string.audio_general_not_tested);
322         mNotRequiredString = resources.getString(R.string.audio_general_not_required);
323         mRequiredString = resources.getString(R.string.audio_general_required);
324 
325         // Pro Audio
326         ((TextView) findViewById(R.id.audio_loopback_pro_audio)).setText(
327                 (mClaimsProAudio ? mYesString : mNoString));
328 
329         // Low Latency
330         ((TextView) findViewById(R.id.audio_loopback_low_latency)).setText(
331                 (mClaimsLowLatency ? mYesString : mNoString));
332 
333         // Media Performance Class
334         ((TextView) findViewById(R.id.audio_loopback_mpc)).setText(
335                 (mClaimsMediaPerformance ? String.valueOf(Build.VERSION.MEDIA_PERFORMANCE_CLASS)
336                         : mNoString));
337 
338         // MMAP
339         ((TextView) findViewById(R.id.audio_loopback_mmap)).setText(
340                 (mSupportsMMAP ? mYesString : mNoString));
341         ((TextView) findViewById(R.id.audio_loopback_mmap_exclusive)).setText(
342                 (mSupportsMMAPExclusive ? mYesString : mNoString));
343 
344         // Device Type
345         ((TextView) findViewById(R.id.audio_loopback_is_watch)).setText(
346                 (mIsWatch ? mYesString : mNoString));
347         ((TextView) findViewById(R.id.audio_loopback_is_TV)).setText(
348                 (mIsTV ? mYesString : mNoString));
349         ((TextView) findViewById(R.id.audio_loopback_is_automobile)).setText(
350                 (mIsAutomobile ? mYesString : mNoString));
351         ((TextView) findViewById(R.id.audio_loopback_is_handheld)).setText(
352                 (mIsHandheld ? mYesString : mNoString));
353 
354         // Individual Test Results
355         mResultsText[TESTROUTE_DEVICE] =
356                 (TextView) findViewById(R.id.audio_loopback_speakermicpath_info);
357         mResultsText[TESTROUTE_ANALOG_JACK] =
358                 (TextView) findViewById(R.id.audio_loopback_headsetpath_info);
359         mResultsText[TESTROUTE_USB] =
360                 (TextView) findViewById(R.id.audio_loopback_usbpath_info);
361 
362         mStartButtons[TESTROUTE_DEVICE] =
363                 (Button) findViewById(R.id.audio_loopback_speakermicpath_btn);
364         mStartButtons[TESTROUTE_DEVICE].setOnClickListener(mBtnClickListener);
365 
366         mStartButtons[TESTROUTE_ANALOG_JACK] =
367                 (Button) findViewById(R.id.audio_loopback_headsetpath_btn);
368         mStartButtons[TESTROUTE_ANALOG_JACK].setOnClickListener(mBtnClickListener);
369 
370         mStartButtons[TESTROUTE_USB] = (Button) findViewById(R.id.audio_loopback_usbpath_btn);
371         mStartButtons[TESTROUTE_USB].setOnClickListener(mBtnClickListener);
372 
373         mTestInstructions = (TextView) findViewById(R.id.audio_loopback_instructions);
374 
375         mAudioManager = getSystemService(AudioManager.class);
376         scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
377 
378         connectLoopbackUI();
379 
380         if (mustRunTest()) {
381             getPassButton().setEnabled(false);
382             enableStartButtons(true);
383         } else {
384             getPassButton().setEnabled(isReportLogOkToPass());
385             enableStartButtons(false);
386         }
387 
388         mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
389 
390         showTestInstructions();
391         handleTestCompletion(false);
392     }
393 
394     //
395     // UI State
396     //
showTestInstructions()397     private void showTestInstructions() {
398         if (mustRunTest()) {
399             mTestInstructions.setText(getString(R.string.audio_loopback_test_all_paths));
400         } else {
401             mTestInstructions.setText(getString(R.string.audio_loopback_test_not_required));
402         }
403     }
404 
enableStartButtons(boolean enable)405     private void enableStartButtons(boolean enable) {
406         if (enable) {
407             for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) {
408                 mStartButtons[routeId].setEnabled(mTestSpecs[routeId].mRouteConnected);
409             }
410         } else {
411             for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) {
412                 mStartButtons[routeId].setEnabled(false);
413             }
414         }
415     }
416 
connectLoopbackUI()417     private void connectLoopbackUI() {
418         mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text);
419         mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar);
420         mMaxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
421         mAudioLevelSeekbar.setMax(mMaxLevel);
422         mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0);
423         refreshLevel();
424 
425         mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
426             @Override
427             public void onStopTrackingTouch(SeekBar seekBar) {}
428 
429             @Override
430             public void onStartTrackingTouch(SeekBar seekBar) {}
431 
432             @Override
433             public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
434                 mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
435                         progress, 0);
436                 Log.i(TAG,"Level set to: " + progress);
437                 refreshLevel();
438             }
439         });
440 
441         mTestStatusText = (TextView) findViewById(R.id.audio_loopback_status_text);
442         mProgressBar = (ProgressBar) findViewById(R.id.audio_loopback_progress_bar);
443         showWait(false);
444     }
445 
446     //
447     // Peripheral Connection Logic
448     //
clearDeviceIds()449     void clearDeviceIds() {
450         for (TestSpec testSpec : mTestSpecs) {
451             testSpec.mInputDeviceId = testSpec.mInputDeviceId = TestSpec.DEVICEID_NONE;
452         }
453     }
454 
clearDeviceConnected()455     void clearDeviceConnected() {
456         for (TestSpec testSpec : mTestSpecs) {
457             testSpec.mRouteConnected = false;
458         }
459     }
460 
scanPeripheralList(AudioDeviceInfo[] devices)461     void scanPeripheralList(AudioDeviceInfo[] devices) {
462         clearDeviceIds();
463         clearDeviceConnected();
464 
465         mSpeakerDeviceId = AudioDeviceInfo.TYPE_UNKNOWN;
466         mMicDeviceId = AudioDeviceInfo.TYPE_UNKNOWN;
467         for (AudioDeviceInfo devInfo : devices) {
468             switch (devInfo.getType()) {
469                 // TESTROUTE_DEVICE (i.e. Speaker & Mic)
470                 // This needs to be handled differently. The other devices can be assumed
471                 // to contain both input & output devices in the same type.
472                 // For built-in we need to see both TYPES to be sure to have both input & output.
473                 case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
474                     mSpeakerDeviceId = devInfo.getId();
475                     break;
476                 case AudioDeviceInfo.TYPE_BUILTIN_MIC:
477                     mMicDeviceId = devInfo.getId();
478                     break;
479 
480                 // TESTROUTE_ANALOG_JACK
481                 case AudioDeviceInfo.TYPE_WIRED_HEADSET:
482                 case AudioDeviceInfo.TYPE_AUX_LINE:
483                     if (devInfo.isSink()) {
484                         mTestSpecs[TESTROUTE_ANALOG_JACK].mOutputDeviceId = devInfo.getId();
485                     } else if (devInfo.isSource()) {
486                         mTestSpecs[TESTROUTE_ANALOG_JACK].mInputDeviceId = devInfo.getId();
487                     }
488                     mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteAvailable = true;
489                     mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteConnected = true;
490                     mTestSpecs[TESTROUTE_ANALOG_JACK].mDeviceName =
491                             devInfo.getProductName().toString();
492                     break;
493 
494                 // TESTROUTE_USB
495                 case AudioDeviceInfo.TYPE_USB_DEVICE:
496                 case AudioDeviceInfo.TYPE_USB_HEADSET:
497                     if (devInfo.isSink()) {
498                         mTestSpecs[TESTROUTE_USB].mOutputDeviceId = devInfo.getId();
499                     } else if (devInfo.isSource()) {
500                         mTestSpecs[TESTROUTE_USB].mInputDeviceId = devInfo.getId();
501                     }
502                     mTestSpecs[TESTROUTE_USB].mRouteAvailable = true;
503                     mTestSpecs[TESTROUTE_USB].mRouteConnected = true;
504                     mTestSpecs[TESTROUTE_USB].mDeviceName = devInfo.getProductName().toString();
505             }
506         }
507 
508         // do we have BOTH a Speaker and Mic?
509         if (hasInternalPath()) {
510             mTestSpecs[TESTROUTE_DEVICE].mOutputDeviceId = mSpeakerDeviceId;
511             mTestSpecs[TESTROUTE_DEVICE].mInputDeviceId = mMicDeviceId;
512             mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true;
513             mTestSpecs[TESTROUTE_DEVICE].mRouteConnected = true;
514             mTestSpecs[TESTROUTE_DEVICE].mDeviceName =
515                     getResources().getString(R.string.audio_loopback_test_internal_devices);
516         }
517 
518         enableStartButtons(mustRunTest());
519     }
520 
521     private class ConnectListener extends AudioDeviceCallback {
ConnectListener()522         ConnectListener() {}
523 
524         //
525         // AudioDevicesManager.OnDeviceConnectionListener
526         //
527         @Override
onAudioDevicesAdded(AudioDeviceInfo[] addedDevices)528         public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
529             scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
530         }
531 
532         @Override
onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices)533         public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
534             scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
535         }
536     }
537 
538     /**
539      * refresh Audio Level seekbar and text
540      */
refreshLevel()541     private void refreshLevel() {
542         int currentLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
543         mAudioLevelSeekbar.setProgress(currentLevel);
544 
545         String levelText = String.format("%s: %d/%d",
546                 getResources().getString(R.string.audio_loopback_level_text),
547                 currentLevel, mMaxLevel);
548         mAudioLevelText.setText(levelText);
549     }
550 
551     //
552     // show active progress bar
553     //
showWait(boolean show)554     protected void showWait(boolean show) {
555         mProgressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
556     }
557 
558     //
559     // Common logging
560     //
561 
562     @Override
getTestId()563     public String getTestId() {
564         return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
565     }
566 
567     @Override
requiresReportLog()568     public boolean requiresReportLog() {
569         return true;
570     }
571 
572     @Override
getReportFileName()573     public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; }
574 
575     @Override
getReportSectionName()576     public final String getReportSectionName() {
577         return setTestNameSuffix(sCurrentDisplayMode, "audio_loopback_latency_activity");
578     }
579 
580     // Test-Schema
581     private static final String KEY_SAMPLE_RATE = "sample_rate";
582     private static final String KEY_IS_PRO_AUDIO = "is_pro_audio";
583     private static final String KEY_IS_LOW_LATENCY = "is_low_latency";
584     private static final String KEY_TEST_MMAP = "supports_mmap";
585     private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive";
586     private static final String KEY_LEVEL = "level";
587     private static final String KEY_HAS_24_BIT_HARDWARE_SUPPORT =
588             "has_24_bit_hardware_support";
589 
recordRouteResults(int routeIndex)590     private void recordRouteResults(int routeIndex) {
591         if (mTestSpecs[routeIndex].mTestRun) {
592             CtsVerifierReportLog reportLog = newReportLog();
593 
594             int audioLevel = mAudioLevelSeekbar.getProgress();
595             reportLog.addValue(
596                     KEY_LEVEL,
597                     audioLevel,
598                     ResultType.NEUTRAL,
599                     ResultUnit.NONE);
600 
601             reportLog.addValue(
602                     KEY_IS_PRO_AUDIO,
603                     mClaimsProAudio,
604                     ResultType.NEUTRAL,
605                     ResultUnit.NONE);
606 
607             reportLog.addValue(
608                     KEY_TEST_MMAP,
609                     mSupportsMMAP,
610                     ResultType.NEUTRAL,
611                     ResultUnit.NONE);
612 
613             reportLog.addValue(
614                     KEY_TEST_MMAPEXCLUSIVE,
615                     mSupportsMMAPExclusive,
616                     ResultType.NEUTRAL,
617                     ResultUnit.NONE);
618 
619             reportLog.addValue(
620                     KEY_SAMPLE_RATE,
621                     mNativeAnalyzerThread.getSampleRate(),
622                     ResultType.NEUTRAL,
623                     ResultUnit.NONE);
624 
625             reportLog.addValue(
626                     KEY_IS_LOW_LATENCY,
627                     mNativeAnalyzerThread.isLowLatencyStream(),
628                     ResultType.NEUTRAL,
629                     ResultUnit.NONE);
630 
631             reportLog.addValue(
632                     KEY_HAS_24_BIT_HARDWARE_SUPPORT,
633                     mNativeAnalyzerThread.has24BitHardwareSupport(),
634                     ResultType.NEUTRAL,
635                     ResultUnit.NONE);
636 
637             mTestSpecs[routeIndex].recordTestResults(reportLog);
638 
639             reportLog.submit();
640         }
641     }
642 
643     @Override
recordTestResults()644     public void recordTestResults() {
645         // Look for a valid route with the minimum latency.
646         int bestRoute = -1;
647         double minLatency = Double.MAX_VALUE;
648         for (int route = 0; route < NUM_TEST_ROUTES; route++) {
649             if (mTestSpecs[route].isMeasurementValid()) {
650                 if (mTestSpecs[route].mMeanLatencyMS < minLatency) {
651                     bestRoute = route;
652                     minLatency = mTestSpecs[route].mMeanLatencyMS;
653                 }
654             }
655         }
656         // Record a single result.
657         if (bestRoute >= 0) {
658             recordRouteResults(bestRoute);
659         }
660     }
661 
startAudioTest(Handler messageHandler, int testRouteId)662     private void startAudioTest(Handler messageHandler, int testRouteId) {
663         enableStartButtons(false);
664         mResultsText[testRouteId].setText("Running...");
665 
666         mTestRoute = testRouteId;
667 
668         mTestSpecs[mTestRoute].startTest();
669 
670         getPassButton().setEnabled(false);
671 
672         mTestPhase = 0;
673 
674         mNativeAnalyzerThread = new NativeAnalyzerThread(this);
675         if (mNativeAnalyzerThread != null) {
676             mNativeAnalyzerThread.setMessageHandler(messageHandler);
677             // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
678             mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION);
679             startTestPhase();
680         } else {
681             Log.e(TAG, "Couldn't allocate native analyzer thread");
682             mTestStatusText.setText(getResources().getString(R.string.audio_loopback_failure));
683         }
684     }
685 
startTestPhase()686     private void startTestPhase() {
687         if (mNativeAnalyzerThread != null) {
688             Log.i(TAG, "mTestRoute: " + mTestRoute
689                     + " mInputDeviceId: " + mTestSpecs[mTestRoute].mInputDeviceId
690                     + " mOutputDeviceId: " + mTestSpecs[mTestRoute].mOutputDeviceId);
691             mNativeAnalyzerThread.startTest(
692                     mTestSpecs[mTestRoute].mInputDeviceId, mTestSpecs[mTestRoute].mOutputDeviceId);
693 
694             // what is this for?
695             try {
696                 Thread.sleep(200);
697             } catch (InterruptedException e) {
698                 e.printStackTrace();
699             }
700         }
701     }
702 
handleTestPhaseCompletion()703     private void handleTestPhaseCompletion() {
704         if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) {
705             double latency = mNativeAnalyzerThread.getLatencyMillis();
706             double confidence = mNativeAnalyzerThread.getConfidence();
707             TestSpec testSpec = mTestSpecs[mTestRoute];
708             testSpec.recordPhase(mTestPhase, latency, confidence);
709 
710             String result = String.format(
711                     "Test %d Finished\nLatency: %.2f ms\nConfidence: %.2f\n",
712                     mTestPhase, latency, confidence);
713 
714             mTestStatusText.setText(result);
715             try {
716                 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
717                 // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500);
718             } catch (InterruptedException e) {
719                 e.printStackTrace();
720             }
721 
722 
723             mTestPhase++;
724             if (mTestPhase >= NUM_TEST_PHASES) {
725                 handleTestCompletion(true);
726             } else {
727                 startTestPhase();
728             }
729         }
730     }
731 
generateStatusString( LoopbackLatencyRequirements requirements, boolean showResult)732     private String generateStatusString(
733             LoopbackLatencyRequirements requirements, boolean showResult) {
734 
735         if (!isReportLogOkToPass()) {
736             return getResources().getString(R.string.audio_general_reportlogtest);
737         }
738 
739         if (!mustRunTest()) {
740             return getResources().getString(R.string.audio_loopback_test_non_handheld);
741         }
742 
743         boolean pass = calcPass(requirements);
744         StringBuilder sb = new StringBuilder();
745         sb.append(requirements.getResultsString());
746         if (showResult) {
747             sb.append("\n" + (pass ? mPassString : mFailString));
748         }
749         return sb.toString();
750     }
751 
mustRunTest()752     private boolean mustRunTest() {
753         return mIsHandheld  && hasInternalPath();
754     }
755 
hasInternalPath()756     boolean hasInternalPath() {
757         return mSpeakerDeviceId != AudioDeviceInfo.TYPE_UNKNOWN
758                 && mMicDeviceId != AudioDeviceInfo.TYPE_UNKNOWN;
759     }
760 
calcPass(LoopbackLatencyRequirements requirements)761     private boolean calcPass(LoopbackLatencyRequirements requirements) {
762         if (!isReportLogOkToPass()) {
763             // Can't pass if we can't write the ReportLog
764             return false;
765         }
766         if (!mustRunTest()) {
767             // just grant a pass on non-handheld devices
768             return true;
769         }
770         boolean pass = requirements.evaluate(mClaimsProAudio,
771                 Build.VERSION.MEDIA_PERFORMANCE_CLASS,
772                 mTestSpecs[TESTROUTE_DEVICE].isMeasurementValid()
773                         ? mTestSpecs[TESTROUTE_DEVICE].mMeanLatencyMS : 0.0,
774                 mTestSpecs[TESTROUTE_ANALOG_JACK].isMeasurementValid()
775                         ? mTestSpecs[TESTROUTE_ANALOG_JACK].mMeanLatencyMS :  0.0,
776                 mTestSpecs[TESTROUTE_USB].isMeasurementValid()
777                         ? mTestSpecs[TESTROUTE_USB].mMeanLatencyMS : 0.0,
778                 mTestSpecs[TESTROUTE_ANALOG_JACK].has24BitHardwareSupport(),
779                 mTestSpecs[TESTROUTE_USB].has24BitHardwareSupport());
780 
781         return pass;
782     }
783 
handleTestCompletion(boolean showResult)784     private void handleTestCompletion(boolean showResult) {
785         TestSpec testSpec = mTestSpecs[mTestRoute];
786         testSpec.handleTestCompletion();
787 
788         // Make sure the test thread is finished. It should already be done.
789         if (mNativeAnalyzerThread != null) {
790             try {
791                 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
792             } catch (InterruptedException e) {
793                 e.printStackTrace();
794             }
795         }
796 
797         mResultsText[mTestRoute].setText(testSpec.getResultString());
798 
799         LoopbackLatencyRequirements requirements = new LoopbackLatencyRequirements();
800         boolean pass = calcPass(requirements);
801 
802         getPassButton().setEnabled(pass);
803 
804         mTestStatusText.setText(generateStatusString(requirements, showResult));
805 
806         showWait(false);
807         enableStartButtons(mustRunTest());
808     }
809 
810     /**
811      * handler for messages from audio thread
812      */
813     private Handler mMessageHandler = new Handler() {
814         public void handleMessage(Message msg) {
815             super.handleMessage(msg);
816             switch(msg.what) {
817                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
818                     Log.v(TAG,"got message native rec started!!");
819                     showWait(true);
820                     mTestStatusText.setText(String.format("[phase: %d] - Test Running...",
821                             (mTestPhase + 1)));
822                     break;
823                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR:
824                     Log.v(TAG,"got message native rec can't start!!");
825                     mTestStatusText.setText("Test Error opening streams.");
826                     handleTestCompletion(true);
827                     break;
828                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR:
829                     Log.v(TAG,"got message native rec can't start!!");
830                     mTestStatusText.setText("Test Error while recording.");
831                     handleTestCompletion(true);
832                     break;
833                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
834                     mTestStatusText.setText("Test FAILED due to errors.");
835                     handleTestCompletion(true);
836                     break;
837                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING:
838                     mTestStatusText.setText(String.format("[phase: %d] - Analyzing ...",
839                             mTestPhase + 1));
840                     break;
841                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
842                     handleTestPhaseCompletion();
843                     break;
844                 default:
845                     break;
846             }
847         }
848     };
849 
850     private class OnBtnClickListener implements OnClickListener {
851         @Override
onClick(View v)852         public void onClick(View v) {
853             int id = v.getId();
854             if (id == R.id.audio_loopback_speakermicpath_btn) {
855                 startAudioTest(mMessageHandler, TESTROUTE_DEVICE);
856             } else if (id == R.id.audio_loopback_headsetpath_btn) {
857                 startAudioTest(mMessageHandler, TESTROUTE_ANALOG_JACK);
858             }  else if (id == R.id.audio_loopback_usbpath_btn) {
859                 startAudioTest(mMessageHandler, TESTROUTE_USB);
860             }
861         }
862     }
863 
864     class LoopbackLatencyRequirements {
865         public static final int MPC_NONE = 0;
866         public static final int MPC_R = Build.VERSION_CODES.R;
867         public static final int MPC_S = Build.VERSION_CODES.S;
868         public static final int MPC_T = Build.VERSION_CODES.TIRAMISU;
869 
870         String mResultsString = new String();
871 
getResultsString()872         String getResultsString() {
873             return mResultsString;
874         }
875 
876         static final int RESULTCODE_NONE = 0;
877         static final int RESULTCODE_PASS = 1;
878         static final int RESULTCODE_FAIL_NOINTERNAL = 2;
879         static final int RESULTCODE_FAIL_BASIC = 3;
880         static final int RESULTCODE_FAIL_MPC = 4;
881         static final int RESULTCODE_FAIL_PROONEPATH = 5;
882         static final int RESULTCODE_FAIL_PROLIMITS_ANALOG = 6;
883         static final int RESULTCODE_FAIL_PROLIMITS_USB = 7;
884         static final int RESULTCODE_FAIL_24BIT = 8;
885         static final int RESULTCODE_FAIL_PRO_NOWIRED = 9;
886         int mResultCode = 0;
887 
checkLatency(double measured, double limit)888         private boolean checkLatency(double measured, double limit) {
889             return measured == LATENCY_NOT_MEASURED || measured <= limit;
890         }
891 
setResultCode(boolean pass, int code)892         private void setResultCode(boolean pass, int code) {
893             // only set the first non-none result code
894             Log.i(TAG, "setResultCode(" + pass + ", " + code + ")");
895             if (!pass && mResultCode == RESULTCODE_NONE) {
896                 mResultCode = code;
897             }
898         }
899 
getResultCodeText()900         public String getResultCodeText() {
901             Resources resources = getResources();
902             switch (mResultCode) {
903                 case RESULTCODE_NONE:
904                     return resources.getString(R.string.audio_loopback_resultcode_none);
905                 case RESULTCODE_PASS:
906                     return resources.getString(R.string.audio_loopback_resultcode_pass);
907                 case RESULTCODE_FAIL_NOINTERNAL:
908                     return resources.getString(
909                             R.string.audio_loopback_resultcode_nointernal);
910                 case RESULTCODE_FAIL_BASIC:
911                     return resources.getString(
912                             R.string.audio_loopback_resultcode_failbasic);
913                 case RESULTCODE_FAIL_MPC:
914                     return resources.getString(R.string.audio_loopback_resultcode_failmpc);
915                 case RESULTCODE_FAIL_PROONEPATH:
916                     return resources.getString(R.string.audio_loopback_resultcode_failpro);
917                 case RESULTCODE_FAIL_PROLIMITS_ANALOG:
918                     return resources.getString(R.string.audio_loopback_resultcode_failproanalog);
919                 case RESULTCODE_FAIL_PROLIMITS_USB:
920                     return resources.getString(R.string.audio_loopback_resultcode_failprousb);
921                 case RESULTCODE_FAIL_24BIT:
922                     return resources.getString(R.string.audio_loopback_resultcode_fail24bit);
923                 case RESULTCODE_FAIL_PRO_NOWIRED:
924                     return resources.getString(
925                             R.string.audio_loopback_resultcode_failproaudiowired);
926                 default:
927                     // this should never happen
928                     return resources.getString(R.string.audio_loopback_resultcode_invalid);
929             }
930         }
931 
evaluate(boolean proAudio, int mediaPerformanceClass, double deviceLatency, double analogLatency, double usbLatency, boolean analog24BitHardwareSupport, boolean usb24BitHardwareSupport)932         public boolean evaluate(boolean proAudio,
933                                        int mediaPerformanceClass,
934                                        double deviceLatency,
935                                        double analogLatency,
936                                        double usbLatency,
937                                        boolean analog24BitHardwareSupport,
938                                        boolean usb24BitHardwareSupport) {
939 
940             Log.i(TAG, "evaluate()");
941             mResultCode = RESULTCODE_NONE;
942 
943             // Required to test the Mic/Speaker path
944             boolean internalPathRun = deviceLatency != LATENCY_NOT_MEASURED;
945             Log.i(TAG, "  internalPathRun:" + internalPathRun);
946             setResultCode(internalPathRun, RESULTCODE_FAIL_NOINTERNAL);
947 
948             // All devices must be under the basic limit.
949             boolean basicPass = checkLatency(deviceLatency, LATENCY_BASIC)
950                     && checkLatency(analogLatency, LATENCY_BASIC)
951                     && checkLatency(usbLatency, LATENCY_BASIC);
952             Log.i(TAG, "  basicPass:" + basicPass);
953             setResultCode(basicPass, RESULTCODE_FAIL_BASIC);
954 
955             // For Media Performance Class T the RT latency must be <= 80 msec on one path.
956             boolean mpcAtLeastOnePass = (mediaPerformanceClass < MPC_T)
957                     || checkLatency(deviceLatency, LATENCY_MPC_AT_LEAST_ONE)
958                     || checkLatency(analogLatency, LATENCY_MPC_AT_LEAST_ONE)
959                     || checkLatency(usbLatency, LATENCY_MPC_AT_LEAST_ONE);
960             Log.i(TAG, "  mpcAtLeastOnePass:" + mpcAtLeastOnePass);
961             setResultCode(mpcAtLeastOnePass, RESULTCODE_FAIL_MPC);
962 
963             // For ProAudio, the RT latency must be <= 25 msec on one path.
964             boolean proAudioAtLeastOnePass = !proAudio
965                     || checkLatency(deviceLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE)
966                     || checkLatency(analogLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE)
967                     || checkLatency(usbLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE);
968             Log.i(TAG, "  proAudioAtLeastOnePass:" + proAudioAtLeastOnePass);
969             setResultCode(proAudioAtLeastOnePass, RESULTCODE_FAIL_PROONEPATH);
970 
971             // For ProAudio, analog and USB have specific limits
972             boolean proAudioLimitsPass = !proAudio;
973             if (proAudio) {
974                 if (analogLatency > 0.0) {
975                     proAudioLimitsPass = analogLatency <= LATENCY_PRO_AUDIO_ANALOG;
976                     setResultCode(proAudioLimitsPass, RESULTCODE_FAIL_PROLIMITS_ANALOG);
977                 } else if (usbLatency > 0.0) {
978                     // USB audio must be supported if 3.5mm jack not supported
979                     proAudioLimitsPass =  usbLatency <= LATENCY_PRO_AUDIO_USB;
980                     setResultCode(proAudioLimitsPass, RESULTCODE_FAIL_PROLIMITS_USB);
981                 }
982             }
983 
984             if (!proAudioLimitsPass) {
985                 setResultCode(false, RESULTCODE_FAIL_PRO_NOWIRED);
986             }
987 
988             // For Media Performance Class T, usb and analog should support >=24 bit audio.
989             boolean has24BitHardwareSupportPass = (mediaPerformanceClass < MPC_T)
990                     || analog24BitHardwareSupport  || usb24BitHardwareSupport;
991             Log.i(TAG, "  has24BitHardwareSupportPass:" + has24BitHardwareSupportPass);
992             setResultCode(has24BitHardwareSupportPass, RESULTCODE_FAIL_24BIT);
993 
994             boolean pass =
995                     internalPathRun
996                     && basicPass
997                     && mpcAtLeastOnePass
998                     && proAudioAtLeastOnePass
999                     && proAudioLimitsPass
1000                     && has24BitHardwareSupportPass;
1001             Log.i(TAG, "  pass:" + pass);
1002 
1003             // Build the results explanation
1004             StringBuilder sb = new StringBuilder();
1005             if (proAudio) {
1006                 sb.append("[Pro Audio]");
1007             } else if (mediaPerformanceClass != MPC_NONE) {
1008                 sb.append("[MPC " + mediaPerformanceClass + "]");
1009             } else {
1010                 sb.append("[Basic Audio]");
1011             }
1012             sb.append(" ");
1013 
1014             sb.append("\nSpeaker/Mic: " + (deviceLatency != LATENCY_NOT_MEASURED
1015                     ? String.format("%.2fms ", deviceLatency)
1016                     : (mNotTestedString + " - " + mRequiredString)));
1017             sb.append("\nHeadset: " + (analogLatency != LATENCY_NOT_MEASURED
1018                     ? String.format("%.2fms ", analogLatency)
1019                     : (mNotTestedString + " - " + mNotRequiredString)));
1020             sb.append("\nUSB: " + (usbLatency != LATENCY_NOT_MEASURED
1021                     ? String.format("%.2fms ", usbLatency)
1022                     : (mNotTestedString + " - " + mNotRequiredString)));
1023 
1024             mResultsString = sb.toString();
1025             if (mResultCode > RESULTCODE_PASS) {
1026                 mResultsString = mResultsString + "\n" + getResultCodeText();
1027             }
1028 
1029             return pass;
1030         }
1031     }
1032 }
1033