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.Context;
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.TextView;
37 
38 import com.android.compatibility.common.util.CddTest;
39 import com.android.compatibility.common.util.ResultType;
40 import com.android.compatibility.common.util.ResultUnit;
41 import com.android.cts.verifier.CtsVerifierReportLog;
42 import com.android.cts.verifier.PassFailButtons;
43 import com.android.cts.verifier.R;
44 import com.android.cts.verifier.audio.audiolib.AudioDeviceUtils;
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.DisplayUtils;
48 import com.android.cts.verifier.audio.audiolib.StatUtils;
49 
50 import org.hyphonate.megaaudio.common.Globals;
51 import org.hyphonate.megaaudio.common.StreamBase;
52 import org.json.JSONArray;
53 import org.json.JSONException;
54 import org.json.JSONObject;
55 
56 import java.util.Locale;
57 
58 /**
59  * CtsVerifier Audio Loopback Latency Test
60  */
61 @CddTest(requirements = {"5.10/C-1-2,C-1-5", "5.6/H-1-3"})
62 public class AudioLoopbackLatencyActivity extends PassFailButtons.Activity {
63     private static final String TAG = "AudioLoopbackLatencyActivity";
64     private static final boolean LOG = false;
65 
66     // JNI load
67     static {
68         try {
69             System.loadLibrary("audioloopback_jni");
70         } catch (UnsatisfiedLinkError e) {
71             Log.e(TAG, "Error loading Audio Loopback JNI library");
72             Log.e(TAG, "e: " + e);
73             e.printStackTrace();
74         }
75 
76         /* TODO: gracefully fail/notify if the library can't be loaded */
77     }
78 
79     Context mContext;
80     protected AudioManager mAudioManager;
81 
82     // UI
83     TextView[] mRouteStatus = new TextView[NUM_TEST_ROUTES];
84 
85     TextView mTestStatusText;
86     ProgressBar mProgressBar;
87     int mMaxLevel;
88 
89     TextView mTestInstructions;
90 
91     private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
92     private Button[] mStartButtons = new Button[NUM_TEST_ROUTES];
93 
94     private Button mCalibrateAudioButton;
95     private Button mAudioDevicesButton;
96 
97     String mYesString;
98     String mNoString;
99 
100     String mPassString;
101     String mFailString;
102     String mNotTestedString;
103     String mRequiredString;
104     String mNoHardwareString;
105     String mUnknownHardwareString;
106 
107     // These flags determine the maximum allowed latency
108     private boolean mClaimsProAudio;
109     private boolean mClaimsLowLatency;
110     private boolean mClaimsMediaPerformance;
111     private boolean mClaimsOutput;
112     private boolean mClaimsInput;
113 
114     // Useful info
115     private boolean mSupportsMMAP = AudioUtils.isMMapSupported();
116     private boolean mSupportsMMAPExclusive = AudioUtils.isMMapExclusiveSupported();
117 
118     private boolean mIsWatch;
119     private boolean mIsTV;
120     private boolean mIsAutomobile;
121     private boolean mIsHandheld;
122     private int     mSpeakerDeviceId = AudioDeviceInfo.TYPE_UNKNOWN;
123     private int     mMicDeviceId = AudioDeviceInfo.TYPE_UNKNOWN;
124 
125     private int mUSBAudioSupport;
126     private int mAnalogJackSupport;
127 
128     // Peripheral(s)
129     private static final int NUM_TEST_ROUTES =       3;
130     private static final int TESTROUTE_DEVICE =      0; // device speaker + mic
131     private static final int TESTROUTE_ANALOG_JACK = 1;
132     private static final int TESTROUTE_USB =         2;
133     private int mTestRoute = TESTROUTE_DEVICE;
134 
135     // Loopback Logic
136     private NativeAnalyzerThread mNativeAnalyzerThread = null;
137 
138     protected static final int NUM_TEST_PHASES = 5;
139     protected int mTestPhase = 0;
140 
141     private static final double CONFIDENCE_THRESHOLD_AMBIENT = 0.6;
142     private static final double CONFIDENCE_THRESHOLD_WIRED = 0.6;
143 
144     public static final double LATENCY_NOT_MEASURED = 0.0;
145     public static final double LATENCY_BASIC = 250.0; // chaned from 300 in CDD 15 for VIC
146     public static final double LATENCY_PRO_AUDIO_AT_LEAST_ONE = 25.0;
147     public static final double LATENCY_PRO_AUDIO_ANALOG = 20.0;
148     public static final double LATENCY_PRO_AUDIO_USB = 25.0;
149     public static final double LATENCY_MPC_AT_LEAST_ONE = 80.0;
150 
151     public static final double TIMESTAMP_ACCURACY_MS = 30.0;
152 
153     // The audio stream callback threads should stop and close
154     // in less than a few hundred msec. This is a generous timeout value.
155     private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000;
156 
157     private static final String LOG_ERROR_STR = "Could not log metric.";
158 
159     private TestSpec[] mTestSpecs = new TestSpec[NUM_TEST_ROUTES];
160     class TestSpec {
161         private static final String TAG = "AudioLoopbackLatencyActivity.TestSpec";
162         // impossibly low latencies (indicating something in the test went wrong).
163         protected static final double LOWEST_REASONABLE_LATENCY_MILLIS = 1.0;
164 
165         final int mRouteId;
166 
167         // runtime assigned device ID
168         static final int DEVICEID_NONE = -1;
169         int mInputDeviceId;
170         int mOutputDeviceId;
171 
172         String mDeviceName;
173 
174         double[] mLatencyMS = new double[NUM_TEST_PHASES];
175         double[] mConfidence = new double[NUM_TEST_PHASES];
176 
177         double[] mTimestampLatencyMS = new double[NUM_TEST_PHASES];
178 
179         double mMeanLatencyMS;
180         double mMeanAbsoluteDeviation;
181         double mMeanConfidence;
182         double mRequiredConfidence;
183         double mMeanTimestampLatencyMS;
184         int mSampleRate;
185         boolean mIsLowLatencyStream;
186         boolean mHas24BitHardwareSupport;
187         int mHardwareFormat;
188 
189         boolean mRouteAvailable; // Have we seen this route/device at any time
190         boolean mRouteConnected; // is the route available NOW
191         boolean mTestRun;
192 
TestSpec(int routeId, double requiredConfidence)193         TestSpec(int routeId, double requiredConfidence) {
194             mRouteId = routeId;
195             mRequiredConfidence = requiredConfidence;
196 
197             mInputDeviceId = DEVICEID_NONE;
198             mOutputDeviceId = DEVICEID_NONE;
199 
200             // Default to true if test not run.
201             mHas24BitHardwareSupport = true;
202         }
203 
startTest()204         void startTest() {
205             mTestRun = true;
206 
207             java.util.Arrays.fill(mLatencyMS, 0.0);
208             java.util.Arrays.fill(mConfidence, 0.0);
209             java.util.Arrays.fill(mTimestampLatencyMS, 0.0);
210         }
211 
recordPhase(int phase, double latencyMS, double confidence, double timestampLatencyMS)212         void recordPhase(int phase, double latencyMS, double confidence,
213                 double timestampLatencyMS) {
214             mLatencyMS[phase] = latencyMS;
215             mConfidence[phase] = confidence;
216             mTimestampLatencyMS[phase] = timestampLatencyMS;
217         }
218 
handleTestCompletion()219         void handleTestCompletion() {
220             mMeanLatencyMS = StatUtils.calculateMean(mLatencyMS);
221             mMeanAbsoluteDeviation =
222                     StatUtils.calculateMeanAbsoluteDeviation(
223                             mMeanLatencyMS, mLatencyMS, mLatencyMS.length);
224             mMeanConfidence = StatUtils.calculateMean(mConfidence);
225             mMeanTimestampLatencyMS = StatUtils.calculateMean(mTimestampLatencyMS);
226             if (mNativeAnalyzerThread != null) {
227                 mSampleRate = mNativeAnalyzerThread.getSampleRate();
228                 mIsLowLatencyStream = mNativeAnalyzerThread.isLowLatencyStream();
229                 mHas24BitHardwareSupport = mNativeAnalyzerThread.has24BitHardwareSupport();
230                 mHardwareFormat = mNativeAnalyzerThread.getHardwareFormat();
231             }
232         }
233 
isMeasurementValid()234         boolean isMeasurementValid() {
235             return mTestRun && mMeanLatencyMS > 1.0 && mMeanConfidence >= mRequiredConfidence;
236         }
237 
has24BitHardwareSupport()238         boolean has24BitHardwareSupport() {
239             return mHas24BitHardwareSupport;
240         }
241 
getResultString()242         String getResultString() {
243             String result;
244 
245             if (!mRouteAvailable) {
246                 result = "Route Not Available";
247             } else if (!mTestRun) {
248                 result = "Test Not Run";
249             } else if (mMeanConfidence < mRequiredConfidence) {
250                 result = String.format(
251                         "Test Finished\nInsufficient Confidence (%.2f < %.2f). No Results.",
252                         mMeanConfidence, mRequiredConfidence);
253             } else if (mMeanLatencyMS <= LOWEST_REASONABLE_LATENCY_MILLIS) {
254                 result = String.format(
255                         "Test Finished\nLatency unrealistically low (%.2f < %.2f). No Results.",
256                         mMeanLatencyMS, LOWEST_REASONABLE_LATENCY_MILLIS);
257             } else {
258                 result = String.format(
259                         "Test Finished\nMean Latency:%.2f ms\n"
260                                 + "Mean Absolute Deviation: %.2f\n"
261                                 + "Confidence: %.2f\n"
262                                 + "Low Latency Path: %s\n"
263                                 + "24 Bit Hardware Support: %s\n"
264                                 + "Timestamp Latency:%.2f ms",
265                         mMeanLatencyMS,
266                         mMeanAbsoluteDeviation,
267                         mMeanConfidence,
268                         mIsLowLatencyStream ? mYesString : mNoString,
269                         mHas24BitHardwareSupport ? mYesString : mNoString,
270                         mMeanTimestampLatencyMS);
271             }
272 
273             return result;
274         }
275 
276         // ReportLog Schema (per route)
277         private static final String KEY_ROUTEINDEX = "route_index";
278         private static final String KEY_LATENCY = "latency";
279         private static final String KEY_CONFIDENCE = "confidence";
280         private static final String KEY_MEANABSDEVIATION = "mean_absolute_deviation";
281         private static final String KEY_IS_PERIPHERAL_ATTACHED = "is_peripheral_attached";
282         private static final String KEY_INPUT_PERIPHERAL_NAME = "input_peripheral";
283         private static final String KEY_OUTPUT_PERIPHERAL_NAME = "output_peripheral";
284         private static final String KEY_TEST_PERIPHERAL_NAME = "test_peripheral_name";
285         private static final String KEY_TIMESTAMP_LATENCY = "timestamp_latency";
286         private static final String KEY_SAMPLE_RATE = "sample_rate";
287         private static final String KEY_IS_LOW_LATENCY = "is_low_latency";
288         private static final String KEY_HAS_24_BIT_HARDWARE_SUPPORT =
289                 "has_24_bit_hardware_support";
290         private static final String KEY_HARDWARE_FORMAT = "hardware_format";
291 
recordTestResults(CtsVerifierReportLog reportLog)292         void recordTestResults(CtsVerifierReportLog reportLog) {
293             reportLog.addValue(
294                     KEY_ROUTEINDEX,
295                     mRouteId,
296                     ResultType.NEUTRAL,
297                     ResultUnit.NONE);
298 
299             reportLog.addValue(
300                     KEY_LATENCY,
301                     mMeanLatencyMS,
302                     ResultType.LOWER_BETTER,
303                     ResultUnit.MS);
304 
305             reportLog.addValue(
306                     KEY_CONFIDENCE,
307                     mMeanConfidence,
308                     ResultType.HIGHER_BETTER,
309                     ResultUnit.NONE);
310 
311             reportLog.addValue(
312                     KEY_MEANABSDEVIATION,
313                     mMeanAbsoluteDeviation,
314                     ResultType.NEUTRAL,
315                     ResultUnit.NONE);
316 
317             reportLog.addValue(
318                     KEY_TEST_PERIPHERAL_NAME,
319                     mDeviceName,
320                     ResultType.NEUTRAL,
321                     ResultUnit.NONE);
322 
323             reportLog.addValue(
324                     KEY_TIMESTAMP_LATENCY,
325                     mMeanTimestampLatencyMS,
326                     ResultType.NEUTRAL,
327                     ResultUnit.NONE);
328 
329             reportLog.addValue(
330                     KEY_SAMPLE_RATE,
331                     mSampleRate,
332                     ResultType.NEUTRAL,
333                     ResultUnit.NONE);
334 
335             reportLog.addValue(
336                     KEY_IS_LOW_LATENCY,
337                     mIsLowLatencyStream,
338                     ResultType.NEUTRAL,
339                     ResultUnit.NONE);
340 
341             reportLog.addValue(
342                     KEY_HAS_24_BIT_HARDWARE_SUPPORT,
343                     mHas24BitHardwareSupport,
344                     ResultType.NEUTRAL,
345                     ResultUnit.NONE);
346 
347             reportLog.addValue(
348                     KEY_HARDWARE_FORMAT,
349                     mHardwareFormat,
350                     ResultType.NEUTRAL,
351                     ResultUnit.NONE);
352         }
353 
addToJson(JSONObject jsonObject)354         void addToJson(JSONObject jsonObject) {
355             try {
356                 jsonObject.put(
357                         KEY_ROUTEINDEX,
358                         mRouteId);
359 
360                 jsonObject.put(
361                         KEY_LATENCY,
362                         mMeanLatencyMS);
363 
364                 jsonObject.put(
365                         KEY_CONFIDENCE,
366                         mMeanConfidence);
367 
368                 jsonObject.put(
369                         KEY_MEANABSDEVIATION,
370                         mMeanAbsoluteDeviation);
371 
372                 jsonObject.put(
373                         KEY_TEST_PERIPHERAL_NAME,
374                         mDeviceName);
375 
376                 jsonObject.put(
377                         KEY_TIMESTAMP_LATENCY,
378                         mMeanTimestampLatencyMS);
379 
380                 jsonObject.put(
381                         KEY_SAMPLE_RATE,
382                         mSampleRate);
383 
384                 jsonObject.put(
385                         KEY_IS_LOW_LATENCY,
386                         mIsLowLatencyStream);
387 
388                 jsonObject.put(
389                         KEY_HAS_24_BIT_HARDWARE_SUPPORT,
390                         mHas24BitHardwareSupport);
391 
392                 jsonObject.put(
393                         KEY_HARDWARE_FORMAT,
394                         mHardwareFormat);
395             } catch (JSONException e) {
396                 Log.e(TAG, LOG_ERROR_STR, e);
397             }
398         }
399     }
400 
401     @Override
onCreate(Bundle savedInstanceState)402     protected void onCreate(Bundle savedInstanceState) {
403         setContentView(R.layout.audio_loopback_latency_activity);
404 
405         super.onCreate(savedInstanceState);
406 
407         mContext = this;
408 
409         // MegaAudio Initialization
410         StreamBase.setup(this);
411 
412         setPassFailButtonClickListeners();
413         setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1);
414 
415         mRequireReportLogToPass = true;
416 
417         mClaimsOutput = AudioSystemFlags.claimsOutput(this);
418         mClaimsInput = AudioSystemFlags.claimsInput(this);
419         mClaimsProAudio = AudioSystemFlags.claimsProAudio(this);
420         mClaimsLowLatency = AudioSystemFlags.claimsLowLatencyAudio(this);
421         mClaimsMediaPerformance = Build.VERSION.MEDIA_PERFORMANCE_CLASS != 0;
422         mIsWatch = AudioSystemFlags.isWatch(this);
423         mIsTV = AudioSystemFlags.isTV(this);
424         mIsAutomobile = AudioSystemFlags.isAutomobile(this);
425         mIsHandheld = AudioSystemFlags.isHandheld(this);
426 
427         mUSBAudioSupport = AudioDeviceUtils.supportsUsbAudio(this);
428         mAnalogJackSupport = AudioDeviceUtils.supportsAnalogHeadset(this);
429 
430         // Setup test specifications
431         double mustLatency;
432 
433         // Speaker/Mic Path
434         mTestSpecs[TESTROUTE_DEVICE] =
435                 new TestSpec(TESTROUTE_DEVICE, CONFIDENCE_THRESHOLD_AMBIENT);
436         mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true;    // Always
437 
438         // Analog Jack Path
439         mTestSpecs[TESTROUTE_ANALOG_JACK] =
440                 new TestSpec(TESTROUTE_ANALOG_JACK, CONFIDENCE_THRESHOLD_WIRED);
441 
442         // USB Path
443         mTestSpecs[TESTROUTE_USB] =
444                 new TestSpec(TESTROUTE_USB, CONFIDENCE_THRESHOLD_WIRED);
445 
446         // Setup UI
447         mYesString = getString(R.string.audio_general_yes);
448         mNoString = getString(R.string.audio_general_no);
449         mPassString = getString(R.string.audio_general_teststatus_pass);
450         mFailString = getString(R.string.audio_general_teststatus_fail);
451         mNotTestedString = getString(R.string.audio_general_not_tested);
452         mRequiredString = getString(R.string.audio_general_required);
453         mNoHardwareString = getString(R.string.audio_general_nohardware);
454         mUnknownHardwareString = getString(R.string.audio_general_unknownhardware);
455 
456         // Pro Audio
457         ((TextView) findViewById(R.id.audio_loopback_pro_audio)).setText(
458                 (mClaimsProAudio ? mYesString : mNoString));
459 
460         // Low Latency
461         ((TextView) findViewById(R.id.audio_loopback_low_latency)).setText(
462                 (mClaimsLowLatency ? mYesString : mNoString));
463 
464         // Media Performance Class
465         ((TextView) findViewById(R.id.audio_loopback_mpc)).setText(
466                 (mClaimsMediaPerformance ? String.valueOf(Build.VERSION.MEDIA_PERFORMANCE_CLASS)
467                         : mNoString));
468 
469         // MMAP
470         ((TextView) findViewById(R.id.audio_loopback_mmap)).setText(
471                 (mSupportsMMAP ? mYesString : mNoString));
472         ((TextView) findViewById(R.id.audio_loopback_mmap_exclusive)).setText(
473                 (mSupportsMMAPExclusive ? mYesString : mNoString));
474 
475         // Device Type
476         ((TextView) findViewById(R.id.audio_loopback_is_watch)).setText(
477                 (mIsWatch ? mYesString : mNoString));
478         ((TextView) findViewById(R.id.audio_loopback_is_TV)).setText(
479                 (mIsTV ? mYesString : mNoString));
480         ((TextView) findViewById(R.id.audio_loopback_is_automobile)).setText(
481                 (mIsAutomobile ? mYesString : mNoString));
482         ((TextView) findViewById(R.id.audio_loopback_is_handheld)).setText(
483                 (mIsHandheld ? mYesString : mNoString));
484 
485         // Individual Test Results
486         mRouteStatus[TESTROUTE_DEVICE] =
487                 (TextView) findViewById(R.id.audio_loopback_speakermicpath_info);
488         mRouteStatus[TESTROUTE_ANALOG_JACK] =
489                 (TextView) findViewById(R.id.audio_loopback_headsetpath_info);
490         mRouteStatus[TESTROUTE_USB] =
491                 (TextView) findViewById(R.id.audio_loopback_usbpath_info);
492 
493         mStartButtons[TESTROUTE_DEVICE] =
494                 (Button) findViewById(R.id.audio_loopback_speakermicpath_btn);
495         mStartButtons[TESTROUTE_DEVICE].setOnClickListener(mBtnClickListener);
496 
497         mStartButtons[TESTROUTE_ANALOG_JACK] =
498                 (Button) findViewById(R.id.audio_loopback_headsetpath_btn);
499         mStartButtons[TESTROUTE_ANALOG_JACK].setOnClickListener(mBtnClickListener);
500 
501         mStartButtons[TESTROUTE_USB] = (Button) findViewById(R.id.audio_loopback_usbpath_btn);
502         mStartButtons[TESTROUTE_USB].setOnClickListener(mBtnClickListener);
503 
504         mCalibrateAudioButton = findViewById(R.id.audio_loopback_calibrate_button);
505         mCalibrateAudioButton.setOnClickListener(mBtnClickListener);
506 
507         mAudioDevicesButton = findViewById(R.id.audio_loopback_devsupport_button);
508         mAudioDevicesButton.setOnClickListener(mBtnClickListener);
509 
510         mTestInstructions = (TextView) findViewById(R.id.audio_loopback_instructions);
511 
512         mAudioManager = getSystemService(AudioManager.class);
513         scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
514 
515         connectLoopbackUI();
516 
517         if (mustRunTest()) {
518             getPassButton().setEnabled(false);
519             enableStartButtons(true);
520         } else {
521             getPassButton().setEnabled(isReportLogOkToPass());
522             enableStartButtons(false);
523         }
524 
525         mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
526 
527         showRouteStatus();
528         showTestInstructions();
529         handleTestCompletion(false);
530 
531         DisplayUtils.setKeepScreenOn(this, true);
532     }
533 
534     //
535     // UI State
536     //
showRouteStatus()537     private void showRouteStatus() {
538         // mRouteStatus[TESTROUTE_DEVICE];
539         // Nothing to do for this route.
540 
541         // mRouteStatus[TESTROUTE_ANALOG_JACK];
542         switch (mAnalogJackSupport) {
543             case AudioDeviceUtils.SUPPORTSDEVICE_NO:
544                 mRouteStatus[TESTROUTE_ANALOG_JACK].setText(
545                         getString(R.string.audio_loopback_noanalog));
546                 break;
547             case AudioDeviceUtils.SUPPORTSDEVICE_YES:
548                 mRouteStatus[TESTROUTE_ANALOG_JACK].setText(
549                         getString(R.string.audio_loopback_headsetpath_instructions));
550                 break;
551             case AudioDeviceUtils.SUPPORTSDEVICE_UNDETERMINED:
552                 mRouteStatus[TESTROUTE_ANALOG_JACK].setText(
553                         getString(R.string.audio_loopback_unknownanalog));
554                 break;
555         }
556 
557         // mRouteStatus[TESTROUTE_USB];
558         switch (mUSBAudioSupport) {
559             case AudioDeviceUtils.SUPPORTSDEVICE_NO:
560                 mRouteStatus[TESTROUTE_USB].setText(getString(R.string.audio_loopback_nousb));
561                 break;
562             case AudioDeviceUtils.SUPPORTSDEVICE_YES:
563                 mRouteStatus[TESTROUTE_USB].setText(
564                         getString(R.string.audio_loopback_usbpath_instructions));
565                 break;
566             case AudioDeviceUtils.SUPPORTSDEVICE_UNDETERMINED:
567                 mRouteStatus[TESTROUTE_USB].setText(getString(R.string.audio_loopback_unknownusb));
568                 break;
569         }
570     }
571 
showTestInstructions()572     private void showTestInstructions() {
573         if (mustRunTest()) {
574             mTestInstructions.setText(getString(R.string.audio_loopback_test_all_paths));
575         } else {
576             mTestInstructions.setText(getString(R.string.audio_loopback_test_not_required));
577         }
578     }
579 
enableStartButtons(boolean enable)580     private void enableStartButtons(boolean enable) {
581         if (enable) {
582             for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) {
583                 mStartButtons[routeId].setEnabled(mTestSpecs[routeId].mRouteConnected);
584             }
585         } else {
586             for (int routeId = TESTROUTE_DEVICE; routeId <= TESTROUTE_USB; routeId++) {
587                 mStartButtons[routeId].setEnabled(false);
588             }
589         }
590         mCalibrateAudioButton.setEnabled(enable);
591         mAudioDevicesButton.setEnabled(enable);
592     }
593 
connectLoopbackUI()594     private void connectLoopbackUI() {
595         mTestStatusText = (TextView) findViewById(R.id.audio_loopback_status_text);
596         mProgressBar = (ProgressBar) findViewById(R.id.audio_loopback_progress_bar);
597         showWait(false);
598     }
599 
600     //
601     // Peripheral Connection Logic
602     //
clearDeviceIds()603     void clearDeviceIds() {
604         for (TestSpec testSpec : mTestSpecs) {
605             testSpec.mInputDeviceId = testSpec.mInputDeviceId = TestSpec.DEVICEID_NONE;
606         }
607     }
608 
clearDeviceConnected()609     void clearDeviceConnected() {
610         for (TestSpec testSpec : mTestSpecs) {
611             testSpec.mRouteConnected = false;
612         }
613     }
614 
scanPeripheralList(AudioDeviceInfo[] devices)615     void scanPeripheralList(AudioDeviceInfo[] devices) {
616         clearDeviceIds();
617         clearDeviceConnected();
618 
619         mSpeakerDeviceId = AudioDeviceInfo.TYPE_UNKNOWN;
620         mMicDeviceId = AudioDeviceInfo.TYPE_UNKNOWN;
621         for (AudioDeviceInfo devInfo : devices) {
622             switch (devInfo.getType()) {
623                 // TESTROUTE_DEVICE (i.e. Speaker & Mic)
624                 // This needs to be handled differently. The other devices can be assumed
625                 // to contain both input & output devices in the same type.
626                 // For built-in we need to see both TYPES to be sure to have both input & output.
627                 case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
628                     mSpeakerDeviceId = devInfo.getId();
629                     break;
630                 case AudioDeviceInfo.TYPE_BUILTIN_MIC:
631                     mMicDeviceId = devInfo.getId();
632                     break;
633 
634                 // TESTROUTE_ANALOG_JACK
635                 case AudioDeviceInfo.TYPE_WIRED_HEADSET:
636                 case AudioDeviceInfo.TYPE_AUX_LINE:
637                     if (devInfo.isSink()) {
638                         mTestSpecs[TESTROUTE_ANALOG_JACK].mOutputDeviceId = devInfo.getId();
639                     } else if (devInfo.isSource()) {
640                         mTestSpecs[TESTROUTE_ANALOG_JACK].mInputDeviceId = devInfo.getId();
641                     }
642                     mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteAvailable = true;
643                     mTestSpecs[TESTROUTE_ANALOG_JACK].mRouteConnected = true;
644                     mTestSpecs[TESTROUTE_ANALOG_JACK].mDeviceName =
645                             devInfo.getProductName().toString();
646                     break;
647 
648                 // TESTROUTE_USB
649                 case AudioDeviceInfo.TYPE_USB_DEVICE:
650                 case AudioDeviceInfo.TYPE_USB_HEADSET:
651                     if (devInfo.isSink()) {
652                         mTestSpecs[TESTROUTE_USB].mOutputDeviceId = devInfo.getId();
653                     } else if (devInfo.isSource()) {
654                         mTestSpecs[TESTROUTE_USB].mInputDeviceId = devInfo.getId();
655                     }
656                     mTestSpecs[TESTROUTE_USB].mRouteAvailable = true;
657                     mTestSpecs[TESTROUTE_USB].mRouteConnected = true;
658                     mTestSpecs[TESTROUTE_USB].mDeviceName = devInfo.getProductName().toString();
659                     break;
660             }
661         }
662 
663         // do we have BOTH a Speaker and Mic?
664         if (hasInternalPath()) {
665             mTestSpecs[TESTROUTE_DEVICE].mOutputDeviceId = mSpeakerDeviceId;
666             mTestSpecs[TESTROUTE_DEVICE].mInputDeviceId = mMicDeviceId;
667             mTestSpecs[TESTROUTE_DEVICE].mRouteAvailable = true;
668             mTestSpecs[TESTROUTE_DEVICE].mRouteConnected = true;
669             mTestSpecs[TESTROUTE_DEVICE].mDeviceName =
670                     getString(R.string.audio_loopback_test_internal_devices);
671         }
672 
673         enableStartButtons(mustRunTest());
674     }
675 
676     private class ConnectListener extends AudioDeviceCallback {
ConnectListener()677         ConnectListener() {}
678 
679         //
680         // AudioDevicesManager.OnDeviceConnectionListener
681         //
682         @Override
onAudioDevicesAdded(AudioDeviceInfo[] addedDevices)683         public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
684             scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
685             AudioDeviceUtils.validateUsbDevice(mContext);
686         }
687 
688         @Override
onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices)689         public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
690             scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
691         }
692     }
693 
694     //
695     // show active progress bar
696     //
showWait(boolean show)697     protected void showWait(boolean show) {
698         mProgressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
699     }
700 
701     //
702     // Common logging
703     //
704 
705     @Override
getTestId()706     public String getTestId() {
707         return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
708     }
709 
710     @Override
requiresReportLog()711     public boolean requiresReportLog() {
712         return true;
713     }
714 
715     @Override
getReportFileName()716     public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; }
717 
718     @Override
getReportSectionName()719     public final String getReportSectionName() {
720         return setTestNameSuffix(sCurrentDisplayMode, "audio_loopback_latency_activity");
721     }
722 
723     // Global Test-Schema
724     private static final String KEY_IS_PRO_AUDIO = "is_pro_audio";
725     private static final String KEY_TEST_MMAP = "supports_mmap";
726     private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive";
727     private static final String KEY_LEVEL = "level";
728 
729     // Contains the results for all routes
730     private static final String KEY_PATHS = "paths";
731 
recordGlobalResults(CtsVerifierReportLog reportLog)732     private void recordGlobalResults(CtsVerifierReportLog reportLog) {
733         // Leave this in to be sure not to break the ReportLog injestion
734         reportLog.addValue(
735                 KEY_LEVEL,
736                 -1,
737                 ResultType.NEUTRAL,
738                 ResultUnit.NONE);
739 
740         reportLog.addValue(
741                 KEY_IS_PRO_AUDIO,
742                 mClaimsProAudio,
743                 ResultType.NEUTRAL,
744                 ResultUnit.NONE);
745 
746         reportLog.addValue(
747                 KEY_TEST_MMAP,
748                 mSupportsMMAP,
749                 ResultType.NEUTRAL,
750                 ResultUnit.NONE);
751 
752         reportLog.addValue(
753                 KEY_TEST_MMAPEXCLUSIVE,
754                 mSupportsMMAPExclusive,
755                 ResultType.NEUTRAL,
756                 ResultUnit.NONE);
757 
758         reportLog.addValue(
759                 Common.KEY_VERSION_CODE,
760                 Common.VERSION_CODE,
761                 ResultType.NEUTRAL,
762                 ResultUnit.NONE);
763     }
764 
recordAllRoutes(CtsVerifierReportLog reportLog)765     private void recordAllRoutes(CtsVerifierReportLog reportLog) {
766         JSONArray jsonArray = new JSONArray();
767         for (int route = 0; route < NUM_TEST_ROUTES; route++) {
768             if (mTestSpecs[route].isMeasurementValid()) {
769                 JSONObject jsonObject = new JSONObject();
770                 mTestSpecs[route].addToJson(jsonObject);
771                 jsonArray.put(jsonObject);
772             }
773         }
774 
775         if (jsonArray.length() > 0) {
776             reportLog.addValues(KEY_PATHS, jsonArray);
777         }
778     }
779 
780     @Override
recordTestResults()781     public void recordTestResults() {
782         // Look for a valid route with the minimum latency.
783         int bestRoute = -1;
784         double minLatency = Double.MAX_VALUE;
785         for (int route = 0; route < NUM_TEST_ROUTES; route++) {
786             if (mTestSpecs[route].isMeasurementValid()) {
787                 if (mTestSpecs[route].mMeanLatencyMS < minLatency) {
788                     bestRoute = route;
789                     minLatency = mTestSpecs[route].mMeanLatencyMS;
790                 }
791             }
792         }
793 
794         if (bestRoute >= 0) {
795             CtsVerifierReportLog reportLog = getReportLog();
796             recordGlobalResults(reportLog);
797             mTestSpecs[bestRoute].recordTestResults(reportLog);
798             recordAllRoutes(reportLog);
799             reportLog.submit();
800         }
801     }
802 
startAudioTest(Handler messageHandler, int testRouteId)803     private void startAudioTest(Handler messageHandler, int testRouteId) {
804         enableStartButtons(false);
805         mRouteStatus[testRouteId].setText(getString(R.string.audio_loopback_running));
806 
807         mTestRoute = testRouteId;
808 
809         mTestSpecs[mTestRoute].startTest();
810 
811         getPassButton().setEnabled(false);
812 
813         mTestPhase = 0;
814 
815         // Set mmap enabled as mmap supported to get best latency
816         Globals.setMMapEnabled(Globals.isMMapSupported());
817 
818         mNativeAnalyzerThread = new NativeAnalyzerThread(this);
819         if (mNativeAnalyzerThread != null) {
820             mNativeAnalyzerThread.setMessageHandler(messageHandler);
821             // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
822             mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION);
823             startTestPhase();
824         } else {
825             Log.e(TAG, "Couldn't allocate native analyzer thread");
826             mTestStatusText.setText(getString(R.string.audio_loopback_failure));
827         }
828     }
829 
startTestPhase()830     private void startTestPhase() {
831         if (mNativeAnalyzerThread != null) {
832             if (LOG) {
833                 Log.d(TAG, "mTestRoute: " + mTestRoute
834                         + " mInputDeviceId: " + mTestSpecs[mTestRoute].mInputDeviceId
835                         + " mOutputDeviceId: " + mTestSpecs[mTestRoute].mOutputDeviceId);
836             }
837             mNativeAnalyzerThread.startTest(
838                     mTestSpecs[mTestRoute].mInputDeviceId, mTestSpecs[mTestRoute].mOutputDeviceId);
839 
840             // what is this for?
841             try {
842                 Thread.sleep(200);
843             } catch (InterruptedException e) {
844                 e.printStackTrace();
845             }
846         }
847     }
848 
handleTestPhaseCompletion()849     private void handleTestPhaseCompletion() {
850         if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) {
851             double latency = mNativeAnalyzerThread.getLatencyMillis();
852             double confidence = mNativeAnalyzerThread.getConfidence();
853             double timestampLatency = mNativeAnalyzerThread.getTimestampLatencyMillis();
854             TestSpec testSpec = mTestSpecs[mTestRoute];
855             testSpec.recordPhase(mTestPhase, latency, confidence, timestampLatency);
856 
857             String result = String.format(
858                     "Test %d Finished\nLatency: %.2f ms\nConfidence: %.2f\n"
859                     + "TimestampLatency: %.2f\n",
860                     mTestPhase, latency, confidence, timestampLatency);
861 
862             mTestStatusText.setText(result);
863             try {
864                 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
865                 // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500);
866             } catch (InterruptedException e) {
867                 e.printStackTrace();
868             }
869 
870 
871             mTestPhase++;
872             if (mTestPhase >= NUM_TEST_PHASES) {
873                 handleTestCompletion(true);
874             } else {
875                 startTestPhase();
876             }
877         }
878     }
879 
generateStatusString( LoopbackLatencyRequirements requirements, boolean showResult)880     private String generateStatusString(
881             LoopbackLatencyRequirements requirements, boolean showResult) {
882 
883         if (!isReportLogOkToPass()) {
884             return getString(R.string.audio_general_reportlogtest);
885         }
886 
887         if (!mustRunTest()) {
888             return getString(R.string.audio_loopback_test_non_handheld);
889         }
890 
891         boolean pass = calcPass(requirements);
892         StringBuilder sb = new StringBuilder();
893         sb.append(requirements.getResultsString());
894         if (showResult) {
895             sb.append("\n" + (pass ? mPassString : mFailString));
896         }
897         return sb.toString();
898     }
899 
mustRunTest()900     private boolean mustRunTest() {
901         return mIsHandheld  && hasInternalPath();
902     }
903 
hasInternalPath()904     boolean hasInternalPath() {
905         return mSpeakerDeviceId != AudioDeviceInfo.TYPE_UNKNOWN
906                 && mMicDeviceId != AudioDeviceInfo.TYPE_UNKNOWN;
907     }
908 
calcPass(LoopbackLatencyRequirements requirements)909     private boolean calcPass(LoopbackLatencyRequirements requirements) {
910         if (!isReportLogOkToPass()) {
911             // Can't pass if we can't write the ReportLog
912             return false;
913         }
914         if (!mustRunTest()) {
915             // just grant a pass on non-handheld devices
916             return true;
917         }
918 
919         boolean pass = true;
920 
921         // Check to see if the tests supported by the hardware have run
922         // Analog Headset
923         if (mAnalogJackSupport == AudioDeviceUtils.SUPPORTSDEVICE_YES
924                 && !mTestSpecs[TESTROUTE_ANALOG_JACK].mTestRun) {
925             pass = false;
926         }
927 
928         if (mUSBAudioSupport == AudioDeviceUtils.SUPPORTSDEVICE_YES
929                 && !mTestSpecs[TESTROUTE_USB].mTestRun) {
930             pass = false;
931         }
932 
933         // Check if the test values have passed
934         // Even if the test is already a fail, this will generate the results string
935         pass &= requirements.evaluate(mClaimsProAudio,
936                 Build.VERSION.MEDIA_PERFORMANCE_CLASS,
937                 mTestSpecs[TESTROUTE_DEVICE].isMeasurementValid()
938                         ? mTestSpecs[TESTROUTE_DEVICE].mMeanLatencyMS : 0.0,
939                 mTestSpecs[TESTROUTE_ANALOG_JACK].isMeasurementValid()
940                         ? mTestSpecs[TESTROUTE_ANALOG_JACK].mMeanLatencyMS :  0.0,
941                 mTestSpecs[TESTROUTE_USB].isMeasurementValid()
942                         ? mTestSpecs[TESTROUTE_USB].mMeanLatencyMS : 0.0,
943                 mTestSpecs[TESTROUTE_ANALOG_JACK].has24BitHardwareSupport(),
944                 mTestSpecs[TESTROUTE_USB].has24BitHardwareSupport(),
945                 mTestSpecs[TESTROUTE_DEVICE].isMeasurementValid()
946                         ? mTestSpecs[TESTROUTE_DEVICE].mMeanTimestampLatencyMS : 0.0,
947                 mTestSpecs[TESTROUTE_ANALOG_JACK].isMeasurementValid()
948                         ? mTestSpecs[TESTROUTE_ANALOG_JACK].mMeanTimestampLatencyMS :  0.0,
949                 mTestSpecs[TESTROUTE_USB].isMeasurementValid()
950                         ? mTestSpecs[TESTROUTE_USB].mMeanTimestampLatencyMS : 0.0);
951 
952         return pass;
953     }
954 
handleTestCompletion(boolean showResult)955     private void handleTestCompletion(boolean showResult) {
956         TestSpec testSpec = mTestSpecs[mTestRoute];
957         testSpec.handleTestCompletion();
958 
959         // Make sure the test thread is finished. It should already be done.
960         if (mNativeAnalyzerThread != null) {
961             try {
962                 mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
963             } catch (InterruptedException e) {
964                 e.printStackTrace();
965             }
966         }
967 
968         mRouteStatus[mTestRoute].setText(testSpec.getResultString());
969 
970         LoopbackLatencyRequirements requirements = new LoopbackLatencyRequirements();
971         boolean pass = calcPass(requirements);
972 
973         getPassButton().setEnabled(pass);
974 
975         mTestStatusText.setText(generateStatusString(requirements, showResult));
976 
977         showWait(false);
978         enableStartButtons(mustRunTest());
979     }
980 
981     /**
982      * handler for messages from audio thread
983      */
984     private Handler mMessageHandler = new Handler() {
985         public void handleMessage(Message msg) {
986             super.handleMessage(msg);
987             switch(msg.what) {
988                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
989                     Log.v(TAG,"got message native rec started!!");
990                     showWait(true);
991                     mTestStatusText.setText(String.format(Locale.getDefault(),
992                             "[phase: %d] - Test Running...", (mTestPhase + 1)));
993                     break;
994                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR:
995                     Log.v(TAG,"got message native rec can't start!!");
996                     mTestStatusText.setText("Test Error opening streams.");
997                     handleTestCompletion(true);
998                     break;
999                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR:
1000                     Log.v(TAG,"got message native rec can't start!!");
1001                     mTestStatusText.setText("Test Error while recording.");
1002                     handleTestCompletion(true);
1003                     break;
1004                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
1005                     mTestStatusText.setText("Test FAILED due to errors.");
1006                     handleTestCompletion(true);
1007                     break;
1008                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING:
1009                     mTestStatusText.setText(String.format(Locale.getDefault(),
1010                             "[phase: %d] - Analyzing ...", mTestPhase + 1));
1011                     break;
1012                 case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
1013                     handleTestPhaseCompletion();
1014                     break;
1015                 default:
1016                     break;
1017             }
1018         }
1019     };
1020 
1021     private class OnBtnClickListener implements OnClickListener {
1022         @Override
onClick(View v)1023         public void onClick(View v) {
1024             int id = v.getId();
1025             if (id == R.id.audio_loopback_speakermicpath_btn) {
1026                 startAudioTest(mMessageHandler, TESTROUTE_DEVICE);
1027             } else if (id == R.id.audio_loopback_headsetpath_btn) {
1028                 startAudioTest(mMessageHandler, TESTROUTE_ANALOG_JACK);
1029             }  else if (id == R.id.audio_loopback_usbpath_btn) {
1030                 startAudioTest(mMessageHandler, TESTROUTE_USB);
1031             } else if (id == R.id.audio_loopback_calibrate_button) {
1032                 (new AudioLoopbackCalibrationDialog(mContext)).show();
1033             } else if (id == R.id.audio_loopback_devsupport_button) {
1034                 (new AudioDevicesDialog(mContext)).show();
1035             }
1036         }
1037     }
1038 
1039     class LoopbackLatencyRequirements {
1040         public static final int MPC_NONE = 0;
1041         public static final int MPC_R = Build.VERSION_CODES.R;
1042         public static final int MPC_S = Build.VERSION_CODES.S;
1043         public static final int MPC_T = Build.VERSION_CODES.TIRAMISU;
1044 
1045         String mResultsString = new String();
1046 
getResultsString()1047         String getResultsString() {
1048             return mResultsString;
1049         }
1050 
1051         static final int RESULTCODE_NONE = 0;
1052         static final int RESULTCODE_PASS = 1;
1053         static final int RESULTCODE_FAIL_NOINTERNAL = 2;
1054         static final int RESULTCODE_FAIL_BASIC = 3;
1055         static final int RESULTCODE_FAIL_MPC = 4;
1056         static final int RESULTCODE_FAIL_PROONEPATH = 5;
1057         static final int RESULTCODE_FAIL_PROLIMITS_ANALOG = 6;
1058         static final int RESULTCODE_FAIL_PROLIMITS_USB = 7;
1059         static final int RESULTCODE_FAIL_24BIT = 8;
1060         static final int RESULTCODE_FAIL_PRO_NOWIRED = 9;
1061         static final int RESULTCODE_WARNING_TIMESTAMP = 10;
1062         int mResultCode = RESULTCODE_NONE;
1063 
checkLatency(double measured, double limit)1064         private boolean checkLatency(double measured, double limit) {
1065             return measured == LATENCY_NOT_MEASURED || measured <= limit;
1066         }
1067 
checkTimestampLatencyAccuracy(double measuredLatency, double timestampLatency)1068         private boolean checkTimestampLatencyAccuracy(double measuredLatency,
1069                 double timestampLatency) {
1070             return (timestampLatency < 0.0) || (measuredLatency == LATENCY_NOT_MEASURED)
1071                     || (Math.abs(measuredLatency - timestampLatency) <= TIMESTAMP_ACCURACY_MS);
1072         }
1073 
setResultCode(boolean pass, int code)1074         private void setResultCode(boolean pass, int code) {
1075             // only set the first non-"none" result code
1076             if (!pass && mResultCode == RESULTCODE_NONE) {
1077                 mResultCode = code;
1078             }
1079         }
1080 
getResultCodeText()1081         public String getResultCodeText() {
1082             StringBuilder sb = new StringBuilder();
1083             Locale locale = Locale.getDefault();
1084 
1085             switch (mResultCode) {
1086                 case RESULTCODE_NONE:
1087                     return getString(R.string.audio_loopback_resultcode_none);
1088                 case RESULTCODE_PASS:
1089                     return getString(R.string.audio_loopback_resultcode_pass);
1090                 case RESULTCODE_FAIL_NOINTERNAL:
1091                     return getString(R.string.audio_loopback_resultcode_nointernal);
1092                 case RESULTCODE_FAIL_BASIC:
1093                     sb.append(getString(R.string.audio_loopback_resultcode_failbasic));
1094                     sb.append(String.format(locale, " [%.2fms] ", LATENCY_BASIC));
1095                     sb.append(getString(R.string.audio_loopback_notmet));
1096                     return sb.toString();
1097                 case RESULTCODE_FAIL_MPC:
1098                     sb.append(getString(R.string.audio_loopback_resultcode_failmpc));
1099                     sb.append(String.format(locale, " [%.2fms] ", LATENCY_MPC_AT_LEAST_ONE));
1100                     sb.append(getString(R.string.audio_loopback_notmet));
1101                     return sb.toString();
1102                 case RESULTCODE_FAIL_PROONEPATH:
1103                     sb.append(getString(R.string.audio_loopback_resultcode_failpro));
1104                     sb.append(String.format(locale, " [%.2fms] ", LATENCY_PRO_AUDIO_AT_LEAST_ONE));
1105                     sb.append(getString(R.string.audio_loopback_notmet));
1106                     return sb.toString();
1107                 case RESULTCODE_FAIL_PROLIMITS_ANALOG:
1108                     sb.append(getString(R.string.audio_loopback_resultcode_failproanalog));
1109                     sb.append(String.format(locale, " [%.2fms] ", LATENCY_PRO_AUDIO_ANALOG));
1110                     sb.append(getString(R.string.audio_loopback_notmet));
1111                     return sb.toString();
1112                 case RESULTCODE_FAIL_PROLIMITS_USB:
1113                     sb.append(getString(R.string.audio_loopback_resultcode_failprousb));
1114                     sb.append(String.format(locale, " [%.2fms] ", LATENCY_PRO_AUDIO_USB));
1115                     sb.append(getString(R.string.audio_loopback_notmet));
1116                     return sb.toString();
1117                 case RESULTCODE_FAIL_24BIT:
1118                     return getString(R.string.audio_loopback_resultcode_fail24bit);
1119                 case RESULTCODE_FAIL_PRO_NOWIRED:
1120                     return getString(R.string.audio_loopback_resultcode_failproaudiowired);
1121                 case RESULTCODE_WARNING_TIMESTAMP:
1122                     return getString(R.string.audio_loopback_resultcode_warningtimestamp);
1123                 default:
1124                     // this should never happen
1125                     return getString(R.string.audio_loopback_resultcode_invalid);
1126             }
1127         }
1128 
evaluate(boolean proAudio, int mediaPerformanceClass, double deviceLatency, double analogLatency, double usbLatency, boolean analog24BitHardwareSupport, boolean usb24BitHardwareSupport, double deviceTimestampLatency, double analogTimestampLatency, double usbTimestampLatency)1129         public boolean evaluate(boolean proAudio,
1130                                        int mediaPerformanceClass,
1131                                        double deviceLatency,
1132                                        double analogLatency,
1133                                        double usbLatency,
1134                                        boolean analog24BitHardwareSupport,
1135                                        boolean usb24BitHardwareSupport,
1136                                        double deviceTimestampLatency,
1137                                        double analogTimestampLatency,
1138                                        double usbTimestampLatency) {
1139 
1140             if (LOG) {
1141                 Log.d(TAG, "evaluate()");
1142             }
1143             mResultCode = RESULTCODE_NONE;
1144 
1145             // Required to test the Mic/Speaker path
1146             boolean internalPathRun = deviceLatency != LATENCY_NOT_MEASURED;
1147             if (LOG) {
1148                 Log.d(TAG, "  internalPathRun:" + internalPathRun);
1149             }
1150             setResultCode(internalPathRun, RESULTCODE_FAIL_NOINTERNAL);
1151 
1152             // All devices must be under the basic limit.
1153             boolean basicPass = checkLatency(deviceLatency, LATENCY_BASIC)
1154                     && checkLatency(analogLatency, LATENCY_BASIC)
1155                     && checkLatency(usbLatency, LATENCY_BASIC);
1156             if (LOG) {
1157                 Log.d(TAG, "  basicPass:" + basicPass);
1158             }
1159             setResultCode(basicPass, RESULTCODE_FAIL_BASIC);
1160 
1161             // For Media Performance Class T the RT latency must be <= 80 msec on one path.
1162             boolean mpcAtLeastOnePass;
1163             if (mClaimsMediaPerformance) {
1164                 mpcAtLeastOnePass =
1165                     (mediaPerformanceClass < MPC_T)
1166                             || checkLatency(deviceLatency, LATENCY_MPC_AT_LEAST_ONE)
1167                             || checkLatency(analogLatency, LATENCY_MPC_AT_LEAST_ONE)
1168                             || checkLatency(usbLatency, LATENCY_MPC_AT_LEAST_ONE);
1169             } else {
1170                 mpcAtLeastOnePass = true;
1171             }
1172             if (LOG) {
1173                 Log.d(TAG, "  mpcAtLeastOnePass:" + mpcAtLeastOnePass);
1174             }
1175             setResultCode(mpcAtLeastOnePass, RESULTCODE_FAIL_MPC);
1176 
1177             // For ProAudio, the RT latency must be <= 25 msec on one path.
1178             boolean proAudioAtLeastOnePass = !proAudio
1179                     || checkLatency(deviceLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE)
1180                     || checkLatency(analogLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE)
1181                     || checkLatency(usbLatency, LATENCY_PRO_AUDIO_AT_LEAST_ONE);
1182             if (LOG) {
1183                 Log.d(TAG, "  proAudioAtLeastOnePass:" + proAudioAtLeastOnePass);
1184             }
1185             setResultCode(proAudioAtLeastOnePass, RESULTCODE_FAIL_PROONEPATH);
1186 
1187             // For ProAudio, analog and USB have specific limits
1188             boolean proAudioLimitsPass = !proAudio;
1189             if (proAudio) {
1190                 if (analogLatency > 0.0) {
1191                     proAudioLimitsPass = analogLatency <= LATENCY_PRO_AUDIO_ANALOG;
1192                     setResultCode(proAudioLimitsPass, RESULTCODE_FAIL_PROLIMITS_ANALOG);
1193                 } else if (usbLatency > 0.0) {
1194                     // USB audio must be supported if 3.5mm jack not supported
1195                     proAudioLimitsPass = usbLatency <= LATENCY_PRO_AUDIO_USB;
1196                     setResultCode(proAudioLimitsPass, RESULTCODE_FAIL_PROLIMITS_USB);
1197                 } else {
1198                     setResultCode(proAudioLimitsPass, RESULTCODE_FAIL_PRO_NOWIRED);
1199                 }
1200             }
1201 
1202             // For Media Performance Class T, usb and analog should support >=24 bit audio.
1203             boolean has24BitHardwareSupportPass = (mediaPerformanceClass < MPC_T)
1204                     || (analog24BitHardwareSupport && usb24BitHardwareSupport);
1205             if (LOG) {
1206                 Log.d(TAG, "  has24BitHardwareSupportPass:" + has24BitHardwareSupportPass);
1207             }
1208             setResultCode(has24BitHardwareSupportPass, RESULTCODE_FAIL_24BIT);
1209 
1210             // Timestamp latencies must be accurate enough.
1211             boolean timestampPass =
1212                     checkTimestampLatencyAccuracy(deviceLatency, deviceTimestampLatency)
1213                     && checkTimestampLatencyAccuracy(analogLatency, analogTimestampLatency)
1214                     && checkTimestampLatencyAccuracy(usbLatency, usbTimestampLatency);
1215             if (LOG) {
1216                 Log.d(TAG, "  timestampPass:" + timestampPass);
1217             }
1218             setResultCode(timestampPass, RESULTCODE_WARNING_TIMESTAMP);
1219 
1220             boolean pass =
1221                     internalPathRun
1222                     && basicPass
1223                     && mpcAtLeastOnePass
1224                     && proAudioAtLeastOnePass
1225                     && proAudioLimitsPass
1226                     && has24BitHardwareSupportPass;
1227             if (LOG) {
1228                 Log.d(TAG, "  pass:" + pass);
1229             }
1230 
1231             // Build the results explanation
1232             StringBuilder sb = new StringBuilder();
1233             if (proAudio) {
1234                 sb.append("[Pro Audio]");
1235             } else if (mediaPerformanceClass != MPC_NONE) {
1236                 sb.append("[MPC " + mediaPerformanceClass + "]");
1237             } else {
1238                 sb.append("[Basic Audio]");
1239             }
1240             sb.append(" ");
1241 
1242             Locale locale = Locale.getDefault();
1243             sb.append("\nSpeaker/Mic: " + (deviceLatency != LATENCY_NOT_MEASURED
1244                     ? String.format(locale, "%.2fms ", deviceLatency)
1245                     : (mNotTestedString + " - " + mRequiredString)));
1246 
1247             // Headset
1248             sb.append("\nHeadset: ");
1249             if (analogLatency != LATENCY_NOT_MEASURED) {
1250                 // we have a legit measurement
1251                 sb.append(String.format(locale, "%.2fms ", analogLatency));
1252             } else {
1253                 // Not measured
1254                 switch (mAnalogJackSupport) {
1255                     case AudioDeviceUtils.SUPPORTSDEVICE_YES:
1256                         sb.append(mRequiredString);
1257                         break;
1258 
1259                     case AudioDeviceUtils.SUPPORTSDEVICE_NO:
1260                         sb.append(mNoHardwareString);
1261                         break;
1262 
1263                     case AudioDeviceUtils.SUPPORTSDEVICE_UNDETERMINED:
1264                     default:
1265                         sb.append(mUnknownHardwareString);
1266                         break;
1267                 }
1268             }
1269 
1270             // USB
1271             sb.append("\nUSB: ");
1272             if (usbLatency != LATENCY_NOT_MEASURED) {
1273                 sb.append(String.format(locale, "%.2fms ", usbLatency));
1274             } else {
1275                 // Not measured
1276                 switch (mUSBAudioSupport) {
1277                     case AudioDeviceUtils.SUPPORTSDEVICE_YES:
1278                         sb.append(mRequiredString);
1279                         break;
1280 
1281                     case AudioDeviceUtils.SUPPORTSDEVICE_NO:
1282                         sb.append(mNoHardwareString);
1283                         break;
1284 
1285                     case AudioDeviceUtils.SUPPORTSDEVICE_UNDETERMINED:
1286                     default:
1287                         sb.append(mUnknownHardwareString);
1288                         break;
1289                 }
1290             }
1291 
1292             mResultsString = sb.toString();
1293             if (mResultCode > RESULTCODE_PASS) {
1294                 mResultsString = mResultsString + "\n" + getResultCodeText();
1295             }
1296 
1297             return pass;
1298         }
1299     }
1300 }
1301